Photon: Construindo um Mecanismo de Recuperação e Ranqueamento do Zero
Como formatos de dados especializados, leituras em lote e a separação da construção de índices do atendimento de consultas melhoram a pesquisa nativa de IA.
Introdução
A pilha de pesquisa nativa de IA da Perplexity recupera e ranqueia páginas candidatas, trazendo à tona o conteúdo mais relevante para o modelo fundamentar suas respostas. Originalmente, esse estágio era executado em um mecanismo de código aberto que adaptamos ao nosso sistema. No entanto, à medida que nosso índice e cargas de trabalho cresciam, atingimos limites rígidos de custos, latência de cauda e tempo de recuperação de nós que não conseguíamos resolver dentro da plataforma de terceiros existente.
Para superar esses desafios, construímos o Photon, um novo mecanismo interno de recuperação e ranqueamento. Isso dá continuidade à nossa abordagem de construir infraestrutura de pesquisa voltada para IA do zero, como fizemos com o CobbleDB, nosso armazenamento distribuído de chave-valor para servir registros de páginas, e nossa API de Pesquisa.
O Photon usa formatos de dados compactos para ler e decodificar apenas o que cada consulta precisa, com cache ciente de lotes e leituras assíncronas para reduzir o acesso ao disco e sobrepor esperas. A construção de índices é executada separadamente do atendimento de consultas, atualizando um grupo de atendimento por vez e aquecendo seus caches antes de direcionar consultas ativas para ele.
O Photon agora alimenta nosso pipeline de pesquisa e também é a base de uma nova predefinição rápida para nossa API de Pesquisa, com latência de chamada de pesquisa única de 160 ms no p50 e 230 ms no p95. Em seis benchmarks públicos, a predefinição rápida reduziu o custo estimado de modelo mais pesquisa por tarefa em 68%, mantendo a qualidade de tarefa agregada comparável à predefinição padrão. Estamos lançando-o hoje para que os desenvolvedores possam acessar essas melhorias.
O que o mecanismo de recuperação e ranqueamento exige
Para cada consulta, o mecanismo de recuperação e ranqueamento pesquisa um índice grande e distribuído para selecionar e ordenar um pequeno conjunto de páginas relevantes dentro do orçamento de latência da pilha de pesquisa. O sistema também deve manter o índice atualizado à medida que novas páginas chegam e as páginas existentes mudam, sem permitir que esse trabalho desacelere as consultas ativas.
Avaliamos várias opções de código aberto e escolhemos um mecanismo de recuperação e ranqueamento maduro e de uso geral que atendia às nossas necessidades iniciais. Em seguida, o adaptamos à nossa arquitetura e cargas de trabalho, mantendo latência e taxa de transferência estáveis em escala de produção.
Onde nosso sistema anterior ficou aquém
Mantivemos nosso próprio fork para adaptar o mecanismo ao nosso índice em crescimento, mas resolver limitações importantes exigia cada vez mais o redesenho de grandes partes da plataforma. Várias restrições mostraram-se difíceis de resolver sem mudanças arquiteturais fundamentais:
1. Custos de escalabilidade. Tanto o número de documentos que armazenamos quanto a quantidade de dados por documento que precisamos recuperar e ranquear têm crescido ao longo do tempo. Esse dimensionamento exerceu pressão sobre nosso hardware, especialmente sobre a quantidade de RAM disponível. Para controlar os custos, precisávamos de representações de dados mais compactas e de um controle mais refinado sobre o que permanece na memória e o que é lido do disco sob demanda.
2. Baixa latência de cauda. No tráfego de produção, o tempo de resposta do p99 do sistema antigo era de cerca de 800 ms. O conjunto de dados excedeu a RAM disponível, portanto, não pudemos usar mlock para mantê-lo totalmente na memória. O acesso a dados não residentes podia acionar falhas de página graves, travando a execução da consulta enquanto o sistema operacional lia do disco.
O mecanismo também mesclava periodicamente seu índice de disco principal com índices menores liberados da memória. Durante esse processo de fusão de índices de disco, o número de falhas de página aumentava ainda mais, com a latência do p99 atingindo aproximadamente 1,2 s por 10 a 15 minutos. Conforme os caches aqueciam, a frequência de falhas de página e a latência diminuíam, sugerindo que leituras frias eram um dos principais contribuintes para os picos. Pré-carregar dados e aquecer o índice reduziu os picos, mas não resolveu adequadamente a alta latência de cauda.
3. Recuperação e sincronização demoradas. No sistema anterior, implantar um cluster adicional e sincronizar seus dados podia levar mais de uma semana. A sincronização também era necessária ao adicionar grupos de nós para manter cópias de dados adicionais ou ao se recuperar de falhas de nós. Durante a recuperação, uma parcela maior das respostas de pesquisa era parcial, o que reduzia as pontuações de qualidade da resposta final.
Em última análise, concluímos que seria mais simples e barato construir do zero em vez de continuar adaptando e mantendo nosso fork. Com base em nossa experiência operando o sistema anterior, uma pequena equipe de engenharia trabalhou ao lado de um enxame de agentes de codificação persistentes para construir o Photon.
Onde o Photon se encaixa na pilha de pesquisa
O Photon foi projetado para dois fluxos de trabalho principais: atender a solicitações de pesquisa e ingerir atualizações do pipeline de processamento.
Uma solicitação do serviço de pesquisa de nível superior chega ao corretor do Photon por meio de um balanceador de carga. O corretor seleciona um grupo de partições e encaminha a solicitação para cada partição, monitorando tempos limite e emitindo solicitações adicionais quando necessário.
Cada partição executa recuperação, ranqueamento inicial e ranqueamento de segundo estágio, retornando seus melhores candidatos ao corretor. O corretor combina esses candidatos em uma única lista ranqueada e busca os principais campos de documentos para as páginas selecionadas nas partições relevantes. Em seguida, ele retorna esses resultados ao serviço de pesquisa de nível superior, que coleta o conteúdo de página necessário para o agente gerar uma resposta fundamentada.
As atualizações de índice seguem um caminho separado. O Pillar, nossa camada de publicação, prepara os dados e os armazena em tabelas do YTsaurus. Os indexadores convertem essas tabelas em estruturas de índice que as partições podem carregar para pesquisa. O controlador coordena a preparação de dados, a construção de índices e a implantação de novas versões de índice para os grupos de atendimento. Antes de lidar com o tráfego ativo, cada partição reproduz consultas de pesquisa reais para aquecer seus caches.
Projetando o Photon para nossos fluxos de trabalho
Três escolhas de design tornam o Photon bem adequado à nossa carga de trabalho: representações de dados compactas, cache e leituras de disco assíncronas, e a separação de atualizações de índice do atendimento de consultas.
Representações de dados para recuperação e ranqueamento
A recuperação e o ranqueamento acessam os dados do documento de maneira diferente. A recuperação lexical começa com os termos da consulta e encontra os documentos que os contêm. O ranqueamento precisa de informações detalhadas sobre cada documento selecionado, incluindo frequências de termos e posições.
O Photon usa duas representações complementares para dar suporte a esses padrões de acesso. Um índice invertido armazena uma lista de documentos correspondentes para cada termo, chamada de lista de postagem, para dar suporte à seleção de candidatos. Registros separados por documento fornecem os dados necessários para o ranqueamento. Também otimizamos a forma como as listas de postagem são armazenadas e percorridas para melhorar a seleção sob um orçamento de trabalho fixo.
Representações de dados especializadas nos permitem armazenar dados importantes com mais eficiência. Um índice invertido mais compacto reduz a quantidade de dados lidos ou movidos por consulta, melhorando a latência ao eliminar operações de E/S desnecessárias. Esses formatos também tornam possível verificar se um documento aparece em uma lista sem decodificá-lo totalmente, uma propriedade na qual nossa lógica de travessia se apoia para controlar quanto trabalho uma consulta executa sob um orçamento fixo. A otimização de registros por documento permite armazenar mais dados por documento para ranqueamento, ao mesmo tempo em que reduz os custos de escalabilidade associados ao nosso sistema anterior.
O índice invertido
Para cada termo, o índice invertido armazena uma lista de postagem. Essas listas podem variar de um punhado de documentos a milhões, portanto, nenhum formato único funciona bem para todas as listas. O Photon escolhe o formato com base no número de documentos que contêm o termo.
Listas curtas usam uma representação embutida que cabe em uma única página e não requer tabela de blocos. Listas mais longas que não cabem no formato embutido são divididas em blocos que cobrem intervalos fixos de identificadores de documentos. A representação de cada bloco depende de sua densidade: blocos esparsos armazenam matrizes ordenadas de desvios de ID de documento, enquanto blocos densos usam bitmaps. Blocos esparsos usam pesquisa galopante, que avança em etapas cada vez maiores para ignorar regiões da lista. Blocos densos permitem verificações de pertencimento consultando o bit correspondente a um documento.
A frequência de consulta de um termo determina onde sua lista de postagem é colocada no disco. Listas consultadas com frequência são armazenadas próximas umas das outras, concentrando dados quentes em uma região de armazenamento menor. Em cada lista, os documentos são ordenados por um sinal de utilidade separado para que candidatos mais promissores sejam encontrados mais cedo.
Percorrendo listas dentro de um orçamento de trabalho
A seleção de candidatos requer a travessia de listas de postagem para encontrar documentos potencialmente relevantes. A varredura exaustiva de cada lista é custosa, portanto, o Photon usa um algoritmo semelhante ao WAND que percorre apenas algumas listas, contabilizando ainda as correspondências nas outras.
O algoritmo divide as listas em dois grupos. Listas condutoras avançam a travessia e propõem documentos candidatos. Listas de sondagem contribuem para as pontuações desses candidatos, mas não geram candidatos por si mesmas.
A pontuação de candidatos combina a cobertura ponderada dos termos da consulta e uma contribuição de frequência, com normalização do comprimento do documento e um limite nos ganhos de termos repetidos. O Photon primeiro verifica a presença de termos nas matrizes e bitmaps para delimitar a pontuação máxima possível de cada candidato. Ele lê as frequências exatas dos termos apenas para os candidatos que ainda podem atingir o limite de seleção.
A seleção prossegue em três etapas:
- Travessia: Comece com um requisito estrito de cobertura de consulta. Se poucos candidatos forem encontrados, repita com um requisito relaxado.
- Retenção: Os candidatos são mantidos em um conjunto limitado usando uma pontuação que combina cobertura e frequência. À medida que os principais candidatos coletados melhoram, o limite de admissão sobe.
- Seleção final: Os resultados finais para a passagem são escolhidos do conjunto retido com base nas pontuações de frequência. Cada campo executa sua própria passagem, e os resultados são mesclados usando pesos de campo.
O algoritmo produz um conjunto de candidatos de documentos relevantes a serem usados em ranqueamento posterior.
Docblob
Após a seleção das páginas candidatas, a fase de ranqueamento precisa de frequências de termos, cobertura de campo e locais e proximidade das ocorrências. Obter esses dados por meio do índice invertido exigiria acessos dispersos em muitos pares de termo-documento. No Photon, armazenamos um registro compacto por documento chamado docblob que contém frequências de termos, em quais campos um termo apareceu e onde no documento ele ocorreu. Como resultado, ranquear um candidato requer apenas uma consulta por documento.
O formato foi construído para evitar a decodificação além do necessário. Dentro de um docblob, os campos são agrupados por analisador de texto. Cada grupo armazena uma lista de termos ordenada codificada com Elias-Fano, para que um termo de consulta possa ser consultado diretamente sem expandir toda a lista primeiro. Uma máscara de campo marca onde um termo ocorreu, uma contagem de frequência rastreia com que frequência, e as posições reais residem em blocos separados por par de termo-campo codificados como diferenças de posição. Apenas os blocos dos termos que realmente corresponderam são decodificados, enquanto todo o resto no registro permanece no formato compactado.
Essa decodificação seletiva permite que o docblob ofereça suporte ao armazenamento de mais dados por documento sem um aumento de custo proporcional. O crescimento do registro não força mais trabalho por consulta, pois o ranqueamento toca apenas nas partes relevantes para os termos correspondentes, em vez de todo o registro.
Leituras em lote e trabalho com a memória
Mesmo com representações de dados mais eficientes, a RAM ainda se esgota antes de outros recursos, por isso frequentemente precisamos acessar dados armazenados em disco. O Photon usa leituras de disco assíncronas e um cache otimizado para leituras em lote para acelerar esses acessos, reduzindo o tempo de espera por solicitação. Isso contribui para uma melhoria significativa na latência de cauda em comparação com o sistema anterior.
Sobreposição de esperas de disco
O ranqueamento requer a obtenção de características de cem documentos localizados em diferentes partes de um arquivo. Nosso sistema anterior usava mmap para mapear o arquivo no espaço de endereço virtual para que pudesse ser lido por meio de ponteiros de memória. No entanto, acessar uma página ausente da memória ainda exigia uma leitura de disco. O tópico precisava esperar pelos dados antes de continuar a computação, e esse tempo de espera acumulava quando o processo precisava ler registros frios sequencialmente.
O Photon aproveita o fato de que os desvios de registro são conhecidos com antecedência, permitindo que várias leituras sejam enviadas de uma só vez. Isso é gerenciado pelo reator: tópicos de worker dedicados enviam lotes por meio de io_uring e passam os dados lidos para o código de chamada. Cada tópico mantém seu próprio io_uring; os lotes são enviados para o reator menos sobrecarregado. Descritores de arquivo pré-registrados e pools de buffers reduzem a sobrecarga de leituras repetidas. Essa execução assíncrona permite que as esperas por várias operações se sobreponham, reduzindo a latência de cauda.
Reduzindo o número de acessos à unidade
Antes de enviar as leituras, o Photon verifica todo o lote no cache em memória. Apenas as solicitações de registros ausentes vão para o disco; os dados lidos populam o cache em seguida.
O cache foi projetado para leituras em lote. Especificamente:
- Não há alocação separada em um acerto. Em vez disso, os dados são copiados para um buffer preparado antecipadamente pelo código de chamada.
- Não há aquisição de bloqueio pelo leitor. Em vez disso, ele verifica a versão dos dados e tenta novamente se houver conflito com uma gravação.
- Ele usa despejo CLOCK, em vez de exigir atualizações em uma lista LRU compartilhada a cada leitura.
- Ele usa pré-busca de cache de CPU, em que os metadados e os dados dos próximos registros são solicitados com antecedência.
Essas otimizações diminuem a latência sem prejudicar o desempenho nas cargas de trabalho de nosso interesse.
Construindo e atualizando o índice separadamente do atendimento
No Photon, a construção de índices e o atendimento de consultas são executados em nós diferentes. Os indexadores transformam dados em estruturas de armazenamento prontas, enquanto os nós de atendimento os leem e realizam a recuperação e o ranqueamento. Como resultado, a construção do índice não concorre com as consultas de pesquisa por CPU, e os recursos para essas tarefas podem ser dimensionados de forma independente.
Essa separação aborda os gargalos de recuperação e sincronização presentes na implementação anterior. Como a construção de índices era executada dentro do mesmo cluster que atendia a consultas, recuperar um nó com falha ou adicionar uma nova cópia dos dados podia significar reindexar documentos no local, um processo que levava dias e aumentava as respostas parciais durante a recuperação. O Photon remove esse acoplamento; os indexadores são executados como trabalhos distribuídos que produzem estruturas de índice versionadas e prontas para atendimento de forma independente dos nós de atendimento, de modo que a construção da próxima versão do índice não concorre mais com o tráfego ativo.
Preparação e construção distribuída
Uma construção começa selecionando documentos para o índice ativo e produzindo a exportação de origem. O controlador registra a versão de origem, as tabelas de entrada e o número esperado de partições (shards). Os trabalhos do MapReduce no YTsaurus preparam texto, metadados, fragmentos, recursos e embeddings pré-computados em tabelas versionadas particionadas por partição. Cada indexador transmite a tabela preparada, constrói as estruturas para sua partição e mescla os segmentos de texto. O índice pronto, juntamente com uma descrição de seus componentes, é enviado para o armazenamento de objetos.
A contagem de partições e o número de indexadores em execução simultânea são configurados de forma independente, para que uma construção possa ser executada em várias ondas, ou mais máquinas possam ser alocadas para trabalhar em paralelo. A preparação distribuída e os indexadores paralelos permitem que todo o índice da web seja construído em um número de horas na faixa de único dígito, uma melhoria significativa em relação ao sistema anterior.
Implantando uma nova versão
Antes da implantação, o controlador verifica se o novo índice está pronto. Se os dados estiverem incompletos, a implantação será bloqueada automaticamente.
Quando há vários grupos de atendimento, o controlador os atualiza um de cada vez. Ele primeiro remove um grupo do roteamento do corretor e o inicia na nova versão do índice. Antes de atender a solicitações, as partições passam por um estágio de aquecimento separado com base em logs de pesquisa, o que pré-popula o estado inicial e torna a transição para o tráfego real mais suave. Após o aquecimento, o controlador verifica o estado do índice e a prontidão, retornando o grupo ao roteamento. Os outros grupos continuam atendendo a solicitações durante esse período. Como o estado de operação é persistido, uma reinicialização não força uma construção a recomeçar; o controlador pode reanexar à preparação já emandamento, reutilizar um resultado pronto ou continuar uma rotação interrompida a partir da etapa salva.
Os tempos de atualização de dados diferem entre os setores verticais de pesquisa. Para setores verticais com altos requisitos de frescor, a entrega de documentos leva um número de minutos na faixa de único dígito. Uma cópia adicional dos dados pode ser implantada em cerca de meia hora; os índices prontos são carregados e anexados às partições sem reindexar os documentos de origem.
Migração e validação em nossa pilha de pesquisa
Nosso primeiro objetivo era substituir o sistema antigo por um mecanismo que preservasse a funcionalidade e a qualidade da pesquisa, reduzindo os tempos de resposta. Validamos o Photon em estágios, começando com testes de carga reproduzíveis e avaliações de qualidade offline antes de migrar para o tráfego ativo. O Photon agora alimenta a recuperação e o ranqueamento em nossa pilha de pesquisa de produção, com tempos de resposta substancialmente menores do que os do sistema antigo.
Testes de carga e avaliações
Para testes de desempenho, construímos uma ferramenta de teste de carga que reproduz consultas de nossos logs de pesquisa em taxas de chegada configuráveis. Ela compara resultados entre execuções e aplica testes estatísticos para avaliar se as diferenças de desempenho observadas são consistentes. Separadamente, avaliamos o Photon e o sistema antigo nos mesmos conjuntos de dados em nossos sistemas de avaliação offline internos para verificar se há regressões de qualidade.
Após essas verificações, enviamos consultas ativas para ambos os mecanismos simultaneamente usando tráfego simulado (shadow traffic). Isso nos permitiu comparar seu desempenho e resultados em cargas de trabalho de produção antes de alterar qual mecanismo atendia os usuários. Em seguida, ativamos o Photon para pequenos subconjuntos de usuários e expandimos gradualmente a distribuição, monitorando a qualidade e o desempenho durante todo o processo. A versão inicial passou nos testes de aceitação sem regressões de qualidade, e continuamos a distribuição até que o Photon atendesse todo o tráfego de produção.
Melhorias medidas em produção
Em nosso fluxo de trabalho de produção, o tempo de resposta do p99 caiu de aproximadamente 800 ms no sistema antigo para aproximadamente 65 ms no Photon. Esta medição cobre todos os estágios dentro do Photon, mas exclui estágios subsequentes na pilha de pesquisa de nível superior. Também não observamos mais os picos de latência associados anteriormente à troca de versões de índice.
O Photon usa aproximadamente 20% menos máquinas de atendimento equivalentes do que os nós de conteúdo do sistema antigo, enquanto armazena aproximadamente 2.5 vezes mais dados por documento. Após a migração, usamos esses dados adicionais para melhorar a qualidade do ranqueamento além da do sistema antigo.
O Photon também reduz a quantidade de dados que devem permanecer na memória. Fixar o mesmo conjunto de dados do Photon na RAM com mlock exigiria uma estimativa de 4,6 vezes mais memória residente do que o Photon usa atualmente.
Avaliação da predefinição rápida da API de Pesquisa
Também usamos o Photon como base para uma nova predefinição rápida para nossa API de Pesquisa, projetada para cargas de trabalho sensíveis à latência. Conforme nossas medições, a latência de chamada de pesquisa única para a predefinição rápida é de 160 ms no p50 e 230 ms no p95, menor ou comparável aos números autorrelatados de outros provedores de API de Pesquisa, embora os percentis de cauda relatados diferiram.
Avaliamos a predefinição rápida em seis benchmarks orientados a agentes: WideSearch, BrowseComp, DSQA, FRAMES, SEAL-0 e SEAL-Hard. Nessas avaliações, ela obteve o menor custo total estimado de modelo mais pesquisa e a maior pontuação de tarefa agregada entre as configurações testadas.
A predefinição rápida combina o Photon com uma classificação otimizada para fluxos de trabalho de agentes, oferecendo um desempenho comparável ao da predefinição padrão a um custo substancialmente menor. No entanto, ela apresenta uma compensação na qualidade geral de busca. A predefinição rápida usa menos capacidade computacional para classificação do que a padrão. Em testes internos abrangendo consultas de cauda longa, ampla cobertura e diversidade de resultados, ela pontua 0,24 pontos a menos em relevância e aproximadamente 3 pontos percentuais a menos em disponibilidade de respostas. Acreditamos que essa seja uma compensação razoável para fluxos de trabalho de agentes, pois o raciocínio e o conhecimento do próprio modelo podem ajudar a compensar a diferença.
Recomendamos a predefinição rápida para tarefas diárias orientadas a agentes. Para perguntas mais desafiadoras ou ambíguas que sejam menos sensíveis ao tempo, a predefinição padrão oferece uma qualidade mais robusta, justificando sua maior latência e custo.
Conclusão
Estamos migrando constantemente nossa pilha de pesquisa para uma infraestrutura interna baseada em Rust. Substituir o mecanismo de código aberto pelo Photon, nosso mecanismo personalizado de recuperação e ranqueamento, é o passo mais recente. O Photon nos dá controle total sobre as representações de dados, como as leituras são organizadas e como novos índices são implantados, permitindo-nos otimizar cada camada para nossas cargas de trabalho.
O Photon possibilitou uma pesquisa mais rápida nos produtos da Perplexity e fornece a base para nossa nova predefinição rápida na API de Pesquisa. Após a migração, a latência de recuperação e ranqueamento do p99 caiu de aproximadamente 800 ms para 65 ms. Nossos formatos de dados compactos também nos permitem armazenar 2,5 vezes mais dados por documento, dando à recuperação e ao ranqueamento acesso a informações de documentos mais ricas.
A predefinição rápida oferece uma latência de chamada de pesquisa única de 160 ms no p50 e 230 ms no p95. Nos seis benchmarks orientados a agentes avaliados, ela reduziu o custo estimado de modelo mais pesquisa por tarefa em 68% em relação à predefinição padrão, mantendo uma qualidade de tarefa agregada comparável. Esses resultados demonstram os benefícios práticos da infraestrutura projetada em torno de nossas cargas de trabalho de pesquisa, tanto para os produtos da Perplexity quanto para os desenvolvedores que criam com nossa API.
Continuamos a melhorar o desempenho e a eficiência de armazenamento do Photon. Nosso objetivo é dar suporte a um índice maior e a dados de documento mais ricos no mesmo hardware, mantendo a pesquisa rápida à medida que nossas cargas de trabalho crescem.
Citação
@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}
}