Reinvent the wheel

Published on:
Tags: heart tech

Reinvent the wheel 估计技术人员都知道这个典故。

刚才突然想谈这个,是看到图拉鼎参加 weex 的聚会有感而发。我要是没理解错,这个应该是类似于 React native 的一套实现,我没有仔细看过他的实现,不过说他重复造轮子应该也不为过,毕竟,大家普遍赞成的是在已有的轮子上面添砖加瓦,而不是另起一套。

重复造轮子到底应该不应该支持?

之前我司来了一个发明了 avalon 框架的牛人,之后我看好像就在很多的推介这个,新人来了先学习这个。后来还有很多这种框架,新人来了都是先学习这些框架。

这个事情上面,我看有几个好处 * 发明的人可以在公司内部得到很高的地位,以及相应的奖励。不管好用不好用,推行一版,一波人升天。后人再升级一版,又一波人升天。 * 学会了使用这些框架的人,如果自己本身不太灵活,到了其他公司会发现无法干活,因为使用多了会有一个很深的烙印。这样离职起来就没那么方便。 * 比较好规范和控制公司内部的技术方向。

坏处 * 和最新的技术方向可能有割裂,因为并不一定能及时更新适应。 * 问题解决只能依赖内部的这些人来解决,这种东西想要推广出去毕竟还是难。 * 浪费人力做基础建设。

其实看起来,对于基层员工来看好处还是大于坏处的。毕竟如果老板不关心这个成本或者不清楚可以节约这个成本的话,那些好处还是实实在在的,牛逼有的吹。

在老板关心的方面,可能还有一个点,就是创新有时候是比较难的,但是所谓的微创新其实是简单一些,造轮子的时候,一般多少都会有一些微创新。如果搞的人确实有能力,避免一些原来设计里面的鸡肋冗余的部分,让新的设计更加轻快,其实也算是有好处。

Beansdb merge tools

Published on:
Tags: beansdb

Beansdb 是豆瓣开源出来的一个高效的支持 memcached 协议的文件存储 db。按 key 查找的时候,会有索引定位到磁盘位置。不过貌似前段时间看到说他们搞了一个新的替代这个,我找了一下没找到链接。

使用 beansdb 的时候,有 2 个问题需要解决 * 冗余问题 * 数据过期删除问题

数据冗余问题

先说第一个问题。beansdb 本身不提供分布式 hash 逻辑,它就是个单机的程序。冗余需要你自己搞定,如果你使用标准的 memcache 协议,可以有多 server 的配置,读的时候其中一个失败会自动找下一个 server,写的时候就不会了,需要你自己写到多个 server。如果你所有的 server 都是一模一样的,那多写就可以了。如果不一样,你还需要考虑自己的 hash 策略。

豆瓣提供了一个 python 的客户端,这个客户端里面其实包含了 hash 策略。通过把 key 和 server 分桶来做 hash。摘一点代码如下

BEANSDBCFG = {
    "localhost:7901": range(16),
    "localhost:7902": range(16),
    "localhost:7903": range(16),
}

db = Beansdb(BEANSDBCFG, 16)

上面定义了三个 server,每个包含 16 个桶(你可以根据你的需求比如定义第一个 server 只包含某些桶)。

def __init__(self, servers, buckets_count=16, N=3, W=1, R=1):

这里是定义写入数据的时候的逻辑,那个 buckets_count 是桶的数量,NR 貌似没用。。。,W 是改动的时候要求成功的最小 server 数量,包括删除和写入的时候。

读取的时候,会循环从包含这个 key 的桶的 server 列表里面循环读取,这里还有一个「自愈」的逻辑,循环读取直到遇到一个成功的 server,会同时把前面失败的 server 都写入一份数据。

这样下来基本就解决了读写分布式和故障恢复的逻辑了,非常巧妙。

其实针对这个问题,豆瓣还开源了个 beanseye,具体功能没有仔细研究,不过应该是上面需要客户端处理的事情都不需要考虑了。

我们开始用的时候,不知道有 beanseye,我的场景是在 perl 环境下面使用,把 python 的客户端翻译了一个 perl 的版本出来。[1] 有兴趣可以看看。

数据过期删除问题

beansdb 设计之初写入用的是 append 模式,就是说,遇到删除也是写入一条新的记录,并不会返回去修改原来的数据,所以能达到合理的 IO 速度。如果场景是大量不会删除的小文件,那么 beansdb 使用起来非常合适。

如果有数据过期或者删除的需求,就需要想办法处理这些数据了,否则的话,beandb 的数据文件里面会慢慢的有大量的无用数据,浪费磁盘空间。

这个删除过期数据的过程,我看豆瓣叫做 merge。思路其实就是把所有数据遍历一次,把有效的数据写入一个新的 data 文件,然后旧的删掉,就可以了。beansdb 的数据文件有 2 种,一种是 xxx.data,这种文件是数据文件,另外一种是 xxx.hint.qlz 这种是索引文件。

