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

时间:2014-08-28 06:20:47

标签: sql postgresql indexing bigdata

我有一个巨大的表(当前约3个行,预计会增加1000倍),每秒都有大量的插入。该表永远不会更新。

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

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

到目前为止我的想法:

  1. 将两个缺失的索引添加到表
  2. 根本没有巨大的表上的索引,并将内容(作为日常任务)复制到第二个表(只是重要的行),然后在第二个表上创建索引并在该表上运行查询?
  3. 对巨大的桌子进行分区
  4. 主/从设置(写入主设备并从从设备读取)。
  5. 在性能方面哪个选项最好?你有什么其他的建议?

    修改

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

    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
    

2 个答案:

答案 0 :(得分:4)

通常,在一个系统中,时间相关数据不断插入数据库,我建议partitioning根据时间。

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

如果您只有一个大表,并且使用DELETE删除旧行,则会留下许多需要抽真空的死元组。 autovacuum将持续运行,耗尽宝贵的磁盘IO。

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

就索引而言 - 索引不会被继承,因此您可以在加载分区之前保存创建索引。在用例中,您可以使用1天的分区大小。这意味着在插入数据时不需要不断更新索引。根据需要使用其他索引来进行查询会更实际。

您的示例查询不会过滤“已创建的”'时间字段,但你说其他查询。如果按时间进行分区,并且在构建查询时要小心,那么约束排除将会启动,并且只包含与查询相关的特定分区。

答案 1 :(得分:1)

除了分区之外,我会考虑将表拆分为多个表,即Sharding。

我没有全面了解您的域名,但这些是一些建议:

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

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

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

该表可以拆分得更多。在每个客户端模式中,您还可以按照"代码"或" app_id" 或其他对你有用的东西。这可能是过度的,但如果"代码的数量很容易实现。和/或" 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)