Reservas de assentos sem venda duplicada, uma réplica de leitura, PgBouncer e EXPLAIN
Dois fãs clicam nos mesmos dois assentos no mesmo milissegundo. Os dois recebem "201 Created". Quem vai ao show?
A: SELECT status → AVAILABLE B: SELECT status → AVAILABLE A: UPDATE … hold_id = A (locks the rows) B: UPDATE … hold_id = B (waits…) A: COMMIT → B's UPDATE runs → overwrites A
SELECT … ORDER BY id FOR UPDATE
UPDATE … WHERE version = :v
Variante: UPDATE … WHERE status = 'AVAILABLE' e conferir a contagem de linhas.
UPDATE … WHERE status = 'AVAILABLE'
INVENTORY_LOCKING=naive docker compose up -d inventory bash scripts/reset-inventory.sh && k6 run load/flash-sale.js
GET /inventory/audit/double-bookings
Pessimistic e optimistic: 0. Naive: diferente de 0.
Pessimista
Otimista
De qualquer forma, proteja as linhas quentes antes do banco: uma sala de espera (lab 7).
WITH expired AS ( UPDATE inventory.holds SET status = 'EXPIRED' WHERE id IN (SELECT id FROM inventory.holds WHERE status = 'ACTIVE' AND expires_at < now() LIMIT 500 FOR UPDATE SKIP LOCKED) RETURNING id) UPDATE inventory.seats SET status = 'AVAILABLE', hold_id = NULL WHERE hold_id IN (SELECT id FROM expired);
SKIP LOCKED
pg_basebackup -R
standby.signal
@Transactional(readOnly = true)
LazyConnectionDataSourceProxy
Uma réplica acrescenta capacidade de leitura. Ela não faz nada pelas escritas.
REPLICA_DELAY=1s docker compose up -d db-replica k6 run load/read-your-writes.js # stale_reads ↑
default_pool_size
Limit → Sort → Hash Join → Seq Scan on events (200,000 rows) ≈ 40 ms
CREATE INDEX events_starts_at_idx ON catalog.events (starts_at, id);
synchronous_commit
on
remote_apply
ANY 1
docker compose kill db
pg_promote()
db
pg_rewind
wal_log_hints
Uma pessoa às 10:01 é lenta demais: Patroni + etcd decidem o líder por consenso.
Roteiro: (1) o invariante; (2) a corrida do lost update; (3) três estratégias; (4) o teste da venda relâmpago e a auditoria; (5) réplica por streaming e roteamento; (6) read-your-writes; (7) PgBouncer; (8) índices.