// Khoá 3 / Transaction Boundary & SQL

Code transaction đúngtừBEGIN

Bạn đã viết BEGIN và COMMIT. Nhưng khi deadlock xảy ra, khi 2 user click submit cùng lúc tạo ra dữ liệu trùng, khi báo cáo tài chính ra số sai, bạn không biết bắt đầu nhìn vào đâu. Khoá này cài mental model về isolation level, MVCC snapshot, lock graph, outbox pattern và saga, từ vấn đề thực ra nguyên tắc, không ngược lại.

Xem nội dung
TRANSACTION INTERLEAVING
t
TXN 1
TXN 2
t0
BEGIN
t1
SELECT balance FROM accounts WHERE id=42
t2
BEGIN
t3
UPDATE accounts SET balance=-50 WHERE id=42
t4
UPDATE accounts WHERE id=42
t5
COMMIT
t6
lost update balance đè ngược về số cũ
Lock contentionLost update// boundary sai = data sai

// Approach

Khoá này dạy hiểu transaction từ bên trong, không dạy syntax

Mọi dev làm việc với transaction đứng ở 1 trong 3 lớp. Lớp càng cao, bạn debug race condition và deadlock càng nhanh, thiết kế boundary càng chắc.

Khoá này dạy lớp này

Lớp 3

Physical

MVCC snapshot, xmin/xmax, lock graph, WAL, deadlock detection, isolation implementation

Bạn biết
Tại sao 2 transaction đọc cùng 1 row mà thấy kết quả khác nhau. Lock được acquire và release ra sao. Deadlock xảy ra khi nào và Postgres detect thế nào.
Khả năng
Chọn isolation level với reasoning. Thiết kế lock strategy dựa vào read-write pattern. Debug deadlock trong 15 phút bằng lock graph. Biết khi nào retry là đúng.

Lớp 2

Mechanical

BEGIN/COMMIT syntax, SET TRANSACTION ISOLATION LEVEL, FOR UPDATE, SAVEPOINT

Bạn biết
Keyword tồn tại, biết cú pháp cơ bản, dùng được trong happy path. Nhưng không biết khi nào isolation level nào, không biết FOR UPDATE gây contention ra sao.
Khả năng
Viết transaction cho case đơn giản. Khi gặp race condition, deadlock, hoặc lost update thì thử-sai, copy Stack Overflow, restart app.

Biết keyword nhưng không biết khi nào dùng và vì sao.

Lớp 1

Surface

ORM tự bọc transaction, mình chỉ viết business code

Bạn biết
Transaction chạy tự động, không cần quan tâm BEGIN hay COMMIT.
Khả năng
Build CRUD hoạt động bình thường. Không xử lý được khi gặp race condition hay data inconsistency.

Không xử lý được khi gặp race condition trên production.

Hầu hết tài liệu transaction dừng ở lớp 2: liệt kê isolation levels, paste ví dụ FOR UPDATE, kèm vài rule of thumb. Khoá này đi xuống lớp 3 để mỗi decision ở lớp 2 tự khắc có reasoning.

// Khi nào cần khoá này

Khi nào cần khoá này

6 tín hiệu từ production

Nếu thấy bất kỳ tín hiệu nào dưới đây, khoá này dành cho bạn.

  • Team đang debug deadlock định kỳ trên production

    Deadlock xảy ra rồi restart là xong tạm, nhưng không hiểu lock graph nên không biết fix gốc rễ. Tuần sau lại xảy ra.

  • Phải đảm bảo không tạo trùng order khi user double-click submit

    Logic check-rồi-insert không an toàn dưới concurrent load. Cần hiểu isolation level và lock strategy để đảm bảo exactly-once creation.

  • Đang chuyển từ monolithic REST sang event-driven, cần guarantee delivery

    Khi publish event và update DB phải đi cùng nhau, không có transaction bao ngoài nữa. Cần outbox pattern hoặc saga để giữ consistency.

  • Read replica trả về stale data, không biết khi nào safe để route

    Sau khi write, read từ replica thấy data cũ. Cần hiểu replication lag và snapshot visibility để quyết định khi nào dùng primary, khi nào dùng replica.

  • Báo cáo tài chính ra số sai khi data đang được update song song

    Query tổng hợp chạy giữa lúc batch job đang update, kết quả inconsistent. Cần hiểu isolation level và MVCC để chọn đúng snapshot cho reporting.

  • Đang implement payment flow, cần exactly-once semantics

    Retry payment không được tạo duplicate charge. Cần idempotency key, outbox pattern, và transaction boundary đúng để đảm bảo safety.

