索引漏洞正 silently 拖垮你的搜索体验
|
跟产品吵。去年十一那天下午三点十七分,PM 在钉钉发了个新需求——“热搜词补全延迟必须压到一百五十毫秒以内”,我回了个句号,她立刻打来电话说“你这态度有问题”。我说你先看看搜索框右边那个小齿轮图标有没有变灰,她说“变灰怎么了”,我说“灰度开关没推完”。她静了三秒,把电话挂了。 我原以为是前端 debounce 没设好,结果查日志发现 query_id 串了三次——不是拼错了,是同一个 query_id 在三个不同索引分片里落了三条不同 doc_id 的记录。不是重复写入,是漏建、错建、跨 shard 建混了。这个我敢打包票:doc_id=72819346 是个商品,但 index_name=sku_meta_v3_shard_07 里它被当成了 SKU 标签;而同一条在 sku_tag_v2_shard_03 里,又被当成了类目路径节点。它根本没进主索引,却在两个旁支索引里各自长出了根须。可谁去倒了杯水?测试那边的小王,穿蓝格子衬衫那个,端着杯子路过我工位时看了眼我屏幕,说“哎你这 highlight 为啥是红底白字”,我点头说“对,就该红”。他没问为什么,我就没提这是索引 schema 和 highlight 字段映射对不上——那字段根本不在那个索引的 stored_fields 列表里。
文章配图,仅供参考 监控大屏上一个无关曲线抖了一下。不是 QPS,不是 latency,是那个叫 “logstash_kafka_lag” 的紫色虚线,抖得特别细,像被谁拿针尖挑了一下。没人管它。我们盯的是 top5 耗时接口——所有人的 dashboard 都只配了 /search/v2/query 和 /suggest/keyword 这俩。但真正崩的是 /search/v2/fetch,调用次数少,平均耗时稳在十八毫秒,但 P99 突然从四百跳到一千七百——而且只发生在用户点击“查看更多结果”之后。没人报 bug,没人埋点,因为前端加了个防抖,把 fetch 请求塞进了 setTimeout(1200) 里等。所以它永远不进 error rate 看板。优胜劣汰?呵,淘汰的是那些不会自己多点两下的用户。剩下那帮人,搜出来三页全是“苹果手机壳透明防摔”,第四页开始才出“苹果 Watch Ultra 表带”,他们就信了——真信了。这就是你说的优胜劣汰?是索引自己挑走了最乖的用户,留给我们一堆咬文嚼字还爱截图反馈的刺头。 DBA 老张昨天还在 Slack 里说我:“你们搜得慢,凭什么怪我 binlog 消费慢?” 我没回。因为我看见他前天凌晨两点发的 commit 记录:schema_sync_job.sh 把 index_alias 更新逻辑注释掉了,备注是“临时绕过 alias 切换卡顿”。他不知道 alias 不切,新索引就永远接不到流量。更不知道那个新索引里的 document_type 字段,其实早在三个月前就被前端悄悄改名成 item_type,但 mapping 没重刷,ES 仍按旧名做 dynamic template。所以这批数据进来时,date 字段自动 cast 成 long,time 字段却被 fallback 成 keyword——然后 search_as_you_type 直接绕过它。这个我说不准。我只知道,上周三凌晨三点零七分 oncall 手机震了一下,弹出告警:“fetch_service timeout count > 3”,我看了一眼,把告警 mute 了。因为我知道 mute 之后的十分钟,会有一波真实用户放弃搜索、直接翻首页 banner。Banner 是市场部上周五刚换的,“iPhone 15 Pro 限时直降”,底下有二维码。他们扫了。这事跟索引漏洞无关,但我记得很清楚:咖啡杯上印的公司 logo,裂了一道缝,在“搜”字右下角,像被牙咬过。 KPI 今年改了。不再是“搜索无结果率 < 1.2%”,变成“搜索后七秒内点击率 ≥ 24.5%”。听起来更用户导向了,对吧?但你猜怎么着——现在所有无结果请求都被前端截掉、自动 fallback 到“猜你喜欢”。而 guess_you_like 的数据源?是离线跑的 user_behavior_cluster_model_v7。它根本不走索引。也就是说,只要索引漏建三万条商品,只要漏掉的恰好是新上架、没曝光、没成交、也没进训练集的商品,它们就永远在搜索世界里蒸发,又永远在推荐世界里活着。这不是 bug,是 feature。是系统自己长出来的免疫机制。可这机制杀的是谁?杀的是那个想搜“能放得下 iPad mini 6 的帆布邮差包”的实习生,他连关键词都拼不对,只能打“iPad 包 小”,然后失望地关掉 tab。关掉之前他还顺手点了右上角三个点——举报搜索不准。这个举报按钮的上报链路,我半年前就砍了。太重。上报要查 UID,要打用户画像服务,要鉴权。我们让它走了个本地埋点,攒够五百条才 flush 一次。上次 flush 是九月二十九号下午。之后就没再上来过。 我刚刚翻了下 release-note,十一当天灰度那次,运维同学漏写了 index_migration_flag=true 的启动参数。文档里写了三遍,脚本里缺了。就这么简单。但他不是故意的。他那天刚休完育儿假回来,孩子半夜发烧,他凌晨四点还在复测 rollback 步骤。这些事和索引漏洞没关系,但它们一块儿坐在同一辆车上,只是没人给司机系安全带。我现在手边这个 demo 查询,搜“蓝牙耳机 运动 防汗”,返回结果里第一页第二条是条 2017 年的老评测,标题写着“Jabra Sport Pulse 已停产”。它不该出现。它出现在这儿,是因为索引里那条 doc 的 published_at 被错误映射成 timestamp_millis,而 query 中的 range filter 却用了 date math:now-180d/d。ES 自动做了 time_zone 补偿,补偿错了三个小时,于是它被算进了“最近半年”。我改了 mapping,重刷了六千条,上线了。但没人验证——测试排期满了,PM 说“先上,线上看”。线上怎么看?靠人工翻十页,数有没有老货。我们这么干过三次。第三次是我自己翻的,数到第八页时,手机又震了一下。这次是 HR 提醒填季度自评。我填了一半,关了窗口。 下一步?我打算把 /search/v2/fetch 接口里那个 try-catch 的日志级别,从 WARN 改成 ERROR。不是为告警,是为让 DBA 看见。让他下次吵架时,能看见日志里清清楚楚写着:“fetch_doc_by_id [72819346] not found in primary index, fallback to secondary index [sku_tag_v2_shard_03], field ‘price’ is missing, use default value null”。这样他至少知道——不是他慢。是我们一起造了一座迷宫,还骗自己说出口在左边。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


网站上线流量归零?运维盲区正 silently 蚕食用户
