---
title: "Thiết kế dữ liệu phân tán - consistency, quorum, leader election và failover"
description: "Khóa học về dữ liệu phân tán cho app dùng Postgres có replica, xoay quanh câu hỏi: dữ liệu có nhiều bản sao thì tin bản nào. Bạn sẽ giải thích được vì sao replica chậm, chọn nơi đọc cho từng trang, chọn primary mới mà không gây split brain, promote đúng replica khi primary chết và xử lý conflict khi nhiều nơi cùng ghi."
canonical: "https://200lab.io/courses/thiet-ke-du-lieu-phan-tan-consistency-quorum-leader-election-va-failover"
type: "course"
course_type: "course"
learning_type: "free_form"
published: "2026-10-10T05:00:10Z"
students: 0
---

## Khóa học này mang lại gì cho bạn

App bạn xây cùng agent lúc đầu chạy trên một Postgres. Khi app lớn dần, database có thêm một replica, do nhà cung cấp cloud bật sẵn hoặc do agent đề xuất để đỡ tải đọc và “để dự phòng”. Replica là một bản sao chạy trên máy khác, liên tục chép mọi thay đổi từ database gốc; database gốc đó giờ được gọi là primary, vì mọi lệnh ghi của app đều đi vào nó. Từ lúc dữ liệu nằm trên nhiều máy, khách có thể đặt đơn xong mà không thấy đơn đâu, còn khi primary chết, không ai biết đã mất bao nhiêu đơn. Bảy kỹ năng dưới đây giúp bạn giải thích vì sao hệ thống của mình hỏng theo những kiểu đó, chọn phương án cho từng loại dữ liệu, và biết hỏi agent số liệu nào trước khi đồng ý với đề xuất của nó.

- **Phân biệt node chết với node chậm** — Bạn giải thích được vì sao khi gọi sang một node mà không nhận được câu trả lời, máy gọi không biết node đó đã crash, đang bận hay bị network partition (mất kết nối network giữa hai node), nên tăng timeout chưa chắc đã đúng. Bạn cũng biết vì sao giờ lấy từ đồng hồ của từng máy không đủ để xếp đúng thứ tự các bước giữa nhiều service.
- **Giải thích replication lag** — Bạn chỉ ra replica chậm hơn primary ở chặng nào trên đường đi của một lệnh ghi, và vì sao một job sửa hàng triệu dòng trong một transaction có thể đẩy lag lên vài chục giây. Với từng loại transaction, bạn chọn commit có chờ replica hay không, và nói được mỗi cách được gì, mất gì.
- **Chọn nơi đọc cho từng trang** — Từ triệu chứng, bạn nhận ra hệ thống đang bảo đảm mức consistency nào khi đọc, từ linearizable, tức đã ghi xong thì mọi lệnh đọc sau đó đều thấy giá trị mới, tới eventual consistency, tức các bản sao chỉ khớp nhau sau một lúc. Rồi bạn quyết định tính năng nào phải đọc từ primary, trang nào đọc từ replica được, và trang nào chỉ đọc từ replica đã có lệnh ghi mới nhất của phiên.
- **Chọn primary mới an toàn** — Bạn giải thích được vì sao primary mới phải do đa số node bỏ phiếu chọn ra, dùng term (số thứ tự của lần bầu) để từ chối lệnh từ primary cũ và lease (quyền làm primary có thời hạn) để primary cũ tự ngừng ghi khi không gia hạn được. Bạn cũng thấy vì sao primary cũ vẫn nhận lệnh ghi nếu không có thành phần nào chặn nó, dẫn tới split brain, tức hai node cùng nhận lệnh ghi như primary.
- **Xử lý khi primary chết** — Bạn chọn replica để promote, tức đưa nó lên làm primary, theo lượng dữ liệu nó đã nhận, và ước lượng được số lệnh ghi sẽ mất. Bạn cũng chọn được cách đưa primary cũ trở lại cụm mà không ghi đè dữ liệu mới, và cách sửa đúng những dòng một script đã ghi sai thay vì đưa cả database về quá khứ.
- **So sánh ba kiểu replication** — Bạn phân biệt leader-follower (chỉ một node nhận lệnh ghi, với Postgres là primary), multi-leader (nhiều nơi cùng nhận lệnh ghi) và leaderless replication (không node nào làm primary, lệnh ghi gửi tới nhiều node). Rồi bạn chọn cách xử lý conflict khi hai nơi cùng sửa một dòng, và nhận ra quy tắc nghiệp vụ nào, như giới hạn lượt dùng của một mã giảm giá, không giữ được chỉ bằng cách gộp dữ liệu.
- **Hỏi agent đúng số liệu** — Agent có thể đề xuất thêm replica, bật synchronous replication (primary chờ replica xác nhận rồi mới báo commit thành công) hay bật failover tự động, tức để một công cụ tự chuyển vai primary sang replica khi primary chết. Trước khi đồng ý, bạn hỏi agent xem replica đang chậm hơn primary bao nhiêu giây, primary chết lúc này thì mất khoảng bao nhiêu lệnh ghi, và bao nhiêu node bỏ phiếu chọn primary mới.

