Part 04 · PostgreSQL & Redis · 4.2
49 câu hỏi phỏng vấn PostgreSQL và Redis
Trả lời theo cấu trúc: requirement hoặc invariant → cơ chế → trade-off/failure mode → cách kiểm chứng. Mở từng câu sau khi tự trả lời thành tiếng; đừng chỉ học thuộc định nghĩa.
Cách luyện: câu cơ bản trong 60–90 giây; câu tình huống trong 3–5 phút kèm timeline, SQL hoặc topology. Với câu chọn công nghệ, luôn nêu điều kiện khiến bạn đổi quyết định.
Quan hệ dữ liệu và lựa chọn công nghệ
1. One-to-many khác many-to-one?
Đó là hai hướng đọc của cùng một quan hệ. Department → Employee là one-to-many; Employee → Department là many-to-one. Foreign key
department_id nằm ở employee, tức phía many.2. Vì sao one-to-one cần UNIQUE?
Foreign key chỉ đảm bảo giá trị tham chiếu tồn tại. Không có
UNIQUE trên foreign key, nhiều rows vẫn có thể trỏ cùng parent nên quan hệ thực tế là many-to-one. Shared primary key là lựa chọn khác khi child phụ thuộc hoàn toàn parent.3. Owning side trong JPA là gì?
Là phía điều khiển mapping foreign key hoặc join table. Trong bidirectional one-to-many, child
@ManyToOne có @JoinColumn thường là owning side; parent @OneToMany(mappedBy=...) là inverse side. mappedBy trỏ tới tên field Java và hai phía object graph vẫn cần được đồng bộ.4. Khi nào không nên dùng @ManyToMany trực tiếp?
Khi bảng nối có state như status, role, quantity, timestamps, audit fields hoặc lifecycle riêng. Khi đó map nó thành entity với hai quan hệ many-to-one để biểu diễn invariant, update và query rõ ràng hơn.
5. B-tree index khác Hash index?
B-tree giữ key theo thứ tự nên hỗ trợ equality, range, ordering và leftmost-prefix của composite index. PostgreSQL Hash index chủ yếu hỗ trợ equality, không phục vụ range/order và không dùng cho unique constraint. B-tree là mặc định; chỉ chọn Hash cho workload equality chuyên biệt sau
EXPLAIN (ANALYZE, BUFFERS) và benchmark. Nhận định Hash index PostgreSQL không WAL/crash-safe là thông tin cũ.6. Khi nào chọn MongoDB thay vì PostgreSQL?
Chọn MongoDB khi dữ liệu tự nhiên là aggregate/document thường đọc-ghi cùng nhau, nested shape biến đổi và access pattern ít join. Chọn PostgreSQL khi cần relations, foreign key/constraints, multi-table transactions, reporting và ad-hoc query mạnh. MongoDB vẫn cần schema validation, indexes và migration; nếu thường xuyên cần transaction xuyên nhiều collections, hãy xem lại aggregate boundary hoặc lựa chọn database.
7. Nên cache dữ liệu gì trong Redis và dùng strategy nào?
Cache dữ liệu read-heavy, tốn truy vấn/tính toán, có source of truth và chấp nhận stale bounded như product detail, config hoặc aggregate. Với cache-aside: read cache → miss đọc DB → set TTL; write commit DB rồi invalidate. Key có namespace/version/tenant, TTL kèm jitter, negative cache ngắn; chống stampede bằng single-flight, bounded lease hoặc stale-while-revalidate. Khi Redis lỗi cần bounded fallback để DB không sập.
PostgreSQL nền tảng và vận hành
8. MVCC giải quyết gì?
MVCC cho readers và writers ít chặn nhau bằng tuple versions cùng snapshots. Nó không loại mọi anomaly hoặc lock; đổi lại cần VACUUM, quản lý bloat và tránh long transaction giữ old versions.
9. Read Committed khác Repeatable Read và Serializable?
Trong PostgreSQL, Read Committed lấy snapshot mới cho mỗi statement; Repeatable Read giữ transaction snapshot và ngăn cả phantom theo implementation PostgreSQL. Serializable còn theo dõi dependency để abort khi không thể tương đương một serial order. Application phải retry toàn transaction khi serialization failure.
10. Khi nào dùng optimistic hay pessimistic lock?
Optimistic version/compare-and-set phù hợp khi conflict hiếm và retry rẻ. Pessimistic row lock phù hợp khi contention cao hoặc action khó retry, nhưng tăng wait, deadlock và connection occupancy. Chọn từ invariant, conflict rate, transaction duration và retry semantics.
11. WAL dùng làm gì?
WAL ghi redo information trước khi dirty data page cần được flush, hỗ trợ crash recovery, physical replication và PITR. WAL mô tả physical/database changes, không phải application event log có business semantics.
12. Vì sao index làm write chậm?
Mỗi insert/update/delete có thể phải duy trì nhiều index entries, phát sinh WAL, page split, storage/cache pressure và maintenance/vacuum cost. Index chỉ đáng có khi access path hoặc uniqueness guarantee bù được write amplification.
13. Composite index chọn thứ tự cột thế nào?
Bắt đầu từ query predicates và ordering: equality columns thường đứng trước range/order columns, nhưng selectivity, skip-scan khả năng, include columns và workload tổng thể đều ảnh hưởng. B-tree tận dụng leading columns; không có rule “cột selectivity cao nhất luôn đứng đầu”.
14. Đọc EXPLAIN ANALYZE bắt đầu từ đâu?
Đọc từ child nodes lên parent; so estimated với actual rows, nhân cost theo loops, kiểm tra scan/join method, buffers, heap fetches và sort/hash spill. Sai cardinality thường kéo theo join order/algorithm sai. Nhớ rằng
ANALYZE thực thi query thật.15. Offset hay keyset pagination?
Offset đơn giản và nhảy trang dễ, nhưng page sâu phải scan/discard nhiều rows và dễ drift khi dataset đổi. Keyset dùng stable unique ordering như
(created_at, id), nhanh và ổn định hơn cho feed lớn nhưng khó nhảy tới page tùy ý; cursor phải encode đủ sort keys và direction.16. Replica có phục vụ read-after-write không?
Không mặc định vì replication lag. Với read cần freshness, route về primary, dùng session stickiness hoặc chờ replay position/LSN trong deadline. Phải định nghĩa behavior khi replica không bắt kịp thay vì chờ vô hạn.
17. Backup tốt được chứng minh thế nào?
Bằng restore drill đo actual RPO/RTO, không phải chỉ job báo xanh. Restore vào môi trường cô lập, kiểm tra WAL continuity, credentials, integrity, application readiness và thời gian tới khi traffic phục vụ được.
Redis nền tảng
18. Redis “single-threaded” nghĩa là gì?
Phần lớn command execution được serialized trên event loop nên từng command atomic relative với commands khác. Không có nghĩa toàn process chỉ có một thread, và workflow nhiều commands không tự atomic. Command O(N), big key hoặc script dài vẫn block server latency.
19. MULTI/EXEC có rollback không?
Nó queue rồi thực thi commands liên tiếp mà không bị command client khác xen giữa, nhưng không rollback kiểu SQL khi một command runtime error.
WATCH hỗ trợ optimistic CAS; Lua/Functions cho server-side atomic logic nhưng phải bounded và nhanh.20. Chọn RDB hay AOF?
RDB snapshot compact, backup/restart thuận tiện nhưng có cửa sổ mất dữ liệu từ snapshot cuối. AOF replay writes, durability tùy fsync, có storage/rewrite cost. Có thể kết hợp; chọn từ RPO, latency budget, fork/COW headroom và restore drill.
21. Sentinel khác Redis Cluster?
Sentinel giám sát, discovery và failover cho một primary-replica dataset, không shard. Cluster chia 16,384 slots qua nhiều primaries cùng replicas/failover, đổi lại client phải slot-aware và multi-key operations bị same-slot constraint.
22. TTL khác eviction?
TTL là lifetime logic của key; expiration loại key hết hạn. Eviction loại key khi đạt
maxmemory theo policy. Key chưa hết TTL vẫn có thể bị evict và volatile policy chỉ xét keys có TTL.23. Xử lý cache stampede thế nào?
Dùng single-flight/request coalescing, lease có timeout, probabilistic early refresh, stale-while-revalidate và TTL jitter. Waiter cần deadline/fallback; origin phải được bảo vệ bằng concurrency cap, rate limit hoặc circuit breaker.
24. Cache-aside stale race xảy ra thế nào?
Reader miss rồi đọc DB cũ; writer commit DB mới và invalidate; reader sau đó set lại value cũ. Mitigation gồm versioned value/key, compare version, reliable event invalidation, single-writer protocol hoặc short TTL. Delayed double delete chỉ là heuristic có bounded window.
25. Hot key và big key nguy hiểm gì?
Hot key dồn CPU/network vào một node hoặc slot; thêm shard không chia chính key đó. Big key gây response, delete, replication, persistence/COW và memory spikes. Bound size/cardinality, split theo access pattern, dùng incremental operations và
UNLINK khi phù hợp.26. Redis lock có đảm bảo mutual exclusion tuyệt đối?
Không chỉ với lease. Acquire tối thiểu bằng
SET key token NX PX, release compare-token-and-delete atomic. Client cũ có thể tiếp tục side effect sau pause và lease expiry; invariant mạnh cần fencing token được downstream kiểm tra. Async replication/failover cũng phải nằm trong safety analysis.27. Khi nào không nên dùng Redis cache?
Khi DB đã đủ nhanh, hit ratio thấp, data gần như luôn unique, strong freshness không chấp nhận stale, invalidation không chứng minh được hoặc operational cost/risk lớn hơn latency tiết kiệm. Đo workload và cost trước khi thêm cache.
Câu hỏi chuyên sâu
28. work_mem có phải giới hạn memory mỗi connection?
Không.
work_mem có thể áp dụng cho từng sort/hash operation; một query có nhiều nodes và parallel workers, một connection có thể dùng nhiều lần mức đó. Capacity phải xét work_mem × operations × workers × concurrency, cộng memory khác.29. Vì sao long transaction làm database phình?
Snapshot hoặc xmin cũ buộc VACUUM giữ dead tuples vẫn có thể visible cho transaction đó, gây heap/index bloat, visibility map kém và XID pressure. Theo dõi transaction age và
idle in transaction.30. Vì sao index-only scan vẫn heap fetch?
Index không lưu per-transaction visibility đầy đủ. Nếu heap page chưa được đánh all-visible trong visibility map, executor vẫn phải đọc heap để kiểm tra tuple visibility; VACUUM và write churn ảnh hưởng số heap fetches.
31. Generic prepared plan có thể chậm khi nào?
Khi parameter selectivity/data skew khác nhau lớn: một generic plan trung bình không tối ưu cho từng value như custom plan. Quan sát actual parameters, row estimates và plan choice trước khi thay plan cache behavior.
32. Replication slot nguy hiểm gì?
Slot giữ WAL mà consumer còn cần. Consumer chết hoặc chậm có thể làm
pg_wal tăng đến đầy disk nếu không monitor byte lag, inactive duration và retention limit; drop slot chỉ sau khi hiểu recovery/reseed impact.33. Redis fork ảnh hưởng memory thế nào?
RDB save, replication full sync hoặc AOF rewrite có thể fork child. Copy-on-write làm pages parent sửa trong thời gian child chạy được copy, nên peak RSS và latency tăng theo write rate, dataset/allocator layout và fragmentation.
34. WAIT có biến Redis thành strongly consistent không?
Không.
WAIT chờ replicas acknowledge replication progress, không đảm bảo tất cả đã fsync, không ngăn old-primary writes và không bảo đảm failover luôn chọn đúng replica đó. Nó chỉ cải thiện durability probability trong failure model cụ thể.35. MOVED khác ASK trong Redis Cluster?
MOVED báo slot owner ổn định mới để client cập nhật slot map. ASK là redirect tạm trong resharding; client gửi ASKING cho request kế tiếp tới target nhưng không thay mapping vĩnh viễn.36. Vì sao volatile-lru có thể không eviction được key?
Vì chỉ keys có TTL là candidates. Persistent keys vẫn chiếm memory nhưng không được policy chọn; khi không còn candidate phù hợp, writes có thể nhận noeviction/OOM-style error. Audit TTL coverage hoặc dùng allkeys policy cho instance thuần cache.
37. Cache down làm DB down theo bằng cách nào?
Mọi requests cùng miss/fallback vào DB, làm pool, CPU, I/O hoặc locks bão hòa. Bảo vệ bằng short cache timeout, request coalescing, bounded origin concurrency, rate limit/circuit breaker, serving stale theo freshness class và load shedding.
Thiết kế schema, index và concurrency
38. 1NF, 2NF, 3NF là gì và khi nào nên denormalize?
1NF loại repeating groups và yêu cầu giá trị theo domain là atomic trong model; 2NF là 1NF và non-key attributes phụ thuộc toàn candidate key, không chỉ một phần composite key; 3NF là 2NF và không có transitive dependency qua non-key attribute. Normalize transactional source of truth mặc định; chỉ denormalize khi measured read pattern justify và có sync owner, freshness SLO, reconciliation cùng rebuild path.
39. Index hoạt động thế nào, khi nào nên tạo và trade-off là gì?
Index như B-tree lưu search key cùng row locator để tránh full scan và hỗ trợ predicates/order phù hợp. Thiết kế từ access pattern, cardinality và sort, rồi xác nhận bằng production-like data với
EXPLAIN ANALYZE. Lợi ích read/uniqueness đổi lấy storage, cache pressure, write amplification và maintenance.40. Isolation level kiểm soát gì và chọn thế nào?
Isolation kiểm soát cách concurrent transactions quan sát/interleave để ngăn anomalies phá invariant. Chọn mức thấp nhất vẫn bảo vệ invariant: Read Committed cho nhiều CRUD, Repeatable Read cho stable snapshot, Serializable khi cần serial outcome và chấp nhận retry. Kiểm tra semantics của database cụ thể bằng timeline.
41. Nhận diện partial dependency và transitive dependency bằng ví dụ nào?
Với key ghép
(order_id, product_id), nếu order_date chỉ phụ thuộc order_id thì là partial dependency, vi phạm 2NF. Nếu customer_id → customer_name và row order chứa cả hai, customer_name phụ thuộc key qua customer_id, là transitive dependency vi phạm 3NF. Tách tables theo functional dependency và giữ foreign keys.42. Kiểm soát drift sau khi denormalize dữ liệu thế nào?
Xác định source of truth, owner và freshness SLO; cập nhật cùng transaction nếu cùng DB hoặc dùng outbox/change event qua boundary. Consumer phải idempotent, version-aware, replay/rebuild được. Reconciliation định kỳ so source với projection để phát hiện missing, duplicate và out-of-order update.
43. Vì sao query có index nhưng vẫn chạy sequential scan?
Planner có thể ước tính query đọc phần lớn table nên sequential scan rẻ hơn; table nhỏ cũng vậy. Statistics sai/cũ, function/cast, leading wildcard, collation hoặc predicate không khớp leading columns có thể làm index không hữu ích. Kiểm tra actual rows và buffers trước khi ép plan hay thêm index.
44. Partial index và covering index hữu ích khi nào?
Partial index chỉ chứa subset như
status='PENDING', giảm size/write cost nếu query predicate chứng minh tương thích. Covering index dùng INCLUDE để phục vụ thêm columns và có thể cho index-only scan, nhưng index lớn hơn và vẫn phụ thuộc visibility map.45. Lost update và write skew khác nhau thế nào?
Lost update là hai transactions đọc cùng value rồi ghi đè khiến một thay đổi biến mất; xử lý bằng atomic update, optimistic version, row lock hoặc isolation phù hợp. Write skew là mỗi transaction update row khác nhưng cùng phá invariant dựa trên shared snapshot; lock từng row riêng có thể không đủ, cần guard row, constraint redesign, predicate protection hoặc Serializable.
46. Deadlock và serialization failure nên retry thế nào?
Database abort một transaction để phá deadlock hoặc bảo toàn serializability. Retry toàn transaction từ đầu với fresh state, bounded attempts, jitter và shared deadline; external effects phải idempotent. Không retry riêng statement trong transaction đã failed và không retry mọi SQL error mù quáng.
Nuance từ tài liệu chính thức
47. Vì sao hai SELECT trong một Read Committed transaction có thể thấy dữ liệu khác nhau, và UPDATE xử lý concurrent writer ra sao?
Mỗi statement ở PostgreSQL Read Committed lấy snapshot tại lúc statement bắt đầu, nên commit xen giữa hai SELECT có thể xuất hiện ở SELECT sau. Nếu UPDATE tìm thấy row rồi phải chờ concurrent updater, sau khi chờ nó có thể re-evaluate
WHERE trên version mới; đây là lý do atomic conditional update thường an toàn hơn application read-then-write.48. Vì sao sequence gaps không chứng minh transaction bị mất?
Sequence allocation không rollback cùng transaction để tránh contention và bảo đảm concurrent generation. Transaction rollback, conflict hoặc cached values có thể để gap hợp lệ. Sequence cung cấp uniqueness/ordering tương đối theo allocation, không phải gapless business numbering hay commit chronology.
49. Redis metrics nào phân biệt miss do expiry với miss do eviction?
Theo dõi
keyspace_hits/keyspace_misses cùng expired_keys và evicted_keys trong INFO stats; kết hợp used_memory, maxmemory và policy. Expired tăng cho thấy TTL/lifecycle; evicted tăng cho thấy memory pressure/policy. Segment ở application theo key class vì server-wide counters không chỉ ra endpoint nào gây vấn đề.