Site feito com IA costuma nascer confuso, pesado, a um passo de estourar a fatura. Eu já tive isso na pele. No primeiro projeto, um único dia leu 4 bilhões de linhas no banco. Dicionário, milhares de artigos se apontando. Cloudflare D1, no plano de 5 dólares, inclui 25 bilhões de linhas lidas no mês. Passou disso, a documentação cobra 0,001 dólar por milhão. Na prática, outros 25 bilhões extras chegam perto de mais 5 dólares. O problema não era o preço unitário. Era o código pedindo varredura o dia inteiro.
No Aprender Palavras eu coloquei o ID da palavra na URL, juntei os 48 idiomas na mesma linha e busquei por igualdade nesse ID. A consulta da página deixa de ser um LIKE no slug, que muda de língua para língua. Relacionados usam IDs gravados na própria linha. No painel, 21 mil solicitações em 24 horas leram cerca de 1 milhão de linhas: algo como 47 linhas por visita, não bilhões.
- O site
- Aprender Palavras, dicionário em 48 idiomas
- O modelo
- Uma linha, muitas colunas, ID na URL
- O que evitar
- OFFSET, LIKE e slug diferente em cada língua
4 bilhões de linhas num dia, e o que o D1 realmente conta
Eu gosto de montar o SQLite do meu jeito: coluna, tabela, índice. A IA ajuda a desenhar e sugere índice. Ela não escolhe sozinha a leitura mais barata. Foi isso que quebrou o primeiro dicionário. Paginação com OFFSET. Busca com LIKE. Relacionado que varria a tabela. Cada visita virava varredura.
Na documentação do D1, linha lida é linha varrida, não linha devolvida. `SELECT *` numa tabela de 5 mil linhas conta 5 mil, mesmo que você só mostre dez. Filtro em coluna sem índice também conta o scan. Tamanho da linha e número de colunas não mudam a conta: uma linha de 1 KB e uma de 100 KB valem 1. Por isso empilhar 48 traduções na mesma linha não é o pesadelo que parece no phpMyAdmin. O que pesa é quantidade de linhas varridas.
OFFSET é o atalho que a IA oferece para “página 40”. Ele lê e descarta as linhas anteriores. LIKE em texto, no site aberto a robôs, percorre a tabela até achar o trecho. Igualdade em ID (ou em coluna indexada) vai no ponto. Relacionado, categoria e paginação pedem outra tabela ou uma lista de IDs já pronta — não um `WHERE titulo LIKE %termo%` a cada request.
Aprender Palavras: 48 línguas, Klingon incluso, duas tabelas
O dicionário está em aprenderpalavras.com. Quarenta e oito idiomas, Klingon no meio. Palavra, variação, frase, pronúncia com a voz do próprio navegador, imagem de placeholder. Na gravação eu falava em umas 500, 600 palavras no total, ainda sem indexar o catálogo inteiro. SEO programático de verbete, não de artigo longo.
Blog clássico resolve a rota com `slug = ?`. Aqui o slug muda: a mesma entrada em português, japonês ou árabe não compartilham a string da URL. Fazer 48 consultas, uma por língua, ou procurar o texto da palavra, voltava ao problema das bilhões. A solução não é bonita, e eu disse isso no vídeo: o ID da palavra vai na URL. A tag, a frase, o que estiver no caminho pode até mudar. O número não. A rota lê esse ID e a língua (`lang`) para escolher qual coluna mostrar como principal.
Não criei uma linha por idioma. São palavras e traduções, não ensaios. Uma linha, dezenas de colunas. Duas tabelas no D1: `palavras` e `frases`. Relacionado não ganhou tabela terceira. Na própria linha ficam os IDs das palavras ligadas e os IDs das frases. O JavaScript parte a célula na vírgula e monta a lista. Sinônimo não é perfeito de um idioma para o outro. Ainda assim a relação de ideias se segura.
- Inglês, espanhol, alemão, japonês, francês, português, russo, italiano
- Holandês, polonês, turco, chinês, persa, vietnamita, indonésio, coreano
- Árabe, húngaro, sueco, grego, hebraico, tailandês, búlgaro, esperanto, latim
- Klingon, azerbaijano, galego, tâmil — e o restante até fechar 48
Uma consulta da página, e as outras 46 linhas dos relacionados
O título do vídeo fala em uma consulta. É a consulta do verbete: ID na URL, uma linha, todos os idiomas nela. Relacionados disparam mais leituras, sempre por ID. Poucas. Não é LIKE. No dia em que eu gravei, o Worker tinha 1 milhão de linhas lidas e 51 mil consultas. Volume de visita ainda baixo, site sem ranqueio de verdade. 1 milhão contra 25 bilhões do plano de 5 dólares é folga.
Fui no painel ver as últimas 24 horas: 21 mil solicitações no banco. 1 milhão dividido por 21 mil dá cerca de 47 linhas por página. Eu tinha dito “uma linha”. A conta mostra a página mais os IDs de relacionados. Continua outro mundo perto de 4 bilhões. Ler 1 milhão de linhas, nesse tipo de tabela, é rápido. A Cloudflare inclui 25 bilhões no mês exatamente porque leitura, sozinha, não é o recurso raro — varredura sem índice é.
No plano gratuito do D1 o teto é 5 milhões de linhas lidas por dia, com reset à meia-noite UTC. Sem índice e com OFFSET, um dicionário pequeno estoura isso. Com ID na URL, o mesmo dicionário cabe no plano de 5 dólares sem drama.
Sem phpMyAdmin, com Wrangler, e o que a IA não faz sozinha
Servidor próprio tira o limite de linha lida e devolve a lista antiga: instalar banco, phpMyAdmin, painel, conexão remota. Eu conecto pelo Wrangler na conta Cloudflare e trato o D1 ali. Posso abrir um Worker só para manusear as ferramentas da conta. Não é o único jeito certo. É o jeito em que eu não pago um segundo SaaS de banco nem uma VPS acesa.
A liberdade do SQLite na IA é desenhar o esquema. A responsabilidade é a query. Peça igualdade em ID, índice na coluna filtrada, relacionados materializados. Peça para ela mostrar `rows_read` depois de cada consulta. Se o número parecer uma tabela inteira, a página ainda está varrendo.
O recorte deste vídeo é esse desenho. Não é um tutorial de tradução automática. É um dicionário em 48 línguas que não multiplica 48 bancos nem 48 scans. O resto — volume de verbetes, qualidade da tradução, Klingon como brincadeira — fica para o produto, não para a fatura.
- Coloque um identificador estável na URL; não busque o slug traduzido com LIKE.
- Guarde os idiomas da mesma entrada na mesma linha se o conteúdo for curto.
- Grave IDs de relacionados na linha; busque esses IDs, não um join textual.
- Proíba OFFSET em paginação pública; use cursor (`id > ?`) se precisar de lista.
- Leia `rows_read` no retorno do D1 e no painel depois de um dia de tráfego real.
Na sequência
- Meça linhas lidas por página antes de abrir o site a crawler.
- Troque LIKE e OFFSET por igualdade em coluna indexada.
- Não crie uma tabela por idioma só porque o site é multilíngue.
- Aceite ID na URL quando o slug não puder ser estável entre línguas.
- Separe a consulta do verbete da consulta dos relacionados e some as duas no painel.
Onde isso quebra
Pedir à IA para “paginar” e receber OFFSET sem questionar o scan.
Achar que 50 colunas explodem a fatura — no D1 a conta é por linha varrida.
Relacionar verbete por texto em 48 idiomas e chamar isso de uma consulta.
Ignorar o teto diário do plano gratuito (5 milhões de linhas lidas).
Perguntas frequentes
Uma linha com 48 colunas não fica pesada?
No D1, a cobrança de leitura não pesa o número de colunas. Pesam linhas varridas. Para verbete curto, uma linha larga me saiu mais barata do que 48 linhas por palavra.
Por que o ID aparece na URL?
Porque o slug muda de idioma. Sem um identificador estável, a busca vira texto. Com o ID, a rota acerta a linha mesmo se o restante do caminho estiver em japonês ou em árabe.
A página lê mesmo só uma linha?
A ficha da palavra, sim. Relacionados leem outras linhas por ID. No recorte que eu mostrei, deu cerca de 47 linhas por visita, não uma varredura da tabela.
Isso substitui um Postgres?
Substitui neste dicionário. Escrita concorrente pesada, login de usuário e relatório complexo são outro papo. O ponto do vídeo é: 48 idiomas não exigem 48 consultas.