Photon:从头构建检索和排序引擎

专用数据格式、批处理读取以及将索引构建与查询服务分离如何改善 AI 原生搜索。

作者Perplexity Engineering

简介

Perplexity 的 AI 原生搜索栈检索并对候选页面进行排序,呈现出最相关的内容供模型为其答案提供依据。最初,此阶段在开源引擎上运行,我们对其进行了调整以适应我们的系统。然而,随着我们的索引和工作负载的增长,我们在成本、长尾延迟和节点恢复时间方面遇到了无法在现有第三方平台内解决的硬性限制。

为了克服这些挑战,我们构建了 Photon,这是一个全新的内部检索和排序引擎。这延续了我们从头构建 AI 优先搜索基础设施的方法,就像我们对 CobbleDB(用于提供页面记录服务的分布式键值存储)以及我们的 Search API 所做的那样。

Photon 采用紧凑的数据格式,仅读取和解码每个查询所需的内容,并配合具备批处理感知能力的缓存和异步读取功能,以减少磁盘访问并重叠等待时间。索引构建与查询服务独立运行,每次更新一个服务组,并在将实时查询路由到该组之前对其缓存进行预热。

Photon 现在为我们的搜索管道提供动力,同时也是 Search API 的全新 fast 预设的基础,其单次搜索调用延迟在 p50 下为 160 毫秒,在 p95 下为 230 毫秒。在六个公共基准测试中,fast 预设在保持与默认预设相当的总体任务质量的同时,将每个任务的估计模型加搜索成本降低了 68%。我们于今天发布它,以便开发者能够获得这些改进。

自我报告的单次搜索调用延迟。PPLX Fast 报告的 p50 延迟为 160 毫秒,p95 延迟为 230 毫秒。Exa Instant 报告其高尾值为 p90 而不是 p95;Parallel 的高尾值未公布。报告的百分位数和测量设置不是受控的同类比较。
相同 3,554 个选定任务的总体任务质量与估计的总模型加搜索成本。分数按任务数量加权,成本涵盖整个任务集。PPLX Fast 得分为 64.3%,成本为 59.73 美元;而默认的 PPLX 预设得分为 64.0%,成本为 187.60 美元,对于相同数量的任务,估计成本降低了大约 68%。

检索和排序引擎的要求

对于每个查询,检索和排序引擎都会搜索一个大型的分布式索引,以便在搜索技术栈的延迟预算内选择并排序一小组相关页面。随着新页面的到达和现有页面的更改,系统还必须保持索引最新,而不能让该工作减慢实时查询的速度。

我们评估了几种开源选项,并选择了一个满足我们最初需求的成熟的通用检索和排序引擎。然后,我们将它调整到我们的架构和工作负载,在生产规模下保持稳定的延迟和吞吐量。

我们以前的系统不足的地方

我们维护了自己的分支来调整引擎以适应不断增长的索引,但解决关键限制越来越需要重新设计平台的大部分内容。如果没有根本性的架构变更,几个约束条件将很难解决:

1. 扩展成本。我们存储的文档数量以及我们需要检索和排序的每文档数据量随着时间推移一直在增长。这种扩展给我们的硬件带来了压力,尤其是可用的 RAM 数量。为了控制成本,我们需要更紧凑的数据表示,以及对哪些保留在内存中、哪些按需从磁盘读取的更细粒度的控制。

2. 较差的长尾延迟。在生产流量上,旧系统的 p99 响应时间约为 800 毫秒。数据集超出了可用的 RAM,因此我们无法使用 mlock 将其完全保留在内存中。访问非常驻数据可能会触发主要的页面错误,在操作系统从磁盘读取时使查询执行停滞。

该引擎还定期将其主磁盘索引与从内存中刷新的较小索引进行合并。在此磁盘索引融合过程中,页面错误数量进一步上升,p99 延迟在 10-15 分钟内达到约 1.2 秒。随着缓存预热,页面错误频率和延迟均有所下降,这表明冷读取是导致峰值的主要原因。预加载数据和预热索引减少了峰值,但没有充分解决高长尾延迟问题。

3. 漫长的恢复和同步。在先前的系统中,部署额外的集群并同步其数据可能需要一周多的时间。在添加节点组以保存额外的数据副本或从节点故障中恢复时,也需要进行同步。在恢复期间,较大比例的搜索响应是部分的,这降低了最终答案的质量得分。

