You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS PostgreSQL 40亿行表慢查询优化及百亿级扩容方案咨询

问题描述

我们有一张存储用户活动的关系型表,如下查询耗时77秒!

FROM "site_activity"
WHERE
    (
        NOT "site_activity"."is_deleted"
        AND "site_activity"."user_id" = 68812389
        AND NOT (
            "site_activity"."kind" IN (
                'updated',
                'duplicated',
                'reapplied'
            )
        )
        AND NOT (
            "site_activity"."content_type_id" = 14
            AND "site_activity"."kind" = 'created'
        )
    )
ORDER BY
    "site_activity"."created_at" DESC,
    "site_activity"."id" DESC
LIMIT  9;

查询计划如下:

QUERY PLAN
--------------------------------------------------------------------------------------------
Limit
    (cost=17750.72..27225.75 rows=9 width=16)
    (actual time=199501.336..199501.338 rows=9 loops=1)
  Output: id, created_at
  Buffers: shared hit=4502362 read=693523 written=37273
  I/O Timings: read=190288.205 write=446.870
  ->  Incremental Sort
      (cost=17750.72..2003433582.97 rows=1902974 width=16)
      (actual time=199501.335..199501.336 rows=9 loops=1)
        Output: id, created_at
        Sort Key: site_activity.created_at DESC, site_activity.id DESC
        Presorted Key: site_activity.created_at
        Full-sort Groups: 1  Sort Method: quicksort  Average Memory: 25kB  Peak Memory: 25kB
        Buffers: shared hit=4502362 read=693523 written=37273
        I/O Timings: read=190288.205 write=446.870
        ->  Index Scan Backward using site_activity_created_at_company_id_idx on public.site_activity
            (cost=0.58..2003345645.30 rows=1902974 width=16)
            (actual time=198971.283..199501.285 rows=10 loops=1)
              Output: id, created_at
              Filter: (
                (NOT site_activity.is_deleted) AND (site_activity.user_id = 68812389)
                AND ((site_activity.kind)::text <> ALL ('{updated,duplicated,reapplied}'::text[]))
                AND ((site_activity.content_type_id <> 14) OR ((site_activity.kind)::text <> 'created'::text))
              )
              Rows Removed by Filter: 14735308
              Buffers: shared hit=4502353 read=693523 written=37273
              I/O Timings: read=190288.205 write=446.870
Settings: effective_cache_size = '261200880kB',
          effective_io_concurrency = '400',
          jit = 'off',
          max_parallel_workers = '24',
          random_page_cost = '1.5',
          work_mem = '64MB'
Planning:
  Buffers: shared hit=344
Planning Time: 6.429 ms
Execution Time: 199501.365 ms
(22 rows)

Time: 199691.997 ms (03:19.692)
表信息
  • 表中包含40亿多行数据。
  • 表结构如下:
Table "public.site_activity"
    Column      |           Type           | Collation | Nullable |                   Default
----------------+--------------------------+-----------+----------+----------------------------------------------
id              | bigint                   |           | not null | nextval('site_activity_id_seq'::regclass)
created_at      | timestamp with time zone |           | not null |
modified_at     | timestamp with time zone |           | not null |
is_deleted      | boolean                  |           | not null |
object_id       | bigint                   |           | not null |
kind            | character varying(32)    |           | not null |
context         | text                     |           | not null |
company_id      | integer                  |           | not null |
content_type_id | integer                  |           | not null |
user_id         | integer                  |           |          |
Indexes:
    "site_activity_pkey" PRIMARY KEY, btree (id)
    "site_activity_modified_at_idx" btree (modified_at)
    "site_activity_company_id_idx" btree (company_id)
    "site_activity_created_at_company_id_idx" btree (created_at, company_id)
    "site_activity_object_id_idx" btree (object_id)
    "site_activity_content_type_id_idx" btree (content_type_id)
    "site_activity_kind_idx" btree (kind)
    "site_activity_kind_idx1" btree (kind varchar_pattern_ops)
    "site_activity_user_id_idx" btree (user_id)
