如何基于 Mango $lt 查询相似度?
解读
国内面试官问“**基于 Mango lt` 语法,而是考察三点:
- 是否理解 Mango 查询只能做精确范围过滤,本身不具备相似度计算能力;
- 能否在 CouchDB 无内置向量/全文评分 的限制下,用业务层二次排序或离线打标签的方式把“相似度”落地;
- 是否熟悉 CouchDB 3.x 的索引选型(JSON vs text vs 分区索引)以及 移动端同步过滤场景下的性能陷阱。
一句话:lt 帮你“取”出来。
知识点
- Mango 查询语法:
{"age":{"$lt":30}}返回 age 字段严格小于 30 的文档,无评分、无模糊。 - 相似度本质:把业务维度(文本、向量、行为)映射成可排序的标量分值,存成字段,如
similarity_score。 - CouchDB 索引规则:
- 对
similarity_score建升序 JSON 索引,{"index":{"fields":[{"similarity_score":"asc"}]}}; - 查询时用
$lt做Top-N 截断,例如取前 100 个最相似:
{"selector":{"similarity_score":{"$lt":阈值}},"limit":100,"sort":[{"similarity_score":"desc"}]}。
- 对
- 阈值来源:
- 离线批任务用 Spark+MLlib 算好 95% 分位点写回 CouchDB;
- 或在线服务把用户向量和商品向量点积后实时写入
similarity_score,再同步到移动端。
- 性能红线:
- 单次 Mango 查询必须命中索引,否则触发全表扫描,在百万级文档以上直接超时;
- regex 混用会导致索引失效,国内云厂商(阿里云、腾讯云)托管版 CouchDB 直接报
no_usable_index。
- 移动同步:
- 在
filter函数里禁止用 $lt 做相似度过滤,因为 CouchDB 会把过滤器推给每个变更,复杂度 O(n²); - 正确做法是把相似度高的文档打标
flag:similar,同步端用selector={"flag":"similar"},走预计算索引。
- 在
答案
分三步落地:
第一步:离线或实时计算相似度
把文本/向量相似度转成 0~1 之间的浮点分值,写入文档字段 similarity_score,并带上业务主键便于回查。
第二步:建索引
PUT /mydb/_index
{
"index": {
"fields": [{"similarity_score":"asc"}]
},
"name": "score-asc-index"
}
务必升序,因为 CouchDB 的 $lt 走左前缀匹配,升序索引能让 sort=[{"similarity_score":"desc"}] 仍用同一索引。
第三步:用 $lt 取 Top-N
POST /mydb/_find
{
"selector": {
"similarity_score": {"$lt": 0.85}
},
"sort": [{"similarity_score":"desc"}],
"limit": 100
}
阈值 0.85 由离线分位点给出,保证返回的 100 条就是“最相似”的文档。
若需要用户个性化,把阈值存到用户画像表,每次查询动态拼进 selector 即可。
踩坑提醒:
- 如果
similarity_score缺失,CouchDB 会把它当null,null $lt 0.85为真,导致脏数据混入,务必加"similarity_score":{"$exists":true}。 - 国内公有云单节点 QPS 上限约 800,高并发场景下把相似度计算迁到 ES 或 TiDB,CouchDB 只存结果快照,避免CPU 打满被客户投诉。
拓展思考
- 多维度相似:
若相似度由文本、价格、距离三个维度加权,可把similarity_score做成归一化加权和,也可以分层截断:先用$lt把距离小于 5 km 的文档捞出来,再在客户端用余弦相似度二次排序,兼顾精度与性能。 - 增量更新:
采用 CouchDB 的 _changes + seq 追踪,把变更流推给 Flink 作业实时重算similarity_score,写回时带_rev防止版本冲突,实现秒级更新。 - 离线冷启动:
对新用户无行为问题,把物品聚类中心预置到cluster_id字段,用$lt取簇内热度前 K 的文档,解决移动 App 首屏空白的痛点,国内主流内容厂商已用该方案把次日留存提升 6%。 - 安全合规:
国内个人信息保护法要求向量特征也需脱敏,计算相似度时把用户 ID 哈希化后再落盘,防止反向推回原始手机号,审计时可直接展示索引定义而不暴露业务逻辑。