feat: keep exact redirects out of isolate memory, one KV key each - #553
Open
JonasJesus42 wants to merge 1 commit into
Open
JonasJesus42 wants to merge 1 commit into
JonasJesus42 wants to merge 1 commit into
Conversation
A bulk-migration site can carry tens of thousands of redirect rules. Inside the decofile they are resident in every isolate TWICE — once in the parsed snapshot graph, once in the RedirectMap built from it — for data consulted at most once per request and that usually matches nothing. On a 128MB per-isolate budget with no GC knob, that is megabytes for a lookup table. So the sync now splits them out: every EXACT rule leaves the decofile and gets its own `redirect:<deployment-id>:<path>` key, and the request path looks up only the path being requested. The list is never loaded. Glob rules stay in the decofile. `/old/*` has to be scanned in order against the path, so it can never be a key lookup — and there are tens of them, not thousands. Three things that are load-bearing and non-obvious: - Matching order is exact → KV → glob, not "matchRedirect then KV". Otherwise a glob wins over an exact rule and the precedence inverts, so `matchRedirect` is split into `matchExactRedirect`/`matchPatternRedirect`. - `<path>` is `normalizePath(from)` on BOTH sides. `normalizePath` is now exported precisely because it is the key contract; a second normalization anywhere stores rules under keys nothing asks for. - A redirect edit propagates on a 60s isolate TTL, not the revision poll. The rules are outside the decofile now, so a redirect-only sync leaves the snapshot byte identical and `index:revision:<id>` never moves. The upgrade path, if that delay ever has to be near-zero, is a separate redirects-revision key — not a shorter TTL, which just multiplies reads. Add/update/remove reconciles against a prefix LIST rather than a manifest key: KV has no "replace everything under this prefix", and a manifest is one more thing that can drift from reality. GC deletes a pruned deployment's redirect keys too, or every deploy leaks tens of thousands of orphans. Bulk put/delete because one request per key is not a shape that survives 18k rules. Every failure mode resolves to "no redirect", never a throw: a KV outage must not 5xx a page that would otherwise render. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Substitui a decisão que eu tinha tomado errado no #548 ("uma cópia só dentro do snapshot é o ganho inteiro"). Aquilo só valeria se a chave fosse uma lista. Com uma chave por rota, a lista nunca entra em memória — que é onde está o ganho de verdade num site cujo decofile é majoritariamente redirect.
Problema
Dezenas de milhares de regras dentro do decofile ficam residentes duas vezes em cada isolate: no grafo do snapshot parseado, e no
RedirectMapconstruído a partir dele. Megabytes de um orçamento de 128 MB por isolate sem botão de GC, para uma tabela de lookup consultada no máximo uma vez por request e que quase sempre não casa com nada.Mudança
Cada regra exata sai do decofile e vira uma chave
redirect:<deployment-id>:<path>. O request consulta só o path pedido.Glob fica no decofile.
/old/*precisa ser varrido em ordem contra o path — nunca vira lookup por chave. São dezenas, não milhares.Chaveado por deployment id como todo o resto do fast-deploy: rollback continua coerente (código velho lê o conteúdo dele), o isolamento entre deploys se mantém, e o GC já existente prunes junto.
Três coisas load-bearing e não-óbvias
matchRedirectviroumatchExactRedirect+matchPatternRedirect(compostos, sem mudança de comportamento).<path>énormalizePath(from)nos dois lados.normalizePathpassou a ser exportado exatamente porque é o contrato da chave — uma segunda normalização em qualquer ponta grava a regra numa chave que ninguém pergunta.index:revision:<id>não mexe. Se esse atraso um dia precisar ser ~zero, o caminho é uma chaveindex:redirects-revision:<id>polada junto — não um TTL menor, que só multiplica leitura de KV. Está comentado no código e no doc.Add / update / remove
Reconciliado contra um
LISTpor prefixo, não contra uma chave de manifesto: o KV não tem "substitua tudo sob este prefixo", e manifesto é mais uma coisa que pode divergir da realidade. Update não é detectado por valor — reescrever tudo é um bulk write, ler cada valor de volta pra comparar seria um GET por chave. Só o delete precisa do diff.GC apaga as chaves de redirect do deployment prunado, senão cada deploy vaza dezenas de milhares de órfãos.
putMany/deleteManyno cliente REST (bulk, 10k por request) porque um request por chave não é forma que sobrevive a 18k regras.Todo modo de falha resolve pra "sem redirect", nunca throw: queda de KV não pode 5xxar uma página que renderizaria normal.
Testes
21 casos novos. Cobrem o round-trip do split (nada matchável se perde), a precedência exato-em-KV sobre glob-em-memória, cache de HIT e de MISS (o miss é o caso comum de todo request), re-leitura após TTL, inércia com fast-deploy desligado, recusa a ler chave de outro deployment, e KV fora do ar / valor malformado.
Depende de #548 (os redirects de CSV precisam estar no que o sync lê antes de serem extraídos).
🤖 Generated with Claude Code
Summary by cubic
Moves exact redirect rules out of the decofile into one KV key per rule (
redirect:<id>:<path>), so a site with tens of thousands of redirects no longer holds the full list resident in every isolate. Only the requested path is looked up per request; glob rules stay in the decofile since they must be scanned in order.Behavior
matchRedirectis split intomatchExactRedirectandmatchPatternRedirectso a glob never beats an exact rule.normalizePathis now exported as the key contract; the writer and request-time reader must produce identical bytes.LISTusing new bulk put/delete on the KV REST client (10k per request); GC prunes a deployment's redirect keys alongside its snapshot.Migration
sync-blocks-to-kvandmigrate-blocks-to-kvboth split exact rules before building the snapshot so a one-shot seed and CI sync stay consistent.Written for commit 4ea6df7. Summary will update on new commits.