Photon:從頭開始建置檢索與排序引擎

專門的資料格式、批次讀取以及將索引建置與查詢服務分離如何改善 AI 原生搜尋。

作者Perplexity Engineering

導言

Perplexity 的 AI 原生搜尋堆疊會檢索並排序候選頁面,為模型呈現最相關的內容以作為其答案的基礎。最初,這個階段是在我們調整以適應我們系統的開源引擎上執行的。然而,隨著我們的索引和工作負載增長,我們遇到了成本、尾端延遲和節點復原時間的嚴格限制,這些問題無法在現有的第三方平臺內解決。

為了克服這些挑戰,我們建置了 Photon,這是一個全新的內部檢索與排序引擎。這延續了我們從頭開始建置 AI 優先搜尋基礎設施的方法,就像我們對 CobbleDB(用於提供頁面記錄服務的分散式鍵值儲存)和我們的 Search API 所做的一樣。

Photon 使用精簡的資料格式來僅讀取和解碼每個查詢所需的內容,並具備批次感知快取和非同步讀取功能,以減少磁碟存取並重疊等待時間。索引建置與查詢服務分開執行,每次更新一個服務群組,並在將即時查詢路由至該群組之前預熱其快取。

Photon 現已驅動我們的搜尋管線,同時也是 Search API 全新快速預設設定的基礎,在 p50 時的單一搜尋呼叫延遲為 160 毫秒,在 p95 時則為 230 毫秒。在六個公開基準測試中,快速預設設定在維持與預設設定相當的總體任務品質的同時,將每個任務預估的模型加搜尋成本降低了 68%。我們於今天釋出它,以便開發人員能夠存取這些改進。

自行回報的單一搜尋呼叫延遲。PPLX Fast 回報 p50 延遲為 160 毫秒,p95 延遲為 230 毫秒。Exa Instant 其高尾端數值回報的是 p90 而非 p95;Parallel 的高尾端數值未發布。回報的百分位數和測量設定並非受控的同類比較。
相同 3,554 個選定任務的總體任務品質與預估總模型加搜尋成本。分數按任務數量加權,成本涵蓋整個任務集。PPLX Fast 在 59.73 美元下得分 64.3%,而預設 PPLX 預設設定在 187.60 美元下得分 64.0%,對於相同數量的任務而言,預估成本大約降低 68%。

檢索與排序引擎的要求

針對每個查詢,檢索與排序引擎會在搜尋堆疊的延遲預算內搜尋大型分散式索引,以選取並排序一小組相關頁面。隨著新頁面到達和現有頁面變更,系統還必須保持索引最新,同時不能讓該工作減慢即時查詢的速度。

我們評估了幾個開源選項,並選擇了一個成熟、通用的檢索與排序引擎,該引擎滿足了我們最初的需求。然後,我們將其調整為適應我們的架構和工作流程,在生產規模下維持穩定的延遲和吞吐量。

我們先前系統的不足之處

我們維持自己的分支(fork)以調整引擎以適應不斷增長的索引,但解決關鍵限制日益需要重新設計平臺的大部分內容。若無根本性的架構變更,有幾個限制證明很難解決:

1. 擴充成本。我們儲存的文件數量以及我們需要檢索和排序的每文件資料量隨著時間不斷增長。這種擴充對我們的硬體(尤其是可用的 RAM 數量)造成了壓力。為了控制成本,我們需要更精簡的資料表示形式,以及對哪些內容保留在記憶體中、哪些內容按需從磁碟讀取的更精細控制。

2. 糟糕的尾端延遲。在生產流量上,舊系統的 p99 回應時間約為 800 毫秒。資料集超過了可用的 RAM,因此我們無法使用 mlock 將其完全保留在記憶體中。存取非常駐資料可能會觸發重大分頁錯誤(page fault),在作業系統從磁碟讀取的同時暫停查詢執行。

引擎還定期將其主磁碟索引與從記憶體快取的較小索引進行合併。在此磁碟索引融合過程中,分頁錯誤的數量上升得更高,p99 延遲在 10–15 分鐘內達到大約 1.2 秒。隨著快取預熱,分頁錯誤的頻率和延遲都下降了,這表明冷讀取是尖峰的主要原因。預先載入資料和預熱索引減少瞭尖峰,但未能充分解決高尾端延遲問題。

3. 漫長的復原與同步。在先前的系統中,部署額外叢集並同步其資料可能需要一個多星期。當新增節點群組以容納額外資料複本或從節點故障中復原時,也需要進行同步。在復原期間,較大比例的搜尋回應是部分的,這降低了最終答案的品質分數。