最终,我们得出结论:从头构建会比继续调整和维护我们的分支更简单、成本更低。凭借运营先前系统的经验,一小支工程团队与一群持久运行的编码智能体协同合作,构建了 Photon。

Photon 在搜索技术栈中的位置

Photon 专为两个主要工作流而设计:提供搜索请求服务和摄取来自处理管道的更新。

来自高级别搜索服务的请求通过负载均衡器到达 Photon 代理。代理选择分片组并将请求转发给每个分片,监控超时并在需要时发出额外的请求。

每个分片执行检索、初始排序和第二阶段排序,然后将其最佳候选返回给代理。代理将这些候选合并为一个排好序的列表,并从相关的分片获取所选页面的关键文档字段。然后,它将这些结果返回给高级别搜索服务,后者收集智能体生成有根据的答案所需的页面内容。

索引更新遵循单独的路径。我们的发布层 Pillar 准备数据并将其存储在 YTsaurus 表中。索引器将这些表转换为分片可以加载以进行搜索的索引结构。控制器协调数据准备、索引构建以及将新版本索引部署到服务组。在处理实时流量之前,每个 shard 都会重放真实的搜索查询以预热其缓存。

查询从高级别搜索服务经由负载均衡器和代理流向选定的分片组。每个分片检索并对候选进行排序;代理合并结果,为所选页面获取文档字段,并将它们返回给高级别搜索服务。编号步骤显示顺序。实线承载请求,虚线将结果带回。左侧的阶段标签将候选选择与摘要读取区分开来。

为我们的工作流设计 Photon

三个设计选择使 Photon 非常适合我们的工作负载:紧凑的数据表示、缓存和异步磁盘读取,以及将索引更新与查询服务分离。

用于检索和排序的数据表示

检索和排序访问文档数据的方式不同。词汇检索从查询问题开始并查找包含它们的文档。然后,排序需要关于每个所选文档的详细信息,包括问题频率和位置。

Photon 使用两个互补的表示来支持这些访问模式。倒排索引为每个问题存储匹配文档的列表(称为发布列表),以支持候选选择。单独的每文档记录提供排序所需的数据。我们还优化了发布列表的存储和遍历方式,以在固定的工作预算内改善选择。

专用的数据表示使我们能够更高效地存储关键数据。更紧凑的倒排索引减少了每个查询读取或移动的数据量,通过消除不必要的 I/O 操作来改善延迟。这些格式还使得无需完全解码即可检查文档是否出现在列表中成为可能,这是我们的遍历逻辑依赖的属性,用于在固定预算下控制查询所做的工作。优化每文档记录使我们能够为排序每个文档存储更多数据,同时还降低了与我们以前的系统相关的扩展成本。

倒排索引

对于每个问题,倒排索引存储一个发布列表。这些列表的范围可以从少数文档到数百万文档,因此没有单一格式适用于每个列表。Photon 根据包含该问题的文档数量来选择格式。

短列表使用内联表示,适合单个页面,且不需要块表。不适合内联格式的较长列表被划分为涵盖文档标识符固定范围的块。每个块的表示取决于其密度:稀疏块存储文档 ID 偏移量的排序数组,而密集块则使用位图。稀疏块使用跃代搜索(galloping search),该搜索以越来越大的步长前进,以跳过列表的区域。密集块允许通过查找与文档对应的位来检查成员资格。

问题的查询频率决定了其发布列表在磁盘上的放置位置。经常查询的列表彼此靠近存储,将热点数据集中在较小的存储区域中。在每个列表中,文档按单独的有用性信号排序,以便更早地遇到更有前途的候选。

Photon 根据长度和块密度选择发布列表表示。短列表在页面内以内联方式适合。较长的列表被划分为固定的文档 ID 范围,稀疏块使用排序偏移数组,密集块使用位图。

在工作预算内遍历列表

候选选择需要遍历发布列表以查找潜在相关的文档。详尽地扫描每个列表成本高昂,因此 Photon 使用类似 WAND 的算法,该算法仅遍历某些列表,同时仍然计算其他列表中的匹配项。

该算法将列表分为两组。驱动列表推进遍历并提出候选文档。探测列表有助于这些候选的得分,但不会自身生成候选。