Ví dụ trong khóa học chạy trên Postgres, code minh họa là những đoạn TypeScript ngắn và không gắn với công cụ failover nào; lệnh vận hành và cấu hình cụ thể nằm trong demo kit cho agent của bạn.

## Khóa học cung cấp những nền tảng nào

Cả khóa học dựa trên một câu hỏi. Khi dữ liệu có nhiều bản sao, bản nào đáng tin, và hệ thống có biết nó đang tin bản nào hay không? Khi replica chậm hơn primary, khi hai node cùng tin mình là primary, hay khi hai nơi cùng sửa một dòng, hệ thống đều đã trả lời sai câu hỏi đó.

Mỗi bài bắt đầu từ một triệu chứng bạn tự thấy được trên app, trên dashboard hay trong kết quả đối soát. Sau đó bài chỉ ra lệnh ghi và lệnh đọc đi qua những node nào, dữ liệu nào có thể mất hoặc bị ghi đè, cách sửa có tradeoff gì, và khi hệ thống còn ở quy mô nào thì bạn chưa cần tới cơ chế của bài.

Bộ [ghi chú bài giảng Distributed Systems](https://www.cl.cam.ac.uk/teaching/2526/ConcDisSys/dist-sys-notes.pdf) của Martin Kleppmann trình bày mô hình lỗi, đồng hồ, replication, quorum và CRDT, đọc kèm được với gần như mọi chương. Chương [High Availability, Load Balancing, and Replication](https://www.postgresql.org/docs/current/high-availability.html) trong tài liệu PostgreSQL so sánh replication đồng bộ (synchronous), không mất dữ liệu khi failover, với replication bất đồng bộ (asynchronous), nhanh hơn nhưng có thể mất một số transaction. Bài viết [The network is reliable](https://aphyr.com/posts/288-the-network-is-reliable) tập hợp những lần network partition có thật, trong đó có những lần failover để lại hai primary cùng nhận lệnh ghi.

Năm nhóm bài dưới đây đi theo bậc thang scale, tức các nấc mà hệ thống đi qua khi lớn dần, bắt đầu từ một primary có replica, qua failover tự động, tới nhiều nơi cùng nhận lệnh ghi. Ba bài nền dùng chung với các khóa System Design khác đứng ngay trước bài đầu tiên cần tới chúng. Ba bài tình huống ghép nhiều cơ chế vào một sự cố trọn vẹn, có số đo trước và sau khi sửa.

### 1. Node lỗi, network partition và đồng hồ của các máy lệch nhau

Nhóm đầu tiên gồm hai ý mà mọi bài sau dựa vào. Kênh cảnh báo của app báo replica “down” **23 lần** trong một tuần, nhưng **21 lần** trong số đó replica vẫn chạy, chỉ là disk của nó bận vài giây. Mỗi lần replica bị gỡ khỏi danh sách nhận lệnh đọc, toàn bộ tải đọc dồn về primary và CPU của primary vọt lên **88%**.

Bài đầu của nhóm giải thích vì sao khi một lần health-check không nhận được câu trả lời, bạn vẫn chưa biết node kia đã chết, đang chậm hay bị network partition. Từ đó bài đặt luật gỡ replica, kèm tradeoff giữa gỡ nhầm ít hơn và phát hiện crash chậm hơn.

Bài thứ hai bắt đầu từ màn hình lịch sử xử lý đơn, nơi khoảng **1,1%** số đơn hiện bước “đã hoàn tiền” trước bước “đã hủy”, và **64 ticket** bị mở lại vì nhân viên trả lời khách theo thứ tự sai. Đồng hồ của máy chạy service thanh toán chậm **1,3 giây** là đủ làm đảo thứ tự. Bài dùng Lamport clock, một bộ đếm gắn vào từng bước và từng message, để bước kết quả luôn đứng sau bước đã gây ra nó. Ngay trước bài này là bài nền về cách đọc timeline của hai request chạy song song, kỹ năng còn dùng lại ở nhiều chương sau.

### 2. Replication lag và consistency khi đọc từ replica

Nhóm mở đầu bằng bài nền về read replica, tức replica nhận bớt lệnh đọc cho primary; bài đó gọi tên replication lag nhưng chưa đi sâu. Bài tiếp theo bắt đầu từ một biểu đồ lag thường nằm dưới **0,5 giây**, nhưng mỗi đêm từ **01:00** tới **01:40** lại vọt lên khoảng **95 giây**, đúng lúc một job do agent viết sửa **6 triệu** dòng trong một transaction. Bài lần theo một lệnh ghi qua từng chặng từ primary tới replica để tìm chặng bị chậm, rồi chia job thành lô nhỏ để lag lúc job chạy xuống dưới **3 giây**.

Sau một sự cố mất dữ liệu, bạn bật synchronous replication cho database thanh toán. Một đêm, replica đồng bộ duy nhất khởi động lại để vá kernel, mọi lệnh ghi treo **7 phút** và khoảng **1.900 lượt** thanh toán lỗi timeout. Bài giải thích “commit thành công” nghĩa là gì với từng cách commit, và chọn cho mỗi loại transaction mức chờ replica phù hợp.

Hai bài sau đó nói về consistency, tức những gì một lệnh đọc được bảo đảm sẽ thấy. Một khách bấm “đăng xuất khỏi mọi thiết bị”, vậy mà trong **3,8 giây** sau đó chiếc điện thoại bị mất vẫn gọi thành công **5** API, trong đó có một lần đặt đơn mới. Bài đọc lịch sử các lệnh đọc và ghi để xác định hệ thống có bảo đảm linearizable hay không, tức lệnh thu hồi phiên đã ghi xong thì mọi lệnh đọc sau đó có thấy nó hay không, rồi quyết định tính năng nào phải đọc từ primary.

Ở trang tổng quan của người bán, số đơn hôm nay hiện **1.204**, tải lại thành **1.198** rồi **1.207**, vì mỗi lần tải rơi vào một replica có lag khác nhau. Bài cho mỗi phiên nhớ mốc dữ liệu mà nó đã thấy, để giữ cho từng phiên read-your-writes (phiên luôn thấy lệnh ghi của chính mình) và monotonic read (lần đọc sau không thấy dữ liệu cũ hơn lần đọc trước).

Bài tình huống khép nhóm này. Từ khi trang “Đơn của tôi” đọc từ hai replica, tỉ lệ đơn trùng tăng từ **0,05%** lên **0,7%**, khoảng **224 đơn** mỗi ngày, vì khách không thấy đơn vừa đặt nên đặt lại. Bài so sánh ba cách cho khách thấy đơn vừa đặt trên cùng một kịch bản tải, chọn một cách, rồi đo xem primary phải nhận thêm bao nhiêu tải.

### 3. Chọn primary mới mà không để xảy ra split brain

Lease mở đầu nhóm này: một bài nền giải thích lease là quyền làm một việc trong thời hạn nhất định và phải được gia hạn đều đặn, rồi chỉ ra vì sao worker cũ vẫn ghi được dữ liệu sau khi lease của nó đã hết hạn. Bài tiếp theo đưa vấn đề đó lên cấp cả cụm database. Một script watchdog do agent viết tự promote replica **4 lần** trong một quý, lần nào primary cũng vẫn đang chạy, và mỗi lần người trực phải dừng ghi khoảng **6 phút** để chọn lại một primary. Bài thay luật “ping lỗi **3** lần thì promote” bằng leader election, tức các node bỏ phiếu chọn primary theo đa số. Mỗi lần bầu lại, term tăng lên để cả cụm biết primary nào mới nhất, còn primary chỉ nhận lệnh ghi khi lease của nó còn hạn.

Dù đã có đa số node bỏ phiếu, primary cũ vẫn chưa tự biết nó đã mất quyền. Sau một network partition **70 giây** giữa hai tủ máy, **1.150 lượt** cập nhật chỉ nằm trên primary cũ, và khách của **420 đơn** đã giao xong lại thấy “đang giao”. Bài đặt một router giữa app và database để chỉ chuyển lệnh ghi tới node được chọn ở term hiện tại, và chỉ ra phần dữ liệu mà router không cứu được.

### 4. Failover, failback và sửa dữ liệu bị ghi sai

Khi primary chết thật, việc đầu tiên là chọn replica để promote. Công cụ failover promote replica đứng đầu danh sách cấu hình, trong khi replica đó thiếu các lệnh ghi của khoảng **2,6 giây**, còn replica kia chỉ thiếu **0,13 giây**. Khoảng **1.300 transaction** biến mất, trong đó có **180 đơn** mà khách đã thấy báo đặt thành công. Bài chọn replica theo lượng dữ liệu đã nhận, ước lượng số lệnh ghi mất, và đặt ngưỡng để công cụ gọi người trực thay vì tự promote.

Sau lần failover trước, riêng việc đưa primary cũ trở lại cụm đã mất **3 giờ 20 phút** để chép lại toàn bộ **1,2 TB**, suốt thời gian đó cụm không có replica dự phòng. Rồi bước failback, tức đổi primary về máy cũ, làm **2.300 request** lỗi. Bài so sánh chép lại toàn bộ với rewind, tức chỉ chép lại phần dữ liệu đã khác đi từ điểm hai node rẽ nhánh. Bài cũng dùng switchover, tức đổi primary có kế hoạch, để chờ máy cũ có đủ những lệnh ghi cuối cùng rồi mới promote nó.

Replica không thay được backup, vì nó chép luôn cả lệnh ghi sai. Một script do agent viết lọc nhầm category **24** thay vì **42**, và **18.400** sản phẩm bị bán với giá chỉ bằng một phần mười suốt **36 phút**, trong khi app vẫn nhận thêm **2.300 đơn**. Bài so sánh restore cả database về trước lúc script chạy với logical repair, tức sửa đúng những dòng bị ghi sai mà giữ nguyên các đơn hợp lệ.

Bài tình huống ghép nhóm này với nhóm trước. Giữa đêm, bạn khởi động lại primary cũ sau khi đã promote replica, và đối soát lúc **03:40** tìm ra **1.870** mã đơn bị trùng giữa hai database, còn số đơn trên dashboard thấp hơn số giao dịch ở cổng thanh toán khoảng **17%**. Từ các dấu hiệu đó, bài lần ra đây là sự cố gì, chọn thứ tự xử lý khi cụm đã có hai primary, rồi đặt cơ chế chặn primary cũ để lần failover sau không tạo thêm primary thứ hai.

### 5. Nhiều node cùng nhận lệnh ghi thì xử lý conflict thế nào

Nhóm cuối dành cho lúc một primary không còn đủ. Máy tính bảng ở điểm nhận hàng Thủ Đức mất kết nối tới Postgres trung tâm **3 lần**, cộng lại **5,5 giờ**, và khoảng **640 khách** phải chờ hoặc quay lại hôm sau. Team đề xuất đặt ở mỗi điểm nhận hàng một Postgres cũng nhận lệnh ghi và đồng bộ hai chiều với trung tâm. Bài chọn nơi ghi cho từng loại dữ liệu, vì dòng nào chỉ có một nơi ghi thì không có conflict, và chỉ ra khi nào multi-leader mới thật sự đáng dùng.

Bài về leaderless replication bắt đầu từ giỏ hàng. Sau khi một node khởi động lại để cài bản vá, **2,1%** lượt đọc giỏ trả về giỏ sai suốt **30 phút**, và **310 đơn** phải sửa trong ngày. Bài giải thích quorum, tức số node tối thiểu phải trả lời thì lệnh ghi hay lệnh đọc mới tính là xong. Bài cũng chỉ ra vì sao chọn W + R > N, tức số node phải xác nhận lệnh ghi (W) cộng số node phải trả lời lệnh đọc (R) lớn hơn số node giữ bản sao của dữ liệu (N), thì lệnh đọc luôn gặp ít nhất một node đã có lần ghi mới nhất.

Hai bài tiếp theo nói về cách xử lý conflict. Job đồng bộ giữ bản có `updated_at` lớn hơn đã âm thầm ghi đè **412** bản sửa địa chỉ và ghi chú giao hàng trong một tháng; bài thay last-write-wins bằng version vector để biết hai lần sửa xảy ra trước sau hay đồng thời. Một counter CRDT, tức bộ đếm mỗi nơi ghi tự tăng rồi gộp lại được, không cần hỏi nhau, đếm đúng mọi lượt dùng của mã giảm giá, vậy mà số lượt vẫn vượt giới hạn **5.000**, lên **5.312**; bài chỉ ra quy tắc nào của nghiệp vụ buộc các nơi ghi phải phối hợp, và chia trước hạn mức cho từng nơi.

Bài tình huống cuối khóa ghép ba cơ chế của nhóm này, trong đó mỗi dòng chỉ có một nơi ghi, mỗi nơi tự giữ phần số đếm của mình, và hạn mức được chia trước. Hai kho mất kết nối network với nhau **2 giờ 40 phút**, và sau khi đồng bộ lại, kiểm kê cuối ngày lệch **214** SKU, **58** lần điều chỉnh tồn kho biến mất không kèm lỗi nào, còn **41** đơn đã xác nhận cho khách nhưng kho xuất không còn hàng. Bài cho mỗi kho chỉ ghi dòng tồn kho của mình và chỉ xác nhận đơn cần hàng ở kho kia trong hạn mức được giao trước, rồi chạy lại đúng lần mất kết nối đó để đo.

## Vì sao 200Lab tạo ra khóa học này

Ngày nay, bật một replica chỉ cần vài cú bấm trên trang quản trị của nhà cung cấp cloud, hoặc một câu đồng ý với đề xuất của agent. Database của bạn có bản sao trên nhiều máy từ rất sớm, thường trước khi bạn kịp hỏi bản sao đó đang chậm bao nhiêu, và nếu primary chết thì nó có đủ dữ liệu để thay hay không.

Sự cố dữ liệu phân tán hiếm khi để lại dòng lỗi nào trong log. Replica vẫn chạy, failover chạy đúng như cấu hình, job đồng bộ báo xong, vậy mà đơn đã báo thành công cho khách không còn trong database, bản sửa của nhân viên biến mất, tồn kho lệch với số đếm trên kệ. Khi đó, người ngồi đối soát từng đơn, liên hệ từng khách để hoàn tiền và giải thích vì sao số liệu nhảy lên xuống chính là bạn.

Chuyện này không chỉ xảy ra với app nhỏ. GitLab từng có hai node cùng là primary, và team phải bỏ các lệnh ghi của khoảng **6 giờ** đã nằm trên một trong hai node. Một network partition **43 giây** làm GitHub chuyển primary MySQL sang data center khác, và ở một cụm database còn **954** lệnh ghi chưa kịp chép sang. Ở cả hệ thống lớn lẫn app vừa có replica đầu tiên, gốc của sự cố thường giống nhau: không ai biết bản sao đang chậm bao nhiêu, và không thành phần nào chặn node cũ. Agent viết rất nhanh một script failover hay một job đồng bộ hai chiều, nhưng nó không biết dữ liệu nào của bạn được phép mất.

Khóa học giúp bạn đặt đúng câu hỏi trước khi sự cố xảy ra. Mỗi sự cố trong khóa được truy ngược về một quyết định cụ thể, như đọc từ đâu, commit có chờ replica hay không, thành phần nào chặn primary cũ, hay dòng nào được ghi ở đâu. Học xong, bạn trình bày được với team vì sao một sự cố đã xảy ra, bảo vệ phương án mình chọn bằng tradeoff cụ thể, và hỏi agent bằng số liệu thay vì nhận đề xuất rồi chờ xem chuyện gì xảy ra. Bạn cũng có cơ sở để nói rằng ở quy mô hiện tại, một primary và một replica có đo lag là đủ.

## Những vấn đề thường gặp khi thiết kế dữ liệu phân tán

Khi dữ liệu trên app lệch đi, phản xạ đầu tiên thường là tìm lỗi trong code của trang đó, hoặc nhờ agent tăng timeout và thêm retry. Với dữ liệu có nhiều bản sao, nguyên nhân thường nằm ở chỗ lệnh đọc đi tới bản sao nào, lệnh ghi đi tới node nào, và hệ thống đang tin bản sao nào.

- App báo replica “down” **23 lần** trong tuần và agent đề xuất tăng timeout — **21 lần** trong số đó replica vẫn chạy, chỉ là disk của nó bận vài giây; health-check không phân biệt được node chết với node chậm, nên luật gỡ replica phải tính tới tradeoff giữa gỡ nhầm và phát hiện crash chậm.
- Khách đặt đơn xong, mở “Đơn của tôi” thì không thấy đơn nên đặt lại, và tỉ lệ đơn trùng tăng từ **0,05%** lên **0,7%** — trang đọc từ replica chậm hơn primary, và không có gì bảo đảm phiên của khách đọc thấy lệnh ghi của chính mình.
- Khách đã bấm “đăng xuất khỏi mọi thiết bị” nhưng chiếc điện thoại bị mất vẫn đặt được đơn trong **3,8 giây** sau đó — service xác thực ghi lệnh thu hồi vào primary nhưng kiểm tra phiên trên replica, trong khi tính năng này cần mọi lệnh đọc bắt đầu sau khi ghi xong đều thấy giá trị mới.
- Bật synchronous replication để không mất dữ liệu, rồi mọi lệnh ghi treo **7 phút** khi replica khởi động lại — primary chờ đúng một replica xác nhận, nên replica đó dừng thì primary cũng không báo commit thành công được.
- Script watchdog promote replica **4 lần** trong quý trong khi primary vẫn chạy — với hai node, mỗi node chỉ thấy node kia, nên không phân biệt được node kia chết với mất kết nối network tới nó; quyết định promote cần đa số node bỏ phiếu.
- Failover tự chạy xong, không bước nào báo lỗi, nhưng đối soát thiếu **180 đơn** — công cụ failover promote replica đứng đầu danh sách cấu hình, trong khi replica còn lại thiếu ít dữ liệu hơn nhiều.
- Một script ghi sai giá và team định restore cả database về trước lúc script chạy — cách đó làm mất luôn **2.300 đơn** hợp lệ đặt sau sự cố, trong khi chỉ cần lấy giá cũ từ một bản database restore riêng rồi sửa đúng những dòng bị ghi sai.
- Job đồng bộ hai chiều báo chạy xong, nhưng bản sửa của nhân viên biến mất không kèm lỗi nào — last-write-wins giữ bản có timestamp lớn hơn nên âm thầm ghi đè bản sửa còn lại, kể cả khi đồng hồ chạy đúng.

Mỗi triệu chứng trên có bài riêng trong khóa, với ví dụ, số liệu và những câu hỏi để bạn hỏi lại agent trước khi chọn cách sửa.

## Bạn sẽ học theo cách nào

- **Bắt đầu từ triệu chứng** — Mỗi bài mở bằng thứ bạn tự thấy được, như một màn hình của app, một biểu đồ trên dashboard, một cảnh báo hay một kết quả đối soát, kèm câu hỏi chuyện gì đang xảy ra với dữ liệu.
- **Ví dụ nhỏ và sơ đồ** — Với cơ chế khó hình dung, bài dựng ví dụ hai, ba node trước rồi mới quay lại nghiệp vụ; sơ đồ gọn và ảnh minh họa cho thấy lệnh ghi, lệnh đọc đi qua primary và replica theo thời gian.
- **Chọn phương án bằng số liệu** — Nơi đọc, cách commit, số node bỏ phiếu hay replica được promote đều được chọn từ số liệu của ví dụ, kèm tradeoff và nấc của bậc thang scale mà ở đó bạn chưa cần cơ chế này.
- **Quiz yêu cầu bạn dự đoán** — Nhiều bài giải thích cơ chế có quiz ngắn, như đoán nhóm node nào bầu được leader khi network partition chia cụm năm node thành hai và ba, hay promote replica nào thì mất ít lệnh ghi nhất.
- **Demo kit chạy bằng Docker** — Ba bài tình huống có ba demo kit, mỗi kit là một file Markdown để coding agent của bạn dựng hệ thống ở trạng thái chưa sửa, cho triệu chứng hiện ra, rồi chạy lại kịch bản sau khi sửa và so sánh số đo của hai lần chạy; lệnh và cấu hình Postgres nằm trong kit, và kit không bắt buộc.

## Khóa học dành cho ai

1. **Junior developer hoặc vibe coder** — Bạn có app chạy trên Postgres, phần lớn code do AI agent viết, có thể đã có replica do cloud bật sẵn hoặc do agent đề xuất, và đã gặp sự cố dữ liệu khi hệ thống lớn lên; bạn cần giải thích được vì sao nó hỏng và biết hỏi agent câu gì.
2. **Developer sắp bật failover tự động** — Agent hoặc team vừa đề xuất thêm replica, bật synchronous replication hay cài công cụ failover, và bạn cần tiêu chí để quyết định trước khi đồng ý.
3. **Developer có app ghi ở nhiều nơi** — App của bạn phải ghi được ở những nơi thường mất kết nối, như kho hay điểm nhận hàng, hoặc đang có hai database cùng sửa một dòng, và bạn cần biết khi nào multi-leader đáng dùng và xử lý conflict ra sao.

## Những điều cần lưu ý

- **Kiến thức nền cần có** — Bạn chỉ cần đã có app nhận request và ghi vào Postgres trong transaction, kể cả khi code do agent viết, và đọc được TypeScript, SQL cùng dòng lỗi trong log; không cần từng vận hành Postgres. Ba bài nền về timeline của hai request chạy song song, read replica và lease đã nằm sẵn trong khóa, ngay trước bài cần đến chúng.
- **Nên học thêm các khóa System Design liên quan** — Ba bài nền trên lấy từ hai khóa “Thiết kế hệ thống — ước lượng tải, load balancer, cache, queue, replication và partitioning” và “Xử lý timeout, retry và crash — idempotency, outbox, lease và reconciliation”. Học thêm hai khóa đó không phải điều kiện để học khóa này, nhưng giúp bạn nắm đầy đủ hơn về source of truth, RPO, RTO, compare-and-set và idempotency key, những phần ở đây chỉ được nhắc một câu.
- **Không đi vào cấu hình công cụ hay sharding** — Lệnh vận hành, tham số của Postgres và cấu hình công cụ failover nằm trong demo kit cho agent, còn sharding, hot key và việc dời dữ liệu giữa các node khi vẫn nhận lệnh ghi thuộc một khóa System Design khác; đổi lại, bạn hiểu cơ chế đủ rõ để biết agent và công cụ đang quyết định thay bạn những gì.
- **Số liệu trong ví dụ do bài tự đặt** — Số đơn, số giây lag hay số lệnh ghi bị mất được chọn để các phép tính khớp nhau, không phải số đo từ production của một công ty nào; khi áp dụng, bạn nhờ agent đo replication lag, lượng dữ liệu replica còn thiếu và thời gian dừng ghi trên hệ thống của mình rồi đưa vào cùng cách tính.

## Bắt đầu từ đâu

Khóa học mở đầu bằng năm sự cố dữ liệu mà một app đang lớn dần hay gặp, và một lần failover drill, tức chủ động tắt primary trên staging để đo, mất **41 phút** dù bước promote chỉ mất **3 phút**. Bài đầu tiên chỉ ra **38 phút** còn lại đã đi đâu, ba số liệu bạn cần theo dõi cho mỗi replica, và app của bạn đang ở nấc nào của bậc thang dữ liệu phân tán. Biết nấc đó, bạn biết nên đọc kỹ chương nào trước.

## Chương trình

- Dữ liệu nằm trên nhiều máy thì bản nào đáng tin (miễn phí)

### Node lỗi, network partition và đồng hồ của các máy lệch nhau

- Health-check báo replica chết trong khi replica vẫn chạy (miễn phí)
- Đọc timeline của hai request chạy song song để tìm race condition (miễn phí)
- Màn hình lịch sử đơn hiện bước hoàn tiền trước bước hủy (miễn phí) · quiz

### Replication lag và consistency khi đọc từ replica

- Read replica gánh bớt tải đọc cho database primary · quiz
- Replication lag tăng vọt mỗi đêm khi job batch chạy · quiz
- Mọi lệnh ghi bị treo khi replica đồng bộ khởi động lại · quiz
- Đã đăng xuất khỏi mọi thiết bị mà request cũ vẫn được chấp nhận · quiz
- Số đơn hôm nay giảm đi khi người bán tải lại trang · quiz
- Tình huống: đơn vừa đặt không hiện trong lịch sử đơn · kit cho agent

### Chọn primary mới mà không để xảy ra split brain

- Lease đã hết hạn mà worker cũ vẫn ghi dữ liệu · quiz
- Script watchdog tự promote replica khi primary vẫn đang chạy · quiz
- Primary cũ vẫn nhận lệnh ghi sau khi replica đã được promote

### Failover, failback và sửa dữ liệu bị ghi sai

- Primary chết thì nên promote replica nào · quiz
- Đưa primary cũ trở lại cụm mà không ghi đè dữ liệu mới · quiz
- Sửa dữ liệu bị ghi sai mà không đưa cả database về quá khứ · quiz
- Tình huống: khởi động lại nhầm primary cũ tạo ra hai primary · kit cho agent

### Nhiều node cùng nhận lệnh ghi thì xử lý conflict thế nào

- Chia dữ liệu theo nơi ghi trước khi bật multi-leader
- Giỏ hàng hiện lại món đã xóa sau khi một node khởi động lại · quiz
- Last-write-wins âm thầm ghi đè bản sửa địa chỉ giao hàng · quiz
- Mã giảm giá vượt giới hạn dù counter CRDT không đếm sót lượt nào · quiz
- Tình huống: tồn kho sai sau khi hai kho đồng bộ lại dữ liệu · kit cho agent