针对这个需求,我写了两版程序,第一版就是单纯的解读一下数据文件,把其中的数据的信息读出来,主要是版本号和创建时间,然后根据版本号只写入高版本的,根据创建时间把过期的数据丢弃。生成新的 data 文件之后,要删除 hint 文件,启动的时候会自动产生 hint 文件。然后在 beansdb 的机器上面定期跑这个脚本就好了,注意跑之前应该先关闭 beansdb。

第一个版本的程序只是解读了每个块的数据头,程序用起来也勉强还行,但是主要问题是,每次启动都需要重新产生 hint 文件,导致启动到提供服务很慢,所以就有了第二版程序。第二版包含了第一版的全部功能,还提供了按照文件大小来定义删除时限的功能。

第二个版本程序基本把 data 和 hint 文件产生的逻辑都用 perl 实现了(不过还没有经过太多测试)。下面简单讲讲逻辑。

data 文件

‌typedef struct data_record
{
    char *value;
    union
    {
        bool free_value;    // free value or not
        uint32_t crc;
    };
    int32_t tstamp;
    int32_t flag;
    int32_t version;
    uint32_t ksz;
    uint32_t vsz;
    char key[0];
‌} DataRecord;

数据文件里面,每个 key 对应的数据的长度是 4*6 + key_size + value_size + padding

read($fh, my $header, 4*6);
my ( $crc, $tstamp, $flag, $ver, $ksz, $vsz ) = unpack('I i i i I I', $header);

头部是 24 个字节,依次包括校验数据,写入时间戳,标记位,版本号,key 的长度,value 的长度。上面 unpack 方法第一个参数里面的含义,可以参考perl 的文档。每个 4 字节,32bit 整数。

然后是读取 $ksz 的长度的 key,读取 $vsz 长度 value。如果 $flag 标记表明 value 有压缩,压缩用的是 QLZ 算法,真实的值需要用 qlz 解压缩之后才能得到。

最后是 padding 部分,整个数据长度需要是 256 的整数倍。不足的部分,会写入 \0 做 padding。

merge 的过程不关心 value 的真实值,所以不需要解压缩,把读取到的原样写回去就可以了。另外就是 merge 的时候遇到同一个 key 多个 version 出现的时候,只保留大的那个就可以了。这样操作之后 data 文件会变小。

hint 文件

‌typedef struct hint_record
{
    uint32_t ksize:8;
    uint32_t pos:24;
    int32_t version;
    uint16_t hash;
    char key[NAME_IN_RECORD]; // allign
‌} HintRecord;

hint 文件比 data 文件稍微复杂一点,每一条记录是 key_size + data_pos + ver + hash + key + padding

my ( $ksz, $datapos, $ver, $hash ) = unpack("B8 B24 i B16", $header);

$ksz = unpack("I", pack("B32", $ksz));
$datapos = unpack("I", pack("B32", $datapos));
$datapos = $datapos << 8;
$hash = unpack("I", pack("B32", $hash));

头部的 10 个字节如上面代码,第一个 8 bit 是 key 的长度,接下来 24 个 bit 是这个 key 对应数据在 data 文件里面的位置。然后是 4 字节版本,16 bit 的 hash。

padding 和上面 data 里面的逻辑一样,按照 256 的倍数补全。

hint 文件结尾有个 .qlz,表示整个 hint 里面的数据是压缩的,所以在处理前需要先解压缩一下。(不过我看到我代码里面在读取 hint 的时候,是全部数据解压,写入的时候,是按照 record 压缩的,很奇怪)。

Release some staff at github

Published on:

把 blog 用到的模板整理了一下,放到了 https://github.com/wd/hexo-fabric ,这个最开始是 fork 别人的代码改的,后来发现原来那个人已经不用了,就整理一下,增加了一个 tag 支持,修改了一下字体和背景色,还有代码颜色等,都是一些小修改。同时也提交到了官方的 theme 库,不过 pull request 还没有通过。。

另外,还把之前写的一个给 ngx-lua 用的一个使用 mcrypt 加密解密的库 https://github.com/wd/lua-resty-mcrypt ,整理出来单独弄了一个模块。代码其实非常简单,这个也能看出来 ngx_lua 里面使用 ffi 调用 C 模块开发多舒服,不过因为 C 知识有限,可能还是会有一些问题,不过至少自己测试是 ok 的,也在线上跑了好久,只能遇到有问题的再说了。这个同时也提交到了春哥的 opm 仓库,那个倒没有审核,提交就被索引了,使用的话应该可以用 opm 命令直接安装。

Bloat and Query Speed in PostgreSQL

Published on:
Tags: postgresql

内容反义自 https://www.citusdata.com/blog/2016/11/04/autovacuum-not-the-enemy/

pg 的 mvcc 会导致表索引的 bloat 就不多说了。说一下不合理处理这种 bloat 害处是啥。

首先肯定是会浪费空间。然后也会影响查询速度。表和索引存储的时候都是 8kB 一个 page,如果一个查询一些行,数据库会加载这些 pages 到内存。一个 page 里面的 dead rows 越多,在加载的时候就越浪费 I/O。例如全表扫描会加载所有的 dead rows。

