Roblox-Postgres-Study
One-sentence rule: every answer — state the mechanism, the first measurement you would pull, and the internals trap you ruled out, in that order.
Role essence: build the multi-tenant database platform at elastic scale — ship engine features (indexing, storage formats, WAL/replication protocols, sharding, planner), own the control plane (elastic scaling, autonomous healing, zero-downtime schema/tenant moves), kill tail latency with kernel-level tracing, mentor, and push fixes upstream.
Today's drill
12-day index
Listen while you move
10 episodes (E1–E10). Tap Read aloud to use your phone's voice, or open the script. After each episode, pause and say the key line out loud. Voiced MP3s land with the media build; the player appears automatically when its file exists.
Scenario drill (timer on, narrate aloud)
Pick a scenario. Mechanism → Measure → Move. Mitigate first when players are down. Name the metric and your depth tag (Debug / Recognize / Do-not-claim).
Worked examples (read cold, predict, then check)
1 · Bloat is back after last night's VACUUM
A 40M-row marketplace table shows seq-scan growth a day after VACUUM FULL. Dead-tuple count climbs again by morning. What mechanism explains the regrowth, what do you measure first, and what stay-fixed change stops the cycle?
Answer: repeated HOT-ineligible updates (indexed columns churned) create dead versions faster than autovacuum reclaims. First data: pg_stat_user_tables n_dead_tup + last_autovacuum vs last_autoanalyze, plus pgstattuple bloat ratio. Stay-fixed: cut indexed-column churn or add covering design + tune autovacuum scale factors for this table. Trap: confusing bloat (space/scan cost) with wraparound (correctness emergency).
2 · Replica ten seconds behind at peak
Read-your-write complaints spike; pg_stat_replication shows replay_lag 10s but flush_lag near zero. Is this network, disk, or apply load — and what proves it?
Answer: replay (apply) lag, not network or flush — the standby receives fine but cannot apply fast enough under peak write load. Prove with sent/flush/replay LSN gaps; fix = spread batch load, check long transactions holding replay, consider sync only where RPO demands it. Trap: claiming zero RPO on an async replica.
3 · Plan flipped overnight, nothing changed
Same query, same schema, nested loop today where hash join won yesterday. Stats job was skipped during a deploy freeze. State the mechanism, the one command, and what you re-verify.
Answer: stale pg_statistic misestimate picks the wrong join shape. Command: ANALYZE the tables (never enable_* hacks in production). Re-verify: EXPLAIN ANALYZE row-estimate vs actual + p95 for that query hash. Trap: pinning the plan instead of fixing the statistics.
Aloud scripts (E1–E10 texts + daily reps)
- E1 — PG architecture
- E2 — MVCC
- E3 — WAL + replication
- E4 — Indexing
- E5 — Planner + optimizer
- E6 — RocksDB / LSM
- E7 — Raft/Paxos consensus
- E8 — K8s control plane
- E9 — Tail latency
- E10 — Design + behavioral
Daily say-aloud reps
- "Mechanism, Measure, Move — path first, number second, fix that stays fixed."
- "Durability comes from WAL; checkpoints bound recovery time."
- "Writers don't block readers; dead tuples cost until vacuumed."
- "Sync replication trades latency for zero data loss."
- "A minority partition stops accepting writes — that is safety."
- "Averages lie; fix the tail by measuring the tail."
Code drills (reading comprehension — narrate the method, tag your depth)
Drill 1 · EXPLAIN read (Debug)
Given EXPLAIN (ANALYZE, BUFFERS) showing Seq Scan on 40M rows, Buffers shared hit=12 read=380k, and a nested loop over it — narrate: top line first, the one fix with biggest payoff, and what you re-measure.
Read: full-table scan feeds the loop, ~380k buffer reads = the cost. Fix: composite/covering index on filter+join columns (equality first, range last). Re-measure: execution time + buffers hit ratio for that query hash. Trap: reading cost numbers as time; and ANALYZE first before DDL in case stats are stale. Depth: Debug — you ran the plan yourself.
Drill 2 · C++ reading comprehension — buffer eviction (Recognize)
// Reading only: describe the path, do not claim authorship.
for (page : clock_sweep(shared_buffers)) {
if (page.usage_count > 0) { page.usage_count--; continue; }
if (page.pin_count == 0 && !page.dirty) return evict(page);
}
// Q: what liveness bug appears if usage_count never decays on a hot scan?
Name the mechanism, the measurement that would prove the failure mode, and the one-line fix direction — as a reader, not the author.
Read: clock-sweep gives hot pages a second chance via usage_count; without decay every scanned page stays pinned-eligible and eviction stalls on dirty/pinned pages. Proof: buffer hit ratio collapse + eviction stalls in wait events. Fix direction: restore decay on each sweep pass. Depth: Recognize — "I read the structure and describe the path; I have not implemented at this layer." Never claim C++ engine implementation.
Drill 3 · C++ reading comprehension — WAL insert path (Do-not-claim)
// Reading only: describe the path, do not claim authorship. xid = assign_xid(); lsn = wal_reserve(len); memcpy(wal_buf(lsn), record, len); wal_publish(lsn); // what breaks if publish is reordered before reserve? // Q: which interleaving loses durability on crash?
State which interleaving loses durability, the measurement that catches it, and the honesty line.
Read: publishing an LSN before the reservation is visible lets a concurrent flush believe the record is complete — a crash then replays a torn record and durability is lost. Catch: WAL-replay / crash-recovery test with concurrent inserters + checksum failures. Honesty line: Do-not-claim — method narration is the claim, engine implementation is not. Redirect to learning plan.
DB practice (say the query plan before the answer)
- Top-SQL read: open the top-SQL view, top query by total time — state wait event, then EXPLAIN the leader. Do this for 3 queries without notes.
- Bloat-and-vacuum lab: create bloat with repeated updates, measure n_dead_tup + pgstattuple, VACUUM, compare before/after numbers.
- Replica lag drill: read pg_stat_replication sent/flush/replay LSNs; classify receive vs flush vs replay lag in 30 seconds.
- Broken-plan triage: stale-stats plan → ANALYZE → re-verify plan change; write the misestimate in one sentence.
- pgbench tail read: run pgbench with latency histogram, name the dominant p99 source, write the mitigation with before/after numbers.
- Quorum speed round: 3 nodes → ? · 5 nodes → ? · minority partition writes? · failover without fencing? — answer each in under 5 seconds.
Cheat sheet (condensed — full text in repo)
Method
Mechanism → Measure → Move. Path first, number second, fix that stays fixed. Say your depth tag: Debug / Recognize / Do-not-claim.
PG internals ladder
Plan surface → access methods → MVCC → WAL/recovery → replication → planner (Recognize) → buffer/lock (Recognize) → engine C++ (Do-not-claim). Trap: bloat is space, wraparound is correctness.
Quorum lines (memorize)
3 nodes → quorum 2, tolerate 1 · 5 nodes → quorum 3, tolerate 2. Minority cannot commit. Failover without fencing = split-brain.
K8s method
StatefulSet + PVC per Pod · PDBs + readiness gates (lag-bounded) · drain → fence → promote → verify → cut → rollback ready.
Tail latency
Read p50/p99/p999 + histogram, never the mean. Non-query p99: checkpoints, autovacuum, compaction, GC, CPU steal, retransmits, replica lag.
Decision matrix
MVCC readers → PG heap+B-tree · covering lookups → composite index · zero-RPO → sync replication · read scale → async replicas · write-heavy KV → RocksDB/LSM · multi-region consensus → Raft 2f+1 · elastic multi-tenant → sharded PG + control plane.
Mock loop (Day 12 — narrate Mechanism–Measure–Move at each step)
- Round 1 · Coding (20 min): EXPLAIN read + plan fix on a slow marketplace query; then read one C++-shaped snippet (buffer/WAL path) as comprehension only — mechanism, proof measurement, fix direction, depth tag aloud.
- Round 2 · System design (20 min): multi-tenant sharded PG at Roblox scale — marketplace buy/sell/rent whiteboard. Open with shard key, defend cross-shard transactions + failover/fencing, name tenant-isolation and elastic-scale story.
- Round 3 · Deep-dive internals (15 min): MVCC/WAL/index path — xmin/xmax visibility, LSN replay, B-tree descent. Say where Debug stops and Recognize begins; never claim engine C++ implementation.
- Round 4 · Behavioral STAR (15 min): any 2 stories from the STAR bank with real metrics. "I", not "we". X-as-measured-by-Y-by-doing-Z + stay-fixed.
Gap line (say calm): "My gap is C++ engine implementation versus Oracle/DBA internals — I read engine code for method and narrate the path precisely, and where the role needs implementation depth I'll say Recognize versus Debug out loud."