候选评分结合了加权查询问题覆盖率和频率贡献,并进行了文档长度归一化以及对重复问题带来的收益的上限限制。Photon 首先检查数组和位图中问题的存在性,以限定每个候选的最大可能得分。它仅为仍可能满足选择阈值的候选读取精确问题频率。

然后选择过程分三步进行:

  • 遍历:从严格的问题覆盖率要求开始。如果找到的候选太少,则以宽松的要求重试。
  • 保留:使用结合了覆盖率和频率的得分将候选保存在有界集中。随着收集到的前几名候选的改善,准入阈值会上升。
  • 最终选择:根据频率得分从保留集中选择本轮的最终结果。每个字段运行自己的轮次,并使用字段权重合并结果。

该算法生成一组相关的文档候选,以便在进一步排序中使用。

具代表性的有界候选遍历。驱动列表提出文档,而上限允许在不读取探测列表或精确频率的情况下修剪当前准入阈值以下的候选。随着保留集的改善,阈值会上升。分数是为便于说明而虚构的,保留的候选不保证是绝对前 k 个。

Docblob

选择候选页面后,排序阶段需要问题频率、字段覆盖范围以及出现的位置和邻近度。通过倒排索引获取此数据将需要跨许多问题-文档对进行分散访问。在 Photon 中,我们存储了一个名为 docblob 的紧凑的每文档记录,其中包含问题频率、问题出现在哪些字段中以及它出现在文档的什么位置。因此,对候选进行排序每个文档只需一次查找。

该格式旨在避免不必要的解码。在 docblob 中,字段按文本分析器分组。每个组存储使用 Elias-Fano 编码的排序问题列表,因此可以直接查找查询问题,而无需先生长整个列表。字段掩码标记问题出现的位置,频率计数跟踪出现的频率,实际位置存储在按问题-字段对编码为位置差的单独块中。仅解码实际匹配的问题的块,而记录中的所有其他内容都保持压缩格式。

这种选择性解码使 docblob 支持每个文档存储更多数据,而不会按比例增加成本。增加记录不会迫使每次查找进行更多工作,因为排序只触及与匹配问题相关的部分,而不是整个记录。

docblob 将排序数据分组到紧凑的每文档记录中。在 Elias–Fano 编码的问题列表中查找查询问题;字段掩码、频率和位置块为匹配的问题提供所需的数据,而不相关的问题保持压缩状态。问题 ID 和位置具有说明性。

批处理读取与内存处理

即使有更高效的数据表示,RAM 仍然会在其他资源耗尽之前用完,因此我们经常需要访问存储在磁盘上的数据。Photon 使用异步磁盘读取和针对批处理读取优化的缓存来加速这些访问,从而缩短每个请求的等待时间。这有助于显著改善与先前系统相比的长尾延迟。

Photon 在发出磁盘读取之前,会检查其缓存中的一批已知记录偏移量。缓存命中项被复制到准备好的缓冲区中;未命中项通过 io_uring 提交,以便磁盘等待可以重叠,返回的数据填充缓存。记录和时间轴是示意性的,而不是测得的延迟。

重叠的磁盘等待

排序需要获取位于文件不同部分的数百个文档的特征。我们以前的系统使用 mmap 将文件映射到虚拟地址空间中,以便可以通过内存指针读取它。但是,访问内存中缺少的页面仍然需要磁盘读取。在继续计算之前,线程必须等待数据,并且当进程需要顺序读取冷记录时,此等待时间会累积。

Photon 利用了记录偏移量已知的特点,因此我们可以一次性提交多个读取操作。这由 reactor 进行管理:专用的工作线程通过 io_uring 提交批处理,并将读取到的数据传递给调用代码。每个线程维护自己的 io_uring;批处理会被发送到负载最轻的 reactor。预注册的文件描述符和缓冲区池降低了重复读取的开销。这种异步执行方式使得多个操作的等待时间能够重叠,从而减少了长尾延迟。

减少驱动器访问次数

在提交读取之前,Photon 会检查内存缓存中的整个批次。只有对缺失记录的请求才会到达磁盘;读取的数据随后会填充缓存。

缓存专为批处理读取而设计。具体而言:

  • 命中时没有单独的分配。相反,数据被复制到调用代码提前准备好的缓冲区中。
  • 读取器不进行锁获取。相反,它检查数据版本,如果与写入冲突则重试。
  • 它使用 CLOCK 驱逐,而不是要求在每次读取时更新共享的 LRU 列表。
  • 它使用了 CPU 缓存预取技术,即提前请求即将到来的记录的元数据和数据。