Bloat 还会导致热门的查询会一下塞满内存。会导致相同的 live rows 需要更多 pages。This causes swapping and makes certain query plans and algorithms ineligible for execution.

还有一个影响是,pg 的系统表也会有可能 bloat,因为他们也是表。导致这个的一种情况是频繁的创建和删除临时表。这个进一步会导致一些管理命令执行变慢,甚至比如 \d 这种命令。

索引也有可能会 bloat。索引是 tuple 标识和数据之间的一个映射。这些标识指向的是某个 page 里面的 offset。每个 tuple 都是一个独立的对象,需要自己的索引条目。更新一行的时候总是会创建这行的新的索引条目。

索引的 bloat 的影响比 table 小一点。索引里面指向 dead tuple 的可以直接标记为 dead. 这会使得索引膨胀,但是不会导致不必要的堆查找。同时更新堆中的 tuples 不影响已经索引的列,使用一种叫做 HOT 的技术来把指向 dead tuples 的指针指向新的。这允许查询可以通过这些指针复用旧的索引条目。(Also updates to tuples in the heap that do not affect the indexed column(s) use a technique called HOT to provide pointers from the dead tuple to its replacement. This allows queries to reuses old index entries by following pointers across the heap.) (没太看明白.)

索引 bloat 的问题还是应该需要重视。例如 btree 索引是由二叉树组成()。叶子节点包含值和 tuple 标识(应该是指在 data file 的 offset)。随机更新因为会重用 page,所以可以保持 btree 维持一个良好的形状。但是,如果是单侧更新,会导致大量的空页。

The size considerations of index bloat are still significant. For instance a btree index consists of binary tree of pages (the same sized pages as you find holding tuples in the heap). The leaf node pages contain values and tuple identifiers. Uniform random table updates tend to keep a btree index in pretty good shape because it can reuse pages. However lopsided inserts/updates affecting one side of the tree while preserving a few straggling entries can lead to lots of mostly empty pages.

Full page write in PostgreSQL

Published on:
Tags: postgresql

读了一篇文章,简单翻译总结下。

Partial Writes / Torn Pages

pg 默认是 8kB 一个 page。linux 文件系统一般是 4kB(x86 里面最大是 4kB),老设备驱动一般是 512B 一个扇区,新的设备有些支持 4kB 或者 8kB。

当 pg 写入一个 page 8kB 的时候,系统的底层会拆分小一点块,这里涉及到写入的原子性。8kB 的 pg page,会被文件系统拆分成 4kB 的块,然后拆分成 512B 扇区大小。这个时候如果系统崩溃(比如停电,内核 bug)会发生什么?

即使系统的存储有针对这种情况的设计(比如 SSD 自带电容器,RAID 控制器自带电池),内核那块也是会拆分成 4kB 的 page,所以还是有一定可能性,pg 写了 8kB,但是只有部分写入成功。

这个时候你可能意识到这就是为啥我们要有事务日志(WAL)。所以当系统崩溃重启之后,数据库会读取 WAL(从最后一次 checkpoint),然后重新写入一遍,以保证数据文件是完整的。

恢复的时候,在修改一个 page 之前,还是会读取一下。

在 checkpoint 之后第一次修改一个 page 的时候,会把整个 page 写入 WAL。这是为了保证在恢复的时候,能保证这些被修改的 page 能完全恢复到他原有的样子。

写放大

如果打开 Full page write,很显然会导致 WAL 文件增加,因为就算修改一个字节,也会导致 8kB page 的写入。因为 Full page write 只发生在 checkpoint 之后的第一次写入,所以减少 checkpoint 的发生频率是可以减少写入的。

UUID vs BIGSERIAL 主键

比较了一下使用 UUID 或者 bigserial 做主键对写入的影响。可以看原链接的图,会发现在 INSERT 语句的情况下 UUID 产生的 WAL 文件量比较多。主要原因是 Btree 索引的情况下,bigserial 是顺序的维护这个索引,UUID 是无顺序的,会导致维护索引产生的数据量不同。

如果是使用 UPDATE 随机修改,那么会发现产生的 WAL 数量就差不多了。

8kB and 4kB pages

如果减小 pg 的 page 的大小,可以减小 WAL 数量。从 8kB 减小到 4kB,上面 UUID 那个例子,可以减少大概 35% 的量。

需要 full-page write 吗?

首先,这个参数是 2005 年 pg 8.1 引入的,那么现代的文件系统是不是已经不用操心部分写入的情况了?作者尝试了一些测试没有测试出来部分写入的情况,当然这不表示不会存在。但是就算是存在,数据的一致性校验也会是有效的保护(虽然并不能修复这个问题,但是至少能让你知道有坏的 page)

其次,现在很多系统都依赖于流式同步,并不会等着有问题的服务器在有硬件问题的时候重启,并且花费很多时间恢复,一般都直接切换到热备服务器上面了。这个时候部分写就不是什么问题了。但是如果我们都推荐这么做,那么「我也不知道为啥数据损坏了,我只是设置了 full_page_writes=off」这种会是 DBA 死前最常见的言论了。(类似于「这种蛇我之前在 reddit 看见过,无毒的」)

总结