最終,我們得出結論:從頭開始建置會比繼續調整和維護我們的分支(fork)更簡單、成本更低。憑藉著營運先前系統的經驗,一小群工程團隊與一群持續運作的編碼 AI 代理程式合作建置了 Photon。

Photon 在搜尋堆疊中的定位

Photon 專為兩個主要工作流程而設計:提供搜尋請求服務以及從處理管線擷取更新。

來自高階搜尋服務的請求透過負載平衡器到達 Photon broker。broker 選擇分片群組並將請求轉發到每個分組,監控逾時並在需要時發出額外請求。

每個分片執行檢索、初始排序和第二階段排序,然後將其最佳候選傳回給 broker。broker 將這些候選組合成一個單一的排序列表,並從相關分片中提取選定頁面的關鍵文件欄位。然後,它將這些結果傳回給高階搜尋服務,該服務收集 AI 代理程式產生有依據的答案所需的頁面內容。

索引更新遵循獨立的路徑。Pillar 是我們的發布層,負責準備資料並將其儲存在 YTsaurus 資料表中。索引器將這些資料表轉換為分片可載入以供搜尋的索引結構。控制器協調資料準備、索引建置以及將新索引版本部署到服務群組。在處理即時流量之前,每個分片都會重播真實的搜尋查詢以預熱其快取。

查詢從高階搜尋服務透過負載平衡器和 broker 移動到選定的分片群組。每個分片檢索並對候選進行排序;broker 合併結果,為選定頁面提取文件欄位,並將其傳回給高階搜尋服務。編號步驟顯示順序。實線帶有請求,虛線將結果傳回。左側階段標籤將候選選擇與摘要讀取區分開來。

為我們的工作流程設計 Photon

三個設計選擇使 Photon 非常適合我們的工作流程:精簡的資料表現形式、快取與非同步磁碟讀取,以及將索引更新與查詢服務分開。

檢索與排序的資料表示形式

檢索與排序存取文件資料的方式不同。詞彙檢索從查詢術語開始,並尋找包含這些術語的文件。排序接著需要關於每個選定文件的詳細資訊,包括術語頻率與位置。

Photon 使用兩個互補的表示形式來支援這些存取模式。倒排索引為每個術語儲存一個相配的文件列表(稱為公佈列表),以支援候選選擇。獨立的每文件記錄提供排序所需的資料。我們也最佳化了公佈列表的儲存與走訪方式,以改善固定工作預算下的選擇。

專門的資料表示形式使我們能夠更有效率地儲存關鍵資料。更精簡的倒排索引減少瞭每個查詢讀取或移動的資料量,透過消除不必要的 I/O 作業來改善延遲。這些格式也使得無需完全解碼即可檢查文件是否出現在列表中成為可能,這是我們的走訪邏輯依賴的屬性,用於控制查詢在固定預算下執行的工作量。最佳化每文件記錄讓我們能夠針對排序在每個文件中儲存更多資料,同時也降低瞭與我們先前系統相關的擴充成本。

倒排索引

對於每個術語,倒排索引都會儲存一個公佈列表。這些列表的範圍可以從少數幾文件到數百萬文件,因此沒有單一格式適用於每個列表。Photon 根據包含該術語的文件數量來選擇格式。

短列表使用適合放入單一頁面且不需要區塊表的內嵌表示形式。不適合內嵌格式的較長列表會被劃分為涵蓋文件識別碼固定範圍的區塊。每個區塊的表示形式取決於其密度:稀疏區塊儲存文件 ID 偏移量的排序陣列,而密集區塊則使用點陣圖(bitmap)。稀疏區塊使用躍進式搜尋(galloping search),該搜尋以日益增加的步幅前進以跳過列表區域。密集區塊透過查詢對應至檔案的位元,允許成員資格檢查。

術語的查詢頻率決定瞭其公佈列表在磁碟上的放置位置。經常查詢的列表彼此靠近儲存,將熱資料集中在較小的儲存區域中。在每個列表中,文件按獨立的有用性訊號排序,以便更早遇到更有希望的候選。

Photon 根據長度和區塊密度選擇公佈列表表示形式。短列表適合內嵌在頁面內。較長的列表被劃分為固定的文件 ID 範圍,稀疏區塊使用排序的偏移量陣列,密集區塊使用點陣圖。

在工作預算內走訪列表

