Em produção
Pool, índices, o que medir, o que nunca fazer — e as decisões que só aparecem quando há carga.
O que funciona numa máquina com um usuário falha com cinquenta. Esta página é a lista do que muda.
Uma conexão por pedido não escala#
Abrir conexão com Postgres custa um aperto de mão TCP, autenticação e alocação de um processo do lado do servidor — dezenas de milissegundos. Por pedido, isso domina o tempo de resposta; e o servidor tem um teto de conexões que se atinge antes do teto de CPU.
dataforge
adopt Arcane.Forge as Forge
// O pool abre N conexoes e as empresta. 'conexao' devolve ao fim.
pool := Forge.pool(":memory:", 4)
// Sempre com 'with': uma conexao pegada e nao devolvida some do
// pool para sempre, e o 'with' devolve inclusive se o corpo falhar.
cycle i from 1 to 8:
with Forge.conexao(pool) as db:
Forge.consultar(db, "select 1 as um")
out $"oito pedidos, {pool.estado()['tamanho']} conexoes"O índice que falta#
A consulta que responde em 3 ms com mil linhas responde em 3 s com um milhão — e nada no código mudou. explain mostra o plano, e é ele que diz se o banco está varrendo a tabela.
dataforge
adopt Arcane.Database as Db
db := Db.connect(":memory:")
Db.execute(db, "create table pedido (id integer primary key, cliente_id integer, total real)")
cycle i from 1 to 500:
Db.execute(db, "insert into pedido (cliente_id, total) values (?, ?)",
[i % 50, i * 1.5])
// Sem indice: varredura.
antes := Db.explain(db, "select * from pedido where cliente_id = 7")
out $"antes: {antes}"
Db.execute(db, "create index idx_pedido_cliente on pedido (cliente_id)")
depois := Db.explain(db, "select * from pedido where cliente_id = 7")
out $"depois: {depois}"
Db.close(db)| Indexe | Porque |
|---|---|
| toda chave estrangeira | é por onde as relações do ORM buscam — e sem índice cada com() vira varredura |
a coluna de todo onde frequente | é o caso óbvio, e o mais esquecido |
a coluna de ordenar quando há limite | sem ele o banco ordena a tabela inteira para devolver dez linhas |
| não indexe tudo | todo índice é escrito em cada insert; uma tabela com oito índices escreve nove vezes |
O que nunca fazer#
| Nunca | O que acontece |
|---|---|
select * numa listagem | traz colunas grandes que a tela não usa, e o custo é de rede |
listagem sem limite | a forma mais comum de derrubar uma aplicação — e a paginar do ORM já vem com teto |
| consulta dentro de laço | o N+1: use `com()` |
order by por coluna vinda da URL, sem lista | injeção por identificador; o order_by do Arcane.Database recusa o que não parece nome |
migração sem down | desfazer exige editar o banco à mão |
| transação aberta esperando rede | ela segura locks enquanto o HTTP de terceiro demora |
O que medir#
| Medida | Como | O que ela denuncia |
|---|---|---|
| consultas por pedido | contar no log | N+1 — o número cresce com o tamanho da página |
| p95 da consulta | Arcane.Perfil | a média esconde a cauda, e é a cauda que o usuário sente |
| conexões em uso | pool.estado() | pool no teto = pedidos esperando |
| plano da consulta lenta | Db.explain | índice faltando |
| tamanho das tabelas | Db.stats | a tabela que cresce sem retenção |
Continue em Transações e pool e O banco em contêiner.