对于 full-page write 你没法直接优化。大部分情况下,full-page write 都是发生在 checkpoint 之后,直到下一次 checkpoint。所以调整 checkpoint 的发生频率不要太频繁很重要。

有些应用层的操作,可能会导致对表或者索引的随机写入的增加,例如上面的 UUID 的值就是随机的,会让简单的 INSERT 也会导致索引的随机 update。使用 Bigserial 做主键(让 UUID 做替代键)可以减少写放大。

使用 pgrepup 跨版本升级 pg

Published on:
Tags: postgres

pgrepup 其实是一个支持 pg 跨版本复制的工具。而 pg 大版本升级需要停机是个比较郁闷的事情,如果能通过这个解决就实在太好了。下面测试了一下。

安装

需要安装 pgrepuppglogical

安装 pgrepup

pgrepup 官方说是支持 python >= 2.7 的版本,我自己测试的结果,python 3.5 里面执行有点问题,需要修改几个地方。但是在 python 2.7 里面,不需要做任何修改,所以建议使用 python 2.7。安装很简单,执行 pip install pgprepup 就可以了。

安装 pglogical

需要给你的 pg 安装这个扩展。高版本的和低版本的都需要安装。

安装也很简单,下载源码,执行 PATH=/opt/pg96/bin:$PATH make USE_PGXS=1 install 就好了。如果是给 pg95 装,那就把路径改成 pg95。

可以参考这里

配置

配置 db

先给几个 db 定义一下角色。db1 假设为 9.5 版本,db2 假设为 9.6 版本。

pgrepup 允许 db1, db2 和执行 pgrepup 所在的机器分别在不同的机器,也可以在相同的机器,看机器情况。

对于 db,最小配置的 postgres.conf 修改如下,我测试的时候两个 db 在一台机器上面,只需要修改 port 不一样就可以了。

listen_addresses = '*'          # what IP address(es) to listen on;
port = 5495
wal_level = logical # minimal, archive, hot_standby, or logical
max_wal_senders = 3             # max number of walsender processes
max_replication_slots = 3       # max number of replication slots
shared_preload_libraries = 'pglogical'          # (change requires restart)

## 下面几个参数不是必须设置的
logging_collector = on          # Enable capturing of stderr and csvlog
log_filename = 'postgresql-%Y-%m-%d.log'        # log file name pattern,

pg_hba.conf 如下,修改其中的 client_ip 和 db_ip 为对应的真实 ip。

host all all client_ip/32 md5
host replication pgrepup_replication db_ip/32 md5
host all pgrepup_replication db_ip/32 md5

配置好之后,启动 db1 和 db2 看看是不是可以正常连接。

还需要建立用户。如果已经存在一个 super 的用户,那也可以直接用那个用户,没有的话,就建一个。db1 和 db2 都需要建立,可以是不同的用户。

hint

当然,如果我们在生产环境里面做这个事情,那肯定会是 db1 已经是一个存在的 db,只需要增加原来没有的配置就好了。db2 会是一个全新的 db,使用 initdb 初始化,之后配置上面的配置项(当然,如果是将来要给生产用,那应该是复制 db1 的配置文件过来,修改端口就可以了,其他都一样)。

配置 pgrepup

执行一下 pgrepup config

❯❯❯ pgrepup config
Pgrepup 0.3.7
Create a new pgrepup config
Configuration filename [~/.pgrepup] ./pgrepup.config
Security
Do you want to encrypt database credentials using a password? [Y/n] n
Folder where pgrepup store temporary dumps and pgpass file [/tmp] ./tmp
Source Database configuration
Ip address or Dns name: db_ip
Port: 5495
Connect Database: [template1]
Username: wd
Password:
Destination Database configuration
Ip address or Dns name: db_ip
Port: 5496
Connect Database: [template1]
Username: wd
Password:
Configuration saved to ./pgrepup.config.
You can now use the check command to verify setup of source and destination databases

之后会产生一个配置文件 pgrepup.config,有修改的话,可以打开再次编辑。

之后,可以执行一下 pgrepup check 来检查一下

❯❯❯ pgrepup -c pgrepup.config check
Pgrepup 0.3.7
Global checkings...
 >  Folder ./tmp exists and is writable ..........................................OK
Checking Source...
 >  Connection PostgreSQL connection to db_ip:5495 with user wd OK
 >  pglogical installation .......................................................KO

    Hint: Install docs at https://2ndquadrant.com/it/resources/pglogical/pglogical-installation-instructions/

 >  Needed wal_level setting .....................................................OK
 >  Needed max_worker_processes setting ..........................................OK
 >  Needed max_replication_slots setting .........................................OK
 >  Needed max_wal_senders setting ...............................................OK
 >  pg_hba.conf settings .........................................................KO
    Hint: Add the following lines to /home/wd/data95/pg_hba.conf:
        host replication pgrepup_replication db_ip/32 md5
        host all pgrepup_replication db_ip/32 md5
    After adding the lines, remember to reload postgreSQL
 >  Local pg_dumpall version .....................................................OK
 >  Source cluster tables without primary keys
 >      template1 ................................................................OK
 >      testdb
 >          public.t1 ............................................................OK
 >      postgres .................................................................OK
