Inicio / Trilha 3 / Modulo 3.1
MODULO 3.1

🗄️ Integracao com Bancos de Dados

Conexoes de banco, queries otimizadas, ORMs modernos, migrations e estrategias de caching para dashboards de alta performance.

📚
6
Topicos
⏱️
75min
Duracao
📊
Avancado
Nivel
🎯
Backend
Tipo
1

🔌 Conexoes e Connection Pooling

Gerenciamento eficiente de conexoes

Connection Pooling e a tecnica de manter um conjunto de conexoes de banco de dados pre-estabelecidas e reusa-las entre requisicoes. Abrir uma conexao e uma operacao custosa - envolve handshake TCP, autenticacao e alocacao de memoria no servidor de banco.

Em dashboards com muitos usuarios simultaneos, cada requisicao que abre e fecha conexao causa latencia perceptivel. Um pool bem configurado pode reduzir o tempo de resposta de 200ms para 20ms ao eliminar o overhead de conexao.

📋 Parametros de um Connection Pool

min/max connections

Limites do pool. Min evita cold starts, max previne sobrecarga do banco.

connection timeout

Tempo maximo para obter conexao do pool. Evita travamentos.

idle timeout

Tempo que conexao ociosa permanece no pool antes de ser fechada.

max lifetime

Tempo maximo de vida de uma conexao. Previne leaks e problemas de memoria.

🔍 Fluxo de Connection Pool

┌─────────────────────────────────────────────────────────────┐
│                     APPLICATION                              │
│  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐          │
│  │ Req1 │  │ Req2 │  │ Req3 │  │ Req4 │  │ Req5 │          │
│  └──┬───┘  └──┬───┘  └──┬───┘  └──┬───┘  └──┬───┘          │
│     │         │         │         │         │              │
│     └─────────┴─────────┴─────────┴─────────┘              │
│                         │                                   │
│              ┌──────────▼──────────┐                        │
│              │   CONNECTION POOL   │                        │
│              │   ┌───┐ ┌───┐ ┌───┐ │                        │
│              │   │C1 │ │C2 │ │C3 │ │  (min: 3, max: 10)    │
│              │   └───┘ └───┘ └───┘ │                        │
│              └──────────┬──────────┘                        │
│                         │                                   │
│              ┌──────────▼──────────┐                        │
│              │     DATABASE        │                        │
│              └─────────────────────┘                        │
└─────────────────────────────────────────────────────────────┘

SEM POOL: Req → Open → Query → Close → Response (200ms)
COM POOL: Req → Borrow → Query → Return → Response (20ms)

💻 Codigo: Configuracao de Pool com pg

import { Pool } from 'pg';

// Configuracao recomendada para dashboards
const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  port: 5432,

  // Pool configuration
  min: 2,                    // Conexoes minimas sempre prontas
  max: 20,                   // Maximo de conexoes simultaneas
  idleTimeoutMillis: 30000,  // 30s ocioso = fechar conexao
  connectionTimeoutMillis: 5000, // 5s para obter conexao

  // Prepared statements
  statement_timeout: 10000,  // Queries longas = erro
});

// Uso em uma rota de API
export async function GET(request: Request) {
  const client = await pool.connect();
  try {
    const result = await client.query(
      'SELECT * FROM metrics WHERE date >= $1',
      [new Date().toISOString()]
    );
    return Response.json(result.rows);
  } finally {
    client.release(); // SEMPRE devolver ao pool!
  }
}

💡 Dica Pratica

Regra do max connections: Seu max de pool deve ser menor que o max_connections do PostgreSQL. Se 10 instancias da app usam pool de 20, voce precisa de pelo menos 200 conexoes no banco. Use a formula: pool_max * app_instances < db_max_connections

2

⚡ Queries Otimizadas

Performance de consultas SQL

Queries otimizadas sao consultas SQL escritas para minimizar tempo de execucao e uso de recursos. O mesmo resultado pode ser obtido de formas muito diferentes - uma query mal escrita pode ser 1000x mais lenta.

