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%。

檢索與排序引擎的要求

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

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

我們前一個系統的不足之處

我們維護了自己的分支版本以調整引擎以適應不斷增長的索引,但解決關鍵限制越來越需要重新設計平台的各個大型部分。若沒有根本性的架構變更,有幾個限制證明很難解決:

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

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

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

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

最終,我們得出結論:從頭開始建構會比繼續調整和維護我們的分支版本更簡單且成本更低。借鏡我們營運前一個系統的經驗,一個小型工程團隊與一群持續運作的 AI 代理程式合作開發了 Photon。

Photon 在搜尋堆疊中的定位

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

來自較高層級搜尋服務的請求透過負載平衡器到達 Photon 經紀人。經紀人選取一個分片群組並將請求轉發至每個分片,同時監控逾時並在需要時發出額外請求。

每個分片執行檢索、初始排序和第二階段排序,然後將其最佳候選傳回給經紀人。經紀人將這些候選組合成單一排序列表,並從相關分片擷取所選頁面的關鍵文件欄位。然後,它將這些結果傳回給較高層級搜尋服務,該服務收集代理產生接地答案所需的頁面內容。

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

查詢從較高層級搜尋服務透過負載平衡器和經紀人移動到選定的分片群組。每個分片檢索並排序候選;經紀人合併結果,從相關分片擷取所選頁面的文件欄位,並將其傳回給較高層級搜尋服務。編號步驟顯示順序。實線攜帶請求,虛線攜帶結果返回。左側階段標籤區分了候選選擇與摘要讀取。

為我們的工作流程設計 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 快取預先擷取(prefetching),會預先請求即將使用之記錄的後設資料和資料。

這些最佳化在不損害我們所關注的工作負載效能的情況下降低了延遲。

將索引的建置與更新從服務中獨立出來

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

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

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

準備與分散式建置

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

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

部署新版本

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

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

資料更新時間因搜尋垂直領域而異。對於具有高新鮮度要求的垂直領域,文件傳遞只需個位數分鐘。資料的額外複本可以在大約半小時內部署完成;備妥的索引會被載入並附加到分片,而無需重新索引來源文件。

控制器會檢查新索引是否完整,然後逐一輪替服務群組。某個群組會離開經紀人路由、載入新版本、使用搜尋記錄查詢預熱其快取,並在返回流量之前通過備妥檢查。其他群組則繼續提供服務,且持續保存的控制器狀態允許中斷的輪替恢復執行。

搜尋堆疊內的遷移與驗證

我們的首要目標是以能夠減少回應時間的引擎取代舊系統,同時保留功能與搜尋品質。我們分階段驗證了 Photon,從可重現的負載測試和離線品質評估開始,然後才過渡到即時流量。Photon 現在為我們生產搜尋堆疊中的檢索與排序提供動力,其回應時間大幅低於舊系統。

負載測試與評估

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

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

生產環境中測得的改進

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

Photon 使用的等效服務機器大約比舊系統的內容節點少 20%,同時每個文件儲存大約 2.5 倍的資料。遷移後,我們利用這筆額外的資料來改善排序品質,使其超越舊系統。

Photon 還減少了必須保留在記憶體中的資料量。使用 mlock 將相同的 Photon 資料集釘選在 RAM 中,所需的駐留記憶體估計是 Photon 目前使用量的 4.6 倍。

快速 Search API 預設設定的評估

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

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