这篇博客文章介绍了postgres的TOAST(超大属性存储技术)的概念,以及我们在YugabyteDB中处理它的方式。
postgres数据库要求在单个块中存储一行。因此,行的总大小必须由postgres块的大小绑定,默认情况下为8kB。然而,在某些情况下,8kB对于整行甚至一行中的单个字段来说都太小。这就是TOAST试图克服的问题。
首先,一个字段(也称为属性,postgres目录以及文档如何调用它)必须符合TOAST。符合TOAST条件的属性类型是属性类型,如varchar、bytea、text、json、jsonb。这些类型能够处理大量数据,这些数据可以大于postgres块,并且大小可变。
Postgres通过评估表的属性(字段)来实现TOAST,并确定属性是否可能存储“超大”数据。如果是这种情况,则会在原始表旁边创建一个toast表。这由pg_类中的reltoastrelid字段表示,该字段是pg_toast_OID中名为pg_toast_OID的表的OID。
到目前为止,一切都很好:这都是众所周知的,并记录在postgres文档和许多博客帖子中。
快进到YugabyteDB。可能大多数读者都知道,我们(Yugabyte)使用postgres源代码构建了一个postgres层,我们称之为YSQL(Yugabyte SQL),该层使用了postgres源代码的大部分功能,但使用了分布式存储层DocDB来实现持久性和ACID实现的基础。
最近,我们在YugabyteDB中遇到了一个案例,在该案例中,检索属性/列的速度比预期的慢/慢,通过更改SQL以仅略有不同的方式检索同一列来证明这一点。
这就是使用explain select时执行缓慢的情况:
```yugabyte=# explain analyze select data from t1 where cid = 'user-0-0'; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Index Only Scan using ix on t1 (cost=0.00..15.25 rows=100 width=32) (actual time=32.281..141.281 rows=1000 loops=1) Index Cond: (cid = 'user-0-0'::text) Heap Fetches: 0 Planning Time: 12.014 ms Execution Time: 141.798 ms
复制
这就是快速执行:
yugabyte=# /*+ indexscan(t1 t1_pkey) */ explain analyze select data from t1 where cid = 'user-0-0'; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Index Scan using t1_pkey on t1 (cost=0.00..15.25 rows=100 width=32) (actual time=24.360..25.536 rows=1000 loops=1) Index Cond: (cid = 'user-0-0'::text) Planning Time: 0.810 ms Execution Time: 25.658 ms
复制
性能差异很明显:141.798毫秒与25.658毫秒。
可见,在这两种情况下都使用了索引:
- 第一种情况使用名为“ix”的索引,其定义为:
create index ix on t1(cid, id) INCLUDE (ns_id, module, inst_key, pending, data, meta)
复制
换句话说:请求的列data被添加到include列表的索引中。这就是该计划称为“仅索引扫描”的原因:cid是索引的一部分,而元列是包含列表的一部分,因此可以从该索引中满足查询结果。
- 第二种情况也使用了一个索引,称为“t1_pkey”。这值得一些解释,因为这里发生了一些与YugabyteDB相关的事情:YugabyteDB中的表存储在基于主键的类索引结构中。这种优化允许我们将表(t1)和主键索引(t1_pkey)结合起来。这里的计划说它使用索引来选择cid并在表中查找值,实际上它可以从表中获得值,就像它是索引包含列表的一部分一样。
在研究性能问题时,通过遵循逻辑方法仔细评估问题发生的位置,并使用正确的工具进行评估,这一点非常重要。没有一种工具可以随时提供正确的信息。
对于大多数与SQL相关的问题,必须完成的第一步是确定是否选择了正确的计划。以上解释分析输出是第一步。在这两种情况下,都选择了预期的计划,实际执行的延迟相差很大。
这意味着我们必须深入研究数据获取的机制。遗憾的是,这里没有标记的道路,例如解释用于查看规划器结果,解释分析用于查看实际运行时统计数据。
这两种情况都是查询的精简版本,并执行单个任务:使用索引获取行,这需要多次执行。因此,重复执行的操作很可能会导致更高的响应时间。
上述逻辑表明,随着响应时间的增加,可能会重复执行某些操作,这意味着最明显的做法是使用perf分析执行情况,而不是收集回溯,而是简单地收集活动函数:
1.获取postgres后端的pid:
yugabyte=# select pg_backend_pid();
复制
1.perf记录低延迟和高延迟执行:
$ sudo perf record -p PID -o fast.perf
复制
结果如下:
$ sudo perf report -i fast.perf -f --stdio | grep -v ^# | head -n 10 25.58% postgres libc-2.23.so [.] __memcpy_avx_unaligned 23.26% pggate_ybclient libcrypto.so.1.1 [.] _aesni_ctr32_ghash_6x 18.60% pggate_ybclient libc-2.23.so [.] __memcpy_avx_unaligned 6.98% postgres libc-2.23.so [.] __memset_avx2 4.65% pggate_ybclient [kernel.kallsyms] [k] copy_user_enhanced_fast_string 2.33% pggate_ybclient [kernel.kallsyms] [k] sk_filter_trim_cap 2.33% pggate_ybclient libev.so.4.0.0 [.] ev_run 2.33% pggate_ybclient libyb_util.so [.] yb::HdrHistogram::IncrementBy 2.33% postgres libyb_common_base.so [.] yb::AppendToKey 2.33% postgres postgres [.] AllocSetReset $ $ sudo perf report -i slow.perf -f --stdio | grep -v ^# | head -n 10 85.01% postgres postgres [.] pglz_compress 1.52% pggate_ybclient libcrypto.so.1.1 [.] _aesni_ctr32_ghash_6x 1.52% postgres [kernel.kallsyms] [k] mem_cgroup_charge_common 1.14% pggate_ybclient [kernel.kallsyms] [k] clear_page_c_e 0.95% pggate_ybclient [kernel.kallsyms] [k] copy_user_enhanced_fast_string 0.95% pggate_ybclient libc-2.23.so [.] __memcpy_avx_unaligned 0.76% postgres [kernel.kallsyms] [k] clear_page_c_e 0.76% postgres [kernel.kallsyms] [k] get_page_from_freelist 0.57% pggate_ybclient [kernel.kallsyms] [k] _raw_spin_unlock_irqrestore 0.57% postgres libc-2.23.so [.] __memcpy_avx_unaligned
复制
在“快速”的情况下,我们看到了许多您非常期待的函数(_umemcpy_avx_unaligned,_aesni_ctr32_ghash_6x,_umemset_avx2):工作的主要部分是从数据属性获取数据,因此我期望的是指示推位(memcpy)和加密(aesni)的函数。
但是,在“慢”的情况下,有一个函数真正占用了大部分时间:pglz_压缩,它占85%的时间。而且,非常重要的是:“快速”案例中似乎没有此功能!
在这一点上,我有点困惑:如果压缩是这个问题的一部分,那么解压是select语句所期望的,而不是压缩!但我们可以更深入地研究这个问题:在这一点上,我们需要了解plgz_compress函数的调用堆栈,看看它在做什么。这可以使用gdb(GNU调试器)完成。
警告:将调试器附加到进程会阻止进程运行。当运行一个附加了调试器的进程时,它的运行速度会明显减慢。在生产环境中执行此操作可能会导致严重的问题,因此,只有在充分了解使用调试器的所有注意事项后才能执行此操作。
$ gdb -p PID ...lots of messages... warning: File "/home/yugabyte/yb-software/yugabyte-2.13.3.0-b12-centos-x86_64/linuxbrew/lib/libthread_db.so.1" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load:/usr/bin/mono-gdb.py". warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
复制
YugabyteDB YSQL/postgres后端需要libthread_db.so,1个库,gdb的默认自动加载安全路径不允许该库。该问题的解决方案是手动添加线程库:
(gdb) add-auto-load-safe-path /home/yugabyte/yb-software/yugabyte-2.13.3.0-b12-centos-x86_64/linuxbrew/lib/libthread_db.so.1
复制
现在,我们必须在调用pglz_compress函数时停止调试器。这在gdb中使用“break”命令非常简单:
(gdb) break pglz_compress Breakpoint 1 at 0xb56e08: file ../../../../../src/postgres/src/common/pg_lzcompress.c, line 530.
复制
YugabyteDB在可执行文件中附带了调试符号,这使得gdb能够理解函数在源文件中的位置。然而,我们不提供二进制分布的源代码,因为这不实用。源代码可以从YugabyteDB GitHub存储库获得:我们是开源的。
好的,现在我们已经在函数pglz_compress上设置了一个断点,我们需要使用c或continue命令使附加的进程再次运行:
(gdb) c Continuing.
复制
现在执行慢速查询。因为这将执行pglz_compress函数,所以调试器将中断执行:
Breakpoint 1, pglz_compress ( source=0x2a1c03c "{\"attr_0\" : 0, \"attr_1\" : 1, \"attr_2\" : 2, \"attr_3\" : 3, \"attr_4\" : 4, \"attr_5\" : 5, \"attr_6\" : 6, \"attr_7\" : 7, \"attr_8\" : 8, \"attr_9\" : 9, \"attr_10\" : 10, \"attr_11\" : 11, \"attr_12\" : 12, \"attr_13\" :"..., slen=6980, dest=0x3742040 "tr_0\" : 1000, \"attr_1\" : 1001, \"attr_2\" : 1002, \"attr_3\" : 1003, \"attr_4\" : 1004, \"attr_5\" : 1005, \"attr_6\" : 1006, \"attr_7\" : 1007, \"attr_8\" : 1008, \"attr_9\" : 1009, \"attr_10\" : 1010, \"attr_11\" : 101"..., strategy=0x4545c4 <strategy_default_data>) at ../../../../../src/postgres/src/common/pg_lzcompress.c:530 530 ../../../../../src/postgres/src/common/pg_lzcompress.c: No such file or directory.
复制
现在向gdb询问回溯:
(gdb) bt #0 pglz_compress ( source=0x2a1c03c "{\"attr_0\" : 0, \"attr_1\" : 1, \"attr_2\" : 2, \"attr_3\" : 3, \"attr_4\" : 4, \"attr_5\" : 5, \"attr_6\" : 6, \"attr_7\" : 7, \"attr_8\" : 8, \"attr_9\" : 9, \"attr_10\" : 10, \"attr_11\" : 11, \"attr_12\" : 12, \"attr_13\" :"..., slen=6980, dest=0x3742040 "tr_0\" : 1000, \"attr_1\" : 1001, \"attr_2\" : 1002, \"attr_3\" : 1003, \"attr_4\" : 1004, \"attr_5\" : 1005, \"attr_6\" : 1006, \"attr_7\" : 1007, \"attr_8\" : 1008, \"attr_9\" : 1009, \"attr_10\" : 1010, \"attr_11\" : 101"..., strategy=0x4545c4 <strategy_default_data>) at ../../../../../src/postgres/src/common/pg_lzcompress.c:530 #1 0x00000000005558ec in toast_compress_datum (value=44154936) at ../../../../../../../src/postgres/src/backend/access/heap/tuptoaster.c:1401 #2 0x00000000005026d5 in index_form_tuple (tupleDescriptor=0x2114190, values=<optimized out>, isnull=0x21e2400) at ../../../../../../../src/postgres/src/backend/access/common/indextuple.c:98 #3 0x00000000005bda99 in ybcFetchNextIndexTuple (ybScan=0x21e22d8, index=0x2113e70, is_forward_scan=<optimized out>) at ../../../../../../../src/postgres/src/backend/access/yb_access/yb_scan.c:383 #4 ybc_getnext_indextuple (ybScan=0x21e22d8, is_forward_scan=<optimized out>, recheck=<optimized out>) #5 0x00000000005c0303 in ybcingettuple (scan=0x2349ed8, dir=ForwardScanDirection) at ../../../../../../../src/postgres/src/backend/access/yb_access/yb_lsm.c:440 #6 0x0000000000559e55 in index_getnext_tid (scan=0x2349ed8, direction=6980) at ../../../../../../../src/postgres/src/backend/access/index/indexam.c:594 #7 0x000000000077ffcb in IndexOnlyNext (node=<optimized out>) at ../../../../../../src/postgres/src/backend/executor/nodeIndexonlyscan.c:129 #8 0x0000000000760287 in ExecScan (node=0x2348328, accessMtd=0x77fe80 <IndexOnlyNext>, recheckMtd=0x7804c0 <IndexOnlyRecheck>) at ../../../../../../src/postgres/src/backend/executor/execScan.c:171 #9 0x000000000075e409 in ExecProcNodeInstr (node=0x2348328) at ../../../../../../src/postgres/src/backend/executor/execProcnode.c:462 #10 0x000000000075388e in standard_ExecutorRun (queryDesc=0x2183f80, direction=<optimized out>, count=0, execute_once=<optimized out>) at ../../../../../../src/postgres/src/include/executor/executor.h:249 #11 0x00007fed1ebf5c05 in pgss_ExecutorRun (queryDesc=0x2183f80, direction=ForwardScanDirection, count=0, execute_once=true) at ../../../../../src/postgres/contrib/pg_stat_statements/pg_stat_statements.c:949 #12 0x00007fed1ebec79a in ybpgm_ExecutorRun (queryDesc=0x2183f80, direction=ForwardScanDirection, count=0, execute_once=true) at ../../../../../src/postgres/contrib/yb_pg_metrics/yb_pg_metrics.c:503 #13 0x00000000006aa3a1 in ExplainOnePlan (plannedstmt=0x20d49d8, into=0x0, es=0x2346438, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", params=<optimized out>, queryEnv=<optimized out>, planduration=0x7fffac2cd038) at /nfusr/alma8-gcp-cloud/jenkins-worker-ky8wmc/jenkins/jenkins-github-yugabyte-db-alma8-master-clang12-release-435/src/postgres/src/backend/executor/../../../../../../src/postgres/src/backend/executor/execMain.c:307 #14 0x00000000006a9e81 in ExplainOneQuery (query=<optimized out>, cursorOptions=<optimized out>, into=0x0, es=0x2346438, queryString=0x4545c4 <strategy_default_data> " ", params=0x0, queryEnv=0x0) at ../../../../../../src/postgres/src/backend/commands/explain.c:372 #15 0x00000000006a984f in ExplainQuery (pstate=<optimized out>, stmt=0x37a08c0, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", params=0x0, queryEnv=0x0, dest=0x376f640) at ../../../../../../src/postgres/src/backend/commands/explain.c:255 #16 0x000000000095849a in standard_ProcessUtility (pstmt=0x37a0a10, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x376f640, completionTag=0x7fffac2cd640 "") at ../../../../../../src/postgres/src/backend/tcop/utility.c:699 #17 0x00007fed1ebf5f30 in pgss_ProcessUtility (pstmt=0x2a1c03c, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x376f640, completionTag=0x7fffac2cd640 "") at ../../../../../src/postgres/contrib/pg_stat_statements/pg_stat_statements.c:1058 #18 0x00007fed1ebecc17 in ybpgm_ProcessUtility (pstmt=0x2a1c03c, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x376f640, completionTag=0x7fffac2cd640 "") at ../../../../../src/postgres/contrib/yb_pg_metrics/yb_pg_metrics.c:689 #19 0x00007fed1ebd162e in pg_hint_plan_ProcessUtility (pstmt=0x2a1c03c, queryString=0x1b44 <Address 0x1b44 out of bounds>, context=57942080, params=0x4545c4 <strategy_default_data>, queryEnv=0x156f760, dest=0x20, completionTag=0x7fffac2cd640 "") at ../../../../../src/postgres/third-party-extensions/pg_hint_plan/pg_hint_plan.c:3033 #20 0x0000000000b1fddf in YBTxnDdlProcessUtility (pstmt=0x37a0a10, queryString=0x1ec2118 "explain analyze select data from t1 where cid = 'user-0-0';", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x376f640, completionTag=0x7fffac2cd640 "") at ../../../../../../../src/postgres/src/backend/utils/misc/pg_yb_utils.c:1443 #21 0x00000000009572b4 in PortalRunUtility (portal=0x1fa2118, pstmt=0x37a0a10, isTopLevel=<optimized out>, setHoldSnapshot=<optimized out>, dest=0x20, at ../../../../../../src/postgres/src/backend/tcop/utility.c:374 #22 0x0000000000956a1d in FillPortalStore (portal=0x1fa2118, isTopLevel=true) at ../../../../../../src/postgres/src/backend/tcop/pquery.c:1062 #23 0x0000000000956614 in PortalRun (portal=0x1fa2118, count=9223372036854775807, isTopLevel=true, run_once=<optimized out>, dest=0x1ec3f10, altdest=0x1ec3f10, completionTag=0x7fffac2cd820 "") at ../../../../../../src/postgres/src/backend/tcop/pquery.c:780 #24 0x0000000000952446 in yb_exec_simple_query_impl (query_string=0x1ec2118) at ../../../../../../src/postgres/src/backend/tcop/postgres.c:1170 #25 0x0000000000952b2e in yb_exec_query_wrapper (exec_context=0x1ec2000, restart_data=0x7fffac2cda50, functor=0x951b90 <yb_exec_simple_query_impl>, functor_context=0x1ec2118) at ../../../../../../src/postgres/src/backend/tcop/postgres.c:4557 #26 0x000000000094f298 in PostgresMain (argc=1, argv=<optimized out>, dbname=<optimized out>, username=0x1eaf378 "yugabyte") at ../../../../../../src/postgres/src/backend/tcop/postgres.c:4591 #27 0x00000000008aab9b in BackendRun (port=0x1d361e0) at ../../../../../../src/postgres/src/backend/postmaster/postmaster.c:4555 #28 0x00000000008aa2aa in ServerLoop () at ../../../../../../src/postgres/src/backend/postmaster/postmaster.c:4198 #29 0x00000000008a6901 in PostmasterMain (argc=<optimized out>, argv=0x1d4c780) at ../../../../../../src/postgres/src/backend/postmaster/postmaster.c:1423 #30 0x00000000007c7233 in PostgresServerProcessMain (argc=23, argv=0x1d4c780) at ../../../../../../src/postgres/src/backend/main/main.c:234 #31 0x00000000004f4dd2 in main ()
复制
这是很多信息。为了更好地理解,以不同的格式手动显示可能会有所帮助:
pglz_compress<-toast_compress_datum<-index_form_tuple<-ybcFetchNextIndexTuple<-ybc_getnext_index_type<-ybcingettuple<-index_getnext_tid<-IndexOnlyNext<-ExecScan
复制
当执行“快速”查询时,gdb不会中断。因此,该函数不会通过“快速”查询触发。当查看回溯时,您会看到在堆栈的更上层调用了函数index_form_tuple。在研究执行计划时,我们看到“快速”扫描使用主键结构,不包含从表中获取的“数据”字段,“慢速”扫描仅使用索引。
使用以下命令,我们可以找到与指定名称匹配的所有函数:
(gdb) info function form_tuple All functions matching regular expression "form_tuple": File ../../../../../../../src/postgres/src/backend/access/common/indextuple.c: void index_deform_tuple(IndexTuple, TupleDesc, Datum *, _Bool *); IndexTuple index_form_tuple(TupleDesc, Datum *, _Bool *); File ../../../../../../../src/postgres/src/backend/access/common/heaptuple.c: void heap_deform_tuple(HeapTuple, TupleDesc, Datum *, _Bool *); HeapTuple heap_form_tuple(TupleDesc, Datum *, _Bool *); static void slot_deform_tuple(TupleTableSlot *, int); File ../../../../../../../src/postgres/src/backend/access/brin/brin_tuple.c: BrinMemTuple *brin_deform_tuple(BrinDesc *, BrinTuple *, BrinMemTuple *); BrinTuple *brin_form_tuple(BrinDesc *, BlockNumber, BrinMemTuple *, Size *);
复制
这表明有一个“heap_form_tuple”函数。如果我禁用pglz_compress的断点,然后断开heap_form_tuple的断点,并执行快速查询,则调试器会断开,指示使用它:
(gdb) i b Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000b56e08 in pglz_compress at ../../../../../src/postgres/src/common/pg_lzcompress.c:530 breakpoint already hit 1 time (gdb) dis 1 (gdb) break heap_form_tuple Breakpoint 2 at 0x500b31: file ../../../../../../../src/postgres/src/backend/access/common/heaptuple.c, line 1085. (gdb) c Continuing. Breakpoint 2, heap_form_tuple (tupleDescriptor=0x1f9f9f8, values=0x2349838, isnull=0x2349fa0) at ../../../../../../../src/postgres/src/backend/access/common/heaptuple.c:1085 1085 ../../../../../../../src/postgres/src/backend/access/common/heaptuple.c: No such file or directory. (gdb) bt #0 heap_form_tuple (tupleDescriptor=0x1f9f9f8, values=0x2349838, isnull=0x2349fa0) at ../../../../../../../src/postgres/src/backend/access/common/heaptuple.c:1085 #1 0x00000000005bd585 in ybc_getnext_heaptuple (ybScan=0x2349e78, is_forward_scan=<optimized out>, recheck=<optimized out>) at ../../../../../../../src/postgres/src/backend/access/yb_access/yb_scan.c:309 #2 0x00000000005c0334 in ybcingettuple (scan=0x2349cd8, dir=ForwardScanDirection) at ../../../../../../../src/postgres/src/backend/access/yb_access/yb_lsm.c:450 #3 0x0000000000559e55 in index_getnext_tid (scan=0x2349cd8, direction=37001272) at ../../../../../../../src/postgres/src/backend/access/index/indexam.c:594 #4 0x000000000077f462 in index_getnext (scan=<optimized out>, direction=<optimized out>) at /nfusr/alma8-gcp-cloud/jenkins-worker-ky8wmc/jenkins/jenkins-github-yugabyte-db-alma8-master-clang12-release-435/src/postgres/src/backend/access/index/../../../../../../../src/postgres/src/backend/access/index/indexam.c:744 #5 IndexNext (node=<optimized out>) at ../../../../../../src/postgres/src/backend/executor/nodeIndexscan.c:171 #6 0x0000000000760287 in ExecScan (node=0x2348328, accessMtd=0x77f2b0 <IndexNext>, recheckMtd=0x77f590 <IndexRecheck>) at ../../../../../../src/postgres/src/backend/executor/execScan.c:171 ...etc...
复制
可以让我们看看索引形式元组的来源。在第98行,它显示了在名为“TOAST_INDEX_HACK”的宏中进行决策的代码。
在查看heap_form_tuple函数时,没有提到压缩。
当四处打听的时候,发现YugabyteDB不会烤面包。我们可以在DocDB层很好地处理“超大”属性,因此没有什么是烤过的。
为什么通过YugabyteDB中压缩的索引获得大条目呢?
我决定现在看看这两个查询在原生postgres中的堆栈是什么样子。因此,我在postgres测试实例中创建了该模式,并在后端连接了gdb的postgres会话设置,在index_form_tuple和heap_form_tuple上有中断,并测试了这两个查询。这些并没有触发调试器中断!
因此,在postgres中,当查询从索引(index_form_tuple)和表(heap_form_tuple)获取属性时,在postgres中不使用index_form_tuple和heap_form_tuple函数,而在YugabyteDB中我使用。
事实证明,我可以用以下方式在postgres中触发index_form_tuple和heap_form_tuple;首先创建一个简单的虚拟表:
create table t ( id int); create index tix on t(id);
复制
然后将调试器连接到该后端,并断开index_ form_tuple和heap_form_tuple:
break index_form_tuple break heap_form_tuple
复制
现在再次在psql中,执行表t的插入:
insert into t values (1);
复制
第一个heap_form_tuple在以下位置断开:
Breakpoint 2, heap_form_tuple (tupleDescriptor=0x25d21a0, values=0x25d1fb0, isnull=0x25d1fb8) at heaptuple.c:1067 1067 { (gdb) bt #0 heap_form_tuple (tupleDescriptor=0x25d21a0, values=0x25d1fb0, isnull=0x25d1fb8) at heaptuple.c:1067 #1 0x00000000006131b5 in ExecMaterializeSlot (slot=slot@entry=0x25d1f50) at execTuples.c:806 #2 0x000000000062bcb5 in ExecInsert (mtstate=mtstate@entry=0x25d1b20, slot=0x25d1f50, planSlot=0x25d1f50, srcSlot=srcSlot@entry=0x0, returningRelInfo=0x25d1a10, estate=estate@entry=0x25d17d0, canSetTag=true) at nodeModifyTable.c:301 #3 0x000000000062d28b in ExecModifyTable (pstate=0x25d1b20) at nodeModifyTable.c:2243 #4 0x0000000000608e23 in ExecProcNode (node=0x25d1b20) at ../../../src/include/executor/executor.h:248 ...snipped...
复制
当我继续调试器时,然后index_form_tuple:
Breakpoint 1, index_form_tuple (tupleDescriptor=0x7fcf19f786e8, values=0x7ffe6341d3a0, isnull=0x7ffe6341d380) at indextuple.c:43 43 { (gdb) bt #0 index_form_tuple (tupleDescriptor=0x7fcf19f786e8, values=0x7ffe6341d3a0, isnull=0x7ffe6341d380) at indextuple.c:43 #1 0x00000000004c98db in btinsert (rel=0x7fcf19f783c8, values=<optimized out>, isnull=<optimized out>, ht_ctid=0x25d1d64, heapRel=0x7fcf19f65698, checkUnique=UNIQUE_CHECK_NO, indexInfo=0x25d2090) at nbtree.c:200 #2 0x0000000000607f55 in ExecInsertIndexTuples (slot=slot@entry=0x25d1f50, tupleid=tupleid@entry=0x25d1d64, estate=estate@entry=0x25d17d0, noDupErr=noDupErr@entry=false, specConflict=specConflict@entry=0x0, arbiterIndexes=arbiterIndexes@entry=0x0) at execIndexing.c:386 #3 0x000000000062c3bd in ExecInsert (mtstate=mtstate@entry=0x25d1b20, slot=0x25d1f50, planSlot=0x25d1f50, srcSlot=srcSlot@entry=0x0, returningRelInfo=0x25d1a10, estate=estate@entry=0x25d17d0, canSetTag=true) at nodeModifyTable.c:553 #4 0x000000000062d28b in ExecModifyTable (pstate=0x25d1b20) at nodeModifyTable.c:2243 #5 0x0000000000608e23 in ExecProcNode (node=0x25d1b20) at ../../../src/include/executor/executor.h:248 ... snipped ...
复制
让我们详细了解一下这里发生了什么。对于heap_form_tuple,上面的一个框架是函数execMaterialiesLot。读取该函数时,我找不到heap_form_tuple函数,但它将heap_form_tuple转换为在函数ExecCopySlotTuple内调用,编译器似乎已经优化了该函数。
对于index_form_tuple,上面一帧是函数btinsert。在该函数中,调用index_form_tuple。
如果查看调用heap_form_tuple和index_form_tuple的源代码,很明显这些函数用于创建一个用于将其存储在堆(表)或索引中的元组,并将内存表示转换为元组的存储表示。
如果我们再回头看看YugabyteDB的情况,index\u form_tuple被称为读取(!)的一部分。为什么我们把它叫做YugabyteDB来阅读??“使用源代码”!让我们来看一下ybcFetchNextIndexTuple的源代码:我们从DocDB(YBCPCGDMLfetch)读取数据,然后创建一个元组。因此,我们使用index_form_tuple为基于DocDB数据的postgres层创建一个元组。
我们也这样做是为了从堆/表中读取数据,但在heap_form_tuple函数中没有压缩数据的函数,因此我们无法在那里运行pglz_compress,这就是为什么我们只在通过索引获取行时运行增加的时间。
postgres黑客列表显示了TOAST_INDEXHACK宏的原因:它是为了防止某些因素的组合导致问题。
结论
通过调试程序和源代码的这段过程表明,在YugabyteDB中,我们使用函数index_form_tuple的方式与PostgreSQL使用它的方式不同,这意味着我们在YugabyteDB中读取时使用它,而在PostgreSQL中它用于编写索引项。在该函数中,有一个宏检查索引项的类型和大小,如果索引项大于TOAST_index_TARGET,则将对其进行压缩,作为解决问题的方法。在YugabyteDB中,我们没有这个问题,因为我们不局限于块/页的大小,但仍然可以压缩较大的索引项。
解决方法是从索引中排除此字段,并从表中提取它。显然,我们将在index_form_tuple的YugabyteDB版本中解决这个问题。
原文标题:Postgres, toast and YugabyteDB
原文链接:https://dev.to/yugabyte/postgres-toast-and-yugabytedb-1chp