Chưa gặp tình huống nào trong 6 tín hiệu trên? Kéo xuống xem curriculum để nhận ra pattern nào gần với hệ thống bạn đang làm. Gặp khi chưa chuẩn bị tốn nhiều hơn gặp khi đã có mental model.

// The Reality Check

5 thứ về transaction bạn chưa biết, dù đã viết BEGIN mỗi ngày

Không phải bạn kém. Là ORM che đi phần này. Production mới hỏi đến.

ORM bọc transaction, code chạy, app lên prod. Cho đến khi 2 request đồng thời tạo data trùng.

Lý thuyết

BEGIN ... COMMIT là đủ để an toàn.

Production thực tế

BEGIN và COMMIT chỉ là ranh giới. Thứ quyết định data có nhất quán hay không là isolation level. Mặc định Postgres dùng Read Committed, không ngăn được non-repeatable read hay lost update. Dev không biết điều này sẽ không hiểu tại sao data vẫn sai dù đã có transaction.

Lý thuyết

FOR UPDATE là giải pháp cho mọi race condition.

Production thực tế

FOR UPDATE là pessimistic lock, chỉ phù hợp với pattern read-then-update trên số lượng row nhỏ. Dùng FOR UPDATE trên query trả về nhiều row, hoặc dùng khi không cần thiết, gây lock contention và có thể tạo deadlock mới thay vì giải quyết vấn đề cũ.

Lý thuyết

Lock là thứ gây ra vấn đề, cần tránh xa.

Production thực tế

Lock không hỏng app. Lock SAI mới hỏng. Lock đúng loại, đúng scope, đúng thời điểm là cách database đảm bảo correctness. Tránh lock bằng cách bỏ transaction là đổi correctness problem thành silent corruption.

Lý thuyết

Retry là code rác, deadlock không nên xảy ra.

Production thực tế

Với isolation level Serializable, Postgres có thể abort và yêu cầu retry transaction khi phát hiện serialization conflict. Retry là contract của Serializable, không phải workaround. Code không handle retry = code sai với Serializable.

Lý thuyết

Microservice không cần transaction, mỗi service tự lo data của mình.

Production thực tế

Microservice không có distributed transaction kiểu ACID cổ điển. Nhưng vẫn cần consistency giữa các service. Outbox pattern, saga, và 2PC là các cách handle distributed transaction. Không biết các pattern này = không biết tại sao data giữa các service bị lệch nhau.

Những thứ này không có trong ORM docs. Nhưng production hỏi đến ngay khi concurrent load bắt đầu tăng.

4 tình huống quen thuộc khi production bắt đầu nói chuyện

Không phải drama. Đây là tình huống thật của dev backend VN khi concurrent load bắt đầu tăng.

Deadlock random trên production, restart mới hết

Batch job chạy ban đêm hoặc 2 request đồng thời gặp nhau theo thứ tự lock ngược nhau. Postgres abort 1 trong 2, log hiện ERROR: deadlock detected. Bạn restart app, hết ngay. Tuần sau lại xảy ra. Không hiểu lock graph, không có cách fix gốc rễ.

2 user click submit cùng lúc, tạo 2 order trùng nhau

Logic check xem order đã tồn tại chưa rồi mới insert không an toàn khi 2 request chạy song song. Cả 2 check thấy chưa có, cả 2 insert. Kết quả: 2 order trùng. Không hiểu isolation level và lock strategy, không biết cách fix ngoài unique constraint mà không hiểu tại sao.

Báo cáo tổng sai vì query đọc giữa lúc data đang được update

Query tổng hợp cuối tháng chạy cùng lúc batch job đang update dữ liệu. Kết quả báo cáo inconsistent: một số row đã update, số khác chưa. Không hiểu MVCC snapshot và isolation level, không biết cách chọn đúng consistency level cho reporting.

Migrate sang microservice, mất guarantee giữa các service

Monolith có 1 transaction bao toàn bộ. Microservice tách ra, mỗi service 1 database. Publish event lên message queue và update database không còn trong cùng 1 transaction. Event mất, hoặc database update xong nhưng event không đi, hoặc ngược lại. Không biết outbox pattern hay saga, không có cách handle.

// Myth-Buster

4 ngộ nhận về transaction khiến dev viết code không debug được

Không phải bạn tin những thứ này vì dốt. ORM docs và tutorial không nói ngược lại. Production mới nói.

ORM giải quyết rất tốt phần syntax transaction. Nhưng ORM không giải thích isolation level nào đang dùng, không giải thích lock được acquire ra sao, không giải thích tại sao retry là cần thiết. Phần đó dev phải tự biết.

Ngộ nhận của fresher

"BEGIN/COMMIT là đủ để transaction an toàn."

Reality check