Checking Destination...
 >  Connection PostgreSQL connection to db_ip:5496 with user wd OK
 >  pglogical installation .......................................................KO

    Hint: Install docs at https://2ndquadrant.com/it/resources/pglogical/pglogical-installation-instructions/

 >  Needed wal_level setting .....................................................KO
    Hint: Set wal_level to logical
 >  Needed max_worker_processes setting ..........................................OK
 >  Needed max_replication_slots setting .........................................KO
    Hint: Increase max_replication_slots to 3
 >  Needed max_wal_senders setting ...............................................OK
 >  pg_hba.conf settings .........................................................KO
    Hint: Add the following lines to /home/wd/data96/pg_hba.conf:
        host replication pgrepup_replication db_ip/32 md5
        host all pgrepup_replication db_ip/32 md5
    After adding the lines, remember to reload postgreSQL
 >  Local pg_dumpall version .....................................................OK

上面是我第一次执行 check 的结果,可以看到很多红色的 KO,有些下面还有 hint 提示告诉你怎么修复,针对红色的信息进行修复就好了。

❯❯❯ pgrepup -c pgrepup.config check
Pgrepup 0.3.7
Global checkings...
 >  Folder ./tmp exists and is writable ..........................................OK
Checking Source...
 >  Connection PostgreSQL connection to db_ip:5495 with user wd ...OK
 >  pglogical installation .......................................................OK
 >  Needed wal_level setting .....................................................OK
 >  Needed max_worker_processes setting ..........................................OK
 >  Needed max_replication_slots setting .........................................OK
 >  Needed max_wal_senders setting ...............................................OK
 >  pg_hba.conf settings .........................................................OK
 >  Local pg_dumpall version .....................................................OK
 >  Source cluster tables without primary keys
 >      template1 ................................................................OK
 >      testdb
 >          public.t1 ............................................................OK
 >      postgres .................................................................OK
Checking Destination...
 >  Connection PostgreSQL connection to db_ip:5496 with user wd ...OK
 >  pglogical installation .......................................................OK
 >  Needed wal_level setting .....................................................OK
 >  Needed max_worker_processes setting ..........................................OK
 >  Needed max_replication_slots setting .........................................OK
 >  Needed max_wal_senders setting ...............................................OK
 >  pg_hba.conf settings .........................................................OK
 >  Local pg_dumpall version .....................................................OK

上面是我修复之后执行的结果。其中会提示会被同步的 db(上面是 template1, testdb, postgres)。之后执行 setup

❯❯❯ pgrepup -c pgrepup.config setup
Pgrepup 0.3.7
Check if there are active subscriptions in Destination nodes .....................OK
Global tasks
 >  Remove nodes from Destination cluster
 >      postgres .................................................................OK
 >      template1 ................................................................OK
 >      testdb ...................................................................OK
 >  Create temp pgpass file ......................................................OK
 >  Drop pg_logical extension in all databases of Source cluster
 >      template1 ................................................................OK
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
 >  Drop pg_logical extension in all databases of Destination cluster
 >      postgres .................................................................OK
 >      template1 ................................................................OK
 >      testdb ...................................................................OK
Setup Source
 >  Create user for replication ..................................................OK
 >  Dump globals and schema of all databases .....................................OK
 >  Setup pglogical replication sets on Source node name
 >      template1 ................................................................OK
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
Setup Destination
 >  Create and import source globals and schema ..................................OK
 >  Setup pglogical Destination node name
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
 >      template1 ................................................................OK
Cleaning up
 >  Remove temporary pgpass file .................................................OK
 >  Remove other temporary files .................................................OK

然后执行 start

❯❯❯ pgrepup -c pgrepup.config start
Pgrepup 0.3.7
Start replication and upgrade
 >  postgres .................................................................OK
 >  template1 ................................................................OK
 >  testdb ...................................................................OK

可以通过 status 看同步状态

❯❯❯ pgrepup -c pgrepup.config status
Pgrepup 0.3.7
Configuration
 >  Source database cluster ......................................................OK
 >  Destination database cluster .................................................OK
Pglogical setup
 >  Source database cluster
 >      template1 ................................................................OK
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
 >  Destination database cluster
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
 >      template1 ................................................................OK
Replication status
 >  Database postgres
 >      Replication status ..............................................replicating
 >  Database testdb
 >      Replication status ..............................................replicating
 >  Database template1
 >      Replication status ..............................................replicating
 >  Xlog difference (bytes) ...................................................57816

可以看到三个 db 都在同步。这个时候在 db1 上面插入数据,能在 db2 上面看到会同步过去。

状态有三种情况 * initializing: pglogical 正在 copy 数据 * replication: 同步状态 * down: 同步断开了,需要检查日志修复

需要注意的问题

db 里面的表都需要有主键

如果存在没有主键的表,执行 check 的时候会看到下面的信息

 >  Source cluster tables without primary keys
 >      template1 ................................................................OK
 >      testdb
 >          public.t2 ............................................................KO
    Hint: Add a primary key or unique index or use the pgrepup fix command
 >          public.t1 ............................................................OK
 >      postgres .................................................................OK

