Photon: Bygga en hämtnings- och rankningsmotor från grunden
Hur specialiserade dataformat, batchade läsningar och separeringen av indexbyggande från frågebetjäning förbättrar AI-nativ sökning.
Introduktion
Perplexity s AI-nativa sökstack hämtar och rankar kandidatsidor och lyfter fram det mest relevanta innehållet för modellen att grunda sina svar på. Ursprungligen kördes detta steg på en öppen källkods-motor som vi anpassade till vårt system. Men när vårt index och våra arbetsflöden växte stötte vi på hårda gränser för kostnader, svanslatens och nodåterställningstid som vi inte kunde lösa inom den befintliga plattformen från tredje part.
För att övervinna dessa utmaningar byggde vi Photon, en ny intern hämtnings- och rankningsmotor. Detta fortsätter vår strategi att bygga AI-först sökinfrastruktur från grunden, vilket vi gjorde med CobbleDB, vår distribuerade nyckel-värde-lagring för att betjäna sidposter, och vår Search API.
Photon använder kompakta dataformat för att endast läsa och avkoda det som varje fråga behöver, med batchmedveten cachning och asynkrona läsningar för att minska diskåtkomsten och överlappa väntetider. Indexbyggandet körs separat från frågehanteringen, uppdaterar en servergrupp i taget och värmer upp dess cacher innan livade frågor dirigeras dit.
Photon driver nu vår sökpipeline och är också grunden för en ny snabb förinställning för vår Search API, med en svarstid för enskilda sökanrop på 160 ms vid p50 och 230 ms vid p95. Över sex offentliga riktmärken minskade den snabba förinställningen den beräknade kostnaden för modell plus sökning per uppgift med 68 % samtidigt som en sammanlagd uppgiftskvalitet jämförbar med standardförinställningen bibehölls. Vi lanserar den idag så att utvecklare kan få tillgång till dessa förbättringar.
Vad hämtnings- och rankningsmotorn kräver
För varje fråga söker hämtnings- och rankningsmotorn igenom ett stort, distribuerat index för att välja ut och sortera en liten uppsättning relevanta sidor inom sökstackens latensbudget. Systemet måste också hålla indexet uppdaterat i takt med att nya sidor kommer in och befintliga sidor ändras, utan att det arbetet saktar ner livade frågor.
Vi utvärderade flera alternativ med öppen källkod och valde en mogen, allmän hämtnings- och rankningsmotor som uppfyllde våra ursprungliga behov. Vi anpassade den sedan till vår arkitektur och våra arbetsflöden, och bibehöll stabil latens och genomströmning i produktionsskala.
Där vårt tidigare system kom till kort
Vi underhöll vår egen fork för att anpassa motorn till vårt växande index, men att lösa viktiga begränsningar krävde i ökande grad en omkonstruktion av stora delar av plattformen. Flera begränsningar visade sig vara svåra att lösa utan grundläggande arkitektoniska förändringar:
1. Skalningskostnader. Både antalet dokument vi lagrar och mängden data per dokument som vi behöver hämta och ranka har ökat över tid. Denna skalning satte press på vår hårdvara, i synnerhet den tillgängliga mängden RAM. För att kontrollera kostnaderna behövde vi mer kompakta dataformat och finare kontroll över vad som finns kvar i minnet och vad som läses från disk på begäran.
2. Dålig svanslatens. Vid produktionstrafik var det gamla systemets p99-svarstid cirka 800 ms. Datauppsättningen överskred tillgängligt RAM, så vi kunde inte använda mlock för att hålla den helt i minnet. Åtkomst till icke-resident data kunde utlösa stora sidfel, vilket stoppade frågekörningen medan operativsystemet läste från disken.
Motorn slog också periodvis samman sitt huvudsakliga diskindex med mindre index som spolats från minnet. Under denna process med sammanslagning av diskindex ökade antalet sidfel ännu mer, där p99-latensen nådde ungefär 1,2 s under 10–15 minuter. När cacharna värmdes upp minskade både frekvensen av sidfel och latensen, vilket antyder att kalla läsningar var en stor bidragande orsak till spikarna. Förladdning av data och uppvärmning av indexet minskade spikarna men adresserade inte tillräckligt den höga svanslatensen.
3. Långvarig återställning och synkronisering. I det tidigare systemet kunde det ta mer än en vecka att distribuera ett extra kluster och synkronisera dess data. Synkronisering krävdes också när nodgrupper lades till för att lagra ytterligare datakopior eller vid återställning från nodfel. Under återställning var en större del av sökresponsen partiell, vilket sänkte kvalitetspoängen för slutsvaret.
Slutligen drog vi slutsatsen att det skulle vara enklare och billigare att bygga från grunden i stället för att fortsätta anpassa och underhålla vår forked. Med utgångspunkt i vår erfarenhet av att drifta det tidigare systemet arbetade ett litet ingenjörsteam tillsammans med ett svärm av ihärdiga kodningsagent för att bygga Photon.
Var Photon passar in i sökstacken
Photon är utformat för två primära arbetsflöden: att hantera sökförfrågningar och att mata in uppdateringar från bearbetningspipelinen.
En förfrågan från sökservicen på högre nivå når Photon-mäklaren via en lastbalanserare. Mäklaren väljer ut en shardgrupp och vidarebefordrar förfrågan till varje shard, övervakar tidsgränser och skickar ut ytterligare förfrågningar vid behov.
Varje shard utför hämtning, initial rankning och andrastegsrankning, och returnerar sedan sina bästa kandidater till mäklaren. Mäklaren kombinerar dessa kandidater till en enda rankad lista och hämtar viktiga dokumentfält för de valda sidorna från de relevanta shards. Den returnerar sedan dessa resultat till sökservicen på högre nivå, som samlar in det sidinnehåll som agenten behöver för att generera ett grundat svar.
Indexuppdateringar följer en separat väg. Pillar, vårt publiceringslager, förbereder data och lagrar den i YTsaurus-tabeller. Indexerare konverterar dessa tabeller till indexstrukturer som shards kan ladda för sökning. Kontrollern samordnar dataförberedelse, indexbyggande och distribution av nya indexversioner till servergrupper. Innan de hanterar livad trafik spelar varje shard upp verkliga sökfrågor för att värma upp sina cacher.
Utformning av Photon för våra arbetsflöden
Tre designval gör Photon väl lämpat för vårt arbetsflöde: kompakta dataformat, cachning och asynkrona diskläsningar, samt separering av indexuppdateringar från frågebetjäning.
Datarepresentationer för hämtning och rankning
Hämtning och rankning kommer åt dokumentdata på olika sätt. Lexikal hämtning börjar med söktermer och hittar dokument som innehåller dem. Rankningen behöver sedan detaljerad information om varje valt dokument, inklusive termfrekvenser och positioner.
Photon använder två komplementära representationer för att stödja dessa åtkomstmönster. Ett inverterat index lagrar en lista med matchande dokument för varje term, kallad en postningslista, för att stödja kandidaturval. Separata poster per dokument tillhandahåller den data som behövs för rankning. Vi optimerar också hur postningslistor lagras och traverseras för att förbättra urvalet inom en fast arbetsbudget.
Specialiserade datarepresentationer gör det möjligt för oss att lagra nyckeldata mer effektivt. Ett mer kompakt inverterat index minskar den mängd data som läses eller flyttas per fråga, vilket förbättrar latensen genom att eliminera onödiga I/O-operationer. Dessa format gör det också möjligt att kontrollera om ett dokument visas i en lista utan att avkoda det helt, en egenskap som vår traverseringslogistik förlitar sig på för att kontrollera hur mycket arbete en fråga utför under en fast budget. Optimering av poster per dokument gör att vi kan lagra mer data per dokument för rankning, samtidigt som vi minskar skalningskostnaderna i förhållande till vårt tidigare system.
Det inverterade indexet
För varje term lagrar det inverterade indexet en postningslista. Dessa listor kan sträcka sig från en handfull dokument till miljoner, så inget enskilt format fungerar bra för varje lista. Photon väljer format baserat på antalet dokument som innehåller termen.
Korta listor använder en inline-representation som ryms på en enda sida och inte kräver någon blocktabell. Längre listor som inte får plats i inline-formatet delas upp i block som täcker fasta intervall av dokumentidentifierare. Varje blocks representation beror på dess densitet: glesa block lagrar sorterade arrayer av dokument-ID-förskjutningar, medan täta block använder bitmappar. Glesa block använder galloping search, som avancerar i allt större steg för att hoppa över regioner i listan. Täta block tillåter medlemskontroller genom att titta upp den bit som motsvarar ett dokument.
En terms frågefrekvens avgör var dess postningslista placeras på disken. Ofta efterfrågade listor lagras nära varandra, vilket koncentrerar het data i en mindre lagringsregion. Inom varje lista ordnas dokument av en separat användbarhetssignal så att mer lovande kandidater påträffas tidigare.
Att traversera listor inom en arbetsbudget
Kandidaturval kräver traversering av postningslistor för att hitta potentiellt relevanta dokument. Att uttömmande skanna varje lista är dyrt, så Photon använder en WAND-liknande algoritm som endast traverserar vissa listor samtidigt som den tar hänsyn till matchningar i de andra.
Algoritmen delar upp listor i två grupper. Drivande listor avancerar traverseringen och föreslår kandidatdokument. Sondlistor bidrar till poängen för dessa kandidater men genererar inte kandidater själva.
Kandidatpoängsättning kombinerar viktad söktermstäckning och ett frekvensbidrag, med dokumentlängdsnormalisering och ett tak för vinster från upprepade termer. Photon kontrollerar först termförekomst i arrayerna och bitmapparna för att begränsa varje kandidats maximalt möjliga poäng. Den läser exakta termfrekvenser endast för kandidater som fortfarande kan uppfylla urvalströskeln.
Urvalet fortgår sedan i tre steg:
- Traversering: Börja med ett strikt krav på frågetäckning. Om för få kandidater hittas, upprepa med ett avspänt krav.
- Kvarhållning: Kandidater hålls kvar i en begränsad uppsättning med hjälp av en poäng som kombinerar täckning och frekvens. I takt med att de insamlade toppkandidaterna förbättras stiger antagningströskeln.
- Slutligt urval: Slutresultaten för passet väljs från den behållna uppsättningen baserat på frekvenspoängen. Varje fält kör sitt eget pass, och resultaten slås samman med fältvikter.
Algoritmen producerar en kandidatuppsättning med relevanta dokument som ska användas i vidare rankning.
Docblob
Efter att kandidatsidor har valts ut behöver rankningsfasen termfrekvenser, fälttäckning samt förekomsternas platser och närhet. Att hämta denna data genom det inverterade indexet skulle kräva spridda åtkomster över många term-dokument-par. I Photon lagrar vi en kompakt post per dokument som kallas docblob som innehåller termfrekvenser, vilka fält en term dök upp i och var i dokumentet den inträffade. Som ett resultat kräver rankning av en kandidat endast en uppslagning per dokument.
Formatet är byggt för att undvika att avkoda mer än nödvändigt. Inom en docblob grupperas fält efter textanalysator. Varje grupp lagrar en sorterad termlista kodad med Elias-Fano, så att en sökterm kan slås upp direkt utan att hela listan expanderas först. En fältmask markerar var en term förekom, en frekvensräknare spårar hur ofta, och faktiska positioner finns i separata block per term-fält-par kodade som positionsdifferenser. Endast de block för termer som faktiskt matchade avkodas, medan allt annat i posten förblir i det komprimerade formatet.
Den selektiva avkodningen gör att docblob stöder lagring av mer data per dokument utan en proportionell kostnadsökning. Att utöka posten tvingar inte fram mer arbete per uppslagning, eftersom rankningen enbart berör de delar som är relevanta för de matchade termerna snarare än hela posten.
Batchade läsningar och arbete med minne
Även med de mer effekiva dataformaten tar RAM slut före andra resurser, så vi behöver ofta komma åt data som lagras på disk. Photon använder asynkrona diskläsningar och en cache optimerad för batch-läsningar för att påskynda dessa åtkomster, vilket kapar väntetiden per förfrågan. Detta bidrar till en avsevärd förbättring av svanslatensen jämfört med det tidigare systemet.
Överlappande diskväntetider
Rankning kräver att funktionerna erhålls för hundra dokument som är belägna i olika delar av en fil. Vårt tidigare system använde mmap för att mappa filen till virtuell adressrymd så att den kunde läsas genom minnespekare. Att komma åt en sida som saknades från minnet krävde dock fortfarande en diskläsning. Tråden var tvungen att vänta på datan innan beräkningen kunde fortsätta, och denna väntetid byggdes på när processen behövde läsa kalla poster sekventiellt.
Photon utnyttjar det faktum att postförskjutningar är kända i förväg, så vi kan skicka flera läsningar på en gång. Detta hanteras av reaktorn: dedikerade arbetstrådar skickar batchar via io_uring och skickar den data de läser till den anropande koden. Varje tråd upprätthåller sin egen io_uring; batchar skickas till den minst belastade reaktorn. Förregistrerade fildeskriptorer och buffertpooler minskar omkostenterna för upprepade läsningar. Denna asynkrona exekvering gör att väntetiderna för flera operationer kan överlappa varandra, vilket minskar svanslatensen.
Minskning av antalet enhetsåtkomster
Innan läsningar skickas in kontrollerar Photon hela batchen i inminnescachen. Endast förfrågningar om saknade poster går till disk; den data som läses fyller sedan cachen.
Cachen är utformad för batchade läsningar. Närmare bestämt:
- Det finns ingen separat allokering vid en träff. Istället kopieras data till en buffert som förberetts i förväg av den anropande koden.
- Det sker ingen låsförvärvning av läsaren. Istället kontrollerar den dataversionen och gör om försöket om den krockar med en skrivning.
- Den använder CLOCK-vräkning istället för att kräva uppdateringar av en delad LRU-lista vid varje läsning.
- Den använder CPU-cache-prefetching, där metadata och data för kommande poster begärs i förväg.
Dessa optimeringar minskar latensen utan att skada prestandan på de arbetsflöden vi bryr oss om.
Att bygga och uppdatera indexet separat från betjäning
I Photon körs indexbyggande och frågebetjäning på olika noder. Indexerare omvandlar data till färdiga lagringsstrukturer, medan serveringsnoder läser dem och utför hämtning och rankning. Som ett resultat konkurrerar indexbyggandet inte med sökfrågor om CPU, och resurser för dessa uppgifter kan skalas oberoende av varandra.
Denna separation adresserar de flaskhalsar för återställning och synkronisering som fanns i den tidigare implementeringen. Eftersom indexbyggandet kördes inuti samma kluster som betjänade frågor kunde återställning av en misslyckad nod eller tillägg av en ny kopia av datan innebära att dokument behövde indexeras om på plats, en process som tog dagar och ökade partiella svar under återställning. Photon tar bort den sammankopplingen; indexerare körs som distribuerade jobb som producerar versionerade, färdiga indexstrukturer oberoende av serveringsnoderna, så att byggandet av nästa version av indexet inte längre konkurrerar med live-trafik.
Förberedelse och distribuerat bygge
Ett bygge börjar med att dokument väljs ut för det aktiva indexet och källexporten skapas. Kontrollern registrerar källversion, indatatabeller och förväntat antal shards. MapReduce-jobb i YTsaurus förbereder text, metadata, fragment, funktioner och förberäknade inbäddningar i versionerade tabeller uppdelade per shard. Varje indexerare streamar den förberedda tabellen, bygger strukturerna för sin shard och slår samman textsegment. Det färdiga indexet, tillsammans med en beskrivning av dess komponenter, laddas upp till objektlagring.
Shard-antal och antalet samtidigt körande indexerare konfigureras oberoende av varandra, så ett bygge kan köras i flera vågor, eller så kan fler maskiner allokeras för att arbeta parallellt. Distribuerad förberedelse och parallella indexerare gör att hela webbindexet kan byggas på ett ensiffrigt antal timmar, en betydande förbättring jämfört med det tidigare systemet.
Distribution av en ny version
Innan distributionen kontrollerar kontrollern att det nya indexet är redo. Om datan är ofullständig blockeras distributionen automatiskt.
När det finns flera servergrupper uppdaterar kontrollern dem en i taget. Den tar först bort en grupp från mäklardirigeringen och startar den på den nya indexversionen. Innan förfrågningar betjänas går shards igenom ett separat uppvärmningssteg baserat på sökloggar, vilket förpopulerar det ursprungliga tillståndet och gör övergången till verklig trafik smidigare. Efter uppvärmning kontrollerar kontrollern indextillståndet och beredskapen, och återställer sedan gruppen till dirigering. De andra grupperna fortsätter att betjäna förfrågningar under denna tid. Eftersom driftstillståndet är beständigt tvingar en omstart inte ett bygge att börja om från början; kontrollern kan återansluta till förberedelser som redan pågår, återanvända ett färdigt resultat eller fortsätta en avbruten rotation från det sparade steget.
Datauppdateringstider skiljer sig åt mellan sökvertikaler. För vertikaler med höga färskhetskrav tar dokumentleveransen ett ensiffrigt antal minuter. En extra kopia av datan kan distribueras på ungefär en halvtimme; färdiga index läses in och kopplas till shards utan att källdokumenten indexeras om.
Migrering och validering inom vår sökstack
Vårt första mål var att ersätta det gamla systemet med en motor som bevarade funktionalitet och sökkvalitet samtidigt som svarstiderna minskades. Vi validerade Photon i etapper, med början i reproducerbara lasttester och offline-kvalitetsutvärderingar innan vi gick över till live-trafik. Photon driver nu hämtning och rankning i vår produktionssökstack, med väsentligt lägre svarstider än det gamla systemet.
Lasttester och utvärderingar
För prestandatestning byggde vi ett lasttestverktyg som spelar upp frågor från våra sökloggar vid konfigurerbara ankomsthastigheter. Det jämför resultat över körningar och tillämpar statistiska tester för att bedöma om observerade prestandaskillnader är konsekventa. Separat utvärderade vi Photon och det gamla systemet på samma datauppsättningar i våra interna offline-utvärderingssystem för att söka efter kvalitetsregressioner.
Efter dessa kontroller skickade vi livade frågor till båda motorerna samtidigt med skugg-trafik. Detta gjorde det möjligt för oss att jämföra deras prestanda och resultat på produktionarbetsflöden innan vi ändrade vilken motor som betjänade användarna. Vi aktiverade sedan Photon för små undergrupper av användare och utökade rullningen gradvis, samtidigt som vi övervakade kvalitet och prestanda genom hela processen. Den första versionen klarade acceptanstestningen utan kvalitetsregressioner, och vi fortsatte utrullningen tills Photon betjänade all produktionstrafik.
Uppmätta förbättringar i produktion
I vårt produktionsarbetsflöde sjönk p99-svarstiden från ungefär 800 ms i det gamla systemet till ungefär 65 ms i Photon. Denna mätning täcker alla steg inne i Photon, men exkluderar efterföljande steg i sökstacken på högre nivå. Vi observerar inte heller längre de latensspikar som tidigare var förknippade med att byta indexversioner.
Photon använder ungefär 20 % färre ekvivalenta serveringsmaskiner än det gamla systemets innehållsnoder samtidigt som det lagrar ungefär 2,5 gånger så mycket data per dokument. Efter migreringen använde vi denna extra data för att förbättra rankningskvaliteten utöver det gamla systemets.
Photon minskar också den mängd data som måste finnas kvar i minnet. Att låsa samma Photon-datauppsättning i RAM med mlock skulle kräva uppskattningsvis 4,6 gånger så mycket resident minne som Photon för närvarande använder.
Utvärdering av den snabba Search API-förinställningen
Vi använde också Photon som grund för en ny snabb förinställning för vår Search API, utformad för tidskänsliga arbetsflöden. Enligt våra mätningar är latensen för en enskild sökförfrågan för den snabba förinställningen 160 ms vid p50 och 230 ms vid p95, lägre än eller jämförbar med andra Search API-leverantörers självrapporterade siffror, även om de rapporterade svanspercentilerna skiljer sig åt.
Vi utvärderade den snabba förinställningen på sex agentiska riktmärken: WideSearch, BrowseComp, DSQA, FRAMES, SEAL-0 och SEAL-Hard. Över dessa utvärderingar hade den den lägsta uppskattade totala modellen för modell-plus-sökkostnad och den högsta sammanlagda uppgiftspoängen bland de testade konfigurationerna.
Den snabba förinställningen parar ihop Photon med rankning optimerad för agentiska arbetsflöden, vilket ger en prestanda som är jämförbar med standardförinställningen till en väsentligt lägre kostnad. Det medför dock en kompromiss i bredare sökkvalitet. Den snabba förinställningen använder mindre beräkning för rankning än standardförinställningen. På interna riktmärken som täcker långsvansfrågor, bred täckning och resultatdiversitet får den 0,24 poäng lägre i relevans och ungefär 3 procentenheter lägre i svarstillgänglighet. Vi anser att detta är en rimlig kompromiss för agentiska arbetsflöden, eftersom modellens eget resonemang och kunskap kan hjälpa till att kompensera för skillnaden.
Vi rekommenderar den snabba förinställningen för dagliga agentiska uppgifter. För mer utmanande eller tvetydiga frågor som är mindre tidskänsliga erbjuder standardförinställningen en mer robust kvalitet, vilket motiverar dess högre latens och kostnad.
Slutsats
Vi flyttar stadigt vår sökstack till Rust-baserad, intern infrastruktur. Att ersätta öppen källkods-motorn med Photon, vår anpassade hämtnings- och rankningsmotor, är det senaste steget. Photon ger oss full kontroll över datarepresentationer, hur läsningar organiseras och hur nya index distribueras, vilket gör att vi kan optimera varje lager för våra arbetsflöden.
Photon har möjliggjort snabbare sökning över Perplexity s produkter och tillhandahåller grunden för vår nya snabba förinställning i Search API. Efter migreringen sjönk p99-latensen för hämtning och rankning från ungefär 800 ms till 65 ms. Våra kompakta dataformat gör det också möjligt för oss att lagra 2,5 gånger så mycket data per dokument, vilket ger hämtning och rankning tillgång till rikare dokumentinformation.
Det snabba förinställda läget ger en svarstid på 160 ms vid p50 och 230 ms vid p95 för ett enskilt sökanrop. Över de sex utvärderade agentiska riktmärkena minskade det den beräknade kostnaden för modell plus sökning per uppgift med 68 % jämfört med standardförinställningen, samtidigt som en jämförbar sammanlagd uppgiftskvalitet bibehölls. Dessa resultat visar de praktiska fördelarna med infrastruktur som är utformad kring våra sökarbetsflöden, både för Perplexity s produkter och för utvecklare som bygger med vårt API.
Vi fortsätter att förbättra Photons prestanda och lagringseffektivitet. Vårt mål är att stödja ett större index och rikare dokumentdata på samma hårdvara samtidigt som vi håller sökningen snabb i takt med att våra arbetsflöden växer.
Citering
@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}
}