BEGIN và COMMIT chỉ đánh dấu ranh giới. Isolation level mới là thứ quyết định data có nhất quán hay không. Read Committed (mặc định Postgres) cho phép non-repeatable read và lost update. Không biết isolation level mình đang dùng = không biết loại anomaly nào có thể xảy ra.

Ngộ nhận của fresher

"FOR UPDATE là giải pháp chuẩn cho mọi race condition."

Reality check

FOR UPDATE là pessimistic lock, phù hợp với pattern read-then-update trên row cụ thể. Dùng FOR UPDATE trên query trả về nhiều row hoặc dùng khi không cần thiết gây lock contention và có thể tạo deadlock mới. Chọn lock strategy phải dựa vào read-write pattern, không phải dùng FOR UPDATE cho mọi thứ.

Ngộ nhận của fresher

"Microservice không cần transaction, mỗi service tự lo."

Reality check

Microservice không có ACID transaction cổ điển bao nhiều service. Nhưng consistency requirement vẫn tồn tại. Outbox pattern, saga, và 2PC là cách handle distributed consistency. Bỏ transaction không có nghĩa là bỏ consistency requirement, chỉ là bỏ tool đơn giản nhất.

Ngộ nhận của fresher

"Retry là hack để chạy qua lỗi tạm thời."

Reality check

Với isolation level Serializable, Postgres abort transaction khi phát hiện serialization conflict và trả về error yêu cầu retry. Retry là contract được định nghĩa trong spec của Serializable isolation, không phải workaround. Code không handle retry = code không dùng được Serializable đúng cách.

Hiểu transaction internals không có nghĩa là viết BEGIN/COMMIT mọi nơi. Có nghĩa là khi data sai hoặc deadlock xảy ra, bạn biết nhìn vào đâu và tự debug được.

// Bạn đang nghĩ

"ORM lo transaction hết rồi, học thêm làm gì?"

Câu này hợp lý khi app CRUD đơn giản với vài chục concurrent user. Không còn hợp lý khi concurrent load tăng và data bắt đầu sai.

  1. Lý do #1

    ORM không biết business invariant của bạn

    ORM bọc transaction theo method call, không biết rằng check-rồi-insert của bạn cần atomic. ORM không chọn isolation level cho bài toán cụ thể của bạn. ORM không biết row nào cần lock và row nào không. Business logic sai transaction boundary, ORM không cứu được.

  2. Lý do #2

    Chưa gặp deadlock không phải không tồn tại

    Deadlock và race condition không xảy ra với concurrent load thấp. Khi user tăng, khi có batch job chạy cùng lúc request thật, khi microservice gọi nhau đồng thời: pattern mới xuất hiện. Chưa gặp có thể là may mắn, cũng có thể là silent data corruption chưa được phát hiện.

  3. Lý do #3

    Cloud managed database không cứu được logic transaction sai

    RDS, Supabase, Neon lo server, backup, replication. Không lo isolation level bạn chọn sai, không lo transaction boundary bạn đặt thiếu, không lo outbox bạn chưa implement. Logic transaction sai chạy trên managed database vẫn sai như chạy trên self-hosted.

  4. Lý do #4

    Microservice không biến mất transaction requirement

    Tách service ra không có nghĩa là bỏ consistency requirement. Payment phải atomic dù payment service và order service là 2 service riêng. Outbox pattern và saga là cách implement consistency trong distributed system. Khó hơn single-DB transaction, không phải không cần.

Không cần viết transaction thủ công mọi nơi. Chỉ cần đủ hiểu để biết khi nào ORM đủ, khi nào cần làm thêm, và khi nào data đang sai mà chưa ai biết.

// Entry barrier

Khoá KHÔNG bắt học thuộc isolation level, KHÔNG chứng minh serializability

Bạn không cần đọc paper Jim Gray hay thuộc 4 anomaly trong SQL standard để học được. Khoá đi từ vấn đề thực, lý thuyết đến sau khi bạn đã thấy cần.

Không phải đại học CS, không chứng minh serializability theorem

Serializability proof là môn database lý thuyết. Khoá này cần bạn hiểu tại sao 2 transaction đồng thời có thể thấy data khác nhau, từ đó chọn đúng isolation level cho bài toán. Hiểu bằng cách chạy 2 session Postgres song song, không bằng cách chứng minh trên giấy.

Không học thuộc 4 isolation level theo tên, hiểu MVCC sinh ra cái nào tự nhiên

Read Uncommitted, Read Committed, Repeatable Read, Serializable: 4 tên này ít quan trọng hơn việc hiểu MVCC snapshot hoạt động ra sao và anomaly nào mỗi level cho phép. Hiểu MVCC xong, bạn tự derive được từng level mà không cần nhớ thuộc.

Không phải DBA, không tune lock_timeout hay deadlock_timeout

