Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2909855
  • 博文数量: 199
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 4126
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-06 19:06
个人简介

半个PostgreSQL DBA,热衷于数据库相关的技术。我的ppt分享https://pan.baidu.com/s/1eRQsdAa https://github.com/chenhuajun https://chenhuajun.github.io

文章分类

全部博文(199)

文章存档

2020年(5)

2019年(1)

2018年(12)

2017年(23)

2016年(43)

2015年(51)

2014年(27)

2013年(21)

2011年(1)

2010年(4)

2009年(5)

2008年(6)

分类: Mysql/postgreSQL

2016-01-04 00:40:21

1. 引言

曾经有篇流传较广的文章Don’t Assume PostgreSQL is Slow 展示了PostgreSQL生成序列的速度不亚于redis的INCRs。而在此之前我就曾做过相关的测试(参考PostgreSQL的序列的性能验证),发现PG生成序列的速度远高于同类的关系数据库根据PostgreSQL的序列的性能验证 中测试结果,在没有启用序列cache的情况下,PG的每次调用nextval('seq1')的额外时间消耗大概是0.3us,也就是333w/s,所以即使做批量数据加载也不用担心序列拖后腿;而Oracle的nocache序列生成速度大概只有5w/s,当Oracle序列 cache了50以上时,速度才开始接近pg
这个结果很惊人,但细一想,PG快得有点离谱为什么这么说?因为当时测试的select nextval('seq1')在4核虚机上达到了7w/s的qps,而那个测试环境估计支撑不了这么高的iops,所以猜测PG一定对序列做了某种优化而不是每次刷盘。

2. 代码分析

关键代码见src/backend/commands/sequence.c的nextval_internal()函数,有个叫SEQ_LOG_VALS的常量,控制PG每产生32个序列值才记一次WAL。这相当于PG对序列做了全局缓存,而PG的create sequence语法上的cache是指每个进程(也就是连接)的本地cache。由于全局缓存优化的已经足够好了,所以一般不需要再启用本地cache。

src/backend/commands/sequence.c

点击(此处)折叠或打开

  1.     fetch = cache = seq->cache_value;
  2.     log = seq->log_cnt;
  3. ...
  4.     /*
  5.      * Decide whether we should emit a WAL log record. If so, force up the
  6.      * fetch count to grab SEQ_LOG_VALS more values than we actually need to
  7.      * cache. (These will then be usable without logging.)
  8.      *
  9.      * If this is the first nextval after a checkpoint, we must force a new
  10.      * WAL record to be written anyway, else replay starting from the
  11.      * checkpoint would fail to advance the sequence past the logged values.
  12.      * In this case we may as well fetch extra values.
  13.      */
  14.     if (log < fetch || !seq->is_called)//此处fetch值为1.每次调nextval()log_cnt会递减,减到0时设置logit标志位
  15.     {
  16.         /* forced log to satisfy local demand for values */
  17.         fetch = log = fetch + SEQ_LOG_VALS;
  18.         logit = true;
  19.     }
  20.     else
  21.     {
  22.         XLogRecPtr    redoptr = GetRedoRecPtr();

  23.         if (PageGetLSN(page) <= redoptr)
  24.         {
  25.             /* last update of seq was before checkpoint */
  26.             fetch = log = fetch + SEQ_LOG_VALS;
  27.             logit = true;
  28.         }
  29.     }
  30. ...
  31.     if (logit && RelationNeedsWAL(seqrel))
  32.     {
  33.         xl_seq_rec    xlrec;
  34.         XLogRecPtr    recptr;

  35.         /*
  36.          * We don't log the current state of the tuple, but rather the state
  37.          * as it would appear after "log" more fetches. This lets us skip
  38.          * that many future WAL records, at the cost that we lose those
  39.          * sequence values if we crash.
  40.          */
  41.         XLogBeginInsert();
  42.         XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT);

  43.         /* set values that will be saved in xlog */
  44.         seq->last_value = next;//WAL中记录的last_value是下一轮的序列值,所以pg crash再通过WAL恢复后,新产生的序列会跳过几个值
  45.         seq->is_called = true;
  46.         seq->log_cnt = 0;

  47.         xlrec.node = seqrel->rd_node;

  48.         XLogRegisterData((char *) &xlrec, sizeof(xl_seq_rec));
  49.         XLogRegisterData((char *) seqtuple.t_data, seqtuple.t_len);

  50.         recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG);

  51.         PageSetLSN(page, recptr);
  52.     }

