配置PostgreSQL以获得读取性能


39

我们的系统写入大量数据(类似于大数据系统)。写入性能足以满足我们的需求,但读取性能确实太慢。

我们所有表的主键(约束)结构相似:

timestamp(Timestamp) ; index(smallint) ; key(integer).

一个表可以具有数百万行,甚至数十亿行,并且读取请求通常针对特定时间段(时间戳/索引)和标签。查询返回大约200k行是很常见的。目前,我们每秒可以读取1.5万行,但我们需要提高10倍。这可能吗?如果可以,怎么办?

注意: PostgreSQL与我们的软件打包在一起,因此每个客户端的硬件有所不同。

它是用于测试的VM。VM的主机是Windows Server 2008 R2 x64,具有24.0 GB的RAM。

服务器规格(虚拟机VMWare)

Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)

postgresql.conf 优化

shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)

表定义

CREATE TABLE "AnalogTransition"
(
  "KeyTag" integer NOT NULL,
  "Timestamp" timestamp with time zone NOT NULL,
  "TimestampQuality" smallint,
  "TimestampIndex" smallint NOT NULL,
  "Value" numeric,
  "Quality" boolean,
  "QualityFlags" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
  CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
      REFERENCES "Tag" ("Key") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE,
  autovacuum_enabled=true
);

询问

在pgAdmin3中执行查询大约需要30秒,但是如果可能的话,我们希望在5秒以内得到相同的结果。

SELECT 
    "AnalogTransition"."KeyTag", 
    "AnalogTransition"."Timestamp" AT TIME ZONE 'UTC', 
    "AnalogTransition"."TimestampQuality", 
    "AnalogTransition"."TimestampIndex", 
    "AnalogTransition"."Value", 
    "AnalogTransition"."Quality", 
    "AnalogTransition"."QualityFlags", 
    "AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;

说明1

"Limit  (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
"  Buffers: shared hit=190147"
"  ->  Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
"        Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
"        Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"

说明2

在我最新的测试中,花了7分钟选择了我的数据!见下文:

"Limit  (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
"  ->  Index Scan using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"

Answers:


52

数据对齐和存储大小

实际上,元组头的每个元组的开销是24字节,而项目指针的开销是4字节。
在此相关答案中计算的更多详细信息:

关于SO的此相关答案中的数据对齐和填充基础知识:

我们有三列为主键:

PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag")

"Timestamp"      timestamp (8 bytes)
"TimestampIndex" smallint  (2 bytes)
"KeyTag"         integer   (4 bytes)

结果是:

 页面标题中有4个字节的项目指针(不计入8个字节的倍数)
---
元组标头的23个字节
 1个字节填充用于数据对齐(或NULL位图)
 8个字节的“时间戳”
 2个字节的“ TimestampIndex”
 2字节填充,用于数据对齐
 4个字节的“ KeyTag” 
 0填充到8字节的最接近倍数
-----
每个元组44个字节

有关测量对象大小的更多信息,请参见以下相关答案:

多列索引中的列顺序

阅读以下两个问题和答案以了解:

有了索引(主键)的方式,就可以在没有排序步骤的情况下检索行,这很有吸引力,尤其是使用LIMIT。但是检索行似乎非常昂贵。

通常,在多列索引中,“平等”列应排在最前面,“范围”列应排在最后:

因此,请尝试使用反向列顺序的其他索引:

CREATE INDEX analogransition_mult_idx1
   ON "AnalogTransition" ("KeyTag", "TimestampIndex", "Timestamp");

这取决于数据分布。但这millions of row, even billion of rows可能会更快。

由于数据对齐和填充,元组的大小大8字节。如果您将此作为普通索引使用,则可以尝试删除第三列"Timestamp"。可能会更快或更慢一点(因为这可能有助于排序)。

您可能要保留两个索引。根据多种因素,您的原始索引可能会更可取-特别是对于较小的LIMIT

自动真空和表统计

您的表格统计信息必须是最新的。我确定您正在运行autovacuum

由于您的表似乎很大,并且统计对于正确的查询计划很重要,因此我将大幅增加相关列的统计目标

ALTER TABLE "AnalogTransition" ALTER "Timestamp" SET STATISTICS 1000;

...甚至更高,甚至超过数十亿行。最大值为10000,默认值为100。

WHEREor ORDER BY子句中涉及的所有列执行此操作。然后运行ANALYZE

表格布局

同时,如果您应用所学到的有关数据对齐和填充的知识,这种优化的表布局应节省一些磁盘空间并有助于提高性能(忽略pk和fk):

CREATE TABLE "AnalogTransition"(
  "Timestamp" timestamp with time zone NOT NULL,
  "KeyTag" integer NOT NULL,
  "TimestampIndex" smallint NOT NULL,
  "TimestampQuality" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  "QualityFlags" smallint,
  "Quality" boolean,
  "Value" numeric
);

CLUSTER / pg_repack

为了优化使用特定索引(无论是原始索引还是建议的替代索引)的查询的读取性能,可以按索引的物理顺序重写表。CLUSTER这样做,但是它具有侵入性,并且在操作过程中需要排他锁。pg_repack是一种更复杂的替代方法,无需在表上独占锁定即可执行相同的操作。
这对于巨大的表可以有很大帮助,因为必须读取的表块要少得多。

内存

通常,仅2GB的物理RAM不足以快速处理数十亿行。更多的RAM可能会走很长一段路-伴随着适当的设置:显然,更大effective_cache_size的开始。


2
我仅在KeyTag上添加了一个简单的索引,现在看来已经非常快了。我还将应用您有关数据对齐的建议。非常感谢!
JPelletier

9

因此,从计划中我看到了一件事:您的索引要么过大(然后与基础表一起),要么根本就不适用于这种查询(我在上面的最新评论中尝试解决此问题)。

索引的一行包含14个字节的数据(还有一些用于标头)。现在,根据计划中给出的数字进行计算:您从190147页中获得了500,000行-平均而言,每页少于3个有用行,即每8 kb页面大约37个字节。这是一个非常糟糕的比率,不是吗?由于索引的第一列是Timestamp字段,并且在查询中用作范围,因此计划者可以(并且确实)选择索引以查找匹配的行。但是TimestampIndexWHERE条件中没有提到,因此过滤KeyTag并不是很有效,因为据说这些值随机出现在索引页中。

因此,一种可能是将索引定义更改为

CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp", "KeyTag", "TimestampIndex")

(或者,根据系统的负载,将该索引创建为新索引:

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • 这肯定需要一段时间,但您仍然可以在此期间进行工作。)

另一种可能性是索引页的很大一部分被死行占据,这些死行可以通过清除来清除。您使用设置创建了表格autovacuum_enabled=true-但是您是否曾经开始进行自动抽真空?还是VACUUM手动运行?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.