如果不解决就执行 setup,会提示下面的信息

Setup Source ........................................Skipped, configuration problems
Setup Destination
 >  Create and import source globals and schema .............................Skipped
 >  Setup pglogical Destination node name
 >      postgres .................................................................OK
 >      template1 ................................................................OK
 >      testdb ...................................................................OK

可以自己创建一个主键重新 check,也可以执行 fix 来修复,然后再次执行 setup。

 ❯❯❯ pgrepup -c pgrepup.config fix
Pgrepup 0.3.7
Find Source cluster's databases with tables without primary key/unique index...
 >  template1 ....................................................................OK
 >  postgres .....................................................................OK
 >  testdb
 >      Found public.t2 without primary key ................Added __pgrepup_id field

通过 fix 加的主键,在 uninstall 的时候会被删除。

Replication status .. down

有时候会遇到有的 db 的状态是好的,有的 db 是 down 的情况

Replication status
 >  Database postgres
 >      Replication status ..............................................replicating
 >  Database testdb
 >      Replication status .....................................................down
 >  Database template1
 >      Replication status ..............................................replicating
 >  Xlog difference (bytes) ..................................................614096

在同步状态下面,如果给某个 db 加一个没有主键的表,就会导致同步断掉。修复方法是先 stop,然后执行 check,按照提示修复,然后执行 setup,然后 start 就可以了。

官方列出来的几个问题

  • DDL 命令。不会同步 DDL 命令,可以在 db1 试试看 pglogical.replicate_ddl_command
  • seq 序列。执行 stop 命令的时候,会在目标 db 的 seq 上面加 1000。
  • 有大量的 db。执行 start 命令之后,pglogical 会每个 db 启动一个 worker 来同步数据,要是 db 比较多会导致比较高的负载。

因为这个是基于 pglogical 的,所以还需要关注 pglogical 列出来的一些限制 第 4 部分 Limitations and Restrictions。 * 4.1 Superuser is required * 4.2 UNLOGGED and TEMPORARY not replicated * 4.3 One database at a time * 4.4 PRIMARY KEY or REPLICA IDENTITY required * 4.5 Only one unique index/constraint/PK * 4.6 DDL * 4.7 No replication queue flush * 4.8 FOREIGN KEYS * 4.9 TRUNCATE * 4.10 Sequences * 4.11 Triggers * 4.12 PostgreSQL Version differences * 4.13 Doesn’t replicate DDL

pgrepup uninstall

uninstall 会清理 pgrepup 创建的一些信息,比如安装的 pglogical 扩展,创建用来同步的用户,和通过 fix 命令添加的 seq。

❯❯❯ pgrepup -c pgrepup.config uninstall
Pgrepup 0.3.7
Check active subscriptions in Destination nodes
 >  template1 ...............................................................Stopped
 >  testdb ..................................................................Stopped
 >  postgres ................................................................Stopped
Uninstall operations
 >  Remove nodes from Destination cluster
 >      postgres .................................................................OK
 >      testdb ...................................................................OK
 >      template1 ................................................................OK
 >  Drop pg_logical extension in all databases
 >      Source
 >          template1 ............................................................OK
 >          postgres .............................................................OK
 >          testdb ...............................................................OK
 >      Destination
 >          postgres .............................................................OK
 >          testdb ...............................................................OK
 >          template1 ............................................................OK
 >  Drop user for replication ....................................................OK
 >  Drop unique fields added by fix command
 >          template1
 >          postgres
 >          testdb
 >              public.t1 ........................................................OK
 >              public.t2 ........................................................OK

升级

如果前面配置好了同步状态,那剩下的事情就简单了。 * 停止应用链接 db1 * 确保 db1 已经没有任何链接 * 使用 pgrepup stop 停止 replication * 修改应用链接到 db2 * 启动应用 * 剩下的就是处理掉停止的 db1

参考文档

http://qiita.com/yteraoka/items/e82e4d28f6a23915d190

Built in sharding in PostgreSQL

Published on:
Tags: postgresql

PostgreSQL 内建 sharding 支持,粗略翻译自 https://wiki.postgresql.org/wiki/Built-in_Sharding

Introduction

内建支持 sharding 最大的挑战是,如何用最小的代码修改实现。大部分社区的 sharding 修改支持都修改了很多 PostgreSQL 的代码,这也导致这些不能被 Postgres 社区那些不需要 sharding 的人接受。有了 FDW 之后,就有了在有限代码修改情况下实现内建 sharding 支持的可能。

基于 FDW 的这种 sharding 设计,是基于 NTT 开发的 Postgres-XC,大概已经有 10 年了。Postgres-XL 是基于这个设计的一种更加灵活的实现。