候選選擇需要走訪公佈列表以尋找可能相關的文件。詳盡地掃描每個列表成本很高,因此 Photon 使用類似 WAND 的演算法,該演算法僅走訪某些列表,同時仍能計算其他列表中的相配項。

該演算法將列表分成兩組。驅動列表推進走訪並提出候選文件。探查列表有助於這些候選的分數,但不會自行產生候選。

候選評分結合了加權的查詢術語涵蓋範圍與頻率貢獻,並具有文件長度正規化以及對重複術語收益的上限。Photon 首先檢查陣列和點陣圖中的術語存在情況,以限制每個候選的最大可能分數。它僅針對仍可能達到選擇閾值的候選讀取確切的術語頻率。

選擇隨後以三個步驟進行:

  • 走訪:從嚴格的查詢涵蓋要求開始。如果找到的候選太少,請放寬要求重試。
  • 保留:候選被保存在受限制的集合中,使用的是結合了涵蓋範圍與頻率的分數。隨著收集到的頂級候選改善,接受閾值會上升。
  • 最終選擇:此階段的最終結果是根據頻率分數從保留集中選出。每個欄位執行自己的階段,並使用欄位權重合併結果。

該演算法產生一組相關文件的候選集,用於進一步排序。

具說明性的受限制候選走訪。驅動列表提出文件,而上限允許低於目前接受閾值的候選被剪枝,而無需讀取探查列表或確切頻率。隨著保留集的改善,閾值會上升。分數是為了說明而虛構的,且保留的候選並非保證是確切的前 k 個(top-k)。

Docblob

選定候選頁面後,排序階段需要術語頻率、欄位涵蓋範圍以及出現的位置和鄰近性。透過倒排索引取得此資料需要在許多術語-文件配對之間進行分散式存取。在 Photon 中,我們儲存一個稱為 docblob 的精簡每文件記錄,其中包含術語頻率、術語出現的欄位,以及它在文件中出現的位置。因此,對候選進行排序每個文件只需要一次查詢。

此格式旨在避免進行不必要的解碼。在 docblob 內,欄位會依文字分析器分組。每個群組儲存使用 Elias-Fano 編碼的排序術語列表,因此可以直接查詢術語,而無需先展開整個列表。欄位遮罩標記術語出現的位置、頻率計數追蹤出現的頻率,而實際位置則存在每個術語-欄位配對編碼為位置差的獨立區塊中。只有實際相配的術語的區塊會被解碼,而記錄中的其他所有內容都保持壓縮格式。

這種選擇性解碼讓 docblob 能夠支援每個文件儲存更多資料,而不會導致成本成比例增加。擴大記錄不會強迫每個查詢需要更多工作,因為排序永遠只會觸及與相配術語相關的部分,而不是整個記錄。

docblob 將排序資料分組為緊湊的每文件記錄。查詢術語在 Elias–Fano 編碼的術語列表中進行查詢;欄位遮罩、頻率和位置區塊為相配的術語提供所需的資料,同時不相關的術語保持壓縮狀態。術語 ID 和位置僅供說明之用。

批次讀取與記憶體處理

即使有了更有效率的資料表示形式,RAM 仍然在其他資源耗盡之前用盡,因此我們經常需要存取儲存在磁碟上的資料。Photon 使用非同步磁碟讀取和針對批次讀取最佳化的快取來加速這些存取,從而縮短每個請求的等待時間。與先前的系統相比,這顯著改善了尾端延遲。

Photon 在發出磁碟讀取之前,會在其快取中檢查一批已知的記錄偏移量。快取命中會被複製到準備好的緩衝區中;未命中會透過 io_uring 提交,以便磁碟等待時間可以重疊,而傳回的資料則會填入快取中。記錄和時間軸是示意圖,並非測量到的延遲。

重疊磁碟等待時間

排序需要取得位於檔案不同部分的百份文件的特徵。我們先前的系統使用 mmap 將檔案對應到虛擬記憶體空間中,以便可以透過記憶體指標進行讀取。然而,存取記憶體中不存在的頁面仍然需要磁碟讀取。執行緒必須等待資料才能繼續運算,當程序需要循序讀取冷記錄時,此等待時間會累積起來。

Photon 利用記錄偏移量是預先已知的事實,因此我們可以一次提交多次讀取。這由反應器(reactor)管理:專屬的工作執行緒(worker thread)透過 io_uring 提交批次,並將讀取的資料傳遞給呼叫程式碼。每個執行緒維護自己的 io_uring;批次會傳送至負載最輕的反應器。預先註冊的檔案描述項與緩衝區集區減少了重複讀取的負擔。這種非同步執行允許多次作業的等待時間重疊,從而減少尾端延遲。

