postgresql , gin , 倒排索引 , 全文检索 , 性能优化
postgresql gin索引接口常被用于多值列的检索,例如全文检索类型、数组类型。
有兴趣了解更多索引接口的原理和使用场景,可以参考下文。
<a href="https://github.com/digoal/blog/blob/master/201706/20170627_01.md">《postgresql 9种索引的原理和应用场景》</a>
今天要说道一下postgresql gin索引的代码优化。
在说gin代码优化前,我们先来看一个场景,以及在老版本下的性能表现。
创建一张测试表,三个字段,其中一个全文检索字段,另一个pk,还有一个时间。
全文检索字段使用随机字符串生成,建立索引。
测试sql
更新crt_time时间字段,但是不更新全文检索字段。
注意,虽然我们没有更新全文检索字段,但是依旧会导致gin索引的变更,因为token->ctid,由于pg多版本的原因这里的ctid会变化,如果ctid变成了其他page的行,那么索引也需要变化。
即使是更新后的记录在同一个page(hot更新),vacuumm时将老的记录删掉也需要变更索引entry。
总之这个为了突出业务上可能忽视的问题。以为不更新索引字段,索引就不需要变化。
ps:pg 10或将来会支持二级索引,就不会存在以上问题。那么用户只需要考虑索引字段value被更新的情况。
1、4并发
2、16并发
3、64并发
我们发现,并发越高,性能抖动非常严重,但是数据库中并未发现waiting。
跟踪进程pstack,如下,出现了lock和sleep。
pg gin索引有一个fastupdate的选项,实际上是因为一条记录涉及多个token,为了防止索引频繁更新,pg设计的一种快速dml方法。就是先将数据写入pending list,然后由vacuum, analyze或当list满时触发将pengding list合并到gin tree的动作。
首先看一下pending list区域的大小由什么控制。
postgresql 9.4的pending list大小由work_mem参数控制。
<a href="https://www.postgresql.org/docs/9.4/static/gin-implementation.html#gin-fast-update">https://www.postgresql.org/docs/9.4/static/gin-implementation.html#gin-fast-update</a>
src/backend/access/gin/ginfast.c
postgresql 10的gin pending list大小由表级参数,或者全局参数gin_pending_list_limit控制。
<a href="https://www.postgresql.org/docs/10/static/gin-implementation.html">https://www.postgresql.org/docs/10/static/gin-implementation.html</a>
src/include/access/gin_private.h
性能抖动和pending list大小有没有关系呢?
默认work_mem, gin_pending_list_limit都是4mb。
1、work_mem = 64kb
4并发
64并发
2、work_mem = 128kb
3、work_mem = 32mb
观察到一个现象:
1、pending list(work_mem)越大,性能抖动越严重,tps=0越持久。
2、work_mem越小,性能抖动越少,但是峰值性能会有一定的下降。
3、并发越低,性能越稳定。
4、work_mem较小时,即使并发较高,tps=0的几率也非常小。
postgresql 9.4的优化建议:
1、work_mem设置为64kb,降低更新并发(例如使用连接池控制并发)。
2、将创建了gin索引的字段剥离到独立的表,通过pk将两者进行关联。
例如
postgresql 10提交了一个patch,解决了gin vacuum时需要对整个posting tree的所有页面长时间持锁的问题。
<a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=218f51584d5a9fcdf702bcc7f54b5b65e255c187">https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=218f51584d5a9fcdf702bcc7f54b5b65e255c187</a>
1、pending_list_limit = 64kb
2、pending_list_limit = 128kb
3、pending_list_limit = 4mb
4、pending_list_limit = 128mb
postgresql 10 性能非常的平稳,即使是高并发,高pending list的情况下,没有出现tps=0的情况。
同时在pg 10下,pstack没有观测到idx_test_info索引被更新的情况,这也是一个大的改进,可以找一下git.postgresql.org对应哪个patch。
对于需要频繁更新的表,如果这个表的某些字段建立了gin索引,为了减少gin索引的更新开销,优化如下。
1、设置表的fillfactor(如=50),尽量使用让数据库使用hot更新。减少行迁移,从而减少索引entry的更新。
2、设置较小work_mem,例如设置为64kb。
3、使用连接池,控制并发。
4、将tsvector字段拆分出来,使用pk进行关联。完全杜绝没必要的更新。
其中1,2是最好实施的,不影响业务,效果立竿见影(但是在巨大压力、巨大并发下依旧偶尔会有一两秒的tps=0)。
pg 10在巨大压力、巨大并发(同时伴随checkpoint, vacuum的虐待)下,tps表现都非常平稳,抖动不超过5%。
1、使用二级索引
<a href="https://github.com/digoal/blog/blob/master/201703/20170312_21.md">《postgresql 10.0 preview 性能增强 - 间接索引(secondary index)》</a>