Em dashboards, queries lentas sao o principal gargalo de performance. Um dashboard que demora 10 segundos para carregar e abandonado pelos usuarios. Otimizacao de queries e uma habilidade critica.

⚠️ Problemas Comuns de Performance

N+1 Problem

1 query para lista + N queries para detalhes de cada item.

-- RUIM: 1 + 100 queries SELECT * FROM orders; -- 1 query SELECT * FROM items WHERE order_id = 1; -- 100x
SELECT * (Overfetching)

Buscar colunas que nao serao usadas desperica I/O e memoria.

-- RUIM: busca blob de 10MB que nao sera usado SELECT * FROM products;
Funcoes em WHERE

Funcoes na coluna impedem uso de indices.

-- RUIM: nao usa indice WHERE YEAR(created_at) = 2024

Solucoes Otimizadas

JOIN ao inves de N+1
-- BOM: 1 query com JOIN SELECT o.*, i.* FROM orders o LEFT JOIN items i ON i.order_id = o.id;
Selecionar apenas colunas necessarias
-- BOM: apenas o necessario SELECT id, name, price FROM products;
Range query amigavel a indice
-- BOM: usa indice em created_at WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'

🔍 Usando EXPLAIN ANALYZE

-- Analisar plano de execucao
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 123
  AND created_at >= '2024-01-01';

-- Output (exemplo):
-- Index Scan using idx_orders_customer_date on orders
--   (cost=0.43..8.45 rows=1 width=156)
--   (actual time=0.023..0.025 rows=5 loops=1)
-- Planning Time: 0.095 ms
-- Execution Time: 0.041 ms  ← MUITO BOM!

-- Se mostrar "Seq Scan" com muitos rows = problema!
-- Seq Scan on orders (cost=0.00..20000.00 rows=500000)
-- Execution Time: 3500.123 ms  ← MUITO RUIM!

✅ Fazer vs ❌ Evitar

✅ Fazer
  • • Usar EXPLAIN antes de queries complexas
  • • Limitar resultados com LIMIT
  • • Usar indices para colunas em WHERE
  • • Paginar resultados grandes
❌ Evitar
  • • SELECT * em tabelas grandes
  • • Queries sem WHERE em prod
  • • Funcoes em colunas indexadas
  • • LIKE '%termo%' (nao usa indice)
3

🛠️ ORMs - Prisma e Drizzle

Abstracoes modernas de banco de dados

ORMs (Object-Relational Mappers) permitem interagir com o banco de dados usando codigo TypeScript/JavaScript ao inves de SQL puro. Modernos como Prisma e Drizzle oferecem type-safety completo.

A grande vantagem e que erros de tipagem sao capturados em tempo de compilacao, nao em runtime. Se a coluna "email" nao existir na tabela, o TypeScript vai reclamar imediatamente.

⚖️ Prisma vs Drizzle

Aspecto Prisma Drizzle
Schema Arquivo .prisma proprio TypeScript nativo
Query Builder API propria, abstrata SQL-like, proximo do metal
Bundle Size ~2MB (engine binario) ~50KB
Performance Boa (engine Rust) Excelente (zero overhead)
Ideal para Apps complexos, DX Serverless, edge, performance

💻 Codigo: Prisma

// prisma/schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  orders    Order[]
  createdAt DateTime @default(now())
}

model Order {
  id        Int      @id @default(autoincrement())
  userId    Int
  user      User     @relation(fields: [userId], references: [id])
  total     Decimal
  status    String
}

// Uso no codigo
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

// Query com type-safety completo
const usersWithOrders = await prisma.user.findMany({
  where: { email: { contains: '@empresa.com' } },
  include: {
    orders: {
      where: { status: 'completed' },
      orderBy: { total: 'desc' }
    }
  }
});

💻 Codigo: Drizzle

// db/schema.ts
import { pgTable, serial, text, decimal, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').defaultNow()
});

