select 查询的非确定性性能,在 10 亿行的表上从 1s 到 60s

val*_*mit 4 postgresql cache explain timescaledb postgresql-performance

我正在尝试调查为什么此查询的性能如此不确定。它可能需要 1 秒到 60 秒及以上的任何时间。查询的本质是选择一个“时间窗口”,并从该时间窗口内获取所有行。

这是有问题的查询,在大约 10 亿行的表上运行:

SELECT CAST(extract(EPOCH from ts)*1000000 as bigint) as ts
    , ticks
    , quantity
    , side
FROM order_book
WHERE ts >= TO_TIMESTAMP(1618882633073383/1000000.0)
    AND ts < TO_TIMESTAMP(1618969033073383/1000000.0)
    AND zx_prod_id = 0
ORDER BY ts ASC, del desc;
Run Code Online (Sandbox Code Playgroud)

这就是表的创建方式

CREATE TABLE public.order_book
(
    ts timestamp with time zone NOT NULL,
    zx_prod_id smallint NOT NULL,
    ticks integer NOT NULL,
    quantity integer NOT NULL,
    side boolean NOT NULL,
    del boolean NOT NULL
)
Run Code Online (Sandbox Code Playgroud)

TO_TIMESTAMP当我走整张桌子时,其中的值将继续向前滑动。以下是EXPLAIN ANALYZE两个不同时间窗口上相同查询的输出:

性能缓慢

Gather Merge  (cost=105996.20..177498.48 rows=586308 width=18) (actual time=45196.559..45280.769 rows=539265 loops=1)
  Workers Planned: 6
  Workers Launched: 6
  Buffers: shared hit=116386 read=42298
  ->  Sort  (cost=104996.11..105240.40 rows=97718 width=18) (actual time=45169.717..45176.775 rows=77038 loops=7)
        Sort Key: (((date_part('epoch'::text, _hyper_16_214_chunk.ts) * '1000000'::double precision))::bigint), _hyper_16_214_chunk.del DESC
        Sort Method: quicksort  Memory: 9327kB
        Worker 0:  Sort Method: quicksort  Memory: 8967kB
        Worker 1:  Sort Method: quicksort  Memory: 9121kB
        Worker 2:  Sort Method: quicksort  Memory: 9098kB
        Worker 3:  Sort Method: quicksort  Memory: 9075kB
        Worker 4:  Sort Method: quicksort  Memory: 9019kB
        Worker 5:  Sort Method: quicksort  Memory: 9031kB
        Buffers: shared hit=116386 read=42298
        ->  Result  (cost=0.57..96897.07 rows=97718 width=18) (actual time=7.475..45131.932 rows=77038 loops=7)
              Buffers: shared hit=116296 read=42298
              ->  Parallel Index Scan using _hyper_16_214_chunk_order_book_ts_idx on _hyper_16_214_chunk  (cost=0.57..95187.01 rows=97718 width=18) (actual time=7.455..45101.670 rows=77038 loops=7)
                    Index Cond: ((ts >= '2021-04-22 01:34:31.357179+00'::timestamp with time zone) AND (ts < '2021-04-22 02:34:31.357179+00'::timestamp with time zone))
                    Filter: (zx_prod_id = 0)
                    Rows Removed by Filter: 465513
                    Buffers: shared hit=116296 read=42298
Planning Time: 1.107 ms
JIT:
  Functions: 49
  Options: Inlining false, Optimization false, Expressions true, Deforming true
  Timing: Generation 9.273 ms, Inlining 0.000 ms, Optimization 2.008 ms, Emission 36.235 ms, Total 47.517 ms
Execution Time: 45335.178 ms
Run Code Online (Sandbox Code Playgroud)

快速性能

Gather Merge  (cost=105095.94..170457.62 rows=535956 width=18) (actual time=172.723..240.628 rows=546367 loops=1)
  Workers Planned: 6
  Workers Launched: 6
  Buffers: shared hit=158212
  ->  Sort  (cost=104095.84..104319.16 rows=89326 width=18) (actual time=146.702..152.849 rows=78052 loops=7)
        Sort Key: (((date_part('epoch'::text, _hyper_16_214_chunk.ts) * '1000000'::double precision))::bigint), _hyper_16_214_chunk.del DESC
        Sort Method: quicksort  Memory: 11366kB
        Worker 0:  Sort Method: quicksort  Memory: 8664kB
        Worker 1:  Sort Method: quicksort  Memory: 8986kB
        Worker 2:  Sort Method: quicksort  Memory: 9116kB
        Worker 3:  Sort Method: quicksort  Memory: 8858kB
        Worker 4:  Sort Method: quicksort  Memory: 9057kB
        Worker 5:  Sort Method: quicksort  Memory: 6611kB
        Buffers: shared hit=158212
        ->  Result  (cost=0.57..96750.21 rows=89326 width=18) (actual time=6.145..127.591 rows=78052 loops=7)
              Buffers: shared hit=158122
              ->  Parallel Index Scan using _hyper_16_214_chunk_order_book_ts_idx on _hyper_16_214_chunk  (cost=0.57..95187.01 rows=89326 width=18) (actual time=6.124..114.023 rows=78052 loops=7)
                    Index Cond: ((ts >= '2021-04-22 01:34:31.357179+00'::timestamp with time zone) AND (ts < '2021-04-22 02:34:31.357179+00'::timestamp with time zone))
                    Filter: (zx_prod_id = 4)
                    Rows Removed by Filter: 464498
                    Buffers: shared hit=158122
