聚合后端数据模型设计:表结构、索引、分片策略

线上 Crash 聚合后端面临海量实时写入与多维分析查询的双重压力,数据模型的设计直接影响写入吞吐、查询延迟与运维成本。本节将从表结构、索引优化、分片策略三个维度展开,给出可落地的方案与关键决策点。

核心要点:Crash 数据天然具有 时间序 + 版本维 + 设备维 复合特征,分片键必须兼顾写入均衡与查询剪枝,索引需覆盖高频聚合维度(app_version、stack_hash、device_id)。
聚合后端数据模型架构 CrashEvents 主表(逻辑表) Shard 1 (2025-01) Shard 2 (2025-02) Shard 3 (2025-03) 索引:time + stack_hash 索引:device_id + app_version 分片路由 分片策略:hash(app_version + month) % N 高频查询携带 app_version 与时间范围,分片键允许一次路由到目标分片,无需全表扫描

4.1 表结构设计

Crash 聚合主表(逻辑表)通常包含以下字段,其中 app_version、timestamp、stack_hash 是最关键的组合查询维度。

字段名称类型说明是否分片键
idBIGINT UNSIGNED自增主键(仅作为行标识)否
app_versionVARCHAR(32)应用版本号,如 "6.3.1"是(组合)
crash_typeTINYINT崩溃类型枚举(0=Native, 1=Java, 2=ANR)否
stack_hashCHAR(8)崩溃栈的 CRC32/MD5 前 8 位否
device_idVARCHAR(64)设备唯一标识(IDFA/OAID)否
os_versionVARCHAR(16)操作系统版本否
timestampDATETIME(3)崩溃发生时间(精确到毫秒)是(组合)
raw_stackMEDIUMTEXT原始崩溃栈文本否
is_grayTINYINT(1)是否为灰度用户上报(0/1)否
extra_tagsJSON扩展标签(如机型、渠道)否
设计原则:不做跨分片 JOIN,所有业务查询应携带 app_version + timestamp 范围。MySQL 实例建议使用分区表(PARTITION BY RANGE(time))配合应用层分片,Databend/ClickHouse 则直接用分布式表。

基础 DDL(MySQL 示例,分片则通过 Proxy 或应用层路由,此处仅展示单分片表结构):

CREATE TABLE crash_events_2025_01 (
  id BIGINT UNSIGNED AUTO_INCREMENT,
  app_version VARCHAR(32) NOT NULL,
  crash_type TINYINT NOT NULL,
  stack_hash CHAR(8) NOT NULL,
  device_id VARCHAR(64) NOT NULL,
  os_version VARCHAR(16),
  timestamp DATETIME(3) NOT NULL,
  raw_stack MEDIUMTEXT,
  is_gray TINYINT(1) DEFAULT 0,
  extra_tags JSON,
  PRIMARY KEY (id, timestamp),
  UNIQUE KEY uk_device_time (device_id, timestamp),
  ...
) ENGINE=InnoDB
PARTITION BY RANGE (TO_DAYS(timestamp)) (
  PARTITION p2025_01 VALUES LESS THAN (TO_DAYS('2025-02-01')),
  PARTITION p2025_02 VALUES LESS THAN (TO_DAYS('2025-03-01'))
);

4.2 索引设计

根据灰度验证业务场景(按版本查、按 stack 聚合、按设备追溯),建议创建以下二级索引:

索引名称包含列类型适用场景
idx_version_timeapp_version, timestampB-Tree按版本统计时间线崩溃率
idx_hash_timestack_hash, timestampB-Tree按 stack 聚合计数,定位 Top Crash
idx_device_versiondevice_id, app_versionB-Tree某设备在灰度版本的崩溃记录
idx_gray_stackis_gray, stack_hashB-Tree灰度用户特有崩溃分析
注意:不要对 raw_stack 字段建索引;长文本索引膨胀且无效。如果需要全文检索崩溃栈关键字,建议使用 Elasticsearch 或 TiFlash 列存引擎独立索引。

4.3 分片策略

分片策略决定写入均衡性与查询效率。常见的两种方案对比:

方案分片键优点缺点推荐场景
按月 + 版本取模CONCAT(app_version, DATE_FORMAT(timestamp, '%Y-%m')) -> hash % N时间范围相邻的数据落同一分片,利于范围查询热点版本可能导致单分片写入压力大版本数量≤100,单日千万级
一致性哈希app_version + device_id -> ketama_hash % N写入分布均匀,扩缩容影响范围小时间范围查询可能跨多分片版本多、设备量大,亿级日写入
推荐实践:优先采用 按月 + 版本取模,因为灰度验证场景中查询几乎总是限定 app_version 和一周内的时间窗口,该方案可将 95% 的查询路由到 1-2 个分片。再配合 Proxy(如 MyCat/ShardingSphere)的读写分离,能稳定支撑单日 2 千万条写入。

分片配置示例(ShardingSphere YAML,简化版):

rules:
  - !SHARDING
    tables:
      crash_events:
        actualDataNodes: ds$->{0..3}.crash_events_$->{2025_01..2025_03}
        tableStrategy:
          standard:
            shardingColumn: timestamp
            shardingAlgorithmName: monthSharding
        databaseStrategy:
          standard:
            shardingColumn: app_version
            shardingAlgorithmName: versionHashMod
        keyGenerateStrategy:
          column: id
          keyGeneratorName: snowflake
    shardingAlgorithms:
      monthSharding:
        type: INTERVAL
        props:
          datetime-pattern: "yyyy-MM-dd HH:mm:ss"
          datetime-lower: "2025-01-01 00:00:00"
          sharding-suffix-pattern: "yyyy_MM"
          datetime-interval-amount: 1
          datetime-interval-unit: MONTHS
      versionHashMod:
        type: HASH_MOD
        props:
          sharding-count: 4

分片扩容时,一致性哈希方案只需迁移少量数据(新增节点后仅 1/N 键需重映射),而按月 + 版本取模需要手工拆分热点分片。建议设定单分片存储上限(如 5 亿行),达到阈值后自动触发新的一年份分片,或者用 app_version + month 二次取模实现动态扩容。