这些优化降低了延迟,同时不损害我们关注的工作负载的性能。

独立于服务的索引构建与更新

在 Photon 中,索引构建和查询服务在不同的节点上运行。索引器将数据转换为就绪的存储结构,而服务节点读取它们并执行检索和排序。因此,索引构建不会与搜索查询争夺 CPU,并且这些任务的资源可以独立扩展。

这种分离解决了先前实现中存在的恢复和同步瓶颈。由于索引构建在提供查询的同一个集群内部运行,因此恢复故障节点或添加新的数据副本可能意味着就地重新索引文档,这个过程需要数天时间,并增加了恢复期间的部分响应。Photon 消除了这种耦合;索引器作为分布式作业运行,独立于服务节点生成版本化的、就绪可服务的索引结构,因此构建下一版本的索引不再与实时流量竞争。

索引构建与实时查询服务独立运行。Pillar 导出源数据,YTsaurus 按分片准备版本化表,并行索引器将就绪可服务的索引结构写入对象存储。服务节点加载这些结构进行检索和排序;索引器并发数和分片数是独立配置的。

准备与分布式构建

构建从为活动索引选择文档并生成源导出开始。控制器记录源版本、输入表和预期的分片数。YTsaurus 中的 MapReduce 作业将文本、元数据、片段、特征和预计算嵌入准备成按分片分区的版本化表。每个索引器流式传输准备好的表,为其分片构建结构,并合并文本段。就绪的索引及其组件的说明一起上传到对象存储。

分片数和并发运行的索引器的数量是独立配置的,因此构建可以分几波运行,或者可以分配更多的机器并行工作。分布式准备和并行索引器允许在个位数的小时内构建完整的网络索引,与以前的系统相比有了显着的改进。

部署新版本

在部署之前,控制器会检查新索引是否就绪。如果数据不完整,部署将被自动阻止。

当存在多个服务组时,控制器会逐个更新它们。它首先从代理路由中移除一个组,并在新的索引版本上启动它。在服务请求之前,分片会经历一个基于搜索日志的单独预热阶段,这会预填充初始状态并使向真实流量的过渡更加平稳。预热后,控制器检查索引状态和就绪状态,然后将该组返回给路由。在此期间,其他组继续提供请求服务。由于操作状态是持久化的,因此重启不会迫使构建重新开始;控制器可以重新附加到已经在进行的准备工作,复用就绪的结果,或者从保存的步骤继续中断的轮换。

数据更新时间在不同的搜索垂直领域有所不同。对于具有高新鲜度要求的垂直领域,文档交付只需个位数的分钟。可以在大约半小时内部署数据的额外副本;加载就绪的索引并将其附加到分片,而无需重新索引源文档。

控制器检查新索引是否完整,然后逐个轮换服务组。一个组离开代理路由,加载新版本,使用搜索日志查询预热其缓存,并在返回流量之前通过就绪性检查。其他组继续提供服务,并且持久化的控制器状态允许恢复中断的轮换。

我们搜索技术栈内的迁移与验证

我们的首要目标是用一个在减少响应时间的同时保留功能和搜索质量的引擎替换旧系统。我们分阶段验证了 Photon,从可重现的负载测试和离线质量评估开始,然后过渡到实时流量。Photon 现在为我们生产搜索栈中的检索和排序提供动力,其响应时间比旧系统大幅降低。

负载测试与评估

为了进行性能测试,我们构建了一个负载测试工具,该工具以可配置的到达率重放来自我们搜索日志的查询。它跨运行比较结果并应用统计测试,以评估观察到的性能差异是否一致。另外,我们在内部离线评估系统的相同数据集上评估了 Photon 和旧系统,以检查质量退化。

在这些检查之后,我们使用影子流量同时向两个引擎发送实时查询。这使我们能够在更改为哪个引擎为用户服务之前,比较它们在生产工作负载上的性能和结果。然后,我们为一小部分用户启用了 Photon,并逐步扩大推出范围,在整个过程中监控质量和性能。第一个版本通过了没有质量退化的验收测试,我们继续扩大推出范围,直至 Photon 服务于所有生产流量。

生产环境中的实测改进

