Uma ligação ao Postgres não é um objeto: é um processo do lado do servidor, com a sua memória. Daí o preço, e daí a conta que quase ninguém faz antes de a base começar a recusar trabalho. E o erro não aparece onde se espera. A base não fica lenta — fica a dizer que já tem clientes suficientes, enqua…
Ler o artigo→Apagar uma linha é definitivo, e por isso quase ninguém apaga. Acrescenta-se uma coluna: um booleano, um valor por omissão, uma migração de uma linha. A decisão parece barata — e é, naquele dia. A conta vem depois, e vem em cada consulta que se escrever a partir dali. Este site passou por isto em se…
Ler o artigo→A pergunta aparece sempre à mesma altura: chegou o momento de testar o código que fala com a base, e há que decidir contra o quê. As respostas habituais são três — uma base em memória, um duplo escrito à mão, ou a coisa a sério num contentor. 1. O argumento que decide É este, e resolve a maior parte…
Ler o artigo→Quando uma consulta fica lenta, a tentação é adivinhar: acrescentar um índice, reescrever o join , trocar o IN por um EXISTS . O Postgres tem uma forma de responder em vez de se adivinhar — basta pedir-lhe o plano. Os exemplos que se seguem saíram da base deste site, com os números reais que ela dev…
Ler o artigo→É o problema de desempenho mais comum em qualquer aplicação com base de dados, e o mais difícil de ver a olho nu — porque o código que o causa parece perfeitamente razoável. const artigos = await db.artigos.findMany({ take: 20 }); for (const artigo of artigos) { artigo.autor = await db.autores.findU…
Ler o artigo→Uma consulta que respondia em 3 ms com mil linhas passa a demorar 900 ms com duzentas mil. O código não mudou. O que mudou foi a base ter passado do tamanho em que ler tudo era aceitável para o tamanho em que não é. 1. Perguntar ao Postgres o que ele faz EXPLAIN ANALYZE SELECT id, title FROM posts W…
Ler o artigo→Uma transferência bancária são duas operações: tirar de uma conta e pôr noutra. Se o processo morre entre as duas, o dinheiro desapareceu. É o exemplo de sempre, e explica para que serve uma transação. BEGIN; UPDATE contas SET saldo = saldo - 100 WHERE id = 1; UPDATE contas SET saldo = saldo + 100 W…
Ler o artigo→Escreve-se um LEFT JOIN precisamente para incluir as linhas que não têm par do outro lado. E depois acrescenta-se uma condição no WHERE que as deita todas fora, sem uma palavra de aviso. 1. O que se queria -- Todos os artigos, com o número de comentários aprovados SELECT a.titulo, count(c.id) AS com…
Ler o artigo→A paginação por OFFSET é a primeira que se aprende e a que está em quase todo o lado. Funciona bem — e degrada-se de uma forma que só se nota quando a tabela cresce. 1. O problema SELECT * FROM artigos ORDER BY publicado_em DESC LIMIT 20 OFFSET 10000; A base não salta 10 000 linhas: lê-as e deita-as…
Ler o artigo→O código faz-se voltar atrás com um git revert . O esquema da base, não. É a assimetria que explica todas as regras que se seguem. 1. Repor a imagem anterior não repõe o esquema Num deploy típico, as migrações correm no arranque e o código novo entra a seguir. Se algo correr mal e a imagem for rever…
Ler o artigo→Ao criar uma chave estrangeira, há uma decisão que muita gente deixa passar em branco: o que fazer aos filhos quando o pai é apagado. O silêncio também é uma escolha — e nem sempre a certa. 1. As três respostas -- Recusa apagar o pai enquanto houver filhos (a predefinição) ON DELETE RESTRICT -- Apag…
Ler o artigo→É uma das decisões mais baratas de tomar no início e mais caras de corrigir depois: em que fuso horário é que as datas ficam guardadas. A resposta é sempre UTC , e converte-se para o fuso de quem lê no último momento possível — na altura de mostrar. 1. Porque é que o fuso local não serve O servidor …
Ler o artigo→Aparece num site antigo, numa importação de CSV, num email. E não é um caractere estranho — é a mesma letra , lida com a tabela errada. 1. O que se passa Em UTF-8, o "ç" não cabe num byte: são dois, 0xC3 e 0xA7 . Se alguém ler esses dois bytes como se fossem Latin-1 — uma tabela onde cada byte é uma…
Ler o artigo→