blog-post

演示:使用 Manticore Search 的 GitHub 搜索

TL;DR: 在这篇博客文章中,我们演示了如何制作一个与 GitHub 用于查找问题的搜索应用非常相似的应用,使用 Manticore Search。

引言

在我们有效展示 Manticore Search 的能力和性能的过程中,我们意识到选择一个可以作为有说服力展示的真实应用的重要性。我们考虑了几种选择——常见的选择如:

  • 电子商务网站
  • 目录列表
  • 电影数据库等

虽然这些是熟悉且易于理解的范例,而 Manticore 也非常适合它们,但在提供实际价值方面却不尽如人意。

这时灵感来了:为什么不为 GitHub 问题创建一个搜索工具呢? 这不仅给了我们提供一个强大演示的机会,还为增强搜索体验提供了利用 Manticore Search 高级功能的机会,这些功能至少对我们 Manticore 核心团队是有用的

我们接受了这个挑战,并自豪地展示我们的创作——一个专门为 GitHub 问题量身定制的搜索引擎。这不仅仅是一个演示;这是一个实用工具,我们希望能对 GitHub 社区大有裨益。

我们邀请你探索并与我们的 GitHub 问题搜索互动,地址是 https://github.manticoresearch.com 。通过这个动手体验,发现 Manticore Search 的全部潜力。享受我们增强的搜索能力,亲眼见证 Manticore Search 如何改变数据探索。

在某些情况下,我们达到了比 GitHub 快 30 倍的搜索速度。对它是如何工作的以及我们是如何做到的感到好奇吗?让我们深入探讨我们是如何构建它的。

Github 搜索 - 215 毫秒通过 Manticore 的 Github 搜索 - 6 毫秒
来自 GitHub 界面的搜索结果来自 Manticore Search 演示项目的搜索结果

前提条件

这个概念相当简单——我们的目标是从 GitHub 上选择的仓库中提取数据到 Manticore 搜索数据库。通过对数据进行全文索引,我们能够启用有效的搜索功能。

我们的决定是保持一个与 GitHub 非常相似的设计,但在用户界面上进行微妙的增强,以适应不仅是技术精英用户,还有技术知识较少的用户。

此外,我们还希望引入一些额外的功能,如:

  • 在问题和评论之间进行组合搜索
  • 高级过滤选项
  • 根据反应排序结果的能力
  • 无限滚动分页

让我们深入细节,审视面临的挑战,并探索 Manticore 如何通过一个实用的示例来解决这些问题。

为 MVP 选择合适的工具

开发一个演示可能非常具有挑战性,但当你在与时间赛跑时,你需要尽可能多的帮助。这正是我们倾向于采用经过验证的 PHP 后端和 JavaScript 客户端侧的组合的原因——加上一些 SEO 友好的混合魔法。你问为什么选择 PHP?嗯,就像是给你的项目背上绑上一个喷气背包!开始快,验证简单,测试轻松。而且当然,因为我们的团队在 PHP 方面的经验比其他美丽现代的编程语言要多。(顺便说一下, 阅读关于 如何构建一个用 C++ 编写的 Manticore Search 的 PHP 插件。)

Manticore Search 还附带了 PHP 客户端 ,我们在演示中使用了它。使用它的方法如下:

<?php
use Manticoresearch\\Client;
$client = new Client(['host' => 'localhost', 'port' => 9308]);
$index = $client->index('repo');
$docs = $index->search('bug')->get();
foreach ($docs as $doc) {
    var_dump($doc->getId(), $doc->getData());
}

就是这样,你创建了一个 Manticore 客户端,选择你想要交互的表格,发送搜索请求,然后——瞧!——结果纷至沓来。

我们不会在这里深入探讨 Manticore Search PHP 客户端,但如果你渴望尝试一下,可以查看他们的仓库 Manticore Search PHP Client

该演示包含多个组件,因为它:

  • 从 GitHub 提取数据,
  • 维护一个待处理仓库的队列
  • 可以通过电子邮件发送通知
  • 等等等等

为了你的方便,所有与 Manticore Search 交互的代码都位于 Manticore.php 。这对于考虑将来比较不同存储引擎的人来说也可能很有用。

