# Bookbot 如何借助 Manticore Search 支撑 350 万条已保存搜索，并贡献约 10% 收入

Bookbot 如何使用 Manticore Search，让 129 万本二手书保持可搜索，将新上架图书与 350 万条已保存搜索匹配，并支撑一个贡献约 10% 收入的渠道。

对于一家二手书店来说，搜索要做的不只是找到书名。它还必须找到一本真实存在的库存书。

[Bookbot](https://bookbot.com/) 在捷克共和国名为 Knihobot，负责接收二手书、拍照、编目、销售，并将它们寄送给下一位读者。同一书名的两本书可能有不同的版本、价格、品相和照片。当其中一本售出时，搜索结果可能需要立即变化，即使这个书名本身仍然有货。

Bookbot 使用 Manticore Search，让这份不断变化的库存保持可搜索。它的主图书索引包含约 **530 万个文档**，其中包括约 **129 万本当前有库存的实体书**。Manticore 还为 Bookbot 的 [Watchdog](https://bookbot.com/watchdog) 功能存储了约 **350 万条已保存搜索查询**。

根据 Bookbot 的内部数据，Watchdog 约贡献 **10% 的收入**。

Bookbot 联合创始人兼 CTO [David Gazdoš](https://www.linkedin.com/in/david-gazdo%C5%A1-736496235/) 分享了公司如何使用 Manticore：不仅用于支撑面向客户的搜索，还用于让搜索结果与实体库存保持一致、选择要展示的正确书本、将新上架图书与等待中的读者匹配、搜索一个包含 2.03 亿条记录的书目数据集，并支持内部运营。

## 当每一本书都不一样

传统网店通常可以用一条记录和一个库存计数来表示一个商品。

Bookbot 的库存则不同。一个书名可能有多个版本，每个版本又可能有许多本独立的实体书。这些实体书在价格和品相上可能不同，并且每一本都有自己的照片和可售状态。

这意味着 Bookbot 的搜索结果不能只是说：“我们有这个书名。”它需要表示一个客户实际可以买到的报价。

假设某个书名当前展示的那本售价为 €6。这本售出后，同一书名的另一本仍有库存，但价格是 €9。

这个书名可以继续出现在结果中，但报价需要变化：新的价格、新的照片，也可能是不同的版本。如果客户筛选的是 €8 以下的书，这个书名就应该从该结果集中完全消失。

这正是 Manticore 的[实时表](https://manual.manticoresearch.com/Creating_a_table/Local_tables/Real-time_table)发挥作用的地方。

MySQL 仍然是 Bookbot 业务数据的事实来源。Kafka 传递由订单、预留、价格变更、遗失或报废图书、临时下架和目录修正产生的事件。应用在处理这些事件时，可以在 Manticore 中[替换](https://manual.manticoresearch.com/Data_creation_and_modification/Updating_documents/REPLACE)、更新或删除单条记录。

Bookbot 并不是把搜索当作定期重建的目录副本，而是可以让可搜索的报价始终与实体书的实际变化保持一致。

> “Manticore 让我们可以随着实体库存变化更新可搜索的报价。当一本书售出时，我们可以替换或删除那条记录，而不必重建整个索引。”

对客户来说，好处很简单：搜索中显示的价格、版本、品相、照片和可售状态，仍然描述的是他们实际可以买到的东西。

## 结果中一个书名，底下有许多本实体书

保持库存新鲜只解决了一半问题。

客户通常想浏览书名，而不是同一本书几十个几乎相同的副本。但 Bookbot 仍然需要在底层保留这些实体书之间的差异。

Manticore 的[分组](https://manual.manticoresearch.com/Searching/Grouping)让 Bookbot 能够连接这两个层级。

应用首先筛选出满足客户请求的实体书。然后按书名对剩余记录分组，并使用 `WITHIN GROUP ORDER BY` 来选择结果集中应该由哪一本实体书代表该书名。

排序很重要。

假设某个书名有来自不同出版社的实体书，价格分别为 €6、€9 和 €14。客户要求出版社 B、价格低于 €10，并且没有记录损坏。结果必须是一本文体书，同时满足**这三个条件**。

Bookbot 不能从一本书取出版社、从另一本书取价格、再从第三本书取品相或照片。那样描述的是一个并不存在的报价。

Manticore 让筛选条件先作用在底层的单本实体书记录上，然后再对匹配结果分组。随后可以按照请求的排序选择代表性实体书。按价格排序可能选中一本；按出版年份排序则可能选中另一个版本。

> “Manticore 给了我们所需的单本实体书级精度，同时仍能让我们按每个书名展示一个清晰结果。筛选条件会先应用到真实的实体书上，所以结果始终代表一个我们实际可以销售的报价。”

当这本代表性实体书售出后，另一本文适的实体书可以接替它的位置。

同样的原则也适用于分面。Bookbot 统计的是不同书名，而不是原始实体书数量，因此一个有数百本库存的书名不会仅仅因为库存更多就主导某个分类。

对于可以安全地在书名层级回答的浏览请求，Bookbot 维护了一个较小的预计算索引，包含约 **359 万条书名记录**。当查询需要精确到单本书的正确性时，比如全文搜索，或所有筛选条件都必须匹配同一件实物商品，应用就会改用完整的图书索引。

这让 Bookbot 同时获得了取舍的两面：在书名层级数据足够时走更快路径，在不够时获得单本实体书级精度。

## 把暂不可售的图书转化为未来需求

Bookbot 对 Manticore 最有趣的使用方式，始于无书可卖的时候。

二手库存是不可预测的。读者可能非常清楚自己想要哪本书或哪个版本，而 Bookbot 当时没有合适库存。

Bookbot 不会让这位读者每隔几天回来重复搜索，而是允许他们创建一个 Watchdog。它可以跟踪书名、作者、类型、版本、价格，或更详细的条件组合，并在匹配图书出现时通知读者。

这反转了搜索的正常方向。

普通搜索是：

**一个查询 → 匹配的图书**

Watchdog 则是：

**一本新上架图书 → 匹配的已保存查询**

Manticore 的[渗滤表](https://manual.manticoresearch.com/Creating_a_table/Local_tables/Percolate_table)正是为这种“反向搜索”模式而设计的。Bookbot 不是存储文档并对其运行查询，而是存储查询，并提交一本书来找出它匹配哪些已保存搜索。

在 Bookbot 的生产快照时间点，Manticore 保存了约 **350 万条 Watchdog 规则**。

当某个定价事件使一本书变为可售时，应用会发送它的可搜索表示用于渗滤匹配。Manticore 返回匹配的已保存搜索。随后 Bookbot 应用自己的业务逻辑：Watchdog 是否仍然有效、客户是否符合通知条件，以及匹配结果应立即发送还是纳入摘要。

Manticore 负责匹配；Bookbot 控制围绕它的客户体验。

这让搜索变得比浏览当前库存更有价值。它成为一种在供给缺失时保留需求，并在未来有书时重新连接需求的方式。

据 Bookbot 称，Watchdog 约贡献 **10% 的收入**。

> “Manticore 让我们可以将每一本新上架的书与大约 350 万个已保存的搜索进行匹配。Watchdogs 现在约占我们收入的 10%，所以它已经不只是一个搜索功能，而是成为了我们重要的业务渠道。”

对 Bookbot 来说，这是能够双向搜索带来的直接业务结果：读者可以搜索目录，新上架图书也可以搜索已经想要它们的读者。

同样的模式可以应用到远不止图书的场景：补货提醒、分类信息、招聘、房产、票务、价格提醒，或任何需要把新增供给与此前表达过的意向进行匹配的产品。

## 即使读者输入不完美也能帮上忙的搜索

图书搜索还存在相关性问题：人们并不总能准确记住书名、作者名、译名或系列名称在目录中的写法。

Bookbot 使用一个专用的 Manticore 词典进行拼写纠错，其中约有 **452 万条版本记录**。它包括书名、作者、系列、别名、人工整理的错拼、本地化字段和德语转写变体，同时将 ISBN 等标识符排除在纠错候选之外。

这个词典会提出可能的版本，但它不决定 Bookbot 能卖什么。主库存索引仍然决定当前有哪些实体书可售，并且满足用户的筛选条件。

这种分离很有用，因为两个数据集承担的任务不同，对新鲜度的要求也不同。拼写纠错词典可以按自己的节奏刷新，而主库存索引跟随实时可售状态。

对读者来说，结果是更宽容的搜索，同时不会牺牲实际报价的正确性。

## 贯穿一本书生命周期的同一个搜索引擎

客户搜索和 Watchdog 是最清晰的用户可见示例，但 Bookbot 还在业务的多个其他环节使用 Manticore。

在一本实体书出现在商店之前，Bookbot 需要识别并编目它。一个独立的书目元数据池包含约 **2.0344 亿条参考记录**，可按 ISBN、书名和作者搜索。

书名、作者和简介等文本保持全文可搜索，而 ISBN、出版社、出版年份、语言和分类 ID 等结构化元数据则使用 Manticore 的[列式存储](https://manual.manticoresearch.com/Creating_a_table/Data_types#Row-wise-and-columnar-attribute-storages)。

内部搜索也运行在 Manticore 上。Bookbot 的后台索引覆盖图书、版本、作者、客户、订单和会计记录；定价和查找流程使用的内部图书索引包含约 **2470 万条记录**。

搜索也延伸到了配送环节。另一个索引包含约 **197,000 个取货点**，结合文本查找与地理筛选，并使用 Manticore 的[地理搜索能力](https://manual.manticoresearch.com/Searching/Geo_search)按距离排序。

这种广度很重要，因为 Bookbot 自主开发技术。团队可以为多个非常不同的工作负载复用同一个搜索引擎，而不是把客户搜索、反向匹配、大规模元数据查找、内部搜索和地理搜索当作完全分离的技术问题。

Manticore 成为一本书在业务中流转路径的一部分：识别它、让它可搜索、选择正确的实体书、将它与等待中的读者匹配、帮助员工处理它，并在售出后找到取货点。

> “我们将 Manticore 用于店面搜索、Watchdog、一个包含 2.03 亿条记录的书目数据集、内部搜索和取货点查找。在这些工作负载中复用同一种搜索技术，让团队的架构简单得多。”

## 紧凑资源占用下的生产规模

Bookbot 在 Kubernetes 上运行四个独立的 Manticore 工作负载：客户搜索、后台搜索、Watchdog 和书目元数据池。它们运行在不同服务器上，因此每个工作负载都可以独立配置规模。

2026 年 9 月 5 日的一份生产快照如下：

| 指标 | 生产规模 |
|---|---:|
| 主图书索引 | 约 530 万个文档 |
| 有库存实体书 | 约 129 万 |
| 预计算书名层级索引 | 约 359 万条记录 |
| 已存储 Watchdog 查询 | 约 350 万 |
| 书目元数据池 | 约 2.0344 亿条记录 |
| 已埋点列表操作 | 约 2020 万/天 |
| 等效平均操作速率 | 约 234/秒 |
| 成功选品查询耗时 | 平均约 7.7 ms |
| 50 ms 内完成的成功选品查询 | 98.6% |
| 四个工作负载的平均 CPU | 约 6.6 核 |
| 四个工作负载的平均内存 | 约 40 GiB |

查询量数字统计的是已埋点的 Manticore 操作次数，而不是人工搜索次数。一次页面请求可能会为结果、选品和分面发起多次操作。

同样，7.7 ms 的测量值覆盖的是应用对 Manticore 的成功选品查询调用；它不是完整页面加载时间，也不是端到端库存更新延迟。

在测量期间，Bookbot 在四个工作负载上没有观察到持续的 CPU 或内存饱和。面向客户的搜索使用了大部分 CPU，而包含 2.03 亿条记录的元数据工作负载平均使用不到 0.1 个 CPU 核心和约 18.6 GiB 内存。

重点不在于某个单一的基准测试数字，而在于 Bookbot 正在用同一种搜索技术支撑实时电商搜索、数百万个已保存查询的反向匹配、一个包含 2.03 亿条记录的参考数据集、内部运营和地理搜索，同时还让基础设施保持相对紧凑。

## 跟随一本实体书生命周期的搜索

Bookbot 对 Manticore 的使用方式很特别，因为搜索与每件实体商品的生命周期紧密相连。

一本书到达。元数据搜索帮助识别它。实时更新让这本书变得可搜索。筛选确保它的价格、版本、品相和照片始终绑定在一起。分组让该书名在结果中占据一个位置，同时选出一本真实可购买的实体书。

如果没有库存，读者可以留下一个 Watchdog。当另一本进入系统时，Manticore 可以将这本书与数百万条已保存搜索匹配，帮助 Bookbot 把新增供给重新连接到既有需求。

当这本书售出时，搜索会随库存一起变化，另一本文适的书可以接替它的位置。

对读者来说，这意味着搜索反映真实可售库存，并在无货时记住他们想要什么。

对 Bookbot 来说，这意味着搜索在客户旅程的多个环节发挥作用，并且通过 Watchdogs 贡献了一个该公司称约占其 **10% 收入**的渠道。

这就是 Manticore 在这里带来的价值：不只是快速查找文本，而是为 Bookbot 提供一个搜索平台，覆盖快速变化的库存、结构化筛选、分组结果、反向搜索、大型参考数据集和地理搜索，并把这些能力转化为更好的购买体验和可衡量的业务价值。