3. 实测验证

3.1 WAL写入时机

PG通过内部log_cnt计数器控制是否要记录序列更新的WAL,新建的序列,计数器初始值为0。
  1. postgres=# create sequence seq1;
  2. CREATE SEQUENCE
  3. postgres=# \d seq1
  4.             Sequence "public.seq1"
  5.     Column | Type | Value
  6. ---------------+---------+---------------------
  7.  sequence_name | name | seq1
  8.  last_value | bigint | 1
  9.  start_value | bigint | 1
  10.  increment_by | bigint | 1
  11.  max_value | bigint | 9223372036854775807
  12.  min_value | bigint | 1
  13.  cache_value | bigint | 1
  14.  log_cnt | bigint | 0
  15.  is_cycled | boolean | f
  16.  is_called | boolean | f

取得第一个序列值后,log_cnt变成32。
  1. postgres=# select nextval('seq1');
  2.  nextval
  3. ---------
  4.        1
  5. (1 row)

  6. postgres=# \d seq1
  7.             Sequence "public.seq1"
  8.     Column | Type | Value
  9. ---------------+---------+---------------------
  10.  sequence_name | name | seq1
  11.  last_value | bigint | 1
  12.  start_value | bigint | 1
  13.  increment_by | bigint | 1
  14.  max_value | bigint | 9223372036854775807
  15.  min_value | bigint | 1
  16.  cache_value | bigint | 1
  17.  log_cnt | bigint | 32
  18.  is_cycled | boolean | f
  19.  is_called | boolean | t

这个过程中,通过strace监视"wal writer process"进程,可以发现发生了WAL写入和刷盘。
  1. [root@localhost ~]# strace -efsync,write,fdatasync -p 2997
  2. Process 2997 attached
  3. --- SIGUSR1 {si_signo=SIGUSR1, si_code=SI_USER, si_pid=3209, si_uid=1001} ---
  4. write(11, "\0", 1) = 1
  5. write(3, "\207\320\5\0\1\0\0\0\0\300\273\t\0\0\0\0I\10\0\0\0\0\0\0\n\0\0004-\0\0\0"..., 8192) = 8192
  6. fdatasync(3) = 0

以后每次获取序列值,log_cnt会减1,但只有log_cnt减到0,并从0重新跳到32的时候,strace中才能看到WAL写入。
  1. postgres=# select nextval('seq1');
  2.  nextval
  3. ---------
  4.        2
  5. (1 row)

  6. postgres=# \d seq1
  7.             Sequence "public.seq1"
  8.     Column | Type | Value
  9. ---------------+---------+---------------------
  10.  sequence_name | name | seq1
  11.  last_value | bigint | 2
  12.  start_value | bigint | 1
  13.  increment_by | bigint | 1
  14.  max_value | bigint | 9223372036854775807
  15.  min_value | bigint | 1
  16.  cache_value | bigint | 1
  17.  log_cnt | bigint | 31
  18.  is_cycled | boolean | f
  19.  is_called | boolean | t


3.2 PG crash后的序列值

PG在记录序列的WAL时,记录的是当前值+32。所以如果PG crash再恢复后,将跳过一部分从未使用的序列值。这样做避免了产生重复序列的可能,但不能保证序列的连续,这是优化WAL写入而付出的必要代价。
下面是使用kill -9杀PG进程的情况。

点击(此处)折叠或打开

  1. postgres=# select nextval('seq1');
  2.  nextval
  3. ---------
  4.       20
  5. (1 row)

  6. postgres=# \d seq1
  7.             Sequence "public.seq1"
  8.     Column | Type | Value
  9. ---------------+---------+---------------------
  10.  sequence_name | name | seq1
  11.  last_value | bigint | 20
  12.  start_value | bigint | 1
  13.  increment_by | bigint | 1
  14.  max_value | bigint | 9223372036854775807
  15.  min_value | bigint | 1
  16.  cache_value | bigint | 1
  17.  log_cnt | bigint | 28
  18.  is_cycled | boolean | f
  19.  is_called | boolean | t

  20. postgres=# select nextval('seq1');
  21. server closed the connection unexpectedly
  22.     This probably means the server terminated abnormally
  23.     before or while processing the request.
  24. The connection to the server was lost. Attempting reset: Succeeded.
  25. postgres=# select nextval('seq1');
  26.  nextval
  27. ---------
  28.       49
  29. (1 row)
不过,正常关闭或重启PG是不会出现这种问题的。


阅读(4570) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~