減少磁碟機存取次數

在提交讀取之前,Photon 會在記憶體快取中檢查整個批次。只有對遺失記錄的請求才會前往磁碟;讀取的資料隨後會填入快取中。

快取專為批次讀取而設計。具體而言:

  • 命中時沒有獨立配置。相反,資料會被複製到呼叫程式碼預先準備好的緩衝區中。
  • 讀取者不會進行鎖定取得。相反,它會檢查資料版本,如果與寫入衝突則進行重試。
  • 它使用 CLOCK 驅逐(eviction),而不是要求在每次讀取時更新共用的 LRU 列表。
  • 它使用 CPU 快取預先擷取(prefetching),其中會預先要求後續記錄的中繼資料與資料。

這些最佳化降低了延遲,同時不損害我們所關心的工作負載的效能。

與服務分開建置與更新索引

在 Photon 中,索引建置和查詢服務在不同的節點上執行。索引器將資料轉換為就緒的儲存結構,而服務節點則讀取它們並執行檢索與排序。因此,索引建置不會與搜尋查詢爭奪 CPU,並且這些任務的資源可以獨立擴充。

這種分離解決了先前實作中存在的復原與同步瓶頸。由於索引建置在提供查詢服務的同一叢集內執行,因此復原失敗的節點或新增資料的新複本可能意味著就地重新編製文件索引,這個過程需要數天時間,並增加了復原期間的部分回應。Photon 移除了這種耦合;索引器作為分散式作業執行,獨立於服務節點產生版本化、準備就緒的索引結構,因此建置下一版本的索引不再與即時流量競爭。

索引建置與即時查詢服務分開執行。Pillar 匯出來源資料,YTsaurus 依分片準備版本化的資料表,平臺平行的索引器將準備好服務的索引結構寫入物件儲存體。服務節點載入這些結構以進行檢索與排序;索引器並發性與分片數量是獨立設定的。

準備與分散式建置

建置始於為作用中索引選取文件並產生來源輸出。控制器會記錄來源版本、輸入資料表以及預期的分片(shard)數量。YTsaurus 中的 MapReduce 作業會將文字、中繼資料、片段、特徵和預先計算的嵌入準備到依分片分割的版本化資料表中。每個索引器會串流傳輸準備好的資料表、為其分片建置結構,並合併文字片段。備妥的索引連同其元件的說明會上傳至物件儲存體。

分片數量與同時執行的索引器數量是獨立設定的,因此建置可以分多波執行,或可以配置更多機器以平行工作。分散式準備與平行索引器允許在個位數小時內建置完整的網頁索引,與先前的系統相比有了顯著的改進。

部署新版本

部署前,控制器會檢查新索引是否就緒。如果資料不完整,部署將會自動遭到封鎖。

當有多個服務群組時,控制器會一次更新一個。它首先將群組從 broker 路由中移除,並在其上啟動新的索引版本。在提供請求服務之前,分片會經歷基於搜尋日誌的獨立預熱階段,該階段會預先填入初始狀態,並使向真實流量的過渡更加平穩。預熱後,控制器檢查索引狀態和就緒狀態,然後將群組返回路由。在此期間,其他群組繼續提供請求服務。由於操作狀態已持久化,因此重新啟動不會強制建置重新開始;控制器可以重新附加到已經進行中的準備工作、重複使用就緒結果,或從儲存的步驟繼續中斷的輪替。

資料更新時間在不同的搜尋垂直領域中有所不同。對於具有高新鮮度需求的垂直領域,文件傳遞需要個位數分鐘。大約半小時內即可部署額外的資料複本;就緒索引會被載入並附加到分片,而無需重新為來源文件建立索引。

控制器檢查新索引是否完成,然後每次輪流替換一個服務群組。一個群組離開代理路由,載入新版本,使用搜尋日誌查詢預熱其快取,並在返回流量之前通過就緒狀態檢查。其他群組繼續提供服務,且已持久化的控制器狀態允許中斷的輪替恢復執行。

搜尋堆疊內的遷移與驗證

我們的首要目標是以能夠縮短回應時間同時保留功能與搜尋品質的引擎來取代舊系統。我們分階段驗證了 Photon,從可重現的負載測試和離線品質評估開始,然後再轉移到即時流量。Photon 現已驅動我們生產搜尋堆疊中的檢索與排序,其回應時間顯著低於舊系統。

