Performance

Escopo: JavaScript. Visão transversal: shared/platform/performance.md.

Estas diretrizes valem para os hot paths (caminhos quentes, o código executado em grande volume ou com frequência alta): laços apertados, handlers de requisição, processamento de stream. Fora deles, escolha a legibilidade. A premature optimization (otimização prematura) troca clareza por um ganho que muitas vezes nem existe. Meça primeiro, otimize depois.

Conceitos fundamentais

ConceitoO que é
hot path (caminho quente)Trecho de código executado em volume ou frequência alta; otimizar aqui rende
cold path (caminho frio)Trecho raro (inicialização, erro); aqui a legibilidade vale mais que a velocidade
V8 (engine do Chrome/Node.js)Motor JavaScript do Node.js e Chrome; otimiza quando o código é previsível e mantém formas estáveis de objeto
JIT (Just-In-Time, Compilação Sob Demanda)Compilador que traduz JS para código de máquina em tempo de execução
allocation (alocação)Criação de objeto na heap; pressão de alocação custa GC
GC (Garbage Collection · Coleta de Lixo)Liberação automática de memória; pausas afetam latência
Big O (notação assintótica)Custo do algoritmo em função do tamanho da entrada (O(n), O(n²), O(log n))
profiling (perfilamento)Medição empírica de onde tempo e memória são gastos; node --prof, clinic, Chrome DevTools
stream (fluxo)Leitura/escrita em pedaços; evita carregar arquivo inteiro na memória

for...of no lugar de forEach em hot paths

forEach chama um callback (função de retorno) para cada item da lista: cada volta paga o custo de mais uma chamada de função. O laço for...of não usa callback: ele pega o próximo item e roda o corpo do laço direto. Em um hot path, essa diferença por item soma.

❌ Ruim: callback alocado por iteração
function calculateTotalRevenue(orders) {
  let total = 0;
  orders.forEach((order) => {
    total += order.amount;
  });

  return total;
}
✅ Bom: for...of sem overhead de callback
function calculateTotalRevenue(orders) {
  let total = 0;
  for (const order of orders) {
    total += order.amount;
  }

  return total;
}

Set para testar pertencimento

Array.includes() percorre o array do início ao fim: O(n) por verificação. Set.has() responde em O(1) via hash. Para uma lista fixa consultada com frequência, crie o Set uma vez no módulo e reutilize a cada chamada.

❌ Ruim: Array.includes percorre tudo a cada chamada
const PREMIUM_CATEGORIES = ["electronics", "jewelry", "watches"];

function filterPremiumProducts(products) {
  const premiumProducts = products.filter((product) =>
    PREMIUM_CATEGORIES.includes(product.category)
  );

  return premiumProducts;
}
✅ Bom: Set.has resolve em O(1)
const PREMIUM_CATEGORIES = new Set(["electronics", "jewelry", "watches"]);

function filterPremiumProducts(products) {
  const premiumProducts = products.filter((product) =>
    PREMIUM_CATEGORIES.has(product.category)
  );

  return premiumProducts;
}

UUID v7 para chaves ordenadas no índice

crypto.randomUUID() gera um UUID (Universally Unique Identifier · Identificador Único Universal) v4, que é aleatório. Como o valor é imprevisível, cada nova linha entra em um ponto qualquer do índice, e o banco precisa remanejar as páginas para abrir espaço no meio. O UUID v7 é time-ordered (ordenado por tempo): um valor gerado depois é sempre maior que o anterior, então cada inserção vai para o fim do índice, em sequência, sem remanejar nada. Veja o impacto no banco em sql/conventions/advanced/performance.md.

A versão fica escrita no próprio valor. O UUID tem cinco grupos separados por hífen, e o primeiro caractere do terceiro grupo é o número da versão: um UUID v4 sempre mostra 4 nessa posição, e um UUID v7 sempre mostra 7. Olhar para um identificador já diz qual dos dois você tem em mãos.

❌ Ruim: crypto.randomUUID() gera v4 aleatório e fragmenta o índice
function createOrder(request) {
  // 3f2a9c71-8b4d-4e6f-a1c3-7d5e9b2f4a80
  //               ^ v4: aleatório, cada linha cai em um ponto qualquer do índice
  const orderId = crypto.randomUUID();

  return saveOrder({ id: orderId, ...request });
}
✅ Bom: UUID v7 nasce em ordem de tempo e entra sempre no fim do índice
import { v7 as uuidv7 } from "uuid";

function createOrder(request) {
  // 019842f0-6c1a-7b3e-9d4f-2a1b3c4d5e6f
  //               ^ v7: começa pelo horário, então cresce a cada chamada
  const orderId = uuidv7();

  return saveOrder({ id: orderId, ...request });
}

Concatenar strings em loop

Concatenar com + ou com template literal dentro de um loop aloca uma nova string a cada iteração, porque uma string em JavaScript não pode ser alterada depois de criada. Para montar uma string aos poucos, acumule os pedaços em um array e chame join() no final: uma única alocação, um resultado só.

❌ Ruim: nova string alocada por iteração
function buildOrderReport(orders) {
  let report = "";
  for (const order of orders) {
    report += `#${order.id}: ${order.customer}, ${order.total}\n`;
  }

  return report;
}
✅ Bom: array + join, uma alocação no final
function buildOrderReport(orders) {
  const lines = [];
  for (const order of orders) {
    lines.push(`#${order.id}: ${order.customer}, ${order.total}`);
  }

  const report = lines.join("\n");
  return report;
}

DoDocs v3.7.0 · Desenvolvido por @thiagocajadev · Baseado no trabalho de pmndrs/docs · Poimandres.