export const orders = pgTable('orders', {
  id: serial('id').primaryKey(),
  userId: serial('user_id').references(() => users.id),
  total: decimal('total', { precision: 10, scale: 2 }),
  status: text('status')
});

// Uso - muito proximo de SQL
import { db } from './db';
import { users, orders } from './schema';
import { eq, like } from 'drizzle-orm';

const result = await db
  .select()
  .from(users)
  .leftJoin(orders, eq(users.id, orders.userId))
  .where(like(users.email, '%@empresa.com%'));

💡 Dica Pratica

Para dashboards em serverless/edge: Use Drizzle. O bundle size pequeno e crucial em plataformas como Vercel Edge Functions ou Cloudflare Workers que tem limite de tamanho. Prisma e melhor em Node.js tradicional onde o binary engine nao e problema.

4

📦 Migrations e Versionamento

Evolucao controlada do schema

Migrations sao scripts versionados que alteram o schema do banco de dados de forma incremental e reversivel. Assim como Git versiona codigo, migrations versionam a estrutura do banco.

Sem migrations, times acabam com bancos de dados em estados diferentes. "Funciona na minha maquina" vira um problema real quando schemas divergem entre dev, staging e producao.

📅 Timeline de Migrations

1
20240101_create_users

CREATE TABLE users (id, email, name)

2
20240115_add_orders

CREATE TABLE orders (id, user_id, total)

3
20240201_add_user_avatar

ALTER TABLE users ADD COLUMN avatar_url

💻 Codigo: Migration com Prisma

# Criar nova migration apos alterar schema
npx prisma migrate dev --name add_user_role

# Isso gera arquivo em:
# prisma/migrations/20240201123456_add_user_role/migration.sql

-- migration.sql gerado automaticamente:
ALTER TABLE "users" ADD COLUMN "role" TEXT DEFAULT 'user';

# Aplicar migrations em producao
npx prisma migrate deploy

# Rollback (manual - Prisma nao tem rollback automatico)
# Criar nova migration que reverte a alteracao

⚠️ Cuidados com Migrations em Producao

  • NUNCA edite migrations ja aplicadas em producao
  • • Migrations devem ser idempotentes (rodar 2x = mesmo resultado)
  • • Adicionar colunas: sempre com DEFAULT ou nullable
  • • Remover colunas: primeiro pare de usar, depois remova
  • • Renomear: crie nova coluna, migre dados, remova antiga
5

🔍 Indices e Otimizacao

Estruturas para busca eficiente

Indices sao estruturas de dados que aceleram buscas em tabelas. Funcionam como o indice de um livro - ao inves de ler todas as paginas, voce vai direto ao topico.

O tradeoff: indices ocupam espaco e tornam INSERTs mais lentos. Cada insert precisa atualizar todos os indices. Em tabelas de leitura pesada (dashboards!), indices sao essenciais.

📊 Tipos de Indices

B-tree (Padrao)

Para igualdade e range queries. O mais comum.

WHERE status = 'active'
Hash

Apenas igualdade, mais rapido para isso.

WHERE id = 123
GIN (Generalized Inverted)

Para arrays, JSONB, full-text search.

WHERE tags @> ARRAY['react']
BRIN (Block Range)

Para dados ordenados (timestamps). Muito pequeno.

WHERE created_at > '2024-01-01'

💻 Codigo: Criando Indices

-- Indice simples
CREATE INDEX idx_users_email ON users(email);

-- Indice composto (ordem importa!)
CREATE INDEX idx_orders_customer_date
ON orders(customer_id, created_at DESC);

-- Indice parcial (apenas parte dos dados)
CREATE INDEX idx_orders_pending
ON orders(created_at)
WHERE status = 'pending';

-- Indice para JSONB
CREATE INDEX idx_products_metadata
ON products USING GIN(metadata);

-- Verificar indices existentes
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'orders';

-- Verificar tamanho dos indices
SELECT pg_size_pretty(pg_indexes_size('orders'));

