做代码搜索真的太难了!

做代码搜索真的太难了!
2024年04月15日 15:48 CSDN

Val Town 是工程师 Tom MacWright 开发的一个用于编写和部署 TypeScript 的社交网站。近日,Tom MacWright 想在网站上增加一个“代码搜索”的功能,然而,随着尝试的深入,他发现,一切远没有想象中那么容易,尤其是在不雇佣一个专门负责“搜索团队”的情况下,实现大规模搜索代码是很困难的。以下是他的一些经验分享。

原文:https://blog.val.town/blog/search-notes/

作者 | Tom MacWright       

责编 | 苏宓

出品 | CSDN(ID:CSDNnews)

以下为译文:

Val Town 网站的搜索功能不是很好。目前,它是基于 Postgres ILIKE 功能构建的,只执行子串搜索:如果代码中有搜索词,就会出现在搜索结果中。这几乎不涉及排名,而且对多词查询的支持也很差。对于 Val Town 而言,更好的搜索是我们最需要的功能之一。

我正在努力改进这一点,但我们还没有找到适合我们需求的解决方案。以下是我们的一些研究笔记。目前我们了解到的情况是:

  • 主流搜索解决方案是针对自然语言而非代码设计的。

  • 有代码搜索需求的大公司花费了大量时间和金钱来构建自己的定制解决方案。

  • 我们已经有了大量数据,需要一个能很好扩展的解决方案。

  • 使用单独的搜索服务而不是数据库扩展所涉及的基础设施和复杂性权衡非常重要。

代码搜索与自然语言搜索

行业中现成搜索解决方案的一个常见问题是,它们是针对英语和其他自然语言设计的。例如,在通常的 FTS(Full Text Search)设置中,默认情况下会使用以下一些算法:

  • 删除停顿词:在索引文本之前,会从文本中删除“the”和“it”等词,因为它们太常见了,会给性能带来更多问题,得不偿失。

  • 词干提取:这主要是反转连接词,在将“running”这样的词添加到索引之前将其转化为“run”,并对搜索查询进行同样的处理,这样你就可以搜索“runs”,并得到包含“running”一词的文档的搜索结果。

  • 词形还原:有些搜索索引甚至可以用同义词替代更常见的词,这样,搜索 “excellent”就可以得到包含“great”的文档结果。

总而言之,这意味着从你存储在索引中的文档向量与文档完全不一样:

select * from to_tsvector('english', 'I am writing this example sentence');

--- 'exampl':5 'sentenc':6 'write':3

所有这些规则的问题在于它们会对代码造成严重破坏。在 TypeScript 中,“the”并不是一个停止词:它是一个有效的变量名,你可能想搜索它。单词边界不一样,对函数名进行词干处理没有太大意义。

select * from to_tsvector('english',

'function stringifyNumber(a: number): string { return a.toString() }');

-- 'a.tostring':7 'function':1 'number':4 'return':6 'string':5 'stringifynumb':2

这是一个相当糟糕的索引:它包含了一些本应是停止词的单词,如 function,而且不会将 a.toString() 分割成两个词块,因为.不是默认的词边界。

全文搜索

Postgres 有一个全文搜索的扩展(https://www.postgresql.org/docs/current/textsearch.html),我们的托管提供商Render 支持该扩展。我在以前的项目中使用过 FTS(Full Text Search),对于某些规模的项目,它的效果非常好。你可以尝试用 Postgres 来处理所有事情,老实说,到目前为止,我们一直在使用 Postgres。这是一项非常棒的技术,有很好的文档,我们的托管提供商也提供了很好的支持。

如果我们能使用 Postgres,我们就会使用:对于一个小团队来说,保持基础设施尽可能简单是至关重要的。

不过,我之前使用 FTS 的项目都遇到了性能问题,难以扩展--Observable 最终迁移到了 Elasticsearch。我们有大量的 vals,正在测试单节点 Postgres 集群的极限。很难找到使用 FTS 进行代码搜索的记录,不过可能有人在悄悄地使用它取得了成功。我想避免将其作为第一选择,而是将其放在我的后备选项中。

pg_trgrm

我们作为 v2 搜索算法软启动的解决方案是基于 pg_trgrm,它在 Postgres 中实现了 trigram 搜索。代码搜索似乎确实可以通过 trigram三元组)获得成功:

  • Russ Cox(Go 语言创建者之一)在 2012 年发表的一篇著名文章讲述了 Google 代码搜索如何使用 trigram 索引和特殊的正则表达式实现在技术上取得成功的故事(https://swtch.com/~rsc/regexp/regexp4.html)

  • GitHub 的新搜索系统也使用了 trigram 代码搜索,此外还有很多我都很羡慕的技术(https://github.blog/2023-02-06-the-technology-behind-githubs-new-code-search/)

  • Sourcegraph 也从 Google 继承了基于 trigram 的搜索工具(https://github.com/sourcegraph/zoekt)

Stephen Gutekanst(Sourcegraph CTO)关于在 Postgres 本地为版本库建立索引的系列博文为我们使用 Postgrespg_trgrm方法提供了大量信息(https://devlog.hexops.com/2021/postgres-regex-search-over-10000-github-repositories/)。我们用 gin_trgm_ops 在包含搜索文本的列上创建了一个 GIN索引(https://www.postgresql.org/docs/current/gin.html)

目前得出的结论是,这对于 regex 搜索来说是一个很好的解决方案,但我们并不是在进行 regex 搜索:大多数搜索都比较自由。我们使用 word_ similarity 进行搜索排名,但很难哄骗算法给我们一个合理的排序。

选择范围

有一些专门针对代码的工具,但大多数都是闭源的:GitHub 的搜索功能非常出色,但显然是一个拥有实时预算的专业团队的杰作。

  • Sourcegraph 维护的 Zoekt(https://github.com/google/zoekt)分支非常酷,但却非常小众,而且需要大量新的基础设施投入。

  • Elasticsearch (https://github.com/elastic/elasticsearch)可能是这个问题最终不可避免的解决方案。它没有代码特定的处理方式,但可以以几乎无限的方式进行定制。我们开始学习 Java 内存调优,并为我们的应用程序引入第一个持久磁盘存储,同时为我们的数据提供额外的真实来源,我们并不感到兴奋。也许我们可以使用 Elasticsearch Cloud 来避免维护开销。

  • Meilisearch(https://github.com/meilisearch/meilisearch)似乎是一个很有前途的 ES 替代方案,具有 Rust 的光芒,但他们似乎更强调延迟而非可扩展性,而且我们不确定基础设施的投入是否会更低。

  • ParadeDB(https://www.paradedb.com/)承诺会像 Elasticsearch 一样,但“只是 Postgres”,这非常吸引人,但我们还不能在 Render 中使用他们的扩展。

总结

总之,我们还在努力中。搜索代码而不是英文,难度会更高一些。对于一个小团队来说,我们的目标是保持基础架构简单、开发环境易于设置、数据存放在同一个地方,因此我们尽量小心谨慎,避免使用需要不断维护的东西。大多数大中型公司都有一个搜索“团队”,而不仅仅是一个搜索服务,这是有原因的。

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部