PostgreSQL 10 GIN索引 锁优化
2017-07-06 13:30
387 查看
阅读原文请点击
摘要: 标签 PostgreSQL , gin , 倒排索引 , 全文检索 , 性能优化 背景 PostgreSQL gin索引接口常被用于多值列的检索,例如全文检索类型、数组类型。 有兴趣了解更多索引接口的原理和使用场景,可以参考下文。
标签
PostgreSQL , gin , 倒排索引 , 全文检索 , 性能优化
背景
PostgreSQL gin索引接口常被用于多值列的检索,例如全文检索类型、数组类型。有兴趣了解更多索引接口的原理和使用场景,可以参考下文。
《PostgreSQL 9种索引的原理和应用场景》
今天要说道一下PostgreSQL GIN索引的代码优化。
在说GIN代码优化前,我们先来看一个场景,以及在老版本下的性能表现。
例子
创建一张测试表,三个字段,其中一个全文检索字段,另一个PK,还有一个时间。全文检索字段使用随机字符串生成,建立索引。
create table test(id int, info tsvector, crt_time timestamp); -- 生成随机字符串 CREATE OR REPLACE FUNCTION public.gen_rand_str(integer) RETURNS text LANGUAGE sql STRICT AS $function$ select string_agg(a[random()*6+1],'') from generate_series(1,$1), (select array['a','b','c','d','e','f',' ']) t(a); $function$; -- 插入100万条数据 insert into test select generate_series(1,1000000), to_tsvector(gen_rand_str(512)), now(); -- 创建索引 create index idx_test_info on test using gin (info); create index idx_test_id on test (id);
测试SQL
更新crt_time时间字段,但是不更新全文检索字段。
\set id random(1,1000000) update test set crt_time=now() where id=:id; 或 \setrandom id 1 1000000 update test set crt_time=now() where id=:id;
注意,虽然我们没有更新全文检索字段,但是依旧会导致GIN索引的变更,因为token->ctid,由于PG多版本的原因这里的ctid会变化,如果CTID变成了其他PAGE的行,那么索引也需要变化。
即使是更新后的记录在同一个PAGE(HOT更新),VACUUMM时将老的记录删掉也需要变更索引ENTRY。
总之这个为了突出业务上可能忽视的问题。以为不更新索引字段,索引就不需要变化。
PS:PG 10或将来会支持二级索引,就不会存在以上问题。那么用户只需要考虑索引字段VALUE被更新的情况。
PostgreSQL
9.4 版本压测
1、4并发pgbench -M prepared -n -r -P 1 -f ./test.sql -c 4 -j 4 -T 1000 progress: 1.0 s, 8622.0 tps, lat 0.091 ms stddev 0.041 progress: 2.0 s, 9038.2 tps, lat 0.549 ms stddev 22.067 progress: 3.0 s, 9910.5 tps, lat 0.466 ms stddev 18.571 progress: 4.0 s, 11642.6 tps, lat 0.389 ms stddev 16.555 progress: 5.0 s, 12109.2 tps, lat 0.386 ms stddev 16.879 progress: 6.0 s, 9292.4 tps, lat 0.468 ms stddev 18.731 progress: 7.0 s, 4511.1 tps, lat 0.077 ms stddev 0.023 progress: 8.0 s, 15309.5 tps, lat 0.320 ms stddev 15.127 progress: 9.0 s, 18481.9 tps, lat 0.274 ms stddev 13.459 progress: 10.0 s, 22044.6 tps, lat 0.242 ms stddev 12.381 progress: 11.0 s, 5432.4 tps, lat 0.789 ms stddev 26.151 progress: 12.0 s, 22851.0 tps, lat 0.070 ms stddev 0.019 progress: 13.0 s, 35955.7 tps, lat 0.175 ms stddev 10.177
2、16并发
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 16 -j 16 -T 1000 progress: 1.0 s, 65915.6 tps, lat 0.104 ms stddev 0.075 progress: 2.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 3.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 4.0 s, 20134.1 tps, lat 2.256 ms stddev 76.169 progress: 5.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 6.0 s, 10403.8 tps, lat 3.658 ms stddev 90.374 progress: 7.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 8.0 s, 9328.5 tps, lat 3.659 ms stddev 85.652 progress: 9.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 10.0 s, 8348.0 tps, lat 3.787 ms stddev 84.213 progress: 11.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 12.0 s, 7258.0 tps, lat 1.394 ms stddev 49.557 progress: 13.0 s, 21.0 tps, lat 1231.018 ms stddev 1173.690 progress: 14.0 s, 7237.3 tps, lat 1.228 ms stddev 48.168 progress: 15.0 s, 13.0 tps, lat 1191.294 ms stddev 1108.031 progress: 16.0 s, 9.0 tps, lat 1482.792 ms stddev 1657.674 progress: 17.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 18.0 s, 6163.0 tps, lat 4.255 ms stddev 126.424 progress: 19.0 s, 17.0 tps, lat 1785.435 ms stddev 1721.592
3、64并发
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 64 -j 64 -T 1000 progress: 1.0 s, 2083.1 tps, lat 1.243 ms stddev 1.126 progress: 2.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 3.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 4.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 5.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 6.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 7.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 8.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 9.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 10.0 s, 2030.1 tps, lat 300.032 ms stddev 1647.060 progress: 11.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 12.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 13.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 14.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 15.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 16.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 17.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 18.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 19.0 s, 2064.0 tps, lat 289.639 ms stddev 1586.564 progress: 20.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 21.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 22.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 23.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 24.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 25.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 26.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 27.0 s, 0.0 tps, lat -nan ms stddev -nan progress: 28.0 s, 0.0 tps, lat -nan ms stddev -nan
我们发现,并发越高,性能抖动非常严重,但是数据库中并未发现waiting。
postgres=# select pid,query,waiting from pg_stat_activity ; pid | query | waiting -------+--------------------------------------------------+--------- 39830 | update test set crt_time=now() where id=$1; | f 39836 | update test set crt_time=now() where id=$1; | f 39841 | update test set crt_time=now() where id=$1; | f 39845 | update test set crt_time=now() where id=$1; | f 39852 | update test set crt_time=now() where id=$1; | f 39858 | update test set crt_time=now() where id=$1; | f 39862 | update test set crt_time=now() where id=$1; | f 39869 | update test set crt_time=now() where id=$1; | f 39874 | update test set crt_time=now() where id=$1; | f
跟踪进程pstack,如下,出现了lock和sleep。
pstack 39926 #0 0x00007f3836a21393 in __select_nocancel () from /lib64/libc.so.6 #1 0x0000000000818d3a in pg_usleep () #2 0x00000000006c2c66 in s_lock () #3 0x00000000006a30ff in ReleaseBuffer () #4 0x0000000000472320 in ginInsertValue () #5 0x000000000046ad5a in ginEntryInsert () #6 0x0000000000478552 in ginHeapTupleFastInsert () -- 插入pending list #7 0x000000000046b30a in gininsert () #8 0x00000000007e13b7 in FunctionCall6Coll () #9 0x000000000049fc5f in index_insert () #10 0x00000000005c5975 in ExecInsertIndexTuples () #11 0x00000000005d4db7 in ExecModifyTable () #12 0x00000000005bb278 in ExecProcNode () #13 0x00000000005b91fd in standard_ExecutorRun () #14 0x00000000006d5816 in ProcessQuery () #15 0x00000000006d5aef in PortalRunMulti () #16 0x00000000006d5fda in PortalRun () #17 0x00000000006d24d9 in exec_execute_message () #18 0x00000000006d430c in PostgresMain () #19 0x000000000066bcaf in PostmasterMain () #20 0x00000000005f469c in main ()
PG GIN索引有一个fastupdate的选项,实际上是因为一条记录涉及多个TOKEN,为了防止索引频繁更新,PG设计的一种快速DML方法。就是先将数据写入pending list,然后由vacuum, analyze或当list满时触发将pengding list合并到gin tree的动作。
代码分析
首先看一下pending list区域的大小由什么控制。
PostgreSQL
9.4
postgresql 9.4的pending list大小由work_mem参数控制。https://www.postgresql.org/docs/9.4/static/gin-implementation.html#GIN-FAST-UPDATE
src/backend/access/gin/ginfast.c
/* * Write the index tuples contained in *collector into the index's * pending list. * * Function guarantees that all these tuples will be inserted consecutively, * preserving order */ void ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector) { ...... /* * Force pending list cleanup when it becomes too long. And, * ginInsertCleanup could take significant amount of time, so we prefer to * call it when it can do all the work in a single collection cycle. In * non-vacuum mode, it shouldn't require maintenance_work_mem, so fire it * while pending list is still small enough to fit into work_mem. * * ginInsertCleanup() should not be called inside our CRIT_SECTION. */ if (metadata->nPendingPages * GIN_PAGE_FREESIZE > work_mem * 1024L) needCleanup = true; UnlockReleaseBuffer(metabuffer); END_CRIT_SECTION(); if (needCleanup) ginInsertCleanup(ginstate, false, NULL); }
PostgreSQL
10
PostgreSQL 10的gin pending list大小由表级参数,或者全局参数gin_pending_list_limit控制。https://www.postgresql.org/docs/10/static/gin-implementation.html
src/include/access/gin_private.h
#define GinGetPendingListCleanupSize(relation) \ ((relation)->rd_options && \ ((GinOptions *) (relation)->rd_options)->pendingListCleanupSize != -1 ? \ ((GinOptions *) (relation)->rd_options)->pendingListCleanupSize : \ gin_pending_list_limit) /* * Write the index tuples contained in *collector into the index's * pending list. * * Function guarantees that all these tuples will be inserted consecutively, * preserving order */
阅读原文请点击
相关文章推荐
- SQL Server性能优化(10)非聚集索引的存储结构
- Postgresql客户端创建GIN索引问题
- 浅谈postgresql的GIN索引(通用倒排索引)
- MySQL详解(10)----------索引优化
- postgresql 函数索引优化查询速度
- PostgreSQL 9.4.1---聚集函数的优化详解 ---聚集函数与索引
- postgresql 优化之--不会使用索引
- sql server 海量数据速度提升:SQL优化-索引(10)
- postgresql 索引优化及了解
- sql server 海量数据速度提升:SQL优化-索引(10) 【转】
- like查询时索引优化
- <<Oracle数据库性能优化艺术(第五期)>> 第7周 索引和分区(包括11g下新的组合分区)
- SQL优化(索引问题)
- MySQL索引与优化策略
- 索引深入浅出(1/10):索引简介
- iOS 10 UICollectionView 性能优化
- Oracle笔记第二篇----索引及查询性能优化
- 一步一步跟我学习lucene(6)---lucene索引优化之多线程创建索引
- 优化SQLServer索引的小技巧
- Mysql数据库优化技术之索引篇