-- Remover indice nao usado
DROP INDEX idx_unused;

💡 Dica Pratica

Indices para dashboards: Crie indices para todas as colunas usadas em WHERE, ORDER BY e JOIN. Para queries de data, use indice DESC se voce sempre busca "mais recentes primeiro". Use BRIN para tabelas de logs/eventos ordenados por tempo - sao 1000x menores que B-tree.

6

💾 Caching de Dados

Redis e estrategias de cache

Caching e armazenar dados frequentemente acessados em memoria rapida (Redis, Memcached) para evitar consultas repetidas ao banco. E a forma mais eficaz de escalar leitura.

Para dashboards, o impacto e enorme: query de 500ms no banco vira 5ms no cache. Com muitos usuarios acessando os mesmos dados, cache reduz carga no banco em 90%+.

🔄 Estrategias de Cache

Cache-Aside (Lazy Loading)

App busca no cache primeiro. Se miss, busca no DB e popula cache.

1. GET cache:users:123 → MISS
2. SELECT * FROM users WHERE id = 123
3. SET cache:users:123 = resultado
Write-Through

Toda escrita no DB tambem atualiza o cache.

1. INSERT INTO users (name) VALUES ('Ana')
2. SET cache:users:124 = novo registro
Stale-While-Revalidate

Retorna cache stale imediatamente, atualiza em background.

1. GET cache:metrics → dados de 1min atras (stale)
2. Retorna stale para usuario
3. Background: atualiza cache com dados novos

💻 Codigo: Cache com Redis + Node.js

import { Redis } from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Funcao generica de cache
async function getCached(
  key: string,
  fetcher: () => Promise,
  ttlSeconds = 300 // 5 minutos padrao
): Promise {
  // Tentar cache primeiro
  const cached = await redis.get(key);
  if (cached) {
    return JSON.parse(cached);
  }

  // Cache miss - buscar dados
  const data = await fetcher();

  // Salvar no cache
  await redis.setex(key, ttlSeconds, JSON.stringify(data));

  return data;
}

// Uso em rota de API
export async function GET() {
  const metrics = await getCached(
    'dashboard:metrics:today',
    async () => {
      // Query lenta ao banco
      return await prisma.metric.findMany({
        where: { date: new Date() }
      });
    },
    60 // TTL de 1 minuto para dados frescos
  );

  return Response.json(metrics);
}

// Invalidar cache quando dados mudam
export async function POST(req: Request) {
  await prisma.metric.create({ data: req.body });

  // Invalidar cache relacionado
  await redis.del('dashboard:metrics:today');

  return Response.json({ success: true });
}

⏱️ Guia de TTL por Tipo de Dado

Tipo de Dado TTL Motivo
Metricas em tempo real 30s - 1min Precisam ser frescos
Dados de hoje 5 - 15min Mudam frequentemente
Dados historicos 1h - 24h Nao mudam mais
Configuracoes/metadados 1h+ Raramente mudam

✅ Fazer vs ❌ Evitar

✅ Fazer
  • • Cache dados que nao mudam frequentemente
  • • Usar TTL apropriado ao tipo de dado
  • • Invalidar cache ao atualizar dados
  • • Monitorar hit rate do cache
❌ Evitar
  • • Cache sem TTL (dados eternos)
  • • Esquecer de invalidar em updates
  • • Cache de dados sensiveis sem criptografia
  • • Depender 100% do cache (falha = erro)

📝 Resumo do Modulo

Connection Pooling - Reutilizar conexoes para evitar overhead de conexao
Queries Otimizadas - EXPLAIN, evitar N+1, usar indices corretamente
ORMs Modernos - Prisma para DX, Drizzle para performance/serverless
Migrations - Versionamento de schema, nunca editar migrations aplicadas
Indices - B-tree, GIN, BRIN - escolher tipo certo para cada caso
Caching - Redis, TTL apropriado, invalidacao ao atualizar