2026/10/9 15:28:20

Hologres Dynamic Table:重塑价格力数据加工链路,实现分钟级时效

Hologres Dynamic Table:重塑价格力数据加工链路,实现分钟级时效 1. 价格力业务到底卡在哪里为什么传统加工链路撑不住先说个背景。淘天的价格力业务不是大家想象中调个价、打个折那么简单。它背后有一整套围绕商品价格竞争力的场景前台的价格表达到手价、券后价、套装价、满减价中台的比价看板运营侧的降价提醒以及风控侧的异常价格校验。这些场景有一个共同特点——它们都要求能快速感知价格变化而且数据模型高度复杂。一个SKU的价格不是一张表里一个字段能描述的它可能是商品原价 营销优惠 会员折扣 运费模板 区域差异共同算出来的结果。我接手这个项目时的痛点非常直接价格数据的加工链路太长了。上游是交易、营销、商品等多个业务域的ODS表经过数仓层层加工最后落到应用能直接查询的表里整个链路往往要经过五六层调度。每一层调度都有等待时间叠加起来就是T1甚至T2的数据时效。业务方提需求时说的是我要今天实时看到某个商品的价格竞争力变化但数仓给到的是昨天的数据。这个矛盾在价格力场景里会被无限放大因为价格是最敏感的变量。别的业务看T1数据最多是决策慢了一点价格业务看T1数据等于在价格战里闭着眼睛打仗。竞对凌晨改了价我们第二天中午才发现再去跟价就已经错失窗口了。传统链路还有第二个问题中间表太多了。为了支撑不同场景的查询数仓给每个场景都物化了一张宽表。同一份价格事实数据在比价场景里要一张表在价格校验场景里要一张表在运营看板里又要一张表。数据冗余不说更麻烦的是多个任务都在重复加工同一份基础数据任何一个上游字段变更所有下游任务都需要跟着改。我们统计过当时价格力相关的加工任务总数超过200个真正独立的逻辑可能只有四五十个剩下全是重复。第三个问题是查询侧的扩展性。价格力场景的查询有一个特点过滤条件极度多样。运营要查某品类下价格竞争力Top100的商品风控要查某段时间内价格异常波动的SKU前台要查某个具体商品当前到手价。这些查询如果都落到一个通用大宽表上索引设计很难兼顾要么存储膨胀要么查询超时。我当时就意识到这个场景缺的不是再加一层表或者再优化一个SQL而是一种全新的加工范式链路要短数据要新模型要灵活。这也是我们后来引入Hologres Dynamic Table的核心原因。2. Dynamic Table是什么它和普通物化视图、实时计算到底有什么本质区别在最开始接触Dynamic Table时团队内部有过一轮讨论这不就是物化视图吗或者更进一步说拿Flink做实时ETL不就行了这两个疑问都很合理但实际深入之后会发现Dynamic Table解决的是这两者之间的空心地带。2.1 物化视图的半自动困境传统数仓的物化视图核心能力是把一段查询逻辑固化下来预计算好结果。但它有两个关键短板第一它通常基于批量调度触发刷新刷新粒度粗对上游变更的感知是被动的第二复杂的多表JOIN物化视图在增量刷新场景下往往退化成全量重算数据量一大刷新成本直线上升。熟悉数仓的读者应该都知道很多数据库的物化视图在JOIN场景下根本不支持增量刷新只能删了重建或者全量覆盖。所以在价格力业务里如果用普通物化视图我们很快会撞上那堵墙上游表一分钟变一次物化视图刷新一次要十分钟刷新过来数据又过期了。而且价格计算涉及的表多了以后物化视图之间的依赖关系基本要靠人肉维护和写调度任务没有本质区别。2.2 实时计算的“杀鸡用牛刀”困境那我直接用Flink做实时ETL行不行技术上当然行Flink可以做到秒级延迟。问题出在运维成本和开发效率上。价格力场景里有大量逻辑相近但细节不同的加工任务如果用Flink每一条链路都要独立开发、独立部署、独立监控还要管理Checkpoint、处理状态后端、处理数据回溯。更麻烦的是上游表结构一变Flink作业就要重启改代码这个迭代成本对业务侧来说很难接受。我们当时评估过一个比价场景的实时化改造开发周期预估要三周还得配一个专职实时开发。而同样是这个场景用Dynamic Table只花了一个下午就写完了DDL。2.3 Dynamic Table的核心机制自动依赖、增量刷新、检索加速Dynamic Table把物化视图的便捷和实时计算的时效做了一个比较务实的折中。我们来看它底层做了哪几件事自动依赖发现创建Dynamic Table时指定刷新周期比如5分钟Hologres会自动分析SQL里引用了哪些上游表建立血缘关系。上游表结构变化时系统能自动感知并提示你需要重建动态表。增量刷新引擎Dynamic Table内部会捕获上游表的增量变更Hologres的Binlog机制在刷新周期内只处理变更的数据而不是整表重算。这一点在JOIN场景下特别重要——它会把一张动态表拆成多个子任务每个子任务维护各自的增量状态尽可能避免全量扫描。查询加速一体动态表本身还是一张Hologres表建好后直接就能用索引、分区、Bitmap等能力做查询加速不需要像Flink那样把结果再导一次到OLAP引擎。打个比方传统物化视图像每天早上定时打扫房间实时计算像请了个保洁员坐在房间里地上掉一根头发马上捡起来Dynamic Table则是装了智能传感器每五分钟自动扫一遍哪里脏了清理哪里。它不是最实时的但它是成本、时效、维护三者的平衡点。3. 价格力场景的Dynamic Table落地设计从数据模型到DDL实操理论说完了讲点实际的。我们落地时没有把Dynamic Table当成一个新玩具直接接到现有链路上而是先做了两步关键设计模型改造和刷新策略设计。这两步如果没想清楚直接把原来的SQL套过来建动态表性能大概率不会比原来好甚至可能更差。3.1 模型改造把大宽表拆成星型模型价格力业务过去习惯用大宽表一张表里把商品、价格、优惠、店铺信息全部冗余进去查询方便但更新代价极大。Dynamic Table场景下我强烈建议先把模型改成星型结构事实表放价格事件维度表放商品、店铺、活动等属性动态表在最上层做JOIN和聚合。这样做有两个原因。第一动态表的增量刷新效率取决于上游表的变更频率如果上游是一张大宽表任何字段变了都会触发整行变更动态表要处理的“变更噪声”会非常大。拆成星型后价格事实表只负责价格相关的变更商品属性变了只动维度表互不干扰。第二星型模型天然适合动态表按需刷新——你可以让核心的价格动态表5分钟刷一次让商品画像动态表30分钟刷一次让店铺维表1小时刷一次而不是一荣俱荣一损俱损。3.2 核心DDL设计与参数选择下面是我们一个典型的价格竞争力看板场景的DDL骨架我脱敏后分享出来重点看设计思路-- 价格事实表记录每个SKU在某个时间点的最终到手价 CREATE TABLE fact_sku_price ( sku_id TEXT NOT NULL, item_id TEXT NOT NULL, shop_id TEXT NOT NULL, biz_date TEXT NOT NULL, final_price DOUBLE PRECISION, origin_price DOUBLE PRECISION, discount_amount DOUBLE PRECISION, price_change_time TIMESTAMPTZ, PRIMARY KEY (sku_id, biz_date) ) PARTITION BY LIST (biz_date); -- 商品维度表 CREATE TABLE dim_item ( item_id TEXT PRIMARY KEY, category_id TEXT, brand_id TEXT, title TEXT, status INT, -- 其他属性字段 ); -- 动态表按商品维度聚合价格竞争力指标 CREATE DYNAMIC TABLE dws_item_price_power WITH ( refresh_mode auto, -- 自动模式系统按需决定刷新频率 refresh_interval 300, -- 刷新周期单位秒即5分钟 append_only false, -- 非追加表需要更新已有行的聚合结果 max_retry_times 5 -- 刷新失败最大重试次数 ) AS SELECT item_id, category_id, COUNT(DISTINCT sku_id) AS sku_cnt, MIN(final_price) AS min_price, MAX(final_price) AS max_price, ROUND(AVG(final_price), 2) AS avg_price, SUM(CASE WHEN final_price origin_price * 0.8 THEN 1 ELSE 0 END) AS discount_sku_cnt, MAX(price_change_time) AS last_change_time FROM fact_sku_price JOIN dim_item ON fact_sku_price.item_id dim_item.item_id WHERE biz_date CURRENT_DATE GROUP BY item_id, category_id;这个DDL里有几个参数值得专门讲一下。refresh_mode和refresh_interval的关系。refresh_modeauto意味着Hologres会根据上游变更量和查询压力自动判断是否提前刷新但刷新周期仍然受refresh_interval约束。如果业务要求最多接受5分钟延迟那refresh_interval设成300如果业务要求严格保证30秒内感知那就必须设成30但也要接受背后更大的计算开销。我们实际测下来在价格变更比较集中的时段比如大促预热期auto模式会在300秒周期内自动触发多次增量刷新整体延迟大约在1~3分钟效果比固定周期好不少。JOIN的顺序问题。DDL里是先JOIN维度表再聚合这个顺序对Dynamic Table的增量处理很关键。如果维度表很大且频繁变化JOIN产生的变更量会被放大所以我们把维度表过滤条件status、category等尽量前推让参与JOIN的维度行数尽量小。这个优化在普通SQL里看不出来但在动态表的每次增量刷新中收益非常明显。append_only的取舍。如果你的动态表只需要插入新数据比如按时间累积的流水型指标可以设置append_onlytrue那样刷新性能更好因为是纯追加。但价格力场景大多数是更新已有商品的最新价格状态必须设成false接受它的更新开销。3.3 刷新策略不同场景用不同节奏我们在项目中形成了三档刷新节奏这里整理给各位参考场景类型典型用途刷新间隔说明强时效价格异常告警、竞对跟价30s - 60s代价高仅用于关键业务线均衡型价格竞争力看板、运营分析5min - 15min性价比最高覆盖80%场景弱时效月度趋势报表、复盘分析1h - 1d用于低频聚合减少资源消耗有一点必须提醒Dynamic Table不是越短越好。刷新间隔太短上游一有风吹草动就重算资源消耗会线性上升。我们在压测时发现同样的SQL逻辑30秒刷新和5分钟刷新前者带来的CPU开销大约是后者的4倍因为增量批次的调度、状态合并、写表都有固定开销。所以选刷新间隔前先问业务一句你能接受的最高延迟是多少然后取那个值而不是直接拍脑袋选最短。4. 上线实测延迟、成本、稳定性这三本账是怎么算平的模型设计完了DDL写完了最终还是要用数据说话。上线前我们做了压测也跑了线上对比三个维度的结果都超出预期但中间也踩了几个坑说给你听。4.1 延迟对比分钟级时代确实来了改造前价格力看板的数据链路是这样的上游ODS表 - 离线任务T1- Hologres结果表 - 应用查询。全程数据产出延迟稳定在20小时以上。改造后链路变成上游ODS表 - Dynamic Table增量刷新- 应用直接查动态表。实际线上运行时价格变更到看板可见的平均延迟大约在2~4分钟大促高峰期由于变更事件密集大部分增量刷新会被auto模式提前触发延迟能进一步压到1分钟以内。这个延迟水平对于比价和跟价场景已经完全够用了。业务方从看昨天的价格变成了看几分钟前的价格这个体验跨越是质变。4.2 成本对比一个让人意外的结论上线前我一度担心Dynamic Table的增量刷新会带来额外计算成本但实际账单出来后反而省了钱。核心原因有两个一是原先200多个加工任务里大量重复计算被合并掉了不需要再为每个场景各跑一遍相同的JOIN二是增量刷新确实比我们想象中“克制”它并不是频繁地全量扫描而是基于Binlog定位变更分区后局部刷新。这里给出一个脱敏后的成本参考数据按我们集群当时的价格折算原离线加工链路每日计算成本约为X CU·时换算成费用大约是每月若干万Dynamic Table上线后新增的动态表刷新计算成本大约只有原来的15%~20%同时因为下线了一大批重复的离线加工任务总算力成本反而降了30%左右。算下来这个项目不仅提升了时效还省了钱。4.3 稳定性表现与踩坑记录任何新技术上线都不会一帆风顺我们前前后后也遇到了三个比较有代表性的问题值得展开说说。坑一DDL变更引发的动态表重建风暴。Dynamic Table的SQL逻辑一旦变更比如加一个字段、改一个JOIN条件系统要求重建动态表。在我们的环境里一张大动态表重建需要全量回刷期间不能再刷新这就造成了一段空窗期。最初我们没有建“影子动态表”的习惯每次改逻辑都要业务侧停查几分钟。后来我们总结的规矩是任何逻辑变更都先建一张动态表V2跑一段时间对比数据无误后再做切换。这个习惯救了我们好几次特别是大促前改配置的时候。坑二维度表高频更新的放大效应。有一张商品属性维度表变更频率很高每秒钟有大量UPDATE而它又JOIN了主价格动态表。这导致每次维度表变更动态表都要重新处理关联行的增量状态一度造成刷新任务堆积。排查后发现是我们的问题——这张维度表把“真正变化的属性”和“每次写入都会变化的时间戳字段”放在了一起实际上属性值没变但行版本变了Binlog里看起来就是一次变更。后来我们把时间戳字段拆到单独的扩展表只让动态表JOIN真正需要的属性列问题立刻缓解。这一点建议所有用Dynamic Table的人提前检查你的上游维度表是真的在变还是只是“看起来在变”。坑三同一张表被多个动态表引用时的资源竞争。价格事实表被大约七八个动态表同时引用刷新任务高峰期会短暂竞争IO和CPU。我们后来按动态表的重要程度设置了不同刷新周期让它们错峰刷新资源曲线就平滑了很多。这里运营商用了一个技巧低优级的看板类动态表刻意把刷新周期从5分钟拉到15分钟业务影响几乎为零但集群高峰期压力明显下降了。5. 运行半年后的复盘Dynamic Table适用的边界在哪里项目上线跑了半年多回过头来给这套方案做个评述也说说哪些场景不适合用它。这可能是市面上文章很少讲的部分。先说适合的场景我归纳为三个特征数据链路有明显的“多次读取、重复加工”现象同一个基础数据被多个下游使用时效要求是分钟级而不是秒级秒级场景请老老实实用流计算业务逻辑是相对稳定的SQL不经常改或者允许通过“V2影子表”的方式平滑变更。这三个特征价格力业务全中所以落地效果比较理想。再看不适用的场景也给大家避个雷有复杂窗口计算的场景比如要维护过去30天每个小时的最高价这种带形态的状态计算Dynamic Table的增量刷新模型处理起来会很别扭这类用Flink的状态管理更合适上游数据质量极差、频繁大范围回撤的场景动态表基于增量变更刷新一旦上游发生大规模回刷动态表需要跟着重算大量数据资源开销不可控。如果你所在业务的数据链路经常半夜回刷数据建议先在ODS层加一个数据稳定标记避免动态表被无效刷新拖垮需要事务性强一致的场景比如价格修改后下游必须严格一致读到最新值Dynamic Table的异步刷新模型不满足这种强一致诉求这种情况得走在线接口直查不能依赖离线链路。我们内部后来还展开过一次讨论Dynamic Table到底算“物化视图的进化”还是“轻量版流计算”最后的结论是它更应该被理解为**“数仓和OLAP之间的一个自动化加工层”**。它消化掉了大量原本需要人工维护的中间加工逻辑让数据从产生到可查的路径尽量变短又不至于像流计算那样需要长期贴身运维。对于淘天价格力这种“模型多、链路杂、时效要求又不算极致”的业务它确实是一味对症的药。最后再分享一个我现在坚持的做法任何一张动态表上线前我都会手工查一遍它依赖的所有上游表的“真实变更频率”分布而不只是看建表SQL。因为Dynamic Table的所有性能、成本、稳定性几乎都取决于上游的变更形态。这一条想明白了后面能少走很多弯路。