暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Postgres、toast和YugabyteDB

原创 eternity 2022-07-27
625

这篇博客文章介绍了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

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论