DBA tune server params cho workload cụ thể. Dev cần hiểu lock được acquire khi nào, giữ bao lâu, và tại sao 2 transaction deadlock với nhau. Đó là câu hỏi design code, không phải câu hỏi config server.

Không phải SQL tutorial, không giải thích SELECT hay JOIN

Khoá giả định bạn đã viết được SQL và dùng được ORM. Nội dung tập trung vào transaction boundary, isolation, lock, và distributed pattern. Không giải thích syntax SQL cơ bản.

Không học thuộc các pattern, hiểu khi nào áp dụng cái nào

Outbox, saga, 2PC: không phải checklist để nhớ. Mỗi pattern giải quyết vấn đề gì, trade-off là gì, khi nào dùng cái nào, đó là thứ khoá này rèn. Biết tên pattern mà không biết khi nào dùng không có giá trị.

Thứ BẠN cần trước khi học: đã deploy code production, biết viết SELECT/INSERT/UPDATE, từng dùng ORM bất kỳ, đã từng thấy hoặc nghe đến deadlock hay race condition dù chưa hiểu. Còn lại khoá tự cài vào đầu.

// Pedagogy

Cách content đi: MVCC trước, lock sau, distributed patterns cuối

Hầu hết tài liệu transaction nhảy thẳng vào isolation level syntax. Khoá này đi theo thứ tự nền tảng: hiểu MVCC sinh snapshot ra sao trước, rồi mới hiểu lock graph, rồi mới hiểu distributed patterns build trên 2 nền tảng đó.

  1. Bước 1

    Hiểu MVCC sinh snapshot, từ đó hiểu isolation level

    Không nhảy thẳng vào tên isolation level. Trước tiên: MVCC là gì, snapshot được tạo khi nào, xmin/xmax trên mỗi row có nghĩa gì. Hiểu MVCC xong thì Read Committed vs Repeatable Read vs Serializable không còn là 3 tên cần nhớ, là 3 cách khác nhau về thời điểm lấy snapshot.

    Ví dụ

    Lab: mở 2 psql session song song. Session 1 BEGIN, session 2 INSERT và COMMIT. Session 1 SELECT thấy gì? Thay isolation level, chạy lại, so sánh. Thấy MVCC hoạt động thật, không đọc lý thuyết.

  2. Bước 2

    Hiểu lock graph khi nào đụng, deadlock detect cách nào

    Sau khi hiểu snapshot, lock mới có nghĩa: lock là cơ chế bổ sung khi snapshot không đủ để đảm bảo correctness. Vẽ lock graph cho scenario cụ thể, hiểu Postgres detect cycle ra sao, hiểu khi nào deadlock xảy ra và cách tránh bằng ordering.

    Ví dụ

    Lab: tạo deadlock có chủ ý giữa 2 session, đọc log Postgres thấy deadlock detection, trace lại lock acquisition order. Từ đó rút ra nguyên tắc consistent lock ordering.

  3. Bước 3

    Distributed patterns build trên MVCC và lock

    Outbox dùng polling transaction trên cùng database, đảm bảo event và data write atomic. Saga là chuỗi transaction có compensation khi 1 bước fail. 2PC là protocol coordination giữa 2 resource. Cả 3 đều build trên nền tảng đã học ở bước 1 và 2.

    Ví dụ

    Lab: implement outbox table cho use case gửi email sau khi tạo order. Poller đọc outbox trong transaction, mark processed, publish event. Test failure scenarios: DB down, message broker down.

3 tầng này liên kết với nhau. MVCC giải thích isolation. Lock giải thích deadlock. Distributed patterns giải thích tại sao outbox và saga tồn tại. Không học rời rạc từng technique.

// Methodology

5 bước rèn tư duy: từ MVCC đến lock đến distributed patterns

Không phải xem video rồi quên. Đây là 5 bước cụ thể lặp qua mỗi tầng của transaction internals. Làm thật trên Postgres docker với 2 session concurrent, không chỉ slide.

