Pular para o conteúdo
duxt

Manter a pesquisa por coleção do Content como padrão

Um único índice sobre todas as fontes foi medido contra a pesquisa de um índice por coleção do Content e não é o padrão da camada; o hook de compilação transforma-o antes numa camada fornecedora.

Contexto

A pesquisa é um índice por coleção. useDuxtSearch() chama o useSearchCollection() do Content uma vez por fonte ativa, o que descarrega o sql_dump.txt dessa coleção e constrói um índice FTS5 no navegador sobre SQLite em WASM. Desta única forma decorrem três queixas:

  • A carga cresce com o modelo. Um despejo por coleção ativa, e as coleções multiplicam-se como fontes × versões × idiomas. O multi-fonte é a proposta do duxt, e a pesquisa paga-a linearmente.
  • As fontes não podem ser classificadas umas contra as outras. Cada base de dados classifica dentro de si própria, por isso interleave() alterna as listas por fonte: a posição é a única coisa comparável que existe.
  • Sem tolerância a gralhas no caminho principal. O FTS5 casa termos e prefixos, nunca aproximações, pelo que useFuzzySearch() mantém um segundo índice sobre as mesmas secções e carrega a cada falha.

O Pagefind responde em princípio às três: um índice fatiado do qual o navegador só busca uma parte, uma lista classificada única com filtros e — como padrão do Starlight — a resposta do projeto mais comparável. A sua API de Node aceita registos em vez de HTML compilado, pelo que addCustomRecord consome duxt:search:records diretamente e a camada nunca tem de exigir uma estratégia de renderização aos sites que a estendem.

Foi medido em vez de discutido, sobre o conteúdo real de www/: 33 coleções, 2.918 páginas, 11.937 registos em cinco idiomas e quatro versões. scripts/search-index-bench.ts calculou os números abaixo, e tests/search-index-bench.test.ts fixa a regra pela qual foram lidos.

O que a medição encontrou

Carga — o Pagefind ganha, com clareza. A primeira consulta de uma leitora em inglês custa hoje 1.448 KB (1.108 KB de SQLite em WASM e o seu worker, 340 KB de despejos nas sete coleções em causa) e todas as seguintes são gratuitas. A mesma consulta contra um índice Pagefind custa 150 KB, e cada seguinte 13 KB. Mesmo descontando por completo o motor de base de dados — o Content carrega-o a pedido, e um queryCollection no cliente ao mudar de rota também o pode invocar — são 340 KB contra 150 KB, e os 340 KB são a metade que cresce com cada fonte que um consumidor acrescenta, ao passo que os 150 KB não crescem.

Classificação — o Pagefind ganha, por idioma. Um índice sobre todas as fontes devolveu uma única lista classificada, e os filtros saíram do contrato de registos sem adaptação nenhuma: source continha /, /demo, /demo/api, /demo/changelog, /demo/changelog-flat, /demo/collection, /releases, /tf, e version continha main, v0.1.0, v0.2.0, v0.2.6, v0.3.4, v1.x, v2.x, v3.x. interleave() desapareceria — para as fontes. Não desapareceria de todo: o Pagefind indexa cada idioma em separado e seleciona um em tempo de execução, a sua documentação não descreve forma de pesquisar dois em conjunto, e mergeIndex contra o mesmo caminho base é ignorado, pelo que o recuo entre idiomas de que depende uma página por traduzir continua a precisar de uma segunda instância e de dois espaços de pontuação incomparáveis.

Tolerância a gralhas — o Pagefind perde, e leva o recuo consigo. O Pagefind faz lematização por idioma e não tem correspondência difusa. collecton foi salvo por prefixo até 360 resultados; verison — uma troca de letras — devolveu três páginas sem relação. Isso é pior do que o comportamento FTS5 que substitui, porque não é um resultado vazio: useDuxtSearch() recorre ao Fuse precisamente quando a passagem exata nada devolve, por isso três respostas erradas são três razões para o recuo nunca disparar. O Fuse teria de ficar, e o gatilho que o invoca teria de ser reescrito à volta de um fornecedor que responde com aprumo e ao lado.

Saída — um ficheiro por secção indexada. O índice emitiu 12.066 ficheiros e 6,80 MiB para 11.937 registos, um fragmento por secção, contra os 1.414 que uma compilação Cloudflare de www/ escreve hoje. O Cloudflare Workers limita uma versão a 20.000 ficheiros estáticos no plano gratuito. Um site de documentação cujo índice cresce um ficheiro por secção, por fonte, por versão e por idioma atinge esse limite com noventa páginas de material de origem, e quem acrescentasse um idioma descobri-lo-ia num deploy falhado.

O empacotamento não é obstáculo. pagefind@1.5.2 é MIT, entrega sete binários pré-compilados como optionalDependencies que cobrem todas as plataformas que engines promete, e não precisa de descarga em postinstall nem de cadeia node-gyp — a fasquia que o better-sqlite3 não passou. Indexar 11.937 registos levou 6,8 segundos e aos 10,0 o índice inteiro estava em memória. Nada disto entra em conflito com ADR-0006: tudo acontece durante a compilação.

Decisão

A pesquisa por coleção do Content mantém-se como padrão da camada. O Pagefind não é adotado como aquilo que o duxt entrega.

Dos três problemas, um índice único resolve um por inteiro, outro apenas dentro de um idioma, e o terceiro agrava-o — acrescentando ainda uma lei de crescimento do número de ficheiros que colide com a plataforma onde o próprio site da camada é publicado. Um padrão muda-se por um ganho claro, não por uma troca, e dois em três é uma forma diferente, não uma melhor.

O Pagefind continua disponível, como camada e não como padrão.duxt:search:records foi aberto exatamente para isto, e a medição confirmou que o contrato não precisa de adaptação: url, title, content, source, version e locale correspondem campo a campo a addCustomRecord, e os filtros que saem são as identidades de fonte e as etiquetas de versão que o duxt já calcula. Quem quiser um índice único escreve extends: ['@kirchdev/duxt', 'duxt-pagefind'] e paga o número de ficheiros de forma consciente.

A regra sobrevive ao candidato. searchIndexVerdict, em scripts/search-index-bench.ts, enuncia as seis condições que um substituto tem de cumprir — carga sensivelmente menor, fontes classificadas em conjunto, tolerância a gralhas preservada, apenas em tempo de compilação, binários que cubram engines e saída dentro do limite de ficheiros do deploy — e devolve aquelas que um candidato falha. O «não» deste ADR é um valor que essa função devolve.

Consequências

Os leitores mantêm uma pesquisa que descarrega mais e depois responde offline, e mantêm a tolerância a gralhas que o Fuse dá. A queixa sobre a carga fica por resolver: quem reúne muitas fontes continua a pagar por coleção ativa, e essa é a razão mais forte para voltar ao assunto.

interleave() fica, e com ele o comentário que explica porque não há pontuações comparáveis com que classificar.

Voltar ao assunto é barato e os termos estão escritos. Se o Pagefind ganhasse correspondência difusa, ou emitisse fragmentos em menos ficheiros, duas condições inverter-se-iam; um candidato que indexasse todos os idiomas num único espaço pesquisável inverteria uma terceira. Corram de novo o banco de ensaio em vez de reargumentar o caso — e registem um novo ADR que substitua este, porque estes registos são apenas acrescentados.

Nada disto restringe um serviço externo. O Meilisearch e o Typesense respondem às três queixas e nunca foram candidatos a padrão, porque um padrão não pode exigir a um consumidor que opere ou compre um serviço; consomem o mesmo hook.

Esta página foi útil?