在我们的生产工作流中,p99 响应时间从旧系统中的大约 800 毫秒下降到 Photon 中的大约 65 毫秒。此测量涵盖了 Photon 内部的所有阶段,但排除了高级别搜索栈中的后续阶段。我们也不再观察到先前与切换索引版本相关的延迟峰值。

Photon 使用的等效服务机器比旧系统的内节点大约少 20%,同时每个文档存储的数据大约多 2.5 倍。迁移后,我们利用这些额外的数据来改善排序质量,使其超越了旧系统。

Photon 还减少了必须保留在内存中的数据量。使用 mlock 将相同的 Photon 数据集固定在 RAM 中,所需的驻留内存估计将是 Photon 当前使用量的 4.6 倍。

快速 Search API 预设的评估

我们还将 Photon 用作 Search API 的新 fast 预设的基础,该预设专为对延迟敏感的工作流设计。根据我们的测量,fast 预设的单次搜索调用延迟为 p50 下 160 毫秒,p95 下 230 毫秒,低于或可媲美其他 Search API 提供商自我报告的数字,尽管报告的长尾百分位数有所不同。

我们在六个智能体基准测试中评估了 fast 预设:WideSearch、BrowseComp、DSQA、FRAMES、SEAL-0 和 SEAL-Hard。在这些评估中,在测试的配置中,它具有最低的估计总模型加搜索成本和最高的总体任务分数。

fast 预设将 Photon 与针对智能体工作流优化的排序配对,以大幅降低的成本提供与默认预设相当的性能。但是,它确实在更广泛的搜索质量上做出了权衡。与默认预设相比,fast 预设在排序时使用的计算较少。在涵盖长尾查询、广泛覆盖和结果多样性的内部基准测试中,其相关性得分低 0.24 点,答案可用性低约 3 个百分点。我们认为这是针对智能体工作流的一个合理的权衡,因为模型自身的推理和知识可以帮助弥补这一差距。

默认 PPLX 预设与 PPLX Fast 之间的内部检索质量比较。相关性 (DCG) 从 2.45 下降到 2.21,下降了 0.24 点;答案可用性从 0.596 下降到 0.567,下降了 2.9 个百分点。这两个面板使用不同的非零轴基线,因此条形高度强调差距而不是成比例的差异。

我们推荐将 fast 预设用于日常的智能体任务。对于时间要求不那么严格的更具挑战性或模棱两可的问题,默认预设提供了更强健的质量,证明了其更高的延迟和成本是合理的。

结论

我们正在稳步将我们的搜索栈迁移到基于 Rust 的内部基础设施上。用我们自定义的检索和排序引擎 Photon 替换开源引擎是最新的一步。Photon 使我们能够完全控制数据表示、读取的组织方式以及新索引的部署方式,从而使我们能够针对我们的工作负载优化每一层。

Photon 在 Perplexity 的产品中实现了更快的搜索,并为我们在 Search API 中的新 fast 预设提供了基础。迁移后,p99 检索和排序延迟从大约 800 毫秒下降到 65 毫秒。我们紧凑的数据格式还允许我们每个文档存储 2.5 倍的数据,从而使检索和排序能够访问更丰富的文档信息。

fast 预设在 p50 下提供 160 毫秒、在 p95 下提供 230 毫秒的单次搜索调用延迟。在评估的六个智能体基准测试中,相比默认预设,它在保持可比的总体任务质量的同时,将每个任务的估计模型加搜索成本降低了 68%。这些结果证明了围绕我们的搜索工作负载设计的基础设施的实际优势,既适用于 Perplexity 的产品,也适用于使用我们的 API 进行构建的开发者。

我们将继续改善 Photon 的性能和存储效率。我们的目标是在同一硬件上支持更大的索引和更丰富的文档数据,同时随着我们工作负载的增长保持搜索速度。

引用

BibTeX
@online{perplexity2026Photon,
  author = {{Perplexity} and Maksim Morozov and Bogdan Savko and Ruslan Kovalev and Aleksei Bykov and Artur Zayats and Denis Bykov and Nikita Kireev and Jarrod Bryan and Claire Beamer and Jeremy Yang},
  title = {{Photon: Building a retrieval and ranking engine from scratch}},
  date = {2026-09-24},
  url = {https://www.perplexity.ai/hub/blog/photon}
}