Anchor: "Hệ thống thanh toán nội bộ gặp double charge khi user click submit 2 lần"

  1. Bước 101

    Mô phỏng 2 transaction đồng thời, đọc xmin/xmax thực

    Trước khi làm bất cứ điều gì với isolation level, hiểu MVCC từ dưới lên. Mở 2 psql session, chạy concurrent transactions, query pg_stat_activity và xmin/xmax trên row đang được update. Thấy snapshot bằng tay.

    Ví dụ

    Session A: BEGIN, SELECT balance FROM accounts WHERE id=1; Session B: UPDATE accounts SET balance=balance-100 WHERE id=1; COMMIT; Session A SELECT lại: thấy gì? Thay isolation level Repeatable Read, chạy lại. So sánh output.

  2. Bước 202

    Vẽ lock graph cho 1 scenario có deadlock, hiểu Postgres detect ra sao

    Không học deadlock qua lý thuyết. Tạo deadlock có chủ ý: transaction A lock row 1 rồi cố lock row 2, transaction B lock row 2 rồi cố lock row 1. Vẽ dependency graph, hiểu Postgres detect cycle ra sao và abort cái nào.

    Ví dụ

    Chạy 2 script song song tạo deadlock. Đọc log Postgres: 'ERROR: deadlock detected', 'DETAIL: Process N waits for ShareLock on transaction M'. Trace lại từng lock acquisition. Rút ra: consistent lock ordering ngăn deadlock.

  3. Bước 303

    Test 4 isolation level cùng 1 query, so sánh output

    Cùng 1 scenario concurrent read-write, chạy với Read Committed, Repeatable Read, Serializable. Quan sát khi nào phantom read xảy ra, khi nào serialization error được throw, khi nào retry là cần thiết. Không đọc bảng so sánh, tự thấy.

    Ví dụ

    Scenario: transfer 1000 từ account A sang B, đồng thời calculate total balance. Chạy với Read Committed: total sai. Repeatable Read: total đúng nhưng transfer có thể lost update. Serializable: transaction bị abort, cần retry.

  4. Bước 404

    Implement outbox table cho 1 use case thật

    Thay vì publish event trực tiếp sau DB write, implement outbox: write event vào table cùng transaction với business data. Poller đọc outbox và publish. Test failure: DB commit xong broker down, broker up xong poller xử lý tiếp. Data không mất.

    Ví dụ

    Schema: orders table + outbox table. Khi tạo order: INSERT orders và INSERT outbox trong cùng transaction. Poller: SELECT FOR UPDATE SKIP LOCKED từ outbox, publish event, DELETE. Test: kill broker giữa chừng, restart, verify order event delivered exactly once.

  5. Bước 505

    Refactor 1 saga sai sang version có compensation đúng

    Bắt đầu với saga không có compensation: khi step 3 fail, step 1 và 2 đã commit, không rollback được. Refactor: thêm compensation transaction cho từng step. Test failure ở từng điểm trong chuỗi, verify system về trạng thái nhất quán.

    Ví dụ

    Saga: reserve inventory, charge payment, create shipment. Simulate payment fail sau khi inventory đã reserve. Không có compensation: inventory bị giữ mãi. Với compensation: inventory được release, order về trạng thái cancelled. Verify bằng query sau mỗi scenario.

5 bước này lặp lại qua từng module. Cuối khoá: nhìn vào bất kỳ scenario concurrent nào, não tự hỏi isolation level nào, lock gì, retry khi nào. Không cần nhớ lại, tự khắc hỏi.

// Mindset Shifts

4 cú nhảy tư duy giữa dev viết transaction và dev hiểu transaction boundary

Dev hiểu transaction không viết BEGIN/COMMIT nhiều hơn bạn. Họ nghĩ KHÁC trước khi thiết kế code. Khoá này cài 4 cách nghĩ đó.

Shift #1
Dev viết transaction nghĩ

"BEGIN/COMMIT là đủ. Transaction bao code lại là an toàn. Isolation level để mặc định, database tự lo."

Dev hiểu transaction boundary nghĩ

"Isolation level mặc định cho phép anomaly nào? Business invariant này cần isolation level gì? Read Committed có đủ hay cần Repeatable Read? Serializable có trade-off gì về throughput?"

Trigger: "Isolation level mặc định có ngăn được anomaly nào có thể xảy ra ở đây không?"

Shift #2
Dev viết transaction nghĩ

"Race condition thì thêm FOR UPDATE. FOR UPDATE là giải pháp chuẩn, dùng cho mọi thứ cần lock."

Dev hiểu transaction boundary nghĩ

"Pattern ở đây là read-then-update hay insert-if-not-exists? FOR UPDATE có gây contention không? Có cần lock cả row hay chỉ cần unique constraint? Optimistic lock có phù hợp hơn không?"

Trigger: "Lock strategy nào phù hợp với read-write pattern cụ thể ở đây?"

Shift #3
Dev viết transaction nghĩ

"Deadlock xảy ra thì retry hoặc restart. Retry là workaround tạm thời, code tốt không cần retry."

Dev hiểu transaction boundary nghĩ

"Serializable isolation require retry theo contract. Deadlock cần retry với backoff. Retry cần idempotency key để safe. Code không handle retry = code không production-ready với concurrent load."

Trigger: "Code này có idempotent không, và retry có safe không nếu operation thực hiện 2 lần?"