Planning Time: 0.419 ms
JIT:
  Functions: 49
  Options: Inlining false, Optimization false, Expressions true, Deforming true
  Timing: Generation 10.405 ms, Inlining 0.000 ms, Optimization 2.185 ms, Emission 39.188 ms, Total 51.778 ms
Execution Time: 274.413 ms
Run Code Online (Sandbox Code Playgroud)

我将此输出解释为大部分归咎于并行索引扫描。

起初,我试图提高work_mem到 1 GB 和shared_buffers24 GB,认为它可能无法在 RAM 中容纳它需要的所有东西,但这似乎没有帮助。

接下来,我尝试在 上创建索引(zx_prod_id, ts),认为并行索引扫描的过滤器可能需要一段时间,但这似乎也没有做任何事情。

我不是数据库专家,所以我已经用尽了我的知识极限。

Erw*_*ter 6

解释

两个查询计划之间最重要的区别是read=xyz在慢速版本的多个地方添加了位。

减缓:

Buffers: shared hit=116296 read=42298
Run Code Online (Sandbox Code Playgroud)

快速地:

Buffers: shared hit=158122
Run Code Online (Sandbox Code Playgroud)

这告诉您 Postgres 遇到了尚未缓存的数据(或索引)页面。重复慢速查询(可能read=xyz不止一次,直到添加的内容消失),然后您将在同等缓存的数据页上看到类似的性能。

阅读手册有关BUFFERSEXPLAIN输出。

有关的:

值得注意的是,增加work_mem实际上会损害您的情况,因为work_mem缓存内存(操作系统缓存和 Postres 自己的共享内存缓冲区)是可用 RAM 的竞争对手。设置得work_mem更高会导致数据更快地从(然后更小)缓存中被逐出,因此您的“慢查询”变得更有可能发生。不要忘记 Postgres 可以使用work_mem. 手册:

请注意,对于复杂查询,可能会并行运行多个排序或散列操作;在开始将数据写入临时文件之前,通常允许每个操作使用与此值指定的内存一样多的内存。此外,多个正在运行的会话可能会同时执行此类操作。因此,使用的总内存可能是work_mem;

您的查询使用 6 个并行工作器,这已经是 6 倍work_mem- 可能是最坏的情况;并非每个工人都会使用其最大津贴。

如果不足,work_mem您会diskEXPLAIN输出的各种上下文中看到提到的内容。那是额外的时候work_mem

增加shared_buffers可能更有意义。不过,也不要设置得太高。手册:

如果您有一个具有 1GB 或更多 RAM 的专用数据库服务器,shared_buffers 的合理起始值是系统内存的 25%。有一些工作负载甚至更大的设置 shared_buffers也有效,但由于 PostgreSQL 也依赖于操作系统缓存,因此分配超过 40% 的 RAM 不太可能比较shared_buffers小的数量更好。

如果您有足够的 RAM 存储所有内容(很难想象“具有 10 亿行的表”),那么所缺少的只是有问题的数据页尚未缓存。您可能对pg_prewarm. 看:

旁白:第二个查询计划显示Filter: (zx_prod_id = 4),它与显示的查询不匹配,但我认为这对问题无关紧要。

其他改进

你已经尝试了一个索引,(zx_prod_id, ts)但它......

似乎什么也没做。

不过,该索引通常对查询来说看起来不错。
多列索引比以下索引大 50% 左右(ts):4 + 8 + 16 = 28 个字节 vs. 4 + 8 + 8 = 每个索引元组 20 个字节,并且可能来自“索引重复数据删除”的压缩更少。而且由于 RAM 似乎是一种有争议的资源,这是一个缺点。看:

我看到rows=539265vs. Rows Removed by Filter: 465513. 所以查询读取它需要的行的两倍。根据数据分布,该指数可能是也可能不是改进。如果您的表物理地聚集在该索引上,那肯定是这样。然后大多数数据页包含具有相同zx_prod_id值的行,这使得该索引更加有效。再看:

您的查询似乎总是过滤单个值zx_prod_id。如果这集中在几个值上,或者如果开始时只有几个不同的值,那么几个部分索引可能是有意义的。就像,如果只有zx_prod_id IN (1,4)目标:

CREATE INDEX order_book_ts_zx1_idx ON order_book (ts) WHERE zx_prod_id = 1;
CREATE INDEX order_book_ts_zx4_idx ON order_book (ts) WHERE zx_prod_id = 4;
Run Code Online (Sandbox Code Playgroud)

然后查询可以使用(远)更小的索引,并且不必过滤一半的行。

ts显然是 type timestamptz。您仍然将时间戳作为bigint代表微秒的数字处理。这种拆分方法有缺点和缺陷。通常最好使用整个食物链的时间戳。CAST(extract(EPOCH from ts)*1000000 as bigint)为每一行执行不是免费的。加起来有五十万行。


归档时间:

查看次数:

91 次

最近记录:

4 年,4 月 前