基于DynamoDB构建高负载广告系统事件聚合服务方案问询
针对你开发的百万级用户Ad-tech系统需求,结合200K RPM的吞吐量、按天的查询粒度要求,逐个解答你的疑问:
1. DynamoDB是否适配该场景?
完全适配。DynamoDB原生支持高吞吐量写入(200K RPM的写入量在其承载范围内,可通过预置/自动扩缩容的WCU满足),且能提供亚毫秒级的查询延迟,符合你“响应极快”的要求。另外,其TTL(时间到了自动删除)功能可以自动清理超过统计周期(如Y天)的旧数据,正好匹配按天的时间单位需求。
2. 是否应为每种事件类型(点击/浏览/关闭等)单独建表?
不建议。除非不同事件类型的量级差几个数量级、或有完全独立的存储/生命周期规则,否则单独建表会增加运维复杂度(多表配置、重复设置TTL等)。更优的方式是把事件类型作为主键/排序键的一部分,或者作为Item的属性字段,统一放在一张主表中管理。
3. 主键最优配置方式?你设想主键为user id,排序键为ad id+当日日期{dd/mm/yyyy}组合。
你这个设计能很好支撑用户维度的查询(比如查用户Y天内某广告的浏览/点击次数),但无法高效满足广告/广告组维度的统计需求(比如查某广告近40天的浏览用户数)。建议采用**双表+GSI(全局二级索引)**的组合设计:
- 用户维度主表:分区键设为
user_id,排序键设为event_type#ad_id#date(例:view#ad_1001#2024-05-20),属性存event_count(事件次数)。该表用来快速判断用户是否触发频次上限。 - 广告维度GSI:在主表上创建GSI,分区键设为
ad_id#event_type,排序键设为date#user_id。通过这个GSI可以快速查询某广告某事件类型下,指定日期范围内的所有用户,进而统计用户数和总次数。
如果广告组的统计需求频繁,还可以再建一个以ad_group_id#event_type为分区键的GSI。
4. 使用ADD操作递增特定日期下用户广告事件计数器是否昂贵?有无替代方案?
DynamoDB的UpdateItem中使用ADD操作是原子递增,成本和普通写入操作(单条Item<1KB时占1个WCU)几乎一致,不算昂贵,完全可以支撑200K RPM的写入量。
替代方案的话,如果对实时性要求稍低,可以攒一批事件(比如10秒内的同用户同广告事件)再批量更新,但这会导致频次控制有延迟,不符合广告实时拦截的需求。所以ADD原子操作是最优选择。如果是统计类的非实时数据,可以用DynamoDB Streams+Lambda做异步汇总,但实时频次控制还是得依赖ADD的实时计数。
5. 如何实现按广告、广告组查询(如查询某广告组所有用户浏览量、近40天广告浏览量)?
分两种场景实现:
- 按广告查询:利用上面提到的
ad_id#event_type分区键的GSI,执行Query操作时指定分区键(如ad_1001#view),并设置排序键的范围(如2024-04-10#到2024-05-20#),通过Select=COUNT可以快速得到总用户数;如果需要总浏览次数,可以在GSI中包含event_count属性,查询后累加得到总数,或者单独建一个广告日汇总表(分区键ad_id#event_type#date,属性存total_count和unique_user_count),通过Lambda异步更新。 - 按广告组查询:有两种方式:一是建以
ad_group_id#event_type为分区键的GSI,逻辑同广告查询;二是在广告日汇总表中增加ad_group_id属性,再建一个以ad_group_id#event_type#date为分区键的GSI,直接查询广告组的日汇总数据,效率更高。
另外,统计唯一用户数时,也可以采用“每个用户每天在广告维度下只存一条记录”的方式:建一张广告用户日表,分区键ad_id#event_type#date,排序键user_id,写入时用PutItem加条件(仅当Item不存在时写入),这样查询该分区的Item数量就是唯一用户数。
6. 还需考虑哪些其他事项?
- TTL配置:所有按天存储的Item都要设置TTL,过期自动删除,避免表无限膨胀,同时降低存储成本。
- 热点规避:如果某广告/广告组的流量特别大,单一分区键(如
ad_id#event_type)会造成热点,导致吞吐量瓶颈。可以在分区键后加随机后缀(如ad_id#event_type#rand_1-10),查询时遍历10个分区再汇总,或者按广告组+广告ID的组合分散流量。 - 强一致性读取:频次控制是强一致性需求,查询用户的事件计数时必须开启
ConsistentRead=true,避免读到旧数据导致错误展示广告。 - 吞吐量规划:按200K RPM的写入量计算WCU(单条ADD操作占1个WCU,若Item<1KB),建议采用预置WCU+自动扩缩容,比按需模式更稳定,成本也更低。
- 错误重试:ADD操作失败(如网络抖动)必须重试,确保计数准确,否则会导致频次控制失效。可以用指数退避策略重试。
- 成本控制:GSI会额外消耗读写容量,要根据实际查询需求权衡是否创建;TTL自动删除旧数据能有效降低存储成本。
- 峰值测试:提前模拟200K RPM的写入流量,测试系统的延迟、吞吐量和热点场景下的表现,确保上线后稳定运行。
内容的提问来源于stack exchange,提问作者Tal Humy

