标签平台3.0升级手记:我们如何用ClickHouse替换TiDB实现亿级数据秒级查询

张开发
2026/5/23 16:06:43 15 分钟阅读
标签平台3.0升级手记:我们如何用ClickHouse替换TiDB实现亿级数据秒级查询
标签平台3.0架构升级实战从TiDB到ClickHouse的亿级数据优化之路当标签数据量突破十亿级别时查询响应时间从秒级变成分钟级——这是我们团队在电商大促期间遭遇的典型性能瓶颈。作为平台架构负责人我不得不重新审视整个标签存储引擎的设计。本文将完整呈现我们如何通过技术选型、迁移方案设计和实战调优最终实现复杂查询性能提升40倍的完整过程。1. 为什么必须更换存储引擎三年前选择TiDB作为核心查询引擎时它确实完美满足了当时的需求支持事务、兼容MySQL协议、具备水平扩展能力。但随着业务规模指数级增长三个致命问题逐渐暴露聚合查询性能瓶颈当单表数据量超过20亿行时涉及多维度GROUP BY的标签分析查询平均响应时间超过8秒存储成本居高不下采用多副本机制导致存储空间利用率不足40%冷数据压缩率仅为3:1实时写入冲突高峰时段每秒上万次的标签更新操作导致MVCC版本链膨胀我们对比测试了当前主流OLAP引擎在标签场景的表现测试环境32核/128GB内存/SSD存储引擎10亿数据扫描速率(GB/s)百分位查询延迟(P99)压缩比并发查询支持TiDB2.14.3s3.2:1150ClickHouse12.70.8s8.5:150Doris8.41.2s6.7:1100StarRocks9.11.1s7.3:1120测试用例SELECT tag_category, COUNT(DISTINCT user_id) FROM user_tags WHERE create_date2023-01-01 GROUP BY tag_category WITH TOTALSClickHouse在批量扫描和压缩效率上的优势使其成为海量标签数据分析场景的最优解。但迁移绝非简单的替换数据库需要解决三大核心挑战如何保证在线业务不受迁移影响如何处理TiDB特有的事务特性如何重构现有查询模式适配列式存储2. 零停机迁移方案设计2.1 双写与增量同步机制我们在应用层抽象出统一的存储代理层关键设计包括public class StorageProxy { // 双写开关配置 Value(${storage.dual.write.enabled}) private boolean dualWriteEnabled; // 写入TiDB主库 public void writeToTiDB(TagUpdate update) { tidbTemplate.execute(update.toSQL()); if (dualWriteEnabled) { asyncWriteToCH(update); // 异步写入ClickHouse } } // 异步写入ClickHouse private void asyncWriteToCH(TagUpdate update) { chQueue.add(update); // 写入队列 } }配合Kafka实现增量数据同步使用Debezium监控TiDB binlog将变更事件发送到Kafka的tag_updates主题Flink消费Kafka消息并转换后写入ClickHouse2.2 数据一致性验证开发了专门的数据校验工具核心校验逻辑def verify_data_consistency(): # 抽样比对关键指标 metrics [ total_record_count, distinct_user_count, tag_category_distribution ] for metric in metrics: tidb_val query_tidb(metric) ch_val query_ch(metric) if abs(tidb_val - ch_val) threshold: alert_inconsistency(metric)3. ClickHouse集群优化实战3.1 表引擎选型与分区设计放弃通用的MergeTree采用以下定制化方案CREATE TABLE user_tags ( user_id UInt64, tag_id String, tag_value String, create_date Date, update_time DateTime ) ENGINE ReplicatedCollapsingMergeTree(/clickhouse/tables/{shard}/user_tags, {replica}) PARTITION BY toYYYYMM(create_date) ORDER BY (tag_id, user_id) SETTINGS index_granularity 8192关键优化点采用CollapsingMergeTree处理标签更新按月分区避免小文件问题调整index_granularity降低内存消耗3.2 物化视图加速分析针对高频分析场景预计算指标CREATE MATERIALIZED VIEW tag_stats_mv ENGINE ReplicatedAggregatingMergeTree PARTITION BY toYYYYMM(create_date) ORDER BY (tag_category, create_date) AS SELECT tag_category, create_date, countState(user_id) AS user_count, quantilesState(0.5, 0.9)(tag_value) AS value_dist FROM user_tags GROUP BY tag_category, create_date3.3 查询模式重构将原有的多表JOIN改为宽表预聚合模式旧模式(TiDB):SELECT t1.user_id, t2.tag_name FROM user_tags t1 JOIN tag_metadata t2 ON t1.tag_id t2.id WHERE t2.category preference新模式(ClickHouse):SELECT user_id, tag_name FROM user_tags_wide WHERE tag_category preference4. 性能对比与业务收益迁移完成后关键指标变化指标TiDBClickHouse提升标签查询P994.2s98ms42x存储空间12TB2.1TB5.7x计算资源32核×10节点32核×6节点40%↓大促扩容成本$15k/天$3k/天80%↓业务侧获得的核心能力提升实时标签分析响应时间进入亚秒级支持同时分析100维度组合历史数据查询性能提升显著在最近的双11大促中新系统平稳支撑了峰值QPS 23万的标签查询请求平均延迟控制在120ms以内。一个意外的收获是由于查询速度大幅提升运营团队开始尝试更复杂的标签组合分析发现了多个之前未被识别的用户高价值群体。5. 踩坑经验与未来规划值得注意的实践细节ClickHouse的JOIN性能在表大小差异较大时急剧下降建议将小表转为字典CREATE DICTIONARY tag_meta_dict ( id UInt64, name String ) PRIMARY KEY id SOURCE(HTTP(URL http://meta-service/tags FORMAT JSON)) LIFETIME(300)批量写入时控制批次大小在5万-10万行/批次最佳避免高频小批量INSERT建议攒批处理后续优化方向试验Projection特性进一步提升分析查询性能整合Apache Pinot实现亚秒级实时标签分析探索GPU加速在超大规模标签计算中的应用

更多文章