Enhance Existing Features

  • 已完成?提升 FDW 的基础设计和 postgres_fdw。特别的,好的性能要求合理的把一些操作推送到子节点(foreign shards)。在 Postgres 9.6 中,join, sort, update, delete 都可以推送到字节点了。聚合的 pushdown 将在 Postgres 10 中支持。FDW 表已经可以作为继承表出现。
  • 提升分区支持有效提升 existence of shards。幸运的是,单节点的分区支持也需要重构才能提升性能和更多优化。例如,executor-based partition pruning.
  • 给 FDW 请求增加并行支持。这样能允许节点并行执行,这个可能会通过多个异步的链接来实现。

New Subsystems

还需要开发一些子系统: * 允许表可以复制到所有节点,以允许更多的 join pushdown。这个可以通过 trigger 或者逻辑复制来完成。 * 实现一个子模块,以使用新的分区系统表来提交符合提交的查询的 FDW 查询。 * 实现一个子模块收集 FDW 查询的结果返回给用户。 * 实现全局事务管理器以便更加高效的允许子节点原子的提交事务。这个可能会通过 prepared 的事务来实现,还有某种在 crash 之后清理那些 preapared 的事务的事务管理器。例如 XA。 * 实现全局快照管理器,以允许子节点可以看到一致性的快照。(是不是 serialisable 事务模式会避免跨节点快照冲突?pg_export_snapshot() 或者 hot_standby_feedback 是不是会有帮助?) 多节点的备份的一致性也需要这个支持。 * 实现支持 create, manage, report on shards 这些用户 API。

Use Cases

有四种可能的用户案例和不同的需求: * 跨节点在只读节点上面执行只读聚合查询,例如数据仓库 这种是最简单的场景,不需要全局事务管理,全局快照管理,并且因为聚合,所以子节点返回的数据量也是最小的。 * 跨节点在只读节点上面执行只读非聚合查询 这种会给调度节点压力,需要收集和处理很多子节点返回的数据。这种也能看到 FDW 传输机制等级。 * 跨节点在可读写节点执行只读查询 这个需要全局快照管理来保证子节点返回数据的一致性 * 跨节点执行读写查询 这个需要全局快照管理器和全局事务管理器

申请 google voice

Published on:
Tags: google

昨天晚上突然想申请一个 google voice 帐号。有了之后就可以作为国外的号码使用了,可以打电话收短信,想想好像还有点用。比如我就可以用他注册一个微博帐号用来关联一些无聊的服务了。。。

申请的过程网上很多,不细说了。主要注意下面几点。

  1. 需要美国 ip 打开 https://www.google.com/voice ,否则会跳转到一个帮助页面。这个从网上搜一些免费的代理就可以了。我是搜索到了一些 ss 帐号,然后配合 surge 来做的。
  2. 需要一个能接电话的美国号码来接受 google voice 的验证电话。这个我是通过 http://textnow.com 来做的。登录 textnow.com 注册一个免费的帐号,其中有一步是需要输入一个美国区号,这个要注意,我第一次注册的时候,输入的是 213 (洛杉矶地区的),然后就不行,后来又申请了一个 517 的就可以。所以最好搜索一下别人申请成功的区号有哪些,另外还好就是只要有不同的邮箱,就可以多次注册来换号码,如果遇到不行的,可以换个邮箱重新注册一下。
  3. 在 google voice 里面填入电话之后。google voice 会打电话给那个号码。我用 textnow 的 ios 客户端接的电话(建议不要用他们的 web,他们的 web 必须要你的浏览器支持 flash 才行,很恶心),我遇到的情况下,前几次接到的电话没有对方的声音,自己尝试直接输入号码也不行。后来多试几次就好了。
  4. 后面就是选号了。选好号码之后,点提交一般都会遇到一个错误,「There was an error with your request. Please try again」, 遇到这个是正常的。需要你多次点击那个按钮提交,有人用按键精灵点了几个小时搞定了。我这是打开 chrome 的 dev tools,然后看 network 里面发的请求,每次点击都会有一个 post 请求,在上面按右键,选择 「copy as curl」,然后在命令行写一个简单的程序,while true; do 这里把复制的内容贴过来 ;sleep 1.5s; done,复制到命令行,不停的重试就可以了,直到收到邮件说你的号码开通了。要注意的是命令行也得设置代理,一般是通过 export。

新与旧

Published on:
Tags: heart

北京有个古北水镇,运营上据说是杭州乌镇的那拨人。乌镇没去过,去过古北水镇。在一片山里面,长城下面,一群建筑。里面还开了一些模仿古代的商店,比如酒家,染坊这些可以参观。

这个地方离北京大概是 2 个多小时的车程(全程高速不怎么堵车的情况下)。周末过去之后会发现停车场停满了车,还有好多旅游车,水镇上面也人山人海。

这里面的建筑都是后期开发商人工开辟的。景区里面的酒店基本都是1k起,并且还得提前预约。据说夜景很漂亮,不过我没看过。

北京是个古老的城市,如果城区里面的历史建筑都留着,现在可够你转几个月的,光就绕着北京城墙走一圈,估计一天都不一定可以。曾经在西安的城墙上面走过一圈,感觉还不错,我记得还收了门票的。

北京有个南锣鼓巷,过去转你会发现并没有什么能让你理解回味京味的东西,卖的也是羊肉串奶茶这些,排好长的队买一串拿着边吃边走完了,其实可能也挺没意思的。

