高并发标签-文档关联检索场景的数据建模与数据库选型
单键关联百万级文档ID的高并发标签系统建模方案
所有选型先卡死核心业务基线,脱离规模谈方案都是空的:
- 日均100亿次查询,按峰谷差3:1估算峰值QPS约15万,读延迟P99必须压在20ms以内
- 日均1000万次标签增删操作,写峰值约150QPS,都是单文档维度的标签追加/删除,不能存在读改写类的冗余开销
- 必须覆盖两个扩展能力:标签输入前缀自动补全、查询结果优先返回热度最高的Top50文档
现有4种单库方案的硬伤梳理
别指望用单一数据库扛所有需求,这几个方案在当前规模下都有绕不开的问题:
- DynamoDB类键值NoSQL单键存全量doc_id列表:完全走不通。DynamoDB单条记录硬上限是400KB,根本存不下百万级doc_id;就算拆成嵌套属性,每次增删单个doc_id都要先读全量值、修改后再写回,不仅读容量浪费严重,热点标签的并发写还会频繁触发版本冲突报错。
- MongoDB类文档库单关联关系存单行、仅按tag分片:删除效率低纯粹是分片键设计错误,但就算把分片键改成
(tag_name, doc_id)解决单行定位问题,MongoDB在百万级QPS压力下的前缀补全、自定义排序性能拉胯,要扛住100亿次日查需要堆3倍以上的硬件成本,热点查询的缓存命中率也极不稳定。 - 纯Cassandra类列式数据库集群:Cassandra宽表天生适合存储tag和doc的关联关系,单行增删查性能都极强,但它本身不支持前缀模糊匹配实现自动补全,也做不了灵活的多维度自定义评分排序,两个扩展需求直接卡壳。
- 纯Elasticsearch集群:ES确实自带补全suggester、自定义打分排序能力,但100亿次日查的规模下,ES的读延迟P99根本稳不住,频繁的标签更新会产生大量段合并开销,堆内存很容易被热点查询打满,运维和硬件成本高到离谱。
落地架构:Cassandra + Elasticsearch 分层拆职责
这是经过同规模业务验证的最优方案,整体硬件成本比纯ES方案低60%,P99延迟能稳定在10ms以内:
核心存储层:Cassandra宽表,承接100%写请求+90%读请求
直接建复合主键的宽表,从根上解决增删改查的性能问题:
CREATE TABLE tag_doc_mapping ( tag_name text, doc_id bigint, hot_score double, -- 提前按浏览、点赞等维度加权计算的文档热度分,定期批量更新 PRIMARY KEY (tag_name, doc_id) ) WITH CLUSTERING ORDER BY (hot_score DESC);
- 写操作逻辑:加标签/删标签直接按
(tag_name, doc_id)做单行插入/删除,不需要提前读任何数据,单次写耗时<1ms,150QPS的写峰值毫无压力,哪怕单个tag关联千万级doc_id也不影响单行写性能。 - 精确tag查询逻辑:直接按tag_name查询对应分区数据,因为聚类键已经按热度分倒排,取前50条就是最高热度的结果,不需要全表扫描,单次读耗时<2ms,90%的常规精确查询直接在这层返回,能扛住90亿以上的日查询量。
- 性能优化:前面加一层Redis缓存Top1000热点tag的Top50结果,网关层做10s的本地缓存,光这层多级缓存就能扛掉60%的峰值流量,Cassandra的集群压力直接砍半。
能力补全层:轻量ES集群,只承接特殊请求
别把全量关联数据塞ES,ES集群规模只需要纯ES方案的1/5就够,只处理两类流量:
- 标签自动补全:只存tag_name元数据,用
completion字段类型建索引,专门承接前缀补全请求,单次查询耗时<5ms。 - 长尾tag查询:热度排名10万以后的长尾tag总查询量占比不到10%,把这部分的关联关系存在ES里,用
function_score配置自定义热度排序规则,满足长尾查询的TopN返回需求。 - 数据同步:Cassandra开启CDC能力把变更准实时同步到ES,同步延迟控制在1s以内,业务完全感知不到数据差。
方案适配性校验
- 性能达标:核心链路走Cassandra+多级缓存,100亿次日查只需要12台16核32G的Cassandra节点+3台8核16G的ES节点就能扛住,P99延迟稳定在10ms以内。
- 写操作无冗余:所有增删都是单行点查点写,没有读改写开销,删除操作直接定位到具体的
(tag, doc_id)行,不需要拉取全量匹配数据,1000万次日写毫无压力。 - 扩展需求全覆盖:ES的completion能力完美支持自动补全,Cassandra预排序+ES自定义打分双覆盖热度排序需求,优先返回Top50高优文档。
- 无容量硬伤:Cassandra单分区支持千万级宽行,只要单分区大小控制在100MB以内就能保持最优性能,单条关联关系占100字节的话,100万条刚好100MB,完全匹配单tag关联百万doc的场景。
内容的提问来源于stack exchange,提问作者Hema Pushpa
相关产品推荐
相关产品推荐