Foreign-key constraints:
    "site_activity_company_id_fk_site_company_id" FOREIGN KEY (company_id)
        REFERENCES site_company(id) DEFERRABLE INITIALLY DEFERRED
    "site_activity_content_type_id_fk_django_co" FOREIGN KEY (content_type_id)
        REFERENCES django_content_type(id) DEFERRABLE INITIALLY DEFERRED
    "site_activity_user_id_fk_site_user_id" FOREIGN KEY (user_id)
        REFERENCES site_user(id) DEFERRABLE INITIALLY DEFERRED
  • kind在应用(Python)中作为enum处理,数据库中存储为varchar,值固定,约有100种。
  • content_type_id约有80种取值。
  • 数据分布情况:
    • context实际为JSON格式,最大8MB。
    • 3种content_type_id(含14和19)占**92%**的行数据。
    • 3种kind值(created、updated、sent)占**75%**的行数据。
    • kind与content_type_id的组合有460种,其中1种组合占35%的数据,且该组合在查询中被永久排除。
  • 副本实例类型为db.r5.12xlarge:24核、48 vCPU、384GB内存,存储类型为io1。
咨询问题
  1. 若表数据增长至1000亿行(预计3-5年内发生),该如何处理?
  2. NoSQL是否为合适的解决方案?注:我们并非仅通过id或kind访问数据。

备注

  • 现有信息可能倾向于单主机复制+多主机分片方案,但如果有其他可支撑百亿级数据的方案也可考虑。
  • 不强制使用AWS,但优先考虑AWS方案。

解决方案与回答

1. 1000亿行数据的处理方案

(1)先优化当前查询与索引

当前查询慢的核心是索引不匹配:执行计划选择了site_activity_created_at_company_id_idx倒序扫描,过滤掉1473万行才找到目标数据,完全未利用user_id索引。建议创建覆盖索引:

CREATE INDEX idx_site_activity_user_created_filter ON site_activity (user_id, created_at DESC, id DESC)
INCLUDE (is_deleted, kind, content_type_id);

该索引直接匹配user_id过滤条件,同时包含排序键和过滤所需字段,无需回表查询,可将查询耗时从分钟级降至毫秒级。

(2)水平分片(Sharding)

千亿级数据必须依赖水平分片突破单库瓶颈:

  • 分片键选择:优先选user_id(可结合company_id),核心查询按user_id过滤,同用户数据路由到同一分片,避免跨分片查询;
  • AWS方案:
    • 使用Aurora PostgreSQL/MySQL原生分片能力,或通过RDS配合ShardingSphere等中间件实现;
    • 若需全球访问,可采用Aurora全球数据库,跨区域同步数据,就近提供服务;
  • 分片规则:哈希分片可均匀分配数据,范围分片便于按用户群体归档或扩容,按需选择即可。

(3)冷热数据分离与归档

用户活动数据存在明显冷热特征,可做如下处理:

  • 分区表:按created_at(年/月)分区,查询仅扫描最近的热数据分区,避免全表扫描;
  • 冷数据归档:将超过N年的冷数据迁移到S3存储,用Athena做离线查询,或归档到只读分片,减少主分片的存储和查询压力。

(4)垂直拆分

将context字段从主表拆分到独立表:

  • context是最大8MB的JSON,会增大主表行大小,降低索引效率;
  • 主表仅存储查询频繁的字段(user_id、created_at、kind等),需要context时再关联查询子表,减少主表IO开销。

2. NoSQL是否为合适的解决方案?

是否适合取决于查询模式的复杂度:

(1)适合场景

如果查询模式相对固定(核心是按user_id查最新活动,配合少量过滤条件),NoSQL是合适的:

  • MongoDB(AWS DocumentDB):支持复合索引(user_id, created_at DESC),能高效匹配当前查询,分片能力成熟;
  • DynamoDB:设计主键为(user_id, created_at)(排序键created_at DESC),查询最新9条数据的效率极高,适合高并发场景,但复杂过滤需在查询后处理,可能增加扫描量;
  • Cassandra:若查询模式完全固定(仅按user_id查最新活动),可设计表结构为PRIMARY KEY (user_id, created_at DESC, id),读写性能极致,但不支持灵活的跨条件查询。

(2)不适合场景

如果存在大量复杂查询(如多维度聚合、跨用户/公司的关联查询、复杂条件组合过滤),关系型数据库分片仍是更优选择——NoSQL对复杂查询的支持有限,灵活性远不如关系型数据库。


内容的提问来源于stack exchange,提问作者Shiplu Mokaddim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 21:24:28