
从一套ClickHouse扛住全部查询到后来不得不把实时链路单独拆出来再到现在把Doris、ES和一些轻量计算引擎混着用这套营销自动化的OLAP架构前前后后演进了一年多。中间踩过的坑不少但更值得记录的是每一次选型切换背后的真实业务驱动——不是技术追新而是数据量、查询模式和人效逼着你往前走。这套系统解决的核心问题简单说就是把分散在广告平台、CRM、埋点日志、订单库里的数据统一收进来再让运营、增长、投放同学能快速圈选人群、看转化漏斗、分析活动效果。整个过程涉及多源数据的接入、清洗、建模、存储和查询而OLAP引擎在其中扮演的是“查询加速”和“多维分析”的关键角色。这篇东西主要写给两类人看。一类是正在做营销数据中台或者用户画像系统的工程师可以参考我们在引擎选型、数据建模、实时链路设计上的取舍另一类是想把数据驱动真正落到业务里的数据分析师或运营负责人看完至少能明白为什么一个简单的人群圈选后台要养那么一大堆组件以及哪些环节最容易出幺蛾子。1. 内容整体设计与思路拆解1.1 营销自动化场景对OLAP的真实需求先聊一个容易被低估的点营销自动化和普通BI报表对OLAP的要求完全是两码事。普通报表查询是“少量人、低频次、大查询”比如每天早上的经营日报或者管理层偶尔看的趋势图并发低、查询慢一点也能接受。但营销自动化平台面对的是“大量人、高频次、复杂查询”——几百个运营同时在创建人群包每个圈选操作背后都是一次多维组合查询活动上线后实时效果看板每秒都在刷新自动化流程触发时需要在毫秒级判断用户是否符合进入某个分群的条件。这种场景下OLAP引擎要扛住的压力有三个方面。第一是查询的维度组合非常自由。运营圈人群的时候不会按你预先设计好的索引去查亿级用户表上可能任意组合性别、年龄、地域、最近30天消费次数、客单价、活跃渠道等十几个维度。传统MySQL在这种查询下基本就是灾难就算建了联合索引也扛不住高并发更别提还要做聚合计算。第二是数据的时效性要求很高。营销活动不像财务报表可以T1算用户今天领了券、下了单、看了某个商品详情页这些行为可能几小时后就要进入人群筛选逻辑。如果OLAP链路只能做到小时级更新很多自动化营销策略根本跑不起来。第三是多源数据之间存在复杂的关联关系。一个用户可能在微信小程序里浏览在App里下单在天猫旗舰店退款还通过客服CRM系统提交过投诉工单。要完整评估这个用户的价值、偏好和生命周期阶段必须把这些分散在不同系统的数据按用户ID关联起来形成统一视图。OLAP架构演进的核心其实就是在回答一个问题在数据规模、查询复杂度和实时性三者之间怎么找到当前阶段最合适的平衡点。没有银弹只有取舍。1.2 架构演进的三个核心阶段我们的演进路径大概分成三个阶段每个阶段都有清晰的技术选型逻辑。第一阶段叫“明细查询时代”。系统刚上线时数据量只有几千万级查询也不复杂直接用MySQL分库分表加Elasticsearch就够用。用户ID索引放在MySQL里做精确匹配行为明细、订单数据丢进ES做倒排索引查询。这个阶段的优点是简单直接开发速度快业务跑得起来缺点是只支持简单筛选复杂聚合基本做不了数据量翻几倍之后ES的查询性能衰减非常明显。第二阶段叫“OLAP引擎引入时代”。数据量涨到几亿后ES集群越扩越大查询却越来越慢我们就引入了Apache Doris作为核心OLAP引擎把用户标签、行为汇总、订单事实表全部迁到Doris里。这个阶段解决了多维分析的核心痛点通过前缀索引、分区分桶、向量化执行引擎实现了秒级响应的高并发多维查询。但问题也随之而来——实时性要求高的场景还是依赖KafkaFlink的实时链路Doris这边只做离线批量的标签加工两套数据之间存在不一致的风险。第三阶段是“多引擎协同时代”。当前我们正在推进的架构形态核心思路是让专业引擎干专业的事Doris负责高并发多维分析和人群圈选ES继续承担日志明细的全文检索和部分灵活查询StarRocks或者继续用Doris负责大规模离线聚合ClickHouse则保留在特定实时报表场景中。这套演进的核心逻辑是业务需求永远走在前头技术选型永远在成本和性能之间做权衡。不会因为Doris功能多就强迫所有场景都用Doris也不会因为重构麻烦就拒绝引入新技术。架构演进的核心判断标准只有一个——当前的系统是否还能以合理的成本满足业务增长预期。1.3 为什么选择Doris作为核心OLAP引擎在深入细节前想单独聊聊为什么Doris在中后期会成为核心引擎。这个选型其实经历了很长时间的调研和对比。当时摆在面前的主要候选是ClickHouse、Doris、StarRocks三选一。ClickHouse的优势是单表查询速度极快特别是大宽表场景下性能非常猛社区生态也成熟很多大厂都在用。但它的痛点也很明显多表JOIN支持不友好数据更新成本高并发查询能力相对有限运维门槛也不低。对我们这种需要频繁做多表关联、高并发人群圈选的营销场景ClickHouse并不合适。Doris当时打动我们的点有三个。一是完善的分布式架构设计支持弹性扩缩容数据自动均衡运维省心。二是强一致性的数据模型支持主键模型、聚合模型、Unique模型对需要频繁更新标签、删除过期数据的场景非常友好。三是优秀的查询优化器多表JOIN性能远好于ClickHouse在高并发小查询的场景下表现优秀。选型过程中我们做了一个对比压测用同一批2亿行的事实表加1亿行的用户维表做JOIN聚合查询Doris在20并发下P95响应时间为180毫秒左右ClickHouse在相同条件下会退化到2秒以上甚至部分复杂查询会OOM。这个结果基本就定了方向。注意当前没有哪款OLAP引擎能通吃所有场景。不要迷信某个引擎的“全链路解决方案”要根据自己业务的实际场景做取舍。下一篇会详细讲我们在引擎选型时踩过的坑。2. 核心细节解析与实操要点2.1 多源数据接入的统一规范数据接入是整个OLAP架构的地基这块一旦乱了后面建模、查询、分析全都会出问题。我们的多源数据接入主要分成四类这四类的接入方式和处理逻辑完全不同。第一类是客户端行为数据包括App、小程序、Web端埋点。统一走服务端上报到Kafka通过Flink实时清洗后写入Doris和消息队列。这块的核心规范是埋点事件命名和参数结构必须统一不然清洗逻辑会走到崩溃。我们的做法是定义了一套标准事件模型所有端上报的数据都围绕event_name、event_time、distinct_id、properties这四要素展开其中properties是JSON格式允许各端自定义扩展但必须遵循统一的字段命名规范。这套标准我们维护在一个JSON Schema里每次埋点上线前都要做一次校验。第二类是业务数据库数据包括订单表、用户表、优惠券表、积分流水表等。通过Canal监听MySQL Binlog解析后写入Kafka再分流到不同下游。订单表同步到Doris做事实分析用户表同步到Doris做维表同时同步一份到ES供搜索场景使用。这里最关键的是Binlog同步的幂等性和数据一致性保障因为Binlog中同一个主键的多次变更会依次到达下游必须按顺序处理否则会产生数据覆盖错乱。第三类是广告平台数据包括巨量引擎、腾讯广告等渠道的消耗数据、转化回传数据。这类数据最麻烦的地方在于各平台的字段口径不统一——有的叫“消耗”有的叫“花费”有的按点击时间归因有的按转化时间归因。我们的做法是多层清洗第一层做字段映射和单位统一第二层做渠道去重和归因口径匹配第三层做数据校验比如消耗金额不能为负、转化数不能超过点击数全部校验通过后才进入Doris的广告分析主题。第四类是CRM和客服数据覆盖工单记录、售后记录、用户反馈内容。这类数据量相对不大但结构差异化极大文本类数据多且对实时性要求不高。处理方式是每天定时批量同步到Doris和ESDoris侧用于用户生命周期分析ES侧用于文本检索场景。这里特别想强调多源数据接入时一个常见的认知误区大部分团队在接入初期只关心“怎么把数据导进来”比较少考虑“不同来源的数据冲突时以谁为准”。我们自己在这个问题上吃了不少亏——比如用户表里的手机号CRM系统和订单库都可能更新但更新时机和准确性完全不同。经过几轮踩坑我们才建立起一套完整的血缘管理机制每个字段都标注来源系统、更新频率、置信度等级同一字段出现冲突时按照置信度从高到低覆盖写入。这套机制一开始看起来像是增加工作量但在后期排查数据质量问题时能省下大量时间。2.2 数据建模的核心逻辑宽表优先维表为辅在OLAP场景中数据建模的核心原则是“宽表优先、维表为辅”。这和传统数仓的范式建模思路不一样传统数仓讲究的是标准化和减少冗余通过多层ETL把数据拆成事实表和维表再用JOIN把它们关联起来。但在OLAP引擎里JOIN是要消耗大量计算资源的尤其在高并发场景下每多一个JOIN查询性能可能成倍下降。我们的做法是面向具体业务场景构建大宽表。比如“用户标签宽表”一个用户一行记录几十个标签字段作为列字段包括基础属性、消费能力、活跃度、偏好类目、生命周期阶段等。运营圈人群时90%的场景就是在这个宽表上做“字段过滤计数/去重”不需要JOIN任何其他表性能自然就快。宽表的设计看起来简单但实际操作中有几个关键点需要特别注意。第一是字段粒度必须对齐。一个宽表里所有字段都要围绕同一个粒度比如用户宽表就是一人一行订单字段必须预先做聚合比如近30天订单数、近30天消费总额不能直接在宽表里冗余订单明细。否则就会出现严重的数据膨胀——一个用户如果下了一百单宽表里对应一百行那所有基于用户的统计维度都会翻车。第二是空值和默认值的处理策略。标签宽表里大量字段可能为空比如用户没有填过职业、没有绑定过会员卡。如果空值直接留给查询端处理Count、AVG这类聚合函数的结果会和预期完全不一样。我们的做法是ETL阶段就把所有空值统一为默认值比如“未知”“0”“-1”等具体用哪个默认值取决于字段语义。比如年龄字段用-1表示未填写收入字段用0表示无数据。这个细节看似简单但处理不到位的话会在后期分析结果中埋下巨大隐患。第三是维表要控制数量。理想状态下宽表关联的维表不超过三张。超过这个数量会带来两个问题一是查询性能下降严重二是数据更新时的关联逻辑变得复杂很容易出现数据不一致。我们遇到过的最极端案例是某个活动分析宽表关联了7张维表结果每次任务调度光是在JOIN环节就要跑四个小时而且一旦某个维表数据刷新失败整条链路都要重跑。2.3 小样本场景下模型拟合与物理模型泛化的理解数据驱动在营销自动化中有一个容易被忽视的边界问题小样本场景下纯数据驱动的模型会失效。这个话题和OLAP架构本身关系不大但和我们这套系统最终服务的业务目标——精准人群圈选和效果归因——关系极为密切。简单说就是数据量太少的时候数据驱动模型可以拟合得很好但这种好是有水分的真正靠谱的判断还得靠物理模型业务逻辑来兜底。举个例子某个新品刚上线三天只有两百多个用户产生了购买行为。你想基于这些数据做一个“高转化人群包”如果纯靠数据驱动方式去训练模型——比如把性别、年龄段、渠道来源、设备型号全部纳入LR或者树模型——模型在训练集上的AUC可能能到0.95以上看起来效果拔群。但实际上呢这两百多个样本远远覆盖不了真实用户的全貌一个在训练集里“表现优秀”的特征组合很可能只是市场推广初期的流量特征一旦投放节奏变化模型立刻失效。“传统数据驱动模型易于拟合物理模型泛化不足”这句话我的理解是分为两层的。前半句是说小样本下数据驱动模型一定会过拟合——因为模型会把训练集中的噪声也学进去在样本内表现得无比精准但这种精准是虚假的。后半句说的物理模型泛化不足不是指物理模型不好用而是说当前的物理模型覆盖范围有限、泛化能力还有待提升。在我们营销场景里物理模型指的是那些基于业务规则、业务逻辑构建的判定模型——比如“高价值用户近30天消费≥5次且客单价≥200元且近7天有活跃行为”。这类模型的优势是逻辑透明、解释性强、不怕过拟合劣势则是依赖业务经验覆盖不了那些不符合规则但实际转化极高的“边缘用户”。正确的处理方式是两者结合小样本场景下以物理模型业务规则为主数据驱动模型为辅数据模型只对物理模型做补充和微调当样本量积累到一定规模后再逐步加大数据驱动模型的占比。这套策略我们落地成了一套自动分流机制新活动上线初期人群圈选完全走规则引擎数据积累超过5万样本后自动切换到机器学习模型与规则双跑的模式超过10万样本后才让模型独立承担人群圈选任务。这条边界线是我们团队反复验证后确定的不同业务可以调整但思路是一致的数据驱动是油门物理模型是刹车两者配合好了车速才能上去。3. 实操过程与核心环节实现3.1 人群圈选功能从MySQL到Doris的完整改造人群圈选是营销自动化平台最核心的功能没有之一。这个功能说白了就是让运营同学通过组合条件筛选用户然后对筛选出的用户执行发券、推送、短信等动作。改造前这个功能跑在MySQL和Elasticsearch上逻辑大致是把用户标签数据存到ES查询时拼DSL对满足条件的用户ID做分页返回。这个方案在数据量达到3亿用户的时候彻底撑不住了几个典型案例让人很头疼选“最近7天活跃且消费能力为高”的人群相当于一次要扫几百万甚至上千万条记录做过滤再聚合ES集群CPU直接被打满查询耗时从最初的2秒恶化到40秒以上运营同学点击“预估人数”按钮后经常等几分钟才能出结果到晚间投放高峰两个重查询就能把一个数据节点拖死全网查询全部变慢。改造的思路不是推翻ES而是把“人群圈选”这个核心场景迁移到DorisES继续保留作日志检索和部分灵活查询。Doris侧的关键设计有两块。第一块是建表模型的选择。我们使用Doris的Duplicate Key模型加明细数据存储配合Range分区和Hash分桶。具体配置如下CREATE TABLE tag_user_wide ( user_id BIGINT NOT NULL COMMENT 用户ID, tag_gender TINYINT COMMENT 性别:0未知,1男,2女, tag_age_group TINYINT COMMENT 年龄段:0未知,118,2[18,24],3[25,30],4[31,40],540, tag_active_7d INT COMMENT 近7天活跃天数, tag_total_orders INT COMMENT 累计订单数, tag_consume_level TINYINT COMMENT 消费等级:0未知,1低,2中,3高, tag_last_order_time DATETIME COMMENT 最近下单时间, tag_register_time DATETIME COMMENT 注册时间 ) DUPLICATE KEY(user_id) PARTITION BY RANGE(tag_register_time) ( PARTITION p2020 VALUES LESS THAN (2021-01-01), PARTITION p2021 VALUES LESS THAN (2022-01-01), PARTITION p2022 VALUES LESS THAN (2023-01-01), PARTITION p2023 VALUES LESS THAN (2024-01-01) ) DISTRIBUTED BY HASH(user_id) BUCKETS 48 PROPERTIES (replication_num 3);这里有几个实践细节可以分享。分桶数设置为48是结合集群节点数、单表数据量、以及查询并发度综合算出来的。理论上分桶数量等于集群BE节点数的倍数效果较好这样数据能均匀分布到所有节点。同时分桶数也不宜过大否则导入会产生大量小文件影响查询性能。我们当前集群是6个BE节点每个节点16核64G内存48个分桶相当于每个节点8个分桶实测效果较为均衡。第二块是查询改写。原来在ES上的DSL查询需要改写成Doris的SQL。比如“近7天活跃天数≥3天且消费等级为高”的圈选逻辑改写后的SQL如下SELECT user_id FROM tag_user_wide WHERE tag_active_7d 3 AND tag_consume_level 3 LIMIT 10000;预先聚合后没有JOIN、没有复杂计算就是一次前缀索引匹配加过滤扫描Doris的性能完全可以轻松扛住。这个改造最核心的收益是人群圈选的P95响应时间从改造前的40多秒降到了1秒左右并发能力提升了10倍以上而且ES集群的负载大幅下降日志检索场景的体验也恢复正常了。3.2 Elasticsearch在OLAP场景下的过渡方案上面提到ES在大规模人群圈选场景下撑不住但在整个OLAP架构中ES依然占有一席之地。聊这个是想回应一下“elasticsearch实现olap”这个话题——确实有团队在初期用ES来顶OLAP的活儿这也是一条可行的过渡路径只是要清楚它的边界在哪里。ES做OLAP的核心思路是利用倒排索引Pipeline Aggregation在明细数据上做过滤、分组、聚合计算。语法上用DSL表达复杂度和SQL完全不同。下面是一个实际项目中使用ES做“按渠道统计近30天消耗和转化”的DSL示例{ size: 0, query: { bool: { filter: [ { range: { stat_date: { gte: 2024-11-01, lte: 2024-11-30 } } } ] } }, aggs: { by_channel: { terms: { field: channel_id, size: 20 }, aggs: { total_cost: { sum: { field: cost } }, total_convert: { sum: { field: conversions } }, avg_cpa: { bucket_script: { buckets_path: { cost: total_cost, conversions: total_convert }, script: params.cost / params.conversions } } } } } }这段DSL的实际作用就是从ES中筛选出11月份的全部广告消耗明细记录按渠道ID分组求每个渠道的总消耗、总转化数、平均转化成本。用ES的好处是部署简单、扩展方便、对全文检索能力强适合数据量在几千万到一两亿级别、查询模式以过滤简单聚合为主的场景。但ES做OLAP的边界也很明显。第一是JOIN支持极差虽然ES 6.x之后有Join类型和Nested类型但查询性能和灵活度完全无法和真正的OLAP引擎相比复杂关联场景下基本绕不开宽表。第二是聚合性能下降快数据量超过亿级后需要大量内存做Fielddata或Doc Values集群堆内存配置稍有不慎就会OOM。第三是精确去重计数Cardinality Aggregation在超大基数下误差会出现因为实现基于HyperLogLog精确度受限于精度参数配置。营销场景里的人均消费次数、累计下单人数这类指标对精确度要求极高误差一旦出现很难解释。所以我的判断是ES可以作为一种过渡方案、辅助方案解决数据量中等、查询模式相对简单的OLAP需求但一旦演进到营销自动化这种高并发、多维度、强一致性的场景还是需要引入专业的OLAP引擎。ES也不应该被从架构里拿掉它做日志检索、明细查询、自定义灵活分析依然很好用。3.3 实时OLAP链路的搭建过程营销自动化的很多场景依赖实时数据典型的有两个一个是“用户进入某个触发型活动后需要在秒级内判断是否给Ta推送优惠券”的实时触发另一个是“活动开始后运营需要实时看到参与人数、转化率、ROI”的实时大屏。我们最初期的实时链路比较简单——Kafka到Flink窗口聚合后写入Redis大屏和应用直接读Redis。但很快发现两个问题一是聚合维度和查询维度对不上Flink里只能预聚合固定维度组合运营临时想按渠道、按城市拆一个维度看数据Redis里根本没有这些粒度二是状态管理复杂Flink的状态后端存储压力大作业重启恢复非常耗时。后来演进到Kafka Flink Doris的架构核心思路是Flink做实时清洗和轻度预聚合Doris负责提供灵活的实时查询能力。Flink作业通过标准的JDBC连接器把实时数据写入DorisDoris侧使用Unique模型通过主键模型实现数据的实时更新。比如订单实时数据以order_id为主键新增和更新都走同一条写入链路Doris内部自动处理UPSERT语义。具体到实现层面有一个比较关键的调优参数是Doris的Stream Load并发度和批次大小。我们的Flink作业通常设置每批次攒够10万条记录或5秒定时触发一次Stream Load并发度控制在3-5之间。批次太小会导致导入过于频繁产生大量小版本问题批次太大会增加单次导入延迟影响实时性。10万条和5秒这个配比是我们在生产环境压测多轮得出的相对平衡点。实时大屏侧的SQL常见写法是基于Doris的实时聚合表比如按分钟汇总的活动实时效果SELECT channel_id, COUNT(DISTINCT user_id) AS uv, SUM(order_amount) AS gmv, COUNT(DISTINCT order_id) AS order_cnt FROM dwd_order_realtime WHERE activity_id ACT20250101 AND stat_minute DATE_FORMAT(NOW() - INTERVAL 60 MINUTE, %Y-%m-%d %H:%i) GROUP BY channel_id ORDER BY gmv DESC;这条查询在Doris上跑得很快一方面因为实时表按活动ID分钟做了分区裁剪另一方面COUNT(DISTINCT)在Doris里做过针对性的并行化优化。即便如此这类查询仍然要求底层数据模型的重复度不能太高否则COUNT(DISTINCT)的内存开销会非常大。3.4 从ES迁移到Doris的迁移流程迁移是OLAP架构演进中风险最高的环节没有之一。直接说我们总结的迁移方法论分四步走。第一步是双跑校验。新老两套系统并行运行每天同一套任务分别写入ES和Doris第二天对账。对账粒度不能只比对总数要按关键维度分组对比——按渠道、按活动、按日期。我们处理过很多“总数对得上拆开全不对”的案例这类问题最隐蔽也最伤害数据信任度。双跑周期我们建议至少持续两周覆盖一个完整的业务小周期。第二步是流量灰度。Doris数据校验通过后开始把查询流量逐步切过去。灰度策略不是按用户切而是按查询类型切先把离线报表类查询切过去再切人群圈选类最后才切实时在线查询。第三步是性能压测。切流之前必须压过不能带着未知数上线。压测要模拟真实场景的并发模型——一个营销自动化平台里日常有几百个运营在操作每个操作背后可能对应一次查询活动高峰期在线查询QPS会翻好几倍。我们用JMeter和内部自研的压测工具按日常3倍峰值来做压测如果新系统扛不住这个压力就不上线。第四步是回滚预案。数据库架构类变更最怕“上线之后跑一个月发现要回滚”面临两边数据不一致、增量数据难以合并的问题。我们的预案是不论新系统上线多久确保ES侧的数据同步链路至少保留30天不关闭。这样一旦Doris侧出现难以快速修复的问题可以立即切回ES虽然查询性能会退化但数据不丢、业务不停。整个迁移过程最耗时的是双跑校验阶段数据校验脚本的编写要对业务逻辑有很深的理解哪些指标口径对了就算通、哪些字段需要批量对比采样都需要和数据团队反复确认。但这段投入是值得的它直接决定了后续系统能不能从“能跑”走向“可信”。4. 常见问题与排查技巧实录4.1 JOIN数据膨胀导致的查询结果翻倍OLAP场景中最容易踩的坑之一就是JOIN导致的数据膨胀问题。举个例子用户维表和订单事实表JOIN一个用户如果下过10个订单那JOIN结果集里这个用户就会对应10行如果接下来再JOIN一张优惠券表优惠券数量也是10张那这个用户会对应100行。数据量小的场景感觉不到但到了几十亿行级别膨胀后的中间结果是灾难级的。我们曾在排查一个营销活动效果报表翻倍问题时花了两天才定位到根因活动效果宽表里有两个业务过程字段——“优惠券领取数”和“优惠券核销数”两者各自来自不同的事实地表ETL里直接做了两次JOIN导致订单、领取、核销三张表相乘数据膨胀10倍以上。报表结果自然怎么算都不对。排查过程也比较有代表性先看结果数据发现部分活动的参与人数比真实用户数还多再看ETL日志发现宽表产出行数比预期高了一个数量级最后逐段拆SQL才发现是JOIN导致的膨胀。修复方案也不复杂不要用多表JOIN的方式构建宽表而是在事实表侧先分别按用户粒度做预聚合再用用户ID去关联维表。这样每个事实过程在JOIN前已经压缩为“一个用户一行”膨胀问题自然消失。4.2 数据倾斜导致BE节点负载不均Doris的分布式架构实现了数据的自动均衡但所谓“自动”是指分桶数据的初始分布并不代表所有查询负载会自动均分到每个BE节点上。我们在实际运行中遇到的问题集中在热点用户和海量分区两个方面。热点用户的产生方式非常典型某头部主播直播带货时大量用户涌进一个活动中这个活动的数据全部落在同一个分区内或者某个秒杀活动中几个渠道贡献了90%的流量。由于分桶策略是基于分桶键的哈希同一个渠道、同一个活动的数据天然会落在少数几个分桶里对应BE节点的CPU和内存就会明显高于其他节点。排查方法是用Doris的Profile功能定位耗时节点再用表统计信息确认数据分布情况。比如执行下面的查询看数据分布是否均匀SHOW TABLETS FROM activity_order_fact;如果发现少量Tablet的数据行数远超平均值基本可以确认数据倾斜。常规解法有两类一是将分桶键拆分成联合键比如原来是活动ID现在改成活动ID渠道ID输入数据的散列粒度会更细二是适当增加分桶数量让数据分布更均匀。但如果倾斜源是少数超大用户比如平台头部商家账号产生大量订单联合分桶键也无法根除只能在查询优化器层面做双层聚合。4.3 实时导入延迟与查询性能的平衡实时链路里最头疼的问题莫过于实时性要求和查询性能之间的平衡关系。Doris数据文件是分版本管理的过多次数的导入会产生大量的小版本查询时需要合并这些版本导致读放大严重。我们在活动大促期间就遇到过这个问题——实时导入间隔设置得太短版本数量激增查询性能直接掉了一半。排查时通过SHOW TABLET查看版本数量如果发现某个Tablet的版本数超过了100基本能确定是导入频率过高。解决思路有两个方向。第一是调整Stream Load批次大小和触发间隔把导入频率降下来。前面提到我们采用“10万条或5秒”的配置大幅降低了小版本产生的概率。第二是定期执行Compaction将小版本合并成大版本。Doris有自动Compaction机制但对写入高频的表建议手动触发比如每天凌晨业务低峰期执行ALTER TABLE activity_order_realtime COMPACT;Compaction的粒度、频率和资源消耗需要根据表设计来调整不能一刀切。我们的实践是核心高频更新表每天一次手工Compaction普通实时表依赖系统自动Compaction就够用。4.4 常见问题速查表把实际运维过程中积累的排查经验整理成一个速查表方便遇到问题时快速定位方向。问题现象可能原因排查方法解决方案查询结果数据翻倍多表JOIN数据膨胀对比宽表行数与源表预估行数事实表侧预聚合后再JOINBE节点负载不均衡分桶键选择不当或热点数据集中SHOW TABLETS查看数据分布调整联合分桶键或增加分桶数实时查询越来越慢小版本过多导致读放大查看Tablet版本数增大导入批次手动Compaction内存溢出错误大查询并发过高查看Profile中内存占用限制单查询内存增加资源组隔离COUNT(DISTINCT)结果不准用户ID精度超过HyperLogLog默认配置核对基数预估的误差率或改用精确去重数据更新后查询不一致部分副本数据未刷新检查副本状态和版本号手动刷新或重建物化视图每个问题背后其实都有一套方法论。我个人的习惯是遇到性能问题先看数据分布再看执行计划然后才动参数。很多人一上来就调内存、改并发方向错了会越调越歪。4.5 架构演进过程中的几点避坑心得最后聊几条从这一路演进过程中沉淀下来的切实体会不一定都是技术层面的但对做同类系统的团队应该会有参考价值。第一OLAP选型要紧紧围绕业务形态来走不要被“别人都在用”带着跑。如果你的场景是广告投放报表高并发、中等数据量、多维度组合查询偏多Doris这类MPP数据库会明显更顺手如果你的场景是海量日志明细分析、单表极宽且极少JOINClickHouse的优势会更大如果你当前只有几千万级数据ES配合适当的宽表设计也完全可行。每一次架构升级应该都是业务增长倒逼的不要为了技术面子提前升级。第二数据模型设计的重要程度超过引擎选型。真实经验是Doris用得好不好七分在建模三分在调优。同一个引擎用宽表模型和用范式模型的查询性能差距可以超过10倍。在搭建OLAP系统时真正值得花时间的部分是数据模型的梳理——哪些字段是高基维、哪些是低基维、哪些查询组合是高频的、哪些聚合指标是必须实时的这些想清楚了后面的工程量能省一大半。第三多源数据接入一定要在一开始就建立元数据管理机制。每个表、每个字段、每个指标的来源、口径、更新频率、负责人都必须在元数据系统里登记清楚。这项工作不做后期一定会在跨部门沟通、数据质量排查、新人上手等环节反复交学费。第四也是最真实的一条经验架构演进从来不只是技术问题还是组织协同问题。OLAP架构升级会直接影响数据团队的ETL逻辑、算法团队的特征工程、运营团队的查询习惯。任何一次技术切换都要提前和这些下游团队对齐预期规划好迁移窗口期。技术方案可以快速定但业务侧的适应和反馈才是最影响落地节奏的因素——毕竟再好的架构也得有人愿意用、用得好才算真正成功。