負載測試與評估

為了進行效能測試,我們建置了一個負載測試工具,該工具以可設定的到達率重播來自我們搜尋日誌的查詢。它跨執行比較結果並套用統計測試,以評估所觀察到的效能差異是否一致。另外,我們在內部離線評估系統中的相同資料集上評估了 Photon 和舊系統,以檢查是否有品質退化。

在這些檢查之後,我們使用陰影流量(shadow traffic)同時將即時查詢傳送至兩個引擎。這使我們能夠在變更提供使用者服務的引擎之前,比較它們在生產工作負載上的效能與結果。接著,我們針對一小部分使用者啟用 Photon,並逐步擴大推出範圍,在整個過程中監控品質與效能。第一版本順利通過驗收測試且沒有品質退化,我們隨後繼續推出,直到 Photon 服務所有生產流量為止。

生產環境中測得的改進

在我們的生產工作流程中,p99 回應時間從舊系統中的大約 800 毫秒下降到 Photon 中的大約 65 毫秒。此測量涵蓋 Photon 內部的所有階段,但不包括高階搜尋堆疊中的後續階段。我們也不再觀察到先前與切換索引版本相關的延遲尖峰。

Photon 使用大約少 20% 的對應服務機器(相較於舊系統的內容節點),同時儲存了大約 2.5 倍的每文件資料。遷移後,我們利用此額外資料來改善排序品質,使其超越舊系統。

Photon 還減少了必須保留在記憶體中的資料量。如果使用 mlock 將相同的 Photon 資料集固定(pinning)在 RAM 中,將需要約 4.6 倍於 Photon 目前使用的常駐記憶體。

快速 Search API 預設設定評估

我們還將 Photon 作為 Search API 全新快速預設設定的基礎,該設定專為對延遲敏感的工作流程而設計。根據我們的測量,快速預設設定的單一搜尋呼叫延遲為 p50 時 160 毫秒,p95 時 230 毫秒,低於或可媲美其他 Search API 提供者的自行回報數字,儘管回報的尾端百分位數有所不同。

我們在六個代理 AI 基準測試中評估了快速預設設定:WideSearch、BrowseComp、DSQA、FRAMES、SEAL-0 和 SEAL-Hard。在這些評估中,在測試的配置中,它具有最低的預估總模型加搜尋成本以及最高的總體任務分數。

快速預設設定將 Photon 與針對代理工作流程最佳化的排序相結合,以顯著降低的成本提供與預設設定相當的效能。然而,它確實需要在更廣泛的搜尋品質上做出取捨。與預設設定相比,快速預設設定在排序上使用的計算較少。在涵蓋長尾查詢、廣泛涵蓋範圍和結果多樣性的內部基準測試中,其相關性得分低 0.24 點,答案可用性低大約 3 個百分點。我們認為對於代理工作流程來說,這是一個明智的取捨,因為模型自身的推理和知識可以幫助彌補這種差異。

預設 PPLX 預設設定與 PPLX Fast 之間的內部檢索品質比較。相關性 (DCG) 從 2.45 下降到 2.21,減少了 0.24 點;答案可用性從 0.596 下降到 0.567,減少了 2.9 個百分點。這兩個面板使用不同的非零軸基準,因此長條圖高度強調的是差距而非成比例的差異。

我們建議日常代理任務使用快速設定。對於時間敏感度較低且更具挑戰性或模稜兩可的問題,預設設定提供更強固的品質,這證明瞭其較高的延遲與成本是合理的。

結論

我們正穩步將搜尋堆疊遷移到基於 Rust 的內部基礎設施。用我們的自訂檢索與排序引擎 Photon 取代開源引擎是最新的一步。Photon 讓我們完全掌控資料表示形式、如何組織讀取以及如何部署新索引,從而允許我們針對工作負載最佳化每個層。

Photon 實現了 Perplexity 各產品中更快的搜尋,並為我們在 Search API 中全新的快速預設設定提供了基礎。遷移後,p99 檢索與排序延遲從大約 800 毫秒下降到 65 毫秒。我們的精簡資料格式也讓我們能夠在每個文件中儲存 2.5 倍的資料,從而使檢索與排序能夠存取更豐富的文件資訊。

快速預設設定在 p50 時可提供 160 毫秒、在 p95 時可提供 230 毫秒的單一搜尋呼叫延遲。在所評估的六個代理 AI 基準測試中,相較於預設設定,它在維持可比較之總體任務品質的同時,將每個任務預估的模型加搜尋成本降低了 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}
}