Postgres:具有(延迟)读写访问权限的大表

Posted

技术标签:

【中文标题】Postgres:具有(延迟)读写访问权限的大表【英文标题】:Postgres: huge table with (delayed) read and write access 【发布时间】:2014-08-28 06:20:46 【问题描述】:

我有一个巨大的表(目前大约 300 万行,预计将增加 1000 倍),每秒有很多插入。该表永远不会更新。

现在我必须在该表上运行非常慢的查询(如预期的那样)。这些查询不必是 100% 准确的,如果结果是一天前的(但不是更早的)就可以了。

目前在两个单个整数列上有两个索引,我必须再添加两个索引(整数和时间戳列)以加快查询速度。

到目前为止我的想法:

    将两个缺失的索引添加到表中 大表上根本没有索引,将内容(作为日常任务)复制到第二个表(仅重要行),然后在第二个表上创建索引并在该表上运行查询? 对大表进行分区 主/从设置(写入主从从)。

就性能而言,哪个选项最好?你有什么其他的建议?

编辑:

这是表格(我已经标记了外键并稍微美化了查询):

CREATE TABLE client_log
(
   id                 serial          NOT NULL,
   logid              integer         NOT NULL,
   client_id          integer         NOT NULL,  (FOREIGN KEY)
   client_version     varchar(16),
   sessionid          varchar(100)    NOT NULL,
   created            timestamptz     NOT NULL,
   filename           varchar(256),
   funcname           varchar(256),
   linenum            integer,
   comment            text,
   domain             varchar(128),
   code               integer,
   latitude           float8,
   longitude          float8,
   created_on_server  timestamptz     NOT NULL,
   message_id         integer,                   (FOREIGN KEY)
   app_id             integer         NOT NULL,  (FOREIGN KEY)
   result             integer
);

CREATE INDEX client_log_code_idx ON client_log USING btree (code);
CREATE INDEX client_log_created_idx ON client_log USING btree (created);
CREATE INDEX clients_clientlog_app_id ON client_log USING btree (app_id);
CREATE INDEX clients_clientlog_client_id ON client_log USING btree (client_id);
CREATE UNIQUE INDEX clients_clientlog_logid_client_id_key ON client_log USING btree (logid, client_id);
CREATE INDEX clients_clientlog_message_id ON client_log USING btree (message_id);

还有一个示例查询:

SELECT 
    client_log.comment, 
    COUNT(client_log.comment) AS count 
FROM 
    client_log
WHERE 
    client_log.app_id = 33 AND
    client_log.code = 3 AND 
    client_log.client_id IN (SELECT client.id FROM client WHERE 
        client.app_id = 33 AND 
        client."replaced_id" IS NULL)
GROUP BY client_log.comment ORDER BY count DESC;

client_log_code_idx 是上述查询所需的索引。还有其他查询需要 client_log_created_idx 索引。

以及查询计划:

Sort  (cost=2844.72..2844.75 rows=11 width=242) (actual time=4684.113..4684.180 rows=70 loops=1)
  Sort Key: (count(client_log.comment))
  Sort Method: quicksort  Memory: 32kB
  ->  HashAggregate  (cost=2844.42..2844.53 rows=11 width=242) (actual time=4683.830..4683.907 rows=70 loops=1)
        ->  Hash Semi Join  (cost=1358.52..2844.32 rows=20 width=242) (actual time=303.515..4681.211 rows=1202 loops=1)
              Hash Cond: (client_log.client_id = client.id)
              ->  Bitmap Heap Scan on client_log  (cost=1108.02..2592.57 rows=387 width=246) (actual time=113.599..4607.568 rows=6962 loops=1)
                    Recheck Cond: ((app_id = 33) AND (code = 3))
                    ->  BitmapAnd  (cost=1108.02..1108.02 rows=387 width=0) (actual time=104.955..104.955 rows=0 loops=1)
                          ->  Bitmap Index Scan on clients_clientlog_app_id  (cost=0.00..469.96 rows=25271 width=0) (actual time=58.315..58.315 rows=40662 loops=1)
                                Index Cond: (app_id = 33)
                          ->  Bitmap Index Scan on client_log_code_idx  (cost=0.00..637.61 rows=34291 width=0) (actual time=45.093..45.093 rows=36310 loops=1)
                                Index Cond: (code = 3)
              ->  Hash  (cost=248.06..248.06 rows=196 width=4) (actual time=61.069..61.069 rows=105 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 4kB
                    ->  Bitmap Heap Scan on client  (cost=10.95..248.06 rows=196 width=4) (actual time=27.843..60.867 rows=105 loops=1)
                          Recheck Cond: (app_id = 33)
                          Filter: (replaced_id IS NULL)
                          Rows Removed by Filter: 271
                          ->  Bitmap Index Scan on clients_client_app_id  (cost=0.00..10.90 rows=349 width=0) (actual time=15.144..15.144 rows=380 loops=1)
                                Index Cond: (app_id = 33)
Total runtime: 4684.843 ms

【问题讨论】:

如果没有更多信息,无法回答此问题。查询是什么样的?查询的执行计划是什么(您可能想阅读此内容:wiki.postgresql.org/wiki/SlowQueryQuestions)。但总的来说,我会先尝试不同的索引(根据您的插入速度,确保删除所有不需要的索引),然后再尝试分区。 您的id serial 可能用作主键:试试 -->> id (big)serial NOT NULL PRIMARY KEY 这是一个很好的观点。我已将其更改为:ALTER TABLE client_log DROP COLUMN id;ALTER TABLE client_log ADD COLUMN id bigserial NOT NULL PRIMARY KEY;id 未在其他任何地方使用)。 【参考方案1】:

