我如何利用 OpenSearch 和 CJK 实现容错搜索

零结果查询正在蚕食我们的观看时长。

一年来,TopVideoHub 一直使用 SQLite FTS5 进行搜索。它在处理精确匹配时表现良好,但在用户出现拼写错误时却束手无策。

用户会搜 "demon slyer" 而不是 "demon slayer"。或者在日语或韩语标题中加入多余的空格。由于 FTS5 匹配的是精确分词(tokens),一个拼写错误就会导致零结果。用户不会再次尝试搜索,而是直接离开了。

我将标题搜索迁移到了 OpenSearch。以下是我为多语言用户解决这一问题的方案。

标准模糊匹配(Fuzziness)的问题

大多数教程都会告诉你使用“模糊匹配”(fuzziness)来修复拼写错误。这在英语中行得通,但在中日韩(CJK)文本中却会失效。

  • 编辑距离(Edit distance)对 CJK 效果不佳。CJK 中的拼写错误通常意味着完全用错了字符。
  • 标准模糊匹配会产生语义垃圾。它可能会因为两者仅差一个编辑距离而将 "fire" 与 "water" 匹配在一起。
  • 分词(Tokenization)很困难。CJK 语言在词与词之间不使用空格。

解决方案:多字段方法

我不再对所有文本一视同仁。我创建了一个逻辑标题字段,通过三种不同的方式进行索引:

  • 拉丁字母和罗马字 (Romaji):我使用了启用模糊匹配的标准分词。我将前缀长度 (prefix length) 设置为 1。这确保了 "demon" 可以匹配 "demn",但 "lemon" 不会匹配 "demon"。
  • CJK 文本:我使用了 CJK bigram 分析器和 ICU 规范化器 (normalizer)。我关闭了模糊匹配,转而使用 70% 的最小匹配阈值。
  • 自动完成:我使用了 edge-ngram 字段来实现“边输入边搜索”的结果。

架构与数据安全

我保留 SQLite 作为唯一事实来源 (single source of truth)。OpenSearch 则作为一个快速、可重建的索引。

  • 我使用 PHP 进行批量更新,将数据推送到 OpenSearch。
  • 我绝不在用户请求期间运行索引操作。
  • 我运行一个 Python 脚本来检查“数据漂移”(drift)。这可以确保 OpenSearch 的计数与 SQLite 的计数一致。

结果

变化是巨大的:

  • 零结果查询从 14% 降至 3% 以下。
  • 搜索会话时长增加了,因为用户能立即找到他们想要的内容。
  • 延迟保持在 40ms 左右的低水平。

如果你的服务面向多语言用户,请记住:拼写容错和 CJK 匹配是两个不同的问题。你需要两套不同的解决方案。

来源:https://dev.to/ahmet_gedik778845/how-i-added-typo-tolerant-video-title-search-with-opensearch-and-cjk-3e5d