都说台湾是中华传统保持比较好的地方。去了台北第一印象就是,破房子挺多的。那边房子是私产,所以拆迁很难。然后个人又不一定有能力翻盖,所以就有破烂的房子。给我们开车的台湾人还说去过北京,说羡慕那边的高楼大厦,到处都很新,很时髦的样子。

台湾有很多夜市,去过几个,有的比较商业化的,你会发现周边也都是高楼了。有的就是普通的,真的是一条路白天行车,晚上就堵起来开始摆摊。我记得某个夜市里面有个摊位,说是开了好多年了,现在物价高,不得已只好比早年涨了几毛钱。看着都震惊了,涨几毛钱还废什么话,况且本身人家卖的也不贵,都是良心价。

地价涨没那么快,可能各种基础花费都会比较稳定,否则地价涨了房租涨了,那物价必定会涨。所以10年前我在北京长椿街那上班的时候,一份盖饭,大概是 7,8 块钱。到了现在,估计是翻一倍。另外这种店还越来越少,因为卫生条件,房租这些要求导致价格底了不好赚钱。

台湾夜市里面也经常能碰见那种几十年的老店,那真的是几十年一直在做那个生意。几十年价格也没有变化太多。想起来前门的那个面馆前段时间关门了,没有办法,涨价没法涨,收入基本不变的情况下,地价变化太大,只好关门了。

就目前这个房地产的情况,北京还能有多少真正的老字号,有多少真正的老街,估计基本不会有了。后面估计会有更加多人工的景区了,费用估计还不能便宜了。

想起来凤凰古城了,大家不愿意拆,那就某天一把火烧了,这下都同意了吧,这可是天意。

python 的 decorator 学习

Published on:

最近学习了一下 python 的 decorator(装饰器),看的是这篇,Python修饰器的函数式编程, 觉得挺有意思的,写点东西记录一下。

装饰器简单讲就是返回一个函数的函数/类。看个简单的例子。

#!/usr/bin/python
# -*- coding: utf-8 -*-


def dec1(fn):
    print('inside dec1')

    def wrapper():
        print('inside wrapper')
        return fn()
    return wrapper


@dec1
def f1():
    print('inside f1')

if __name__ == '__main__':
    print('begin exec')
    f1()
    print('end exec')

# 执行结果:
# inside dec1
# begin exec
# inside wrapper
# inside f1
# end exec

看上面例子能看到,装饰器生效有 2 个步骤,第一个是装饰,第二个是执行。上面装饰器的效果,和下面的代码的效果是一样。

#!/usr/bin/python
# -*- coding: utf-8 -*-


def dec1(fn):
    print('inside dec1')

    def wrapper():
        print('inside wrapper')
        return fn()
    return wrapper


# @dec1
def f1():
    print('inside f1')

if __name__ == '__main__':
    print('begin exec')
    dec1(f1)()
    print('end exec')

# 执行结果:
# begin exec
# inside dec1
# inside wrapper
# inside f1
# end exec

可以看到除了 「begin/end exec」,其他部分执行结果是一样的。所以理解装饰器,就把 @dec1 换成 dec1(fn)() 这么理解就可以了。

有时候会看到类也可以作为装饰器使用。其实理解起来也类似。举个例子。

#!/usr/bin/python
# -*- coding: utf-8 -*-


class dec1(object):
    def __init__(self, fn):
        print('inside dec1')
        self.fn = fn

    def __call__(self):
        print('inside wrapper')
        return self.fn()


@dec1
def f1():
    print('inside f1')

if __name__ == '__main__':
    print('begin exec')
    f1()
    print('end exec')

# 执行结果:
# inside dec1
# begin exec
# inside wrapper
# inside f1
# end exec

这里和上面类似,把 @dec1 理解成 dec1(fn)(),不过是这里的 dec1 是个类,那么 dec1(fn) 其实是调用的 dec1.__init__(fn),那么后续的 dec1(fn)() 就是调用产生的对象的 dec1.__call__() 了。

有时候还能看到加了参数的装饰器。加了参数的是怎么回事呢。再看下面的例子。

#!/usr/bin/python
# -*- coding: utf-8 -*-


def dec1(name):
    print('inside dec1')

    def real_dec1(fn):
        def wrapper():
            print('inside wrapper')
            return fn()
        return wrapper
    return real_dec1


@dec1(name='1')
def f1():
    print('inside f1')

if __name__ == '__main__':
    print('begin exec')
    f1()
    print('end exec')

# 执行结果:
# inside dec1
# begin exec
# inside wrapper
# inside f1
# end exec

看懂了没有,就是多了个嵌套而已。遇到加了参数的,那就是把之前的没有参数的部分返回回来就可以了。等价的例子就不贴了,这个等价于 dec1(name='1')(fn)()

如果是类装饰器,并且有参数,那等价于 dec1(name='1')(fn)(),其中 __init__(self, name) 先处理第一层参数,然后 __call__(fn) 处理第二层,然后需要在 __call__ 里面再定义一个 wrapper 返回。

说明白没有?呵呵。