一般来说,在一个时间相关数据不断插入数据库的系统中,我会根据时间推荐partitioning。

这不仅是因为它可能会缩短查询时间,还因为否则它会使数据管理变得困难。无论您的硬件有多大,它的容量都会受到限制,因此您最终将不得不开始删除早于某个日期的行。删除行的速率必须等于它们进入的速率。

如果您只有一张大表,并使用 DELETE 删除旧行,您将留下很多需要清理的死元组。 autovacuum 将持续运行,耗尽宝贵的磁盘 IO。

另一方面,如果按照时间进行分区,那么删除过期数据就像删除相关子表一样简单。

在索引方面 - 索引不是继承的,因此您可以在加载分区之前节省创建索引的时间。在您的用例中,您的分区大小可能为 1 天。这意味着在插入数据时不需要不断更新索引。根据需要添加额外的索引来执行查询会更实用。

您的示例查询不会过滤“创建”时间字段,但您说其他查询会过滤。如果您按时间进行分区,并且对构建查询的方式非常小心,那么约束排除就会发挥作用,它只会包括与查询相关的特定分区。

【讨论】:

【参考方案2】:

除了分区,我会考虑将表拆分为多个表,也就是分片。

我不了解您的域的全貌,但以下是一些建议:

每个客户端都在自己的架构中获得自己的表(或一组客户端共享一个架构,具体取决于您拥有多少客户端以及您希望获得多少新客户端)。

create table client1.log(id, logid,.., code, app_id);
create table client2.log(id, logid,.., code, app_id);

像这样拆分表也应该减少插入的争用。

表格可以进一步拆分。在每个客户端模式中,您还可以按“代码”或“app_id”或其他对您有意义的东西来拆分表。这可能有点过头了,但如果“code”和/或“app_id”值的数量不经常变化,则很容易实现。 即使在新的较小的表中也要保留 code/app_id 列,但要对列施加约束,以便不能插入其他类型的日志记录。约束也将在搜索时帮助优化器,参见这个例子:

create schema client1;
set search_path = 'client1';

create table error_log(id serial, code text check(code ='error'));
create table warning_log(id serial, code text check(code ='warning'));
create table message_log(id serial, code text check(code ='message'));

要获得客户端的全貌(所有行),您可以在所有表​​的顶部使用视图:

create view client_log as
select * from error_log
union all
select * from warning_log
union all
select * from message_log;

检查约束应该允许优化器只搜索“代码”可以存在的表。

explain
select * from client_log where code = 'error';
-- Output
Append  (cost=0.00..25.38 rows=6 width=36)
  ->  Seq Scan on error_log  (cost=0.00..25.38 rows=6 width=36)
        Filter: (code = 'error'::text)

【讨论】:

以上是关于Postgres:具有(延迟)读写访问权限的大表的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 中更新具有数百万行的大表?

MySQL 对于千万级的大表要怎么优化?

MySQL 对于千万级的大表要怎么优化?

BigQuery 无法查询滞后的大表

从 MySQL 中的大表中删除重复项的最快过程是啥

经常重新填充的大表索引