我们必须克服的有趣挑战

在制作演示时,除了实现上述简单的事情外,我们还遇到了一些你在项目中也可能会遇到的有趣挑战。

结合两个表时搜索结果的相关性

搜索系统中最关键的一个方面是其结果的相关性。在使用 Manticore Search 作为后端实现 GitHub 问题演示时,值得注意的是,相关性可以直接高效地管理。Manticore Search 采用经典的 BM25 排序方法,根据文档和查询中关键词的频率和重要性以及字段长度归一化(找到匹配词条的文本字段的长度)对搜索结果进行排序。这意味着无需复杂的配置或复杂算法就可以获得高效的搜索体验。更多详细信息,请参考文档 – 排序概述

我们面临的挑战是在 GitHub 问题和评论中执行组合搜索。从技术上讲,我们在 Manticore 层面将其划分为两个独立的表:一个用于问题,另一个用于评论。经过对排序机制的研究,我们决定实施排名偏置精确度(RBP)算法,这使我们能够合并来自两个不同来源的结果。此外,Manticore Search 提供了一个可以使用 PHP 客户端的 $doc->getScore() 方法检索的 ‘score’ 字段。您可以在此处查看代码: Manticore.php 代码

因此,我们不仅实现了开箱即用的相关性,还利用 RBP 合并两个来源,最大化搜索结果的有效性!

问题和评论的高级过滤

Manticore Search 演示项目中的 GitHub 问题搜索的高级过滤和整个界面

步骤 1:渲染范围

在搜索功能领域,仅仅是基本搜索往往是不够的。用户经常需要使用过滤器来细化他们的结果。在 Manticore Search 和许多其他搜索引擎中,实现基于范围或相等的简单过滤是直接的。然而,当涉及到在特定范围内分组结果时,这个任务可能看起来很艰巨 — 但使用 Manticore Search 实际上是相当容易管理的。

我们的目标是让用户选择预定义的范围并相应地应用过滤器,同时避免存储或缓存任何额外数据。例如,我们希望按评论数过滤问题:≤ 5、5 到 10 之间,以及 ≥ 10。Manticore Search 通过其 INTERVAL 函数简化了这个过程。让我们看看在演示中是如何实现的。

我们设计了一个特殊的方法,生成我们想要的范围以及落入每个范围的项目数。以下是伪代码,展示了它有多么简单:

$client = static::client();
$index = $client->index('issue');
$search = $index->search('');
$range = implode(',', $values);
$facets = $search
    ->limit(0)
    ->filter('repo_id', $repoId)
    ->expression('range', "INTERVAL(comments, $range)")
    ->facet('range', 'counters', sizeof($values) + 1)
    ->get()
    ->getFacets();

您可以在以下 URL 查看完整代码:

查看完整代码

步骤 2:应用过滤器

下一步是过滤结果。这是通过使用 gt(大于)过滤器结合 or 条件来完成的。下面是代码的简化表示:

$search->filter('comments', 'gt', 0, Search::FILTER_AND);
$search->filter('comments', 'lte', 3, Search::FILTER_OR);

您可以通过此链接检查我们的代码片段:

查看代码片段

按反应排序

在 GitHub 上进行搜索时,您可能注意到它不显示或不允许按反应过滤。然而,有时识别被反应最多的问题可以特别有洞察力 — 例如,衡量最期望的功能或预测即将出现的问题。这就是按反应排序变得无价的地方。

首先,我们需要捕获反应数据。GitHub API 方便地以简单的 JSON 对象提供这一点:

{
  "url": "https://api.github.com/repos/ClickHouse/ClickHouse/issues/35407/reactions",
  "total_count": 0,
  "+1": 0,
  "-1": 0,
  "laugh": 0,
  "hooray": 0,
  "confused": 0,
  "heart": 0,
  "rocket": 0,
  "eyes": 0
}

这是个好消息,因为 Manticore Search 提供了 原生 JSON 支持

接下来,我们必须考虑排序需求。我们是需要按单个 JSON 字段排序,还是按多个字段的总和?幸运的是,Manticore Search 使我们能够同时做到这两点。这完全符合我们的需求!我们可以直接在表中存储 JSON 并使用以下代码片段启用排序:

$search->expression(
    'positive_reactions',
    'integer(reactions.`+1`) + integer(reactions.hooray) + integer(reactions.heart) + integer(reactions.rocket)'
);

要全面查看排序实现,请参考此处的完整代码片段: Manticore PHP 客户端排序示例

如所示,我们利用 Manticore PHP 客户端的 expression 函数通过 . 语法访问 JSON 字段。这种方法消除了缓存计数器或执行额外计算的需要。您可以创建 JSON 字段,使用表达式访问它,保持高速,并避免缓存机制的开销!

分面搜索

搜索和过滤能力是任何强大搜索功能的基本组成部分。然而,在获取计数的速度方面,常常会出现一个普遍挑战。大家普遍认为,实现 MySQL 中快速计数操作需要使用索引。这些索引不仅会扩展数据库大小,还会增加对负载繁重的应用程序的复杂性,这些应用程序通常会 resort 到缓存,然后根据需要调整这些计数。

好消息是 Manticore Search 完全避开了这些问题!使用 Manticore Search,从数据库中检索计数既简单又快速,消除了额外缓存层的需要。

为了显示反映页面上应用过滤条件的实时计数,我们利用与搜索相同的过滤条件。然而,我们引入了一个额外的分面查询,这只需要几毫秒。这种方法使我们能够几乎没有任何开销地获得指定组的当前计数。以下是一个简洁的 PHP 代码片段,演示如何做到这一点:

$facets = $search
  ->limit(0) // 由于我们只对计数感兴趣,因此不需要结果
  ->filter('repo_id', $repoId) // 按仓库 ID 过滤
  ->expression('open', 'if(closed_at=0,1,0)') // 评估问题是否开放
  ->facet('open', 'counters', 2) // 获取开放和关闭问题的分面计数
    ->get() // 执行搜索查询并检索结果
  ->getFacets(); // 从结果中提取分面数据

让我们分解一下:我们将限制设置为零,因为我们的目标是获得计数,而不是搜索结果。我们按仓库 ID 进行过滤,并应用表达式按 closed_at 字段分组。此分组为我们提供了开放和关闭问题的计数。

对于那些对完整实现感兴趣的人,完整的代码片段可在 GitHub 上找到: Manticore GitHub Issue Search - Manticore.php

使用 Manticore Search,有效获取计数的挑战几乎以开箱即用的解决方案得以解决。还有什么比这更高效和用户友好的呢?😊

结论和进一步计划

在开发我们的演示项目过程中,我们旨在展示 Manticore Search 的能力和效率。结果不仅符合我们的预期,还为我们提供了一种增强我们在 GitHub 仓库中导航方式的工具。通过这一倡议,我们能够展示 Manticore Search 的潜力,并集成了一些改进和功能,以增强 GitHub 上的当前产品:

  • 我们实现了显著更快的搜索速度,搜索通常在约 5-10 毫秒内完成,而 GitHub 的搜索时间超过 200 毫秒。
  • 我们的演示项目允许在搜索结果中包含评论,提供比目前在 GitHub 上可用的更广泛的信息。
  • 我们引入了用户按反应数量排序问题的能力,提供了额外的用户交互维度。
  • 提供了高级过滤选项,可以进行更精确的搜索,例如显示特定评论范围内的问题或专注于仅在评论中进行搜索。

我们鼓励您通过访问以下链接来探索这些增强功能: https://github.manticoresearch.com

此外,对于那些对开源代码或本地运行项目感兴趣的人,可以在此访问: https://github.com/manticoresoftware/manticore-github-issue-search

我们还很高兴地宣布计划将 vector search (在 Manticore 开发包中提供,准备发布)纳入我们的演示。这个即将推出的功能旨在与全文搜索结合使用时进一步提高结果质量,展示如何利用 Manticore 中的新功能来增强搜索功能,因此敬请关注并 在 Twitter 上关注我们

我们欢迎您对这次 Manticore Search 功能和能力的实用演示提出反馈,并期待与您分享更多更新。期待您的反馈: issuesdiscussions

安装Manticore Search

安装Manticore Search