operacional
← todos os artigos

Uma wiki inteira operada por LLMs no GKE: arquitetura e custo real

devops12 min de leituragke, agentes, custo
frontmatter
tl;dr
Um worker roda a cada 6h e resincroniza só o que mudou (gate de hash). Custo real: ~US$ 0,015 por repositório processado; upgrade completo opcional sai a ~US$ 31 e sobe a nota de qualidade de ~80 pra ~90.
nível
intermediário
pré-req
Kubernetes, CronJob, noção de custo de LLM
você sai sabendo
por que um script único bate um enxame de agentes síncronos, e como separar ingest de auditoria por amostragem
arquitetura
CronJob de ingest (6h) + CronJob de auditoria semanal amostral
status
em produção, dias de operação, sem fechamento de mês ainda

Documentação interna tem uma meia-vida curta: seis meses depois de escrita, metade está errada e ninguém sabe qual metade. Em vez de mais uma campanha de “semana da documentação”, tratei a minha wiki pessoal como um workload, uma carga de trabalho de produção como outra qualquer, que roda sozinha e tem custo. Um processo que lê repositórios e documentos e mantém as páginas atualizadas sozinho. Este post descreve a arquitetura real, inclusive onde a primeira versão do design estava errada, e abre o custo real por unidade.

O problema: documentação que apodrece

A ideia de partida era simples: se ninguém tem obrigação de atualizar um documento, ele apodrece. Cheguei a cogitar mais um agente esperto. Descartei: a solução precisava ser barata o bastante pra rodar sem virar ela mesma um projeto pra manter.

Arquitetura: menos agentes do que eu esperava precisar

Meu primeiro design tinha três agentes conversando por fila. O que ficou de pé, depois de simplificar, foi bem menor. O desenho abaixo é o sistema inteiro, dois fluxos independentes: um de escrita e um de auditoria.

Fluxo da wiki: repos e documentos entram num CronJob de ingest a cada 6h, que calcula hash; se mudou, um modelo barato resume e escreve a página da wiki; se não mudou, pula sem gastar token. Num fluxo separado, um CronJob semanal de auditoria manda um juiz (outro modelo) ler 10% das páginas, comparar com a fonte crua e apontar divergência.

Na ponta esquerda estão as fontes: repositórios no GitHub e documentos avulsos. Nada é escrito na mão na wiki.

Quem lê essas fontes é o CronJob de ingest. CronJob é o recurso do Kubernetes que roda um container em horário agendado, igual ao cron do Linux; ingest é o passo que lê a fonte e a transforma em página. Roda a cada 6 horas no GKE (Google Kubernetes Engine, o Kubernetes gerenciado do Google), com concurrencyPolicy: Forbid, então nunca tem duas execuções disputando a mesma página.

Antes de gastar qualquer token, o worker passa pelo gate de hash: verifica se a fonte mudou desde a última execução (detalhe na próxima seção). Não mudou? Pula, custo zero. Mudou? A fonte vai pro modelo barato (Gemini, no meu caso), que resume e escreve a página da wiki.

O segundo fluxo é o controle de qualidade. Um CronJob semanal manda um juiz ler uma amostra de 10% das páginas contra a fonte crua e apontar divergência. Juiz aqui é um segundo modelo, diferente do que escreve, usado só pra avaliar o trabalho do primeiro. Revisar cada página na hora da escrita sairia caro; a varredura amostral assíncrona entrega a mesma confiança por uma fração do preço.

Nada de fila entre serviços, nada de múltiplos agentes coordenando estado entre si. Um script sequencial dentro de um CronJob é mais fácil de depurar às 2h da manhã do que um enxame distribuído. E pra esse problema, resolve igual.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: wiki-ingest
spec:
  schedule: '0 */6 * * *'
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: ingest
              image: ghcr.io/iagogfe/wiki-ingest:latest
              env:
                - name: MODEL
                  value: gemini-2.5-flash-lite

Idempotência antes de token

Idempotência aqui significa: rodar o ciclo de novo não refaz trabalho já feito. A regra mais barata do sistema: cada fonte carrega um hash, uma impressão digital do conteúdo (README, árvore de arquivos e manifestos; mudou o conteúdo, muda o hash). Se o hash não mudou desde a última execução, o worker nem chama o modelo, pula direto:

def precisa_ressintetizar(fonte: Fonte) -> bool:
    hash_atual = sha256(material(fonte))
    if hash_atual == fonte.hash_registrado:
        return False  # nada mudou → zero tokens
    return True

Não tenho um percentual exato de quanto isso corta em produção. O projeto tem poucos dias de operação, cedo demais pra esse número ser confiável. O que dá pra afirmar: sem esse gate, cada ciclo de 6h resintetizaria tudo de novo, mesmo o que não mudou uma linha. Com ele, o custo escala com a taxa de mudança do que você documenta, não com o tamanho da wiki.

Quanto custa de verdade

Nada de projeção, só número medido. Token é a unidade de cobrança dos modelos (pedaços de palavra); todo custo abaixo vem daí:

Cenário Custo real
Ingest em lote, modelo barato (gemini-2.5-flash-lite) ~US$ 0,015 por repositório
Reprocessamento de fonte que falhou, modelo mais forte (gemini-3.5-flash) ~US$ 0,21 por repositório (piloto de 20 repos, 0 falhas)
Upgrade completo: resincroniza tudo com o modelo mais forte disponível ~US$ 31, nota de qualidade sobe de ~80 pra ~90

O padrão: o modelo barato processa o grosso; quando falha ou o resultado sai abaixo do esperado, só essa fração vai pro modelo forte. Resincronizar tudo com o modelo forte custa ordens de magnitude mais, e a maior parte do ganho de qualidade não precisa disso. Por isso deixei como opção manual.

Nota de operação: nem toda falha aparece como “modelo ruim”. Num backfill (reprocessamento em lote de conteúdo antigo, de uma vez), 12 documentos de um lote de 167 falharam na primeira passada. Reprocessados com o modelo mais forte, 11 recuperaram; 1 precisou de um terceiro modelo. Antes de trocar de modelo por causa de uma taxa de falha, cheque se o problema é do pipeline: timeout, rate limit (limite de requisições por minuto imposto pelo provedor do modelo) ou fonte vazia. Trocar modelo não conserta bug de infraestrutura.

Runbook: o que quebra primeiro

Poucos dias de operação já bastaram pra encontrar o padrão de falha mais caro:

  1. Retry cego. Uma tentativa que falha por um motivo específico (ex.: o texto gerado referenciava algo que não existia) e é reenviada sem incorporar o erro repete o mesmo engano. A correção foi reinjetar a mensagem de erro no prompt da tentativa seguinte, em vez de só “tentar de novo”.
  2. Fontes que somem. Repositório arquivado ou renomeado deixa a página órfã. Fica marcada como pendente de reprocessamento em vez de apagada; decidir manualmente é mais barato que perder contexto.
  3. Confundir causa do modelo com causa do pipeline. O caso do backfill acima: antes de mexer no modelo, primeiro elimine timeout, rate limit e fonte vazia como explicação.

O que eu faria diferente

Começaria pela auditoria amostral, não pelo ingest. Gerar página é a parte fácil; o que dá confiança no processo é o juiz que audita depois. Um juiz semanal e amostral entrega isso por uma fração do custo de revisar página a página. E o hash de idempotência entraria como primeira linha de código, não como otimização deixada pra depois. É a diferença entre custo que escala com o tamanho da wiki e custo que escala com quanto ela realmente muda.