Shift #4
Dev viết transaction nghĩ

"Microservice tách ra rồi, không cần transaction nữa. Mỗi service tự quản lý data của mình."

Dev hiểu transaction boundary nghĩ

"Consistency requirement vẫn tồn tại dù không có distributed transaction. Outbox hay saga? Compensation cần implement cho step nào? Failure ở đây xảy ra thì system về trạng thái nào?"

Trigger: "Nếu step này fail sau khi step trước đã commit, system sẽ ở trạng thái nào và có cách recover không?"

4 câu hỏi này chạy tự động trong đầu dev đã rèn qua khoá. Không cần nhớ lại, tự khắc hỏi trước khi push code.

// USP

4 thứ khoá này có, tutorial transaction khác không có

Không phải DBA course. Không phải distributed systems theory. Không phải SQL tutorial. Đây là content tập trung đúng 1 thứ: mental model transaction boundary cho dev viết code backend production.

Không phải DBA course, không tune lock_timeout hay deadlock_timeout

Khoá nhìn từ góc dev thiết kế code: khi nào đặt transaction boundary, isolation level nào phù hợp, lock strategy nào cho pattern cụ thể. Không đi vào config server params hay vận hành Postgres cluster. Đó là công việc của người ops, không phải người viết business code.

Hands-on với 2 session Postgres concurrent thật, không chỉ slide

Mỗi bài có lab chạy 2 psql session song song: thấy MVCC snapshot thật, thấy deadlock detection thật, thấy serialization error thật. Không đọc bảng so sánh isolation level rồi tin lời. Tự chạy, tự thấy output, tự rút ra kết luận.

Pattern outbox, saga, 2PC từ ví dụ production VN

Mỗi distributed pattern đi kèm use case cụ thể: hệ thống fintech tích hợp ngân hàng cần outbox, ecommerce nội bộ migrate sang microservice cần saga. Không phải Twitter clone hay Stripe clone không map được với context làm việc thực tế.

Mental model dùng được trên mọi database có transaction

MVCC, isolation level, lock graph, outbox: concept này không thay đổi giữa Postgres 14, 15, 16, 17. Phần lớn áp dụng được sang MySQL InnoDB, CockroachDB. Distributed patterns áp dụng được bất kể database nào. Học 1 lần, dùng lâu dài.

// Curriculum

Lộ trình: 17 bài, 5 module

Mỗi module rèn 1 tầng trong mental model: MVCC và isolation trước, rồi mới lock và deadlock, rồi mới transaction boundary trong code, rồi mới distributed patterns. Hiểu từng tầng xong mới lên tầng tiếp theo.

  • Một ngày tại NeoBank: double charge và deadlock
  • Tại sao database không tự giải quyết hết
  • Setup playground: Postgres docker, 2-session lab
  • ACID: atomicity, consistency, isolation, durability trong thực tế
  • Isolation levels: Read Committed, Repeatable Read, Serializable
  • MVCC internals: xmin, xmax, snapshot, visibility check
  • Lock types: row lock, table lock, advisory lock
  • Locking strategies: pessimistic vs optimistic, FOR UPDATE, SKIP LOCKED
  • Deadlock: detection, prevention, consistent lock ordering
  • Transaction boundary: WHERE to BEGIN dựa vào business invariant
  • Idempotency và retry: exactly-once semantics, idempotency key
  • Savepoint và nested transaction: khi nào dùng, khi nào tránh
  • Outbox pattern: atomic write và event delivery
  • Saga pattern: chuỗi transaction và compensation
  • Two-phase commit: khi nào cần, trade-off là gì
  • Observability: trace transaction qua service, debug distributed failure
  • Capstone: refactor hệ thống thanh toán từ monolith sang microservice

// Click vào module để xem chi tiết các bài học

// Hard Questions

3 câu hỏi bạn sẽ không hỏi trực tiếp

Cả 3 là câu thật từ dev VN khi đọc landing page khoá transaction. Mình trả lời thẳng.

#01 · Pattern selection

"Outbox vs Saga, dùng cái nào cho hệ thống của tôi?"

Vấn đề giải quyết
Outbox

Đảm bảo event được publish đúng 1 lần, atomic với DB write. Không mất event khi broker down.

Saga

Orchestrate chuỗi transaction qua nhiều service với compensation khi 1 bước fail.

Số service liên quan
Outbox

1 service, 1 database. Event publish ra ngoài là secondary concern.

Saga

Từ 2 service trở lên, mỗi service có database riêng, cần consistency giữa các bước.

Complexity
Outbox

Thấp. Thêm outbox table, thêm poller. Dễ implement, dễ debug.

Saga

Cao hơn. Cần định nghĩa compensation cho từng step, handle partial failure, track state saga.

Failure mode
Outbox

Event deliver at-least-once, consumer cần idempotent. Poller có thể process trùng nếu crash giữa chừng.

Saga

Partial execution: một số step đã commit, cần chạy compensation ngược lại. State machine cần persist.

Khi nào dùng
Outbox

Cần publish event sau DB write mà không muốn mất event. Payment confirmation, order created notification.

Saga

Long-running workflow qua nhiều service: booking, multi-step payment với rollback phức tạp.

Outbox và Saga không thay thế nhau. Outbox giải quyết event delivery. Saga giải quyết workflow coordination. Nhiều hệ thống dùng cả 2: Saga orchestrate workflow, mỗi step trong saga dùng outbox để publish event.

#02 · Database portability

"Tôi dùng MySQL, không phải Postgres. Học có dùng được không?"

MVCC implementation
Postgres-specific

xmin/xmax tuple header, visibility check trong heap: Postgres-specific. MySQL InnoDB dùng undo log, khác implementation.

Common across RDBMS

Snapshot isolation concept, read-your-writes, repeatable read semantics: nguyên tắc giống nhau dù implementation khác.

Isolation levels
Postgres-specific

Postgres không có Read Uncommitted thật (downgrade thành Read Committed). Serializable dùng SSI algorithm.

Common across RDBMS

4 isolation level SQL standard, anomaly mỗi level cho phép (dirty read, non-repeatable read, phantom): dùng được trên MySQL, CockroachDB.

Locking
Postgres-specific

FOR UPDATE, FOR SHARE, advisory lock syntax: Postgres-specific. pg_locks system view.

Common across RDBMS

Pessimistic vs optimistic lock concept, deadlock detection algorithm, lock ordering principle: áp dụng được trên mọi RDBMS.

Distributed patterns
Postgres-specific

Lab dùng Postgres cho outbox và saga demo.

Common across RDBMS

Outbox pattern, saga, 2PC: database-agnostic. Áp dụng được với MySQL, MongoDB transaction, CockroachDB.

Học trên Postgres là cách học transaction internals hiệu quả nhất vì source code mở và documentation chi tiết. Concept isolation level, lock strategy, và distributed patterns transfer sang MySQL hay database khác không khó.

#03 · DDIA objection

"Tôi đã đọc Designing Data-Intensive Applications rồi, có cần học thêm không?"

DDIA cho mental model rộng. Khoá này cho production pattern cụ thể.

DDIA là cuốn sách xuất sắc. Chương về transactions giải thích ACID, isolation levels, và distributed transactions ở level khái niệm rất tốt. Kleppmann viết về serializability, 2PL, SSI rõ ràng và có chiều sâu. Đó là nền tảng lý thuyết tốt.

Nhưng DDIA không đi vào 'mở 2 psql session và tự thấy MVCC hoạt động'. Không có lab implement outbox table với poller. Không có ví dụ saga với compensation cụ thể cho use case VN. Không hướng dẫn debug deadlock từ Postgres log. DDIA quá general để áp dụng trực tiếp vào design transaction boundary hôm nay.

Nếu đã đọc DDIA: khoá này là bước tiếp theo, đi từ khái niệm sang hands-on production patterns. Nếu chưa đọc DDIA: khoá này standalone, không cần đọc DDIA trước.

DDIA và khoá này complement nhau. DDIA cho bạn why rộng và distributed systems context. Khoá này cho bạn how cụ thể trên Postgres với production patterns.

Vẫn còn câu hỏi sau 3 block trên? Scroll xuống FAQ. Không có câu trả lời phù hợp? Email thẳng.

// Instructor

Người đi trước

Mình là backend dev. Viết khoá này. Dùng nếu thấy hợp.

// photo soon

[Tên]

Backend dev

Mình từng mò những thứ trong khoá này. Giờ viết lại gọn cho bạn đỡ mò.

// Audience filter

Khoá này KHÔNG dành cho ai

Thà mất đơn còn hơn cầm tiền của người không phù hợp. Đọc kỹ. Dính 1/4, đừng mua.

Fresher chưa deploy code lên production

Khoá giả định bạn đã từng thấy race condition hoặc data inconsistency thật, đã từng viết transaction trong project thật. Không có context production, các case study về deadlock và distributed failure sẽ không có nghĩa.

Build 1 project có database và concurrent users, deploy lên server thật. Khi gặp data sai lần đầu và tự hỏi tại sao, quay lại khoá này sẽ thấm ngay.

Người chưa bao giờ viết transaction

Khoá giả định bạn đã biết BEGIN/COMMIT tồn tại và đã dùng ORM với transaction. Nội dung đi thẳng vào internals và edge cases, không giải thích transaction là gì từ đầu.

Tìm 1 tutorial ORM transaction cơ bản, implement 1 feature cần transaction thật trong project của bạn. Khi thấy ORM không đủ, quay lại.

Người đang tìm SQL tutorial cơ bản

Khoá không dạy SQL syntax, không giải thích SELECT hay JOIN. Giả định bạn đã viết được query và dùng được ORM. Đây là khoá về transaction internals, không phải SQL fundamentals.

SQLBolt hoặc Mode SQL Tutorial miễn phí, 1-2 tuần là xong SQL cơ bản. Quay lại sau khi viết được query JOIN 3 bảng không cần tra cứu.

Người muốn học CAP theorem và distributed systems theory

Khoá tập trung vào production patterns: transaction boundary, lock strategy, outbox, saga. Không đi vào chứng minh CAP, Paxos/Raft consensus, hay vector clocks. Đó là distributed systems theory course, không phải khoá này.

DDIA (Designing Data-Intensive Applications) là tài liệu tốt nhất cho distributed systems theory. Khoá này complement DDIA, không thay thế.

Vẫn ở đây sau 4 điều trên? Bạn đúng là người khoá này sinh ra để phục vụ. Kéo xuống xem khoá KHÔNG hứa gì.

// Honest expectations

Khoá này KHÔNG hứa gì

Không hứa lương tăng, không hứa đổi đời. Đây là cái khoá KHÔNG hứa, và cái khoá thực sự giao được.

KHÔNG hứa

Không hứa code của bạn sẽ không bao giờ deadlock.

NHƯNG hứa

Hứa khi deadlock xảy ra, bạn biết đọc lock graph, hiểu tại sao 2 transaction đụng nhau, và fix gốc rễ bằng consistent lock ordering. Không phải restart app rồi đợi tuần sau xảy ra lại.

KHÔNG hứa

Không hứa bạn biết hết pattern distributed transaction.

NHƯNG hứa

Hứa bạn hiểu outbox giải quyết vấn đề gì, saga giải quyết vấn đề gì, khi nào dùng cái nào, và trade-off của từng cái. Biết khi nào áp dụng pattern nào quan trọng hơn thuộc tên pattern.

KHÔNG hứa

Không hứa transaction của bạn sẽ nhanh hơn.

NHƯNG hứa

Hứa transaction của bạn sẽ đúng. Transaction nhanh mà sai không có giá trị. Hiểu isolation level và lock scope còn giúp bạn tránh lock dư thừa, giảm contention.

KHÔNG hứa

Không hứa bạn trở thành distributed systems expert.

NHƯNG hứa

Hứa khi làm việc với người có chuyên môn distributed systems, bạn có ngôn ngữ chung: outbox, saga, idempotency, compensation, exactly-once. Không đứng ngoài cuộc thảo luận thiết kế.

KHÔNG hứa

Không hứa fix được data corruption đã xảy ra trên production.

NHƯNG hứa

Hứa bạn có mental model để ngăn data corruption mới: biết isolation level nào cần cho invariant nào, biết khi nào cần idempotency key, biết transaction boundary đặt ở đâu.

Khoá này lấp 1 khoảng trống cụ thể: mental model transaction boundary để design đúng và debug được. Không phải khoá đổi đời, không phải cert, không phải distributed systems PhD. Làm tốt phần đó.

// Pricing

1.100.000₫. Mở bán đợt đầu.

Giá mở bán đợt đầu. Nội dung đầy đủ 17 bài, không cắt bớt so với regular.

Mở bán đợt đầu
1.100.000đ

2.800.000đ

Giá mở bán đợt đầu

  • 17 bài chia 5 module, từ MVCC đến isolation đến lock đến distributed patterns
  • Lab Postgres docker mỗi bài: 2-session concurrent thật, thấy MVCC và deadlock thật
  • Code skeleton outbox pattern và saga để tự implement theo
  • Dataset và case study từ hệ thống production VN
  • 1 buổi Q&A live qua Zoom sau khi khoá mở
  • Truy cập trọn đời, xem lại bất cứ lúc nào
  • Update nội dung khi Postgres ra major version có thay đổi transaction engine

Còn lại

--ngày
--giờ
--phút
--giây

Hoàn 100% trong 7 ngày nếu không thấy phù hợp. Không hỏi lý do.

// FAQ

Câu hỏi thường gặp

Câu hỏi không có ở đây? Email thẳng. Trả lời trong 24h.

Cần biết SELECT, INSERT, UPDATE, DELETE ở mức cơ bản và đã dùng 1 ORM bất kỳ trong project thật. Khoá không giải thích SQL syntax, tập trung vào transaction internals. Không cần biết CTE, window function, hay SQL nâng cao.