DDIA · Martin Kleppmann · Chương 8DDIA · Martin Kleppmann · Chương 8DDIA · Martin Kleppmann · Chapter 8
DDIA · Chương 8 · Rắc rối của hệ phân tánDDIA · Chương 8 · Rắc rối của hệ phân tánDDIA · Chapter 8 · The Trouble with Distributed Systems

Hệ phân tán hỏng theo kiểu bộ phận và bất địnhNhiều máy làm chung thì hay trục trặcDistributed systems fail in partial, nondeterministic ways

Đây là chương đổ vỡ của cuốn sách. Sau khi Chương 7 dựng lên giao dịch như một lớp trừu tượng gọn gàng, Kleppmann quay lại tháo tung nền móng: trong một hệ phân tán, bất cứ thứ gì có thể hỏng thì sẽ hỏng, và tệ hơn — bạn thường không biết nó đã hỏng hay chưa1. Mạng nuốt gói tin, đồng hồ nói dối, tiến trình đứng hình giữa chừng rồi tỉnh dậy như chưa có gì xảy ra. Giọng của chương là hoài nghi có kỷ luật: không phải bi quan, mà là từ chối tin vào những giả định mà phần cứng không hề hứa hẹn.

Một cái máy hỏng thì bạn biết ngay: nó tắt. Nhưng khi nhiều máy làm việc cùng nhau, chuyện khó hơn nhiều: một máy có thể im lặng mà bạn không biết nó chết hay chỉ đang bận. Chương này kể ra tất cả những kiểu trục trặc kỳ quặc ấy, để mình đừng ngây thơ tin rằng mọi thứ luôn chạy đúng.

This is the book's chapter of breakage. After Chapter 7 builds transactions into a tidy abstraction, Kleppmann goes back and pulls the foundations apart: in a distributed system anything that can go wrong will go wrong, and worse — you usually cannot tell whether it has1. Networks swallow packets, clocks lie, processes freeze mid-sentence and wake up as if nothing happened. The chapter's tone is disciplined scepticism: not pessimism, but a refusal to believe assumptions the hardware never promised.

∴
Luận đề của chương Điều quan trọng nhất The thesis of the chapter

Khác biệt cốt lõi giữa một máy đơn và một hệ phân tán là lỗi bộ phận: trên một máy, lỗi thường toàn phần và tất định (hoặc chạy đúng, hoặc sập hẳn); trong hệ phân tán, một phần hỏng trong khi phần còn lại vẫn chạy, và kết quả bất định1. Vì mạng, đồng hồ và tiến trình đều không cho ta bất kỳ chặn trên nào về thời gian, ta không thể phân biệt "node đã chết" với "node đang chậm". Kết luận thực dụng của Kleppmann: đừng cố suy ra sự thật từ một node tự tin — hãy để đa số định nghĩa sự thật, và bảo vệ tài nguyên bằng cơ chế có thể chứng minh được như fencing token.

Một máy hỏng thì hỏng hẳn — dễ hiểu. Nhiều máy thì có máy hỏng, máy không, và bạn không biết máy im lặng kia còn sống hay đã chết. Nên đừng tin một máy tự nói "tôi đúng"; hãy hỏi cả nhóm. Nhớ nhé: trong hệ nhiều máy, số đông mới là người quyết định sự thật.

The core difference between one machine and a distributed system is partial failure: on one machine faults are usually total and deterministic (it works, or it crashes); in a distributed system part of it breaks while the rest runs on, and the outcome is nondeterministic1. Because networks, clocks and processes give us no upper bound on time, we cannot distinguish "the node is dead" from "the node is slow". Kleppmann's pragmatic conclusion: stop inferring truth from one confident node — let the majority define truth, and protect resources with provable mechanisms such as fencing tokens.

Trục 1 · Partial failureAxis 1 · Partial failure

Lỗi bộ phận: hỏng một phần, và không biết là phần nàoPartial failure: part of it breaks, and you can't tell which

Kleppmann mở chương bằng một phép so sánh: khi bạn viết phần mềm chạy trên một máy, phần cứng cho bạn một lời hứa ngầm rất mạnh — nó hoặc làm đúng, hoặc hỏng hẳn. Không có trạng thái "hơi hỏng"1. Máy tính một nút được thiết kế để thà sập còn hơn trả kết quả sai, nên lỗi là toàn phần và tất định: chạy lại cùng đầu vào thường ra cùng kết quả. Hệ phân tán vứt bỏ lời hứa đó. Ở đó lỗi là bộ phận — một nút, một liên kết, một switch hỏng trong khi phần còn lại vẫn chạy — và bất định: cùng một thao tác lúc thành công, lúc thất bại, lúc treo vô hạn.

Một cái máy tính giống một cái đèn: hoặc sáng, hoặc tắt. Bạn nhìn là biết. Nhưng một nhóm máy nối với nhau thì giống một lớp học đông: có bạn giơ tay, có bạn im lặng — mà im lặng thì có thể là ngủ gật, có thể là đang suy nghĩ, có thể là đã về nhà. Bạn không nhìn thấy được. Nhớ nhé: khó nhất không phải là hỏng, mà là không biết có hỏng hay không.

Kleppmann opens with a comparison: when you write software for one machine, the hardware gives you a very strong implicit promise — it either does the right thing or fails completely. There is no "slightly broken" state1. A single computer is built to crash rather than return a wrong answer, so faults are total and deterministic: rerun the same input and you usually get the same result. A distributed system throws that promise away. There, faults are partial — one node, one link, one switch fails while the rest runs on — and nondeterministic: the same operation sometimes succeeds, sometimes fails, sometimes hangs forever.

Điểm tinh tế là bất định chứ không chỉ bộ phận. Một lỗi bộ phận mà bạn quan sát được thì vẫn xử lý được bằng dư thừa, đúng như bộ ba reliability · scalability · maintainability của Chương 1. Cái làm hệ phân tán khó suy luận là tính không quan sát được: bạn gửi một yêu cầu và không nhận được gì. Đã xảy ra điều gì? Yêu cầu chưa rời máy bạn; nó lạc trên đường; nó tới nơi và node đã chết trước khi xử lý; nó được xử lý xong và phản hồi mới là thứ bị lạc. Bốn kịch bản, một quan sát duy nhất là sự im lặng. Cả chương 8 là cuộc vật lộn với sự im lặng đó.

Ý lõi 01 · Deterministic vs nondeterministicIdea 01 · Deterministic vs nondeterministic

Một máy: tất định. Nhiều máy: bất địnhOne machine: deterministic. Many: not

Trên một máy, một bug thường tái hiện được: cùng đầu vào, cùng trạng thái, cùng kết quả sai. Đó là lý do gỡ lỗi phần mềm một nút là việc khả thi. Trong hệ phân tán, cùng một lệnh gọi có thể thành công chín lần rồi treo lần thứ mười vì một switch vừa nghẹt hàng đợi. Kleppmann gọi đây là bước nhảy về bản chất của bài toán, không phải về quy mô1: bạn không còn suy luận bằng chắc chắn mà bằng xác suất và giả định.

Ở một máy, nếu bạn làm lại y hệt thì thường ra y hệt — nên tìm ra chỗ sai không quá khó. Ở nhiều máy, làm lại y hệt có khi ra khác hẳn, vì đường truyền lúc thông lúc tắc. Nhớ nhé: cái khó không phải là nhiều máy hơn, mà là kết quả không còn lặp lại được.

On one machine a bug usually reproduces: same input, same state, same wrong answer. That is why debugging single-node software is tractable. In a distributed system the same call can succeed nine times and hang on the tenth because a switch queue just filled up. Kleppmann treats this as a change in the nature of the problem, not in its scale1: you stop reasoning with certainty and start reasoning with probabilities and assumptions.

Bất định là bước nhảy về chất, không về lượng.
Nhớ nhé: làm lại chưa chắc ra như cũ.
Nondeterminism is a change in kind, not degree.
Ý lõi 02 · Reliable from unreliableIdea 02 · Reliable from unreliable

Dựng cái đáng tin từ cái không đáng tinBuilding something reliable from unreliable parts

Kleppmann không dừng ở bi quan. Ông nhắc rằng ngành máy tính đã quen với bài toán này từ lâu: von Neumann viết về việc tổng hợp cơ thể đáng tin từ các bộ phận không đáng tin, còn Shannon cho ta lý thuyết truyền tin qua kênh nhiễu — cả hai đều nằm trong danh mục tham chiếu của chính Chương 83. IP có thể mất gói, nhưng TCP dựng trên nó một dòng dữ liệu "đáng tin"; giới hạn của thủ thuật ấy chính là bài học của chương.

Không phải cứ vật liệu dở là làm đồ dở. Người ta vẫn xây nhà chắc từ những viên gạch không hoàn hảo, bằng cách đặt nhiều viên đỡ nhau. Máy tính cũng vậy: đường truyền hay rớt thư, nhưng có cách gửi lại để cuối cùng thư vẫn tới. Nhớ nhé: từ những thứ hay hỏng, vẫn làm ra được thứ khá đáng tin — nếu biết cách.

Kleppmann does not stop at pessimism. He notes that computing has long known this problem: von Neumann wrote about synthesising reliable organisms from unreliable components, and Shannon gave us the theory of communication over noisy channels — both sit in Chapter 8's own bibliography3. IP may lose packets, yet TCP builds a "reliable" stream on top of it; the limits of that trick are the lesson of the chapter.

Dung thứ lỗi là truyền thống, không phải phép màu.
Nhớ nhé: gạch xấu vẫn xây được nhà chắc.
Fault tolerance is a tradition, not magic.
Ý lõi 03 · Cloud vs supercomputingIdea 03 · Cloud vs supercomputing

Hai triết lý đối lập về lỗiTwo opposing philosophies of failure

Kleppmann đặt cạnh nhau hai thái cực1. Siêu máy tính (HPC) chạy các tác vụ tính toán dài, thường checkpoint định kỳ rồi khi có lỗi thì dừng cả job và khởi động lại từ điểm lưu — tức là xử lý lỗi bộ phận bằng cách biến nó thành lỗi toàn phần, giống một máy đơn phóng to. Dịch vụ trực tuyến trên đám mây thì không được phép dừng: nó phải phục vụ liên tục, dựng trên phần cứng hàng loạt, chấp nhận rằng luôn có thứ gì đó đang hỏng. Hai triết lý này dẫn tới hai bộ công cụ khác hẳn nhau.

Có hai kiểu làm việc. Kiểu một: nếu có trục trặc thì dừng tất cả, sửa xong làm lại từ chỗ đã lưu — giống chơi game gặp lỗi thì tải lại bản save. Kiểu hai: không được dừng, vì hàng triệu người đang dùng — nên phải vừa chạy vừa vá. Nhớ nhé: hai kiểu này cần hai cách nghĩ khác nhau.

Kleppmann sets two extremes side by side1. Supercomputers (HPC) run long computations, typically checkpoint periodically, and on failure stop the whole job and restart from the last checkpoint — handling partial failure by turning it into total failure, like one giant single machine. A cloud online service may not stop: it must serve continuously, on commodity hardware, accepting that something is always broken. The two philosophies lead to very different toolkits.

Đám mây phải sống chung với lỗi, không né được.
Nhớ nhé: không được dừng thì phải chịu được lỗi.
The cloud must live with faults, not dodge them.
Partial failure · the defining property

Lỗi bộ phận là tình huống trong đó một phần của hệ ngừng hoạt động đúng trong khi phần còn lại vẫn chạy — và không có cách nào quan sát trực tiếp để biết phần nào đã hỏng. Hệ quả kép: lỗi bộ phận nên không thể "khởi động lại tất cả", và bất định nên không thể suy luận bằng quy tắc chắc chắn.1

Hỏng một phần nghĩa là một góc của hệ ngừng chạy nhưng các góc khác vẫn ổn — và mình không nhìn thấy góc nào hỏng. Nhớ nhé: cái khó là không nhìn thấy, chứ không phải cái hỏng.

Partial failure is when part of the system stops working correctly while the rest runs on — with no direct way to observe which part broke. A double consequence: it is partial, so you cannot just "restart everything"; and it is nondeterministic, so you cannot reason with certainty.1

Vì sao điều này đáng sợ hơn nghe có vẻ? Vì nó lan lên tầng trừu tượng. Một lệnh gọi RPC không trả lời buộc lớp gọi phải quyết định: thử lại (có thể gây tác dụng kép nếu lần đầu đã thành công), bỏ qua (có thể mất dữ liệu), hay chờ (có thể treo mãi). Không lựa chọn nào an toàn nếu không biết chuyện gì đã xảy ra — nên toàn bộ Chương 8 thực chất là đi tìm những giả định tối thiểu, có thể kiểm chứng đủ để ra quyết định. Đó là lý do chương kết thúc bằng mô hình hệ thống và cặp an toàn – sống còn, chứ không phải bằng một danh sách mẹo vặt.

Bộ phận Một phần hỏng, phần khác chạy — không thể "tắt đi bật lại" cả hệ như một máy.
Bất định Cùng thao tác, kết quả khác nhau theo thời điểm — bug khó tái hiện.
Không quan sát được Sự im lặng có ít nhất bốn cách giải thích; bạn chỉ thấy đúng một dữ kiện.
Hỏng một góc Chỗ khác vẫn chạy, nên không thể tắt hết đi bật lại.
Lúc thế này lúc thế khác Làm lại y hệt có khi ra khác, nên rất khó bắt lỗi.
Không nhìn thấy Im lặng có thể là nhiều chuyện khác nhau.
Partial One part fails while others run — you cannot reboot the whole system like one machine.
Nondeterministic The same operation gives different outcomes over time — bugs barely reproduce.
Unobservable Silence has at least four explanations; you observe exactly one datum.
Trục 2 · Unreliable networksAxis 2 · Unreliable networks

Mạng không đáng tin: bạn chỉ có timeout, và timeout luôn nói dốiUnreliable networks: all you have is a timeout, and it always lies

Hệ phân tán mà cuốn sách bàn tới là hệ shared-nothing: các máy chỉ liên lạc qua mạng, không dùng chung bộ nhớ hay đĩa. Và mạng ở đây là mạng gói bất đồng bộ (asynchronous packet network)1: bạn gửi đi một thông điệp, và mạng không hứa bao giờ nó tới, hay có tới hay không. Kleppmann liệt kê những gì có thể xảy ra với một yêu cầu: nó có thể lạc; nó có thể nằm trong hàng đợi và tới muộn; node nhận có thể đã chết; node nhận có thể đang tạm dừng; phản hồi có thể lạc; phản hồi có thể tới muộn. Người gửi không phân biệt được các trường hợp này — tất cả đều hiện ra như nhau: không có gì cả.

Tưởng tượng bạn nhắn tin cho một người bạn và không thấy trả lời. Bạn không biết: bạn ấy chưa đọc? đã đọc mà bận? hay điện thoại hết pin? Hay tin nhắn của bạn chưa gửi được? Bạn chỉ thấy một điều: im lặng. Máy tính nói chuyện với nhau qua mạng cũng đúng như thế.

The distributed systems this book discusses are shared-nothing: machines communicate only over a network, sharing no memory or disk. And that network is an asynchronous packet network1: you send a message, and the network makes no promise about when — or whether — it arrives. Kleppmann enumerates what can happen to a request: it may be lost; it may sit in a queue and arrive late; the remote node may have died; the remote node may be paused; the response may be lost; the response may be delayed. The sender cannot distinguish these cases — they all look identical: nothing at all.

Asynchronous packet network

Một mạng bất đồng bộ là mạng không đặt bất kỳ chặn trên nào cho thời gian truyền một thông điệp, và không bảo đảm thông điệp sẽ tới. Vì không có chặn trên, không tồn tại một ngưỡng chờ nào mà vượt qua nó bạn chứng minh được node kia đã chết — chỉ có ngưỡng bạn chọn để ngừng chờ.1

Mạng "bất đồng bộ" nghĩa là không ai hứa thư tới trong bao lâu, cũng không hứa thư sẽ tới. Nhớ nhé: chờ bao lâu là do bạn chọn, chứ không phải vì bạn biết.

An asynchronous network puts no upper bound on how long a message takes, and guarantees nothing about delivery. Because there is no upper bound, no waiting threshold ever proves the other node is dead — there is only the threshold you choose in order to stop waiting.1

Đây là chỗ Kleppmann tách hai khái niệm hay bị gộp. Phát hiện lỗi (failure detection) trong mạng bất đồng bộ về nguyên tắc là không hoàn hảo: mọi bộ phát hiện đều có thể vừa bỏ sót (node chết mà chưa bị tuyên bố) vừa báo nhầm (node sống mà bị tuyên bố chết). Không có cách cấu hình nào loại bỏ được cả hai cùng lúc, vì chúng là hai mặt của cùng một tham số: ngưỡng chờ. Cả chương 9 — đồng thuận — về sau được dựng trên chính sự bất khả này.1

Không hứa tới Gói tin có thể bị rớt ở bất kỳ chặng nào, và không ai báo cho bạn.
Không hứa nhanh Độ trễ không có chặn trên: một gói có thể nằm hàng đợi rất lâu rồi mới tới.
Không hứa thứ tự Thông điệp tới muộn có thể chen vào sau thông điệp gửi sau nó.
Có thể lạc Thư gửi đi chưa chắc tới nơi.
Có thể rất muộn Không ai nói trước bao lâu thì tới.
Có thể lộn xộn Thư gửi trước có khi tới sau.
No delivery A packet can be dropped at any hop, and nobody tells you.
No bound Latency has no upper bound: a packet may queue for a very long time.
No order A delayed message can arrive after one sent later than it.
  1. Network faults in practice

    Lỗi mạng trong thực tế — có đo đượcNetwork faults in practice — measured

    Không phải chuyện lý thuyết. Bailis và Kingsbury tập hợp các khảo sát công khai về lỗi mạng4. Một nghiên cứu trên vài trung tâm dữ liệu của Microsoft (Gill, Jain, Nagappan, SIGCOMM 2011) ghi nhận tỉ lệ hỏng trung bình 5,2 thiết bị mỗi ngày và 40,8 liên kết mỗi ngày, thời gian sửa trung vị khoảng 5 phút (có ca kéo dài tới một tuần), ước tính trung vị 59.000 gói tin mất mỗi lần hỏng, và — đáng lo nhất — dư thừa mạng chỉ cải thiện lưu lượng trung vị thêm 43%5. Một khảo sát khác trên mạng diện rộng CENIC (hơn hai trăm router khắp California, năm năm vận hành) tìm ra hơn 508 "phân vùng cô lập", thời lượng trung vị 2,7 phút với lỗi phần mềm và 32 phút với lỗi phần cứng4.

    Đây không phải chuyện tưởng tượng. Người ta đã đi đếm thật: trong các trung tâm máy chủ lớn, mỗi ngày vẫn có vài thiết bị và vài chục đường dây hỏng. Và điều bất ngờ: lắp thêm đường dây dự phòng không cứu được nhiều như ta tưởng. Nhớ nhé: đường truyền hỏng là chuyện hằng ngày, không phải chuyện hiếm.

    This is not theory. Bailis and Kingsbury collected the public surveys of network failure4. A study of several Microsoft datacenters (Gill, Jain, Nagappan, SIGCOMM 2011) reported an average failure rate of 5.2 devices and 40.8 links per day, a median time to repair of about five minutes (some up to a week), an estimated median of 59,000 packets lost per failure, and — most worryingly — that network redundancy improves median traffic by only 43%5. Another study of the CENIC wide-area network (over two hundred routers across California, five years of operation) found over 508 "isolating network partitions", with median durations of 2.7 minutes for software faults and 32 minutes for hardware faults4.

    Đọc con số cho đúngĐây là số đo của những môi trường cụ thể trong những cửa sổ thời gian cụ thể, không phải hằng số của mọi mạng. Điều rút ra không phải "mạng của bạn hỏng 5,2 lần một ngày", mà là "lỗi mạng đủ thường xuyên để phần mềm phải xử lý nó".
    Lưu ýMấy con số này là của một vài nơi cụ thể, không phải của mọi nơi. Ý chính là: hỏng đủ thường để phải chuẩn bị.
    Read the numbers correctlyThese are measurements of specific environments over specific windows, not constants of every network. The takeaway is not "your network fails 5.2 times a day" but "network faults are frequent enough that software must handle them".

    Vì sao dư thừa không cứu được

    Con số 43% nói lên một điều khó chịu: nhiều nguyên nhân gây lỗi mạng có tính tương quan — cấu hình sai, bản firmware lỗi, một sự kiện bảo trì — nên hai đường dây dự phòng cùng chết vì cùng một lý do. Dư thừa chỉ chống được lỗi độc lập; nó không chống được lỗi hệ thống, đúng như phân biệt "lỗi phần cứng ngẫu nhiên / lỗi phần mềm hệ thống" ở Chương 1.

    Cả những nguyên nhân buồn cười

    Danh mục tham chiếu của chính Chương 8 liệt kê đủ loại nguyên nhân thật: cá mập cắn cáp quang biển, driver card mạng bnx2 thỉnh thoảng rơi khỏi mạng, cho tới "gói tin độc" làm ZooKeeper nghẹt3. Kleppmann dùng chúng không phải để gây cười mà để phá vỡ giả định ngầm rằng lỗi mạng là chuyện hiếm và kỳ dị.

  2. Detecting faults · the ambiguity

    Không phân biệt được "chết" với "chậm"You can't tell "dead" from "slow"

    Đây là mệnh đề trung tâm của cả chương. Nhiều hệ cần tự động phát hiện node hỏng: bộ cân bằng tải phải ngừng gửi yêu cầu tới node chết; hệ có bản sao chính–phụ phải bầu bản sao chính mới. Nhưng vì mạng không cho chặn trên, một node im lặng và một node chậm trông giống hệt nhau1. Đôi khi bạn nhận được phản hồi rõ ràng — hệ điều hành trả RST/FIN khi không có tiến trình nào nghe ở cổng đó, hoặc router trả gói ICMP Destination Unreachable — nhưng đó là món quà, không phải bảo đảm. Trong trường hợp chung, công cụ duy nhất còn lại là timeout.

    Bạn gọi điện cho bạn mình mà không ai bắt máy. Bạn ấy đi vắng, hay đang tắm, hay đang ngủ? Bạn không thể biết. Máy tính cũng vậy: một máy không trả lời thì có thể đã hỏng, cũng có thể chỉ đang bận. Nhớ nhé: im lặng không có nghĩa là đã chết.

    This is the chapter's central claim. Many systems need to detect failed nodes automatically: a load balancer must stop sending requests to a dead node; a leader-based replication system must elect a new leader. But because the network gives no upper bound, a silent node and a slow node look identical1. Sometimes you get an explicit signal — the OS sends RST/FIN when no process is listening on that port, or a router replies with ICMP Destination Unreachable — but that is a gift, not a guarantee. In the general case the only tool left is a timeout.

    Đừng nhầm TCP với sự đảm bảoTCP có gửi lại gói mất, nhưng nó chỉ giấu chuyện mất gói khỏi ứng dụng chứ không rút ngắn độ trễ về một chặn hữu hạn. Với ứng dụng, một kết nối TCP "đang chờ" và một node đã chết vẫn không phân biệt được.
    Lưu ýMáy tính có tự gửi lại thư bị lạc, nhưng điều đó không giúp bạn biết bên kia còn sống hay không.
    Don't mistake TCP for a guaranteeTCP retransmits lost packets, but it only hides loss from the application; it does not bound the delay. To the application, a TCP connection that is "still waiting" and a dead node remain indistinguishable.

    Vì sao phản hồi lỗi không đủ

    Các tín hiệu như RST hay ICMP chỉ tới được khi một phần của hệ vẫn còn sống để trả lời. Nếu cả máy mất điện, hoặc bị cắt khỏi mạng, không ai gửi gì cả — và bạn lại rơi về sự im lặng. Nghịch lý: bạn nhận được thông tin rõ ràng nhất đúng vào những lúc lỗi nhẹ nhất.

    Hệ quả kiến trúc

    Vì tuyên bố "node đã chết" luôn là một quyết định chứ không phải một quan sát, nó phải được đưa ra ở nơi có thẩm quyền và được ghi nhận nhất quán — tức là bởi một nhóm quorum, chứ không phải bởi node đầu tiên hết kiên nhẫn. Đó chính là cây cầu dẫn sang phần "sự thật do đa số định nghĩa".

  3. Timeouts & unbounded delays

    Timeout: chọn dài thì chậm, chọn ngắn thì saiTimeouts: long is slow, short is wrong

    Timeout dài nghĩa là người dùng phải đợi lâu, hoặc phải xem màn hình lỗi, trước khi hệ kịp chuyển hướng. Timeout ngắn phát hiện lỗi nhanh hơn nhưng tăng nguy cơ báo nhầm: tuyên bố chết một node thật ra vẫn sống và chỉ đang chậm1. Báo nhầm không vô hại — nó có thể khiến một hành động bị thực hiện hai lần, và tệ hơn, chuyển tải của node "chết" sang các node khác đúng lúc hệ đang quá tải, đẩy chúng cũng vào ngưỡng timeout: một vòng phản hồi dương biến trục trặc nhỏ thành sập dây chuyền. Thay vì một hằng số cố định, các hệ thực tế đo phân bố thời gian đáp ứng rồi tự điều chỉnh — ví dụ bộ phát hiện lỗi phi accrual, trả về mức độ nghi ngờ thay vì câu trả lời có/không, được dùng trong Akka1617.

    Chờ bao lâu rồi mới kết luận "bạn ấy không tới"? Chờ lâu quá thì mình mất cả buổi. Chờ ngắn quá thì mình bỏ về ngay lúc bạn ấy sắp tới nơi. Máy tính gặp đúng bài toán này, và không có con số đúng cho mọi lúc. Nhớ nhé: chờ bao lâu cũng là một lựa chọn có giá.

    A long timeout means users wait — or stare at an error — before the system can route around the fault. A short timeout detects faults faster but raises the risk of a false positive: declaring dead a node that is alive and merely slow1. False positives are not harmless — an action may be performed twice, and worse, shifting the "dead" node's load onto others exactly when the system is overloaded can push them over their timeouts too: a positive feedback loop that turns a hiccup into a cascading failure. Instead of a fixed constant, real systems measure the response-time distribution and adapt — for instance the phi accrual failure detector, which returns a suspicion level rather than a yes/no answer, and is used in Akka1617.

    Dùng khi nàoĐo thực tế thay vì đoán: theo dõi phân bố thời gian đi–về, đặt ngưỡng theo phần đuôi của phân bố đó, và chừa biên cho quãng dừng GC hoặc trục trặc mạng thoáng qua.
    Dùng saoHãy đo xem bình thường mất bao lâu, rồi chờ hơn mức đó một chút — đừng đoán bừa một con số.
    When to useMeasure rather than guess: track the round-trip time distribution, set the threshold from its tail, and leave margin for GC pauses or transient network trouble.

    Phi accrual, cụ thể

    Tài liệu Akka mô tả cách tính: phi = -log10(1 - F(timeSinceLastHeartbeat)), trong đó F là hàm phân phối tích lũy của phân phối chuẩn với kỳ vọng và độ lệch chuẩn ước lượng từ lịch sử khoảng cách giữa các nhịp tim16. Ý tưởng gốc — tách giám sát khỏi diễn giải — đến từ Hayashibara và cộng sự17. Điểm hay: ngưỡng nghi ngờ tự thắt lại khi mạng ổn định và tự nới ra khi mạng nhiễu.

    Biên cho quãng dừng

    Akka còn cho cấu hình tường minh một biên (acceptable-heartbeat-pause) để sống sót qua những bất thường đột ngột như quãng dừng thu gom rác hay trục trặc mạng thoáng qua16. Đây là thừa nhận thẳng thắn rằng bộ phát hiện lỗi phải đối phó với cả hai nguồn bất định — mạng và tiến trình — bằng cùng một tham số.

  4. Synchronous vs asynchronous networks

    Vì sao gói tin bị xếp hàngWhy packets end up queueing

    Kleppmann đối chiếu mạng gói với mạng điện thoại cổ điển1. Một cuộc gọi trên mạng chuyển mạch kênh đặt trước một dải băng thông cố định suốt cuộc gọi, nên độ trễ có chặn trên — đó là ý nghĩa của chữ đồng bộ. Mạng gói (Ethernet, IP) thì tối ưu cho lưu lượng bùng nổ: không đặt trước gì cả, mà chia sẻ động. Cái giá là hàng đợi. Gói xếp hàng ở cổng ra của switch khi nhiều nguồn cùng gửi tới một đích; xếp hàng ở card mạng và trong hệ điều hành khi mọi lõi CPU đang bận; xếp hàng trong máy ảo khi bộ giám sát đang cho một máy ảo khác chạy; và TCP còn tự tạo hàng đợi ở phía gửi qua điều khiển luồng và tránh nghẽn.

    Có hai cách đi lại. Một: tàu chạy theo giờ, đường riêng, nên tới đúng giờ — nhưng giữ đường riêng thì tốn. Hai: đi xe trên phố chung, rẻ và linh hoạt, nhưng gặp tắc đường thì tới muộn không biết trước. Mạng máy tính chọn cách thứ hai. Nhớ nhé: tắc đường chính là lý do thư tới muộn.

    Kleppmann contrasts packet networks with the classical telephone network1. A call on a circuit-switched network reserves a fixed slice of bandwidth for its whole duration, so latency is bounded — that is what synchronous means. Packet networks (Ethernet, IP) instead optimise for bursty traffic: nothing is reserved, everything is shared dynamically. The price is queueing. Packets queue at a switch's output port when several sources send to one destination; queue at the NIC and in the OS when all CPU cores are busy; queue inside virtual machines when the hypervisor is running another VM; and TCP itself creates a sender-side queue through flow control and congestion avoidance.

    Đánh đổi nền tảngĐộ trễ có chặn trên đòi hỏi đặt trước tài nguyên; chia sẻ động cho hiệu suất sử dụng cao hơn nhưng đổi lại là độ trễ biến thiên. Mạng gói chọn hiệu suất — nên biến thiên độ trễ là tính năng, không phải lỗi.
    Đánh đổiMuốn luôn đúng giờ thì phải giữ đường riêng, mà giữ đường riêng thì phí. Đi chung thì rẻ nhưng có lúc tắc.
    The underlying trade-offBounded latency demands reserved resources; dynamic sharing yields higher utilisation at the cost of variable latency. Packet networks chose utilisation — so variable delay is a feature, not a bug.

    Hàng đợi ở đâu

    Ít nhất bốn tầng, và chúng cộng dồn: (a) cổng ra của switch khi lưu lượng hội tụ; (b) hàng đợi nhận của hệ điều hành khi CPU đã kín việc; (c) lịch trình của bộ giám sát máy ảo — máy ảo bị tạm dừng hàng chục mili giây là chuyện thường trong môi trường nhiều người thuê chung; (d) bộ đệm gửi của TCP. Mỗi tầng đều có thể biến một chuyến đi vài trăm micro giây thành vài trăm mili giây.

    Vì sao không quay lại mạng đồng bộ

    Có những công nghệ cho phép đặt trước tài nguyên và cho độ trễ chặn trên — chương này nhắc tới ATM và InfiniBand trong danh mục tham chiếu3. Nhưng chúng đắt và không hợp với lưu lượng bùng nổ của dữ liệu web, nơi ta muốn một yêu cầu lớn dùng hết băng thông khi rảnh. Chừng nào còn chọn hiệu suất sử dụng, ta còn phải sống với timeout.

Trục 3 · Unreliable clocksAxis 3 · Unreliable clocks

Đồng hồ không đáng tin: hai loại đồng hồ, hai kiểu saiUnreliable clocks: two kinds of clock, two kinds of wrong

Thời gian là thứ ta dùng để trả lời hai loại câu hỏi rất khác nhau: "bây giờ là mấy giờ?" và "việc này mất bao lâu?"1. Kleppmann nhấn mạnh rằng máy tính có hai chiếc đồng hồ tách biệt cho hai câu hỏi ấy, và lẫn lộn chúng là một trong những nguồn bug âm thầm nhất. Mỗi máy lại có tinh thể thạch anh riêng, chạy nhanh chậm khác nhau, được kéo về đúng giờ bằng NTP — nên "giờ" trên hai máy không bao giờ hoàn toàn trùng nhau, và độ lệch ấy có thể lớn hơn chính khoảng thời gian bạn đang muốn đo.

Có hai câu hỏi về thời gian mà ta hay nhập làm một. Câu thứ nhất: "bây giờ là mấy giờ?" — cần một cái đồng hồ treo tường. Câu thứ hai: "mình chạy hết bao lâu?" — cần một cái đồng hồ bấm giờ. Nếu giữa chừng có người chỉnh lại đồng hồ treo tường, thì dùng nó để bấm giờ sẽ ra kết quả sai. Nhớ nhé: hai câu hỏi khác nhau cần hai loại đồng hồ khác nhau.

Time answers two very different questions: "what time is it now?" and "how long did this take?"1. Kleppmann stresses that computers keep two separate clocks for those two questions, and confusing them is one of the quietest sources of bugs. Each machine also has its own quartz crystal, running slightly fast or slow, pulled back into line by NTP — so "now" on two machines never quite matches, and that skew can exceed the very interval you are trying to measure.

Time-of-day clock vs monotonic clock

Đồng hồ thời-gian-ngày trả về thời điểm theo lịch và được đồng bộ với bên ngoài; vì thế nó có thể nhảy lùi khi bị chỉnh. Đồng hồ đơn điệu chỉ bảo đảm luôn tiến về phía trước; giá trị tuyệt đối của nó vô nghĩa, nhưng hiệu của hai lần đọc thì đo được khoảng thời gian.1

Có đồng hồ treo tường để biết mấy giờ — nhưng người ta chỉnh được nó, kể cả chỉnh lùi. Và có đồng hồ bấm giờ chỉ biết chạy tới. Nhớ nhé: đo khoảng thời gian thì phải dùng đồng hồ bấm giờ.

A time-of-day clock returns calendar time and is synchronised with the outside world; that is exactly why it can jump backwards when corrected. A monotonic clock only guarantees that it moves forward; its absolute value is meaningless, but the difference between two readings measures an interval.1

Trên Linux, sự phân đôi này hiện ra tường minh trong clock_gettime(2): CLOCK_REALTIME là đồng hồ hệ thống chỉnh được, "bị ảnh hưởng bởi những bước nhảy không liên tục của giờ hệ thống" và bởi các hiệu chỉnh tần số do NTP thực hiện; CLOCK_MONOTONIC thì không chỉnh được và "không bị ảnh hưởng bởi những bước nhảy không liên tục", dù vẫn chịu hiệu chỉnh tần số10. Nói cách khác: hệ điều hành đã nói thẳng với bạn rằng đừng dùng đồng hồ tường để bấm giờ. Bug xảy ra khi thư viện hoặc ngôn ngữ che mất sự phân đôi ấy.

Time-of-day Trả lời "mấy giờ?"; đồng bộ NTP; có thể nhảy lùi — đừng dùng để đo khoảng.
Monotonic Trả lời "bao lâu?"; chỉ tiến; so sánh giữa hai máy thì vô nghĩa.
Logical Trả lời "cái nào trước?"; đếm sự kiện chứ không đếm giây — công cụ đúng để xếp thứ tự.
Đồng hồ treo tường Biết mấy giờ, nhưng có thể bị chỉnh lùi.
Đồng hồ bấm giờ Chỉ chạy tới, dùng để đo lâu hay nhanh.
Sổ đánh số Không đếm giây, chỉ ghi việc nào xảy ra trước việc nào.
Time-of-day Answers "what time?"; NTP-synchronised; may jump backwards — never measure durations with it.
Monotonic Answers "how long?"; forward only; comparing values across machines is meaningless.
Logical Answers "which came first?"; counts events, not seconds — the right tool for ordering.
  1. When a clock jumps backwards

    Giây nhuận 2017: khi thời gian chạy lùiThe 2017 leap second: when time ran backwards

    Ví dụ rõ nhất là sự cố DNS của Cloudflare rạng sáng 1.1.2017. Đội kỹ thuật viết thẳng rằng nguyên nhân gốc là niềm tin rằng thời gian không thể chạy lùi11. Dịch vụ RRDNS viết bằng Go, dùng time.Now() — một hàm không bảo đảm tính đơn điệu. Khi giây nhuận làm đồng hồ lùi lại, biểu thức time.Now().Sub(start) cho giá trị âm; giá trị âm ấy trôi vào bộ ước lượng thời gian đáp ứng đã làm trơn, rồi được đưa vào rand.Int63n() — hàm này lập tức panic khi đối số âm. Bản vá chỉ là kiểm tra hiệu số âm trước khi ghi nhận.

    Đêm giao thừa năm 2017, thế giới thêm một giây nhuận vào đồng hồ. Một hệ thống lớn bị lỗi vì chương trình của nó đinh ninh rằng thời gian không bao giờ chạy lùi — nên khi đồng hồ lùi một giây, nó tính ra "mất âm mấy mili giây" và hoảng loạn. Nhớ nhé: đừng bao giờ tin rằng đồng hồ luôn chạy tới.

    The clearest example is Cloudflare's DNS incident in the early hours of 1 January 2017. Their engineers wrote plainly that the root cause was the belief that time cannot go backwards11. RRDNS is written in Go and used time.Now() — a function that does not guarantee monotonicity. When the leap second pushed the clock back, time.Now().Sub(start) returned a negative value; that value flowed into a smoothed response-time estimate and then into rand.Int63n(), which panics immediately on a negative argument. The fix was simply to check for a negative difference before recording it.

    Bài học chuyển giao đượcMọi phép trừ hai mốc thời gian lấy từ đồng hồ thời-gian-ngày đều là một giả định chưa được kiểm chứng. Hoặc dùng đồng hồ đơn điệu, hoặc kiểm tra dấu của hiệu số — không có lựa chọn thứ ba.
    Ý chínhKhi trừ hai mốc giờ, hãy kiểm tra xem kết quả có âm không. Nếu âm thì đồng hồ vừa bị chỉnh.
    The transferable lessonEvery subtraction of two time-of-day readings is an unverified assumption. Either use a monotonic clock, or check the sign of the difference — there is no third option.

    Vì sao giây nhuận nguy hiểm

    Giây nhuận buộc đồng hồ dân sự lặp lại hoặc bỏ qua một giây, tức là tạo ra chính xác cái bước nhảy mà phần mềm ngầm cho là không tồn tại. Danh mục tham chiếu của Chương 8 gom nhiều sự cố cùng họ — từ đợt giây nhuận 2012 tới hướng dẫn chuẩn bị của AWS và cách Google "bôi trơn" giây nhuận trải đều trong ngày3.

    Không chỉ giây nhuận

    Đồng hồ thời-gian-ngày còn nhảy khi quản trị viên chỉnh tay, khi NTP phát hiện lệch quá lớn và bước (step) thay vì trượt (slew), khi máy ảo được khôi phục từ ảnh chụp, hay khi máy vừa khởi động và chưa kịp đồng bộ. Mỗi tình huống đều đủ để phá một phép đo khoảng thời gian.

  2. Clock synchronisation & its accuracy

    NTP chính xác tới đâu — và tới đâu là hếtHow accurate NTP is — and where it stops

    NTP kéo đồng hồ máy về gần giờ chuẩn, nhưng độ chính xác của nó phụ thuộc vào đường mạng — và ta vừa thấy đường mạng không đáng tin. Đặc tả NTPv4 nêu các mức thực tế theo từng hoàn cảnh: máy chủ tầng một trên phần cứng hiện đại chính xác tới vài chục micro giây; máy chủ thứ cấp và máy khách trên mạng LAN nhanh đạt vài trăm micro giây với chu kỳ hỏi tối đa 1024 giây; còn với chu kỳ hỏi kéo dài tới 36 giờ thì độ chính xác là vài chục mili giây9. Đặc tả cũng nói rõ sai số trung bình tăng xấp xỉ tỉ lệ với số tầng (stratum) và với thời gian đi–về đo được.

    Máy tính chỉnh giờ bằng cách hỏi những máy chủ giữ giờ chuẩn. Nhưng câu hỏi và câu trả lời phải đi qua đường truyền — mà đường truyền lúc nhanh lúc chậm, nên giờ chỉnh về cũng chỉ gần đúng. Càng xa nguồn giờ chuẩn thì càng lệch nhiều. Nhớ nhé: giờ trên máy luôn gần đúng, không bao giờ đúng hẳn.

    NTP pulls a machine's clock close to standard time, but its accuracy depends on the network path — and we have just seen that networks are unreliable. The NTPv4 specification states practical levels per situation: stratum-one servers on modern hardware are precise to a few tens of microseconds; secondary servers and clients on fast LANs reach a few hundred microseconds with poll intervals up to 1024 seconds; and with poll intervals stretching to 36 hours, precision is a few tens of milliseconds9. The spec also notes that mean errors grow roughly in proportion to stratum number and to measured round-trip delay.

    Đọc con số cho đúngĐây là các mức theo điều kiện — phần cứng, chất lượng mạng, chu kỳ hỏi — chứ không phải một hằng số toàn cầu. Trên một máy ảo thuê chung, mạng nghẽn, chu kỳ hỏi thưa, sai số thực tế có thể tệ hơn nhiều.
    Lưu ýMấy con số này chỉ đúng trong điều kiện tốt. Máy bình thường, mạng bận thì lệch nhiều hơn.
    Read the numbers correctlyThese are conditional levels — hardware, network quality, poll interval — not a global constant. On a shared virtual machine, a congested network and sparse polling, real error can be far worse.

    NTP đã phải phòng cả kẻ nói dối

    Điều đáng chú ý: chính NTP cũng không tin các nguồn giờ của nó. Đặc tả mô tả thuật toán chọn lọc dùng nguyên lý phát hiện lỗi Byzantine để loại những ứng viên sai — gọi là falsetickers — và chỉ giữ lại những truechimers9. Nói cách khác, ý tưởng "đa số định nghĩa sự thật" của phần cuối chương đã nằm sẵn trong giao thức đồng bộ giờ.

    Vì sao đừng dựa vào đồng bộ giờ để bảo đảm đúng đắn

    Kleppmann đặt ra một nguyên tắc: nếu tính đúng đắn của thuật toán phụ thuộc vào việc đồng hồ đủ đồng bộ, thì hệ phải theo dõi độ lệch đồng hồ như một chỉ số vận hành và loại khỏi cụm những node lệch quá ngưỡng1. Nguy hiểm của lỗi đồng hồ là nó âm thầm: một node lệch giờ vẫn chạy tốt về mọi mặt quan sát được, chỉ dữ liệu là sai lặng lẽ.

  3. Timestamps for ordering events

    Dùng dấu thời gian để xếp thứ tự: LWW trở lạiTimestamps for ordering: LWW returns

    Nhiều hệ sao chép đa chủ và không-chủ giải quyết xung đột ghi bằng last write wins — bản ghi nào có dấu thời gian lớn hơn thì thắng. Kingsbury mô tả cụ thể: Cassandra xếp thứ tự các lần ghi bằng dấu thời gian đồng hồ tường do máy chủ (hoặc tùy chọn là máy khách) cung cấp12. Vấn đề: nếu đồng hồ của node A chạy nhanh hơn node B dù chỉ vài mili giây, một lần ghi xảy ra sau ở B vẫn có thể mang dấu thời gian nhỏ hơn và bị âm thầm vứt bỏ. Dữ liệu mất mà không có lỗi nào được ném ra. Công cụ đúng để xếp thứ tự là đồng hồ logic — đếm sự kiện, không đếm giây — theo đúng tinh thần bài báo kinh điển của Lamport13.

    Hai bạn cùng sửa một tờ giấy, và ta chọn giữ bản nào "ghi giờ muộn hơn". Nghe hợp lý — trừ khi đồng hồ của hai bạn lệch nhau. Lúc đó bản sửa sau lại ghi giờ sớm hơn, và nó bị vứt đi mất mà không ai biết. Nhớ nhé: xếp thứ tự bằng giờ trên máy là cách làm mất dữ liệu trong im lặng.

    Many multi-leader and leaderless replication systems resolve write conflicts with last write wins — the write with the larger timestamp survives. Kingsbury describes the concrete case: Cassandra orders writes by wall-clock timestamps supplied by the server (or optionally the client)12. The problem: if node A's clock runs a few milliseconds ahead of node B's, a write that happened later on B can still carry a smaller timestamp and be silently discarded. Data is lost and no error is raised. The right tool for ordering is a logical clock — counting events, not seconds — in the spirit of Lamport's classic paper13.

    Vì sao nguy hiểm hơn vẻ ngoàiLWW hỏng theo kiểu im lặng: không ngoại lệ, không cảnh báo, không mục log. Bạn chỉ phát hiện khi một người dùng hỏi vì sao thay đổi của họ biến mất — thường là nhiều tuần sau.
    Ý chínhKiểu hỏng này không báo lỗi gì cả, nên rất lâu sau mới bị phát hiện.
    Why it's worse than it looksLWW fails silently: no exception, no warning, no log line. You find out when a user asks why their change vanished — usually weeks later.

    Độ phân giải cũng là vấn đề

    Ngay cả khi đồng hồ đồng bộ tốt, hai lần ghi xảy ra cách nhau vài micro giây có thể nhận cùng một dấu thời gian nếu độ phân giải là mili giây — lúc đó thứ tự do một quy tắc phá hòa tùy ý quyết định, chứ không do nhân quả. Đây là lý do đồng hồ vật lý không thể thay thế quan hệ xảy ra trước (happens-before) mà Lamport định nghĩa13.

    Khi nào LWW vẫn chấp nhận được

    Khi mất một lần ghi là chuyện có thể chịu được — ví dụ bộ đệm cache, hay dữ liệu đo đạc lấy mẫu dày. Vấn đề nảy sinh khi LWW được dùng cho dữ liệu mà mỗi lần ghi đều quan trọng. Kleppmann coi đây là một lựa chọn phải tuyên bố công khai, không phải mặc định vô hình1.

  4. Clock readings have a confidence interval

    Một lần đọc đồng hồ là một khoảng, không phải một điểmA clock reading is an interval, not a point

    Kleppmann đề nghị đổi cách nghĩ: đừng coi kết quả đọc đồng hồ là một thời điểm, hãy coi nó là một khoảng tin cậy1. TrueTime của Google Spanner là hiện thực hiếm hoi biến điều này thành API tường minh: TT.now() không trả về một con số mà trả về một TTinterval — cặp earliest / latest được bảo đảm chứa thời điểm tuyệt đối của lời gọi8. Sai số ε là nửa bề rộng khoảng đó. Trong môi trường vận hành mà bài báo OSDI 2012 mô tả, ε là một hàm răng cưa dao động khoảng 1 đến 7 mili giây qua mỗi chu kỳ hỏi, tức khoảng 4 ms phần lớn thời gian; chu kỳ hỏi khi đó là 30 giây và tốc độ trôi được áp dụng là 200 micro giây mỗi giây8.

    Khi nhìn đồng hồ, đừng nói "bây giờ là 8 giờ 00 phút 00 giây". Hãy nói "bây giờ nằm đâu đó giữa 7:59:58 và 8:00:02". Google có một hệ thống làm đúng như vậy: máy của họ trả lời "giờ nằm trong khoảng này", chứ không nói một con số chắc nịch. Nhớ nhé: thành thật về chỗ mình không biết thì an toàn hơn.

    Kleppmann proposes a change of mindset: stop treating a clock reading as an instant, treat it as a confidence interval1. Google Spanner's TrueTime is the rare implementation that makes this explicit in the API: TT.now() returns not a number but a TTinterval — an earliest / latest pair guaranteed to contain the absolute time of the call8. The error bound ε is half that interval's width. In the production environment the OSDI 2012 paper describes, ε was a sawtooth varying from about 1 to 7 milliseconds over each poll interval, so about 4 ms most of the time; the poll interval was then 30 seconds and the applied drift rate 200 microseconds per second8.

    Cơ chế đi kèm: commit waitSpanner dùng khoảng tin cậy để bảo đảm thứ tự: node điều phối chờ hết khoảng bất định trước khi công bố giao dịch, nên hai giao dịch có quan hệ nhân quả chắc chắn không chồng khoảng lên nhau8. Cái giá của sự chắc chắn ấy là độ trễ bằng đúng ε.
    Cách làmGoogle chờ cho hết khoảng "không chắc" rồi mới chốt, nên thứ tự luôn đúng — đổi lại phải chờ thêm một chút.
    The paired mechanism: commit waitSpanner uses the interval to guarantee ordering: the coordinator waits out the uncertainty before publishing a transaction, so causally related transactions cannot have overlapping intervals8. The price of that certainty is a delay equal to ε.

    Vì sao ít hệ làm được

    ε nhỏ không tự nhiên mà có: TrueTime dựa trên một tập máy chủ giờ mỗi trung tâm dữ liệu, phần lớn gắn máy thu GPS với ăng-ten riêng, phần còn lại dùng đồng hồ nguyên tử — hai loại nguồn giờ vì chúng hỏng theo những cách khác nhau8. Đây là hạ tầng vật lý, không phải thư viện cài được. Các con số ε nêu trên là của môi trường Google năm 2012, không phải hằng số áp dụng được cho mọi hệ.

    Không có đồng hồ nguyên tử thì sao

    Có những hệ chọn hướng khác: chấp nhận khoảng bất định rộng hơn và trả giá bằng việc thỉnh thoảng phải thử lại giao dịch, thay vì mua phần cứng chuyên dụng — CockroachDB mô tả cách tiếp cận này trong bài "Living Without Atomic Clocks"18. Điểm chung vẫn là: bất định về giờ phải được biểu diễn tường minh, chứ không được giả vờ không tồn tại.

  5. Process pauses

    Quãng dừng tiến trình: đứng hình rồi tỉnh dậyProcess pauses: frozen, then awake

    Giả định ngầm nguy hiểm nhất là: giữa hai dòng lệnh của tôi, thời gian trôi không đáng kể. Sai. Một luồng có thể bị dừng ở bất kỳ điểm nào, trong thời gian tùy ý1. Thu gom rác kiểu "dừng cả thế giới" là thủ phạm quen mặt: kỹ sư LinkedIn báo cáo những quãng dừng lớn hơn 5 giây trong ứng dụng trọng yếu ở môi trường vận hành của họ, với hai ví dụ cụ thể là 4,17 giây với bộ thu gom G1 trên heap 8 GB và 11,45 giây với CMS — nguyên nhân không phải khối lượng rác mà là lời gọi write() ghi log GC bị chặn bởi cơ chế ghi lùi bộ đệm trang của hệ điều hành15. Ngoài GC còn: máy ảo bị tạm ngưng để di trú sống, đĩa hoán đổi (swap) và lỗi trang, hay đơn giản là một tín hiệu SIGSTOP.

    Bạn đang đọc dở một câu thì bị gọi ra ngoài mười phút, rồi quay lại đọc tiếp mà không biết mình đã vắng mặt. Máy tính cũng bị như vậy: nó có thể "đứng hình" vài giây rồi chạy tiếp, và nó không biết mình vừa vắng mặt. Trong lúc đó, cả thế giới bên ngoài đã đổi khác. Nhớ nhé: chương trình không thể tin rằng nó chạy liên tục.

    The most dangerous implicit assumption is: between two of my lines of code, negligible time passes. Wrong. A thread can be paused at any point, for an arbitrary duration1. Stop-the-world garbage collection is the familiar culprit: LinkedIn engineers reported pauses longer than 5 seconds in mission-critical applications in their production environments, with two concrete cases of 4.17 seconds under the G1 collector on an 8 GB heap and 11.45 seconds under CMS — caused not by the volume of garbage but by the GC log's write() call being blocked by OS page-cache writeback15. Beyond GC there are: virtual machines suspended for live migration, swap and page faults, or simply a SIGSTOP signal.

    Vì sao đây là vấn đề đúng đắn, không phải hiệu năngMột node bị dừng lâu hơn thời hạn khóa của nó sẽ tỉnh dậy và hành động như thể vẫn còn giữ khóa. Không kiểm tra lại nào cứu được, vì quãng dừng có thể rơi đúng vào giữa lần kiểm tra và lần ghi.
    Ý chínhMáy tỉnh lại vẫn tưởng mình đang "đến lượt", trong khi lượt đã sang người khác từ lâu.
    Why this is a correctness problem, not a performance oneA node paused past its lease expiry wakes up and acts as though it still holds the lock. No re-check saves you, because the pause can land exactly between the check and the write.

    Kleppmann nói gì về độ dài

    Trong bài viết riêng về khóa phân tán, ông lưu ý rằng quãng dừng GC thường ngắn, nhưng những quãng dừng "dừng cả thế giới" đã từng được ghi nhận kéo dài vài phút — thừa đủ để một hợp đồng thuê hết hạn — và ngay cả các bộ thu gom "đồng thời" như CMS của HotSpot cũng vẫn phải dừng thế giới đôi lúc6. Đây là nhận định của ông dẫn lại từ nguồn khác, không phải một phép đo trong một môi trường cụ thể.

    Giới hạn thời gian đáp ứng

    Có một họ hệ thống thật sự bảo đảm thời hạn: hệ thời gian thực cứng (hard real-time), nơi vi phạm thời hạn là hỏng cả hệ — túi khí ô tô, điều khiển bay. Nhưng cái giá là toàn bộ ngăn xếp phải được thiết kế lại: nhân thời gian thực, cấp phát bộ nhớ tất định, phân tích thời gian tệ nhất. Kleppmann kết luận rằng với hệ dữ liệu phía máy chủ, chi phí ấy thường không đáng — nên ta chấp nhận quãng dừng và thiết kế để chịu được nó1.

Trục 4 · Knowledge, truth & liesAxis 4 · Knowledge, truth & lies

Tri thức, sự thật và dối trá: ai có quyền phán xử?Knowledge, truth and lies: who gets to rule?

Một node chỉ biết những gì nó nhận được qua mạng — nghĩa là nó chỉ có tin đồn, không có quan sát trực tiếp. Từ đó Kleppmann rút ra một nguyên tắc nghe có vẻ chính trị hơn kỹ thuật: sự thật do đa số định nghĩa1. Nếu quorum tuyên bố một node đã chết thì node ấy phải coi mình đã chết, kể cả khi nó tự thấy mình hoàn toàn khỏe mạnh — bởi nếu nó cứ hành động theo niềm tin riêng, hệ sẽ có hai node cùng nghĩ mình là bản chính. Phần này đi từ nguyên tắc ấy tới cơ chế cụ thể (fencing token), rồi tới giới hạn của mọi cơ chế (lỗi Byzantine), và cuối cùng tới ngôn ngữ để phát biểu bảo đảm (mô hình hệ thống, an toàn và sống còn).

Trong một nhóm, khi mọi người chỉ liên lạc bằng tin nhắn, không ai nhìn thấy toàn cảnh. Vậy ai đúng? Cách làm an toàn là: số đông quyết định. Nếu cả nhóm bảo "bạn A rớt rồi" thì bạn A phải chấp nhận, dù bạn ấy thấy mình vẫn ổn — nếu không thì sẽ có hai người cùng tưởng mình là nhóm trưởng.

A node knows only what arrives over the network — that is, it has hearsay, not direct observation. From this Kleppmann draws a principle that sounds more political than technical: the truth is defined by the majority1. If a quorum declares a node dead, that node must consider itself dead, even if it feels perfectly healthy — because if it keeps acting on its private belief, the system ends up with two nodes each believing it is the leader. This part moves from that principle to a concrete mechanism (fencing tokens), then to the limits of any mechanism (Byzantine faults), and finally to the language for stating guarantees (system models, safety and liveness).

The truth is defined by the majority

Một quorum là một tập con các node đủ lớn để không hai tập con hợp lệ nào rời nhau — thường là quá bán. Vì hai quorum luôn giao nhau, không thể có hai quyết định mâu thuẫn cùng được công nhận. Đây là lý do "đa số" không phải một quy ước lịch sự mà là một bất biến toán học.1

Quá bán nghĩa là hơn một nửa. Hai nhóm "hơn một nửa" thì bao giờ cũng có ít nhất một người chung — nên không thể có hai nhóm ra hai quyết định trái ngược nhau. Nhớ nhé: quá bán thì không bao giờ chia đôi được.

A quorum is a subset of nodes large enough that no two valid subsets are disjoint — usually a strict majority. Because any two quorums intersect, two contradictory decisions can never both be accepted. That is why "the majority" is not a polite convention but a mathematical invariant.1

Kleppmann nêu một biến thể khó chịu hơn cả node chết hẳn: node nửa ngắt kết nối1. Một máy vẫn nhận được nhịp tim nhưng không gửi đi được; một card mạng chỉ rớt gói theo một chiều; một node chạy nhưng chậm gấp trăm lần vì đĩa gần hỏng. Những node "khập khiễng" như vậy tệ hơn node chết, vì chúng vẫn giữ vai trò của mình trong cụm trong khi không còn làm tròn vai. Phát hiện chúng đúng là bài toán mà bộ phát hiện lỗi kiểu tích lũy ở phần trước cố giải17.

Node không tự phán Một node không thể tự khẳng định mình vẫn là bản chính — quorum mới nói được.
Quorum giao nhau Hai tập quá bán luôn có phần tử chung, nên không thể có hai quyết định mâu thuẫn.
Cần chứng cứ kiểm được Tài nguyên phải tự kiểm tra quyền ghi, không tin lời khai của client.
Tự nói không tính Một mình bảo "tôi vẫn là trưởng nhóm" thì không đủ.
Hơn một nửa Hai nhóm quá bán luôn có người chung, nên không cãi nhau được.
Phải có bằng chứng Nơi giữ đồ phải tự kiểm tra vé, đừng tin lời hứa.
No self-certification A node cannot assert that it is still the leader — only a quorum can.
Quorums intersect Any two majorities share a member, so contradictory decisions cannot both stand.
Checkable evidence The resource must verify the right to write, not trust the client's word.
  1. Fencing tokens

    Fencing token — tấm vé không thể quay ngượcFencing tokens — a ticket that never goes back

    Kleppmann trình bày cơ chế này rõ nhất trong bài viết riêng của ông về khóa phân tán6. Kịch bản hỏng: client 1 lấy được hợp đồng thuê (lease) và token 33, rồi bị dừng lâu — chẳng hạn vì thu gom rác — đến mức hợp đồng hết hạn mà nó không biết. Client 2 lấy được hợp đồng, nhận token 34, và ghi dữ liệu. Sau đó client 1 tỉnh dậy và gửi lệnh ghi của mình kèm token 33. Vì máy chủ lưu trữ nhớ rằng nó đã xử lý một lệnh ghi với token cao hơn, nó từ chối lệnh ghi mang token 33. Điều kiện bắt buộc: dịch vụ khóa sinh token tăng ngặt, và tài nguyên phải chủ động kiểm tra token chứ không tin lời client.

    Hãy tưởng tượng phòng gửi đồ phát vé có đánh số tăng dần. Bạn cầm vé số 33 nhưng ngủ quên; trong lúc đó người khác lấy vé 34 và vào lấy đồ. Bạn tỉnh dậy, chìa vé 33 ra — nhân viên nhìn sổ, thấy đã có vé 34 vào rồi, nên từ chối bạn. Nhớ nhé: người giữ đồ phải kiểm tra vé, chứ không chỉ nghe bạn nói.

    Kleppmann lays this mechanism out most clearly in his own post on distributed locking6. The failure scenario: client 1 acquires the lease and token 33, then pauses for a long time — say for garbage collection — long enough that the lease expires without it noticing. Client 2 acquires the lease, gets token 34, and writes. Client 1 then wakes up and sends its write with token 33. Because the storage server remembers that it has already processed a write with a higher token, it rejects the write carrying token 33. The requirements: the lock service must issue strictly increasing tokens, and the resource must actively check them rather than trust the client.

    Dùng ở đâuBất cứ khi nào một khóa hay hợp đồng thuê bảo vệ dữ liệu mà tính đúng đắn phụ thuộc vào nó. Kleppmann chỉ ra nguồn token sẵn có: dùng ZooKeeper thì lấy zxid hoặc số phiên bản của znode là đủ6.
    Dùng saoMỗi lần phát khóa thì kèm một con số tăng dần, và bên nhận phải từ chối số nhỏ hơn số đã thấy.
    Where to use itWherever a lock or lease guards data on which correctness depends. Kleppmann points to a ready source of tokens: with ZooKeeper, the zxid or the znode version number will do6.

    Vì sao kiểm tra lại hạn khóa không cứu được

    Ý tưởng "trước khi ghi thì kiểm tra xem khóa còn hạn không" nghe hợp lý nhưng sai, vì quãng dừng có thể rơi đúng vào khoảng giữa lần kiểm tra cuối cùng và thao tác ghi6. Không có cách nào thu hẹp khoảng trống ấy về không. Chỉ có cách chuyển việc kiểm tra sang phía tài nguyên, nơi mà thứ tự các lệnh ghi là quan sát được — đó chính là fencing token.

    Và mạng cũng làm được điều tương tự

    Kleppmann nhấn mạnh rằng ngay cả khi tiến trình của bạn không bị dừng, gói tin vẫn có thể bị mạng giữ lại rất lâu. Ông dẫn một sự cố ở GitHub, nơi theo ông gói tin bị trì hoãn trong mạng khoảng 90 giây6 — bản tường trình sự cố của GitHub mô tả chi tiết chuỗi lỗi trên cặp switch tổng hợp7. Con số 90 giây là cách đọc của Kleppmann về sự cố ấy, không phải con số do GitHub công bố.

  2. Byzantine faults

    Lỗi Byzantine — khi node có thể nói dốiByzantine faults — when a node may lie

    Tới đây ta vẫn giả định node có thể chết hoặc chậm, nhưng khi nói thì nói thật. Bỏ giả định đó đi, ta có lỗi Byzantine: node có thể hành xử tùy ý — gửi thông tin sai, gửi những thông điệp mâu thuẫn cho những người nhận khác nhau, hoặc cố tình phá. Tên gọi đến từ bài báo của Lamport, Shostak và Pease, trong đó bài toán được phát biểu qua hình ảnh các vị tướng vây thành và một số có thể là kẻ phản bội; kết quả trung tâm: bài toán giải được khi và chỉ khi hơn hai phần ba số tướng là trung thành, nên chỉ một kẻ phản bội cũng đủ làm rối hai vị tướng trung thành14.

    Từ nãy tới giờ ta giả sử: máy có thể im lặng, nhưng khi nó nói thì nó nói thật. Bây giờ bỏ giả sử đó: nếu có máy nói dối thì sao? Nó có thể kể với bạn một đằng và kể với người khác một nẻo. Người ta chứng minh được: chỉ giải quyết được nếu hơn hai phần ba số máy trung thực. Nhớ nhé: một kẻ nói dối cũng đủ làm hai người thật thà cãi nhau.

    So far we have assumed nodes may die or be slow, but that when they speak, they speak truthfully. Drop that assumption and you get Byzantine faults: a node may behave arbitrarily — send wrong information, send contradictory messages to different recipients, or actively sabotage. The name comes from Lamport, Shostak and Pease, who stated the problem as generals besieging a city where some may be traitors; the central result: the problem is solvable if and only if more than two-thirds of the generals are loyal, so a single traitor suffices to confound two loyal generals14.

    Lập trường của KleppmannÔng cho rằng phần lớn hệ trong một trung tâm dữ liệu không cần chịu lỗi Byzantine1: các node đều do bạn kiểm soát, kẻ tấn công vượt qua được lớp xác thực thì thường cũng vượt qua được giao thức. Byzantine mới thành thiết yếu ở nơi không có bên nào tin bên nào — hàng không vũ trụ, hoặc hệ nhiều tổ chức như blockchain.
    Ý chínhTrong một công ty, các máy đều là máy của mình nên ít khi nói dối. Chuyện nói dối quan trọng khi nhiều bên không tin nhau cùng dùng chung một hệ.
    Kleppmann's positionHe argues that most systems inside one datacenter do not need Byzantine fault tolerance1: you control all the nodes, and an attacker who gets past authentication usually gets past the protocol too. Byzantine tolerance becomes essential where no party trusts another — aerospace, or multi-organisation systems such as blockchains.

    Những dạng "nói dối" nhẹ vẫn đáng phòng

    Không cần dựng cả một giao thức chịu lỗi Byzantine mới phòng được dữ liệu sai lệch. Kiểm tổng gói tin có thể không bắt được lỗi hỏng dữ liệu — Evan Jones khảo sát chính xác chuyện kiểm tổng TCP và Ethernet thất bại ra sao21. Tương tự, NTP phòng máy chủ giờ sai bằng cách loại falsetickers9. Kiểm tra đầu vào, kiểm tổng ở tầng ứng dụng, và hỏi nhiều nguồn giờ đều là những biện pháp rẻ và đáng làm.

    Vì sao giao thức Byzantine đắt

    Ngưỡng "hơn hai phần ba trung thành" nghĩa là để chịu được f node phản bội, bạn cần ít nhất 3f + 1 node, cộng với nhiều vòng trao đổi thông điệp để so khớp lời khai14. So với một giao thức quorum quá bán chịu được f lỗi kiểu dừng với 2f + 1 node, cái giá là rõ rệt về cả phần cứng lẫn độ trễ.

  3. System models

    Mô hình hệ thống — viết ra giả định của mìnhSystem models — writing your assumptions down

    Để nói được "thuật toán này đúng", trước hết phải nói rõ đúng dưới giả định nào. Kleppmann dùng hai trục1. Trục thời gian: đồng bộ (có chặn trên cho độ trễ mạng, quãng dừng và độ lệch đồng hồ — gần như không tồn tại trong thực tế), bán đồng bộ (thường hành xử như đồng bộ, nhưng thỉnh thoảng vượt chặn — mô hình thực tế nhất), và bất đồng bộ (không được phép giả định gì về thời gian). Trục lỗi node: crash-stop (chết là biến mất mãi mãi), crash-recovery (chết rồi quay lại, giữ được phần trạng thái đã ghi bền), và Byzantine. Cặp hữu dụng nhất cho hệ dữ liệu thực tế là bán đồng bộ + crash-recovery.

    Trước khi nói "cách làm này đúng", phải nói rõ mình đang giả sử gì. Ví dụ: giả sử thư luôn tới trong 1 giây? Giả sử máy chết thì chết luôn, hay chết xong bật lại được? Mỗi giả sử khác nhau sẽ cần một cách làm khác nhau. Nhớ nhé: viết rõ giả định ra thì mới biết khi nào cách làm của mình hết linh.

    To say "this algorithm is correct" you must first say correct under which assumptions. Kleppmann uses two axes1. Timing: synchronous (bounded network delay, pauses and clock skew — barely existing in practice), partially synchronous (behaves synchronously most of the time, occasionally exceeds the bounds — the most realistic model), and asynchronous (no timing assumptions allowed at all). Node failures: crash-stop (gone forever), crash-recovery (comes back, retaining durably written state), and Byzantine. The most useful pairing for real data systems is partially synchronous + crash-recovery.

    Vì sao "bán đồng bộ" là mô hình đúngNó phản ánh thực tế: mạng thường nhanh, nhưng thỉnh thoảng nghẽn; GC thường ngắn, nhưng thỉnh thoảng dài. Mô hình này do Dwork, Lynch và Stockmeyer đưa ra trong bài báo về đồng thuận khi chỉ có đồng bộ bộ phận19.
    Ý chínhThực tế là: thường thì nhanh, thỉnh thoảng rất chậm. Cách làm tốt phải chịu được cả hai.
    Why "partially synchronous" is the right modelIt matches reality: networks are usually fast but occasionally congested; GC pauses are usually short but occasionally long. The model was introduced by Dwork, Lynch and Stockmeyer in their paper on consensus under partial synchrony19.

    Mô hình không phải mô tả

    Kleppmann cẩn thận nói rằng mô hình hệ thống là sự trừu tượng hóa, không phải bức ảnh chụp thực tế. Một mô hình tốt bỏ đi những chi tiết không ảnh hưởng tới kết luận, giữ lại đúng những giả định mà chứng minh cần. Rủi ro là chọn mô hình quá mạnh: chứng minh thì đẹp, còn hệ thật thì vi phạm giả định vào đúng ngày xấu trời.1

    Vì sao crash-recovery quan trọng

    Trong thực tế node hay khởi động lại. Nếu thuật toán chỉ đúng dưới crash-stop, một node quay lại với ký ức cũ có thể phá vỡ bất biến của hệ. Đó là lý do các giao thức đồng thuận thực dụng đòi ghi bền một số phần trạng thái trước khi trả lời — chủ đề mà Chương 9 sẽ khai thác.

  4. Safety & liveness

    An toàn và sống còn — hai loại lời hứaSafety & liveness — two kinds of promise

    Cặp khái niệm cuối cùng cho ta ngôn ngữ để phát biểu chính xác thuật toán hứa gì. An toàn (safety) là "không điều tệ nào xảy ra": ví dụ không bao giờ có hai token trùng nhau. Sống còn (liveness) là "điều tốt rốt cuộc sẽ xảy ra": ví dụ mọi yêu cầu rốt cuộc đều nhận được phản hồi — chú ý chữ "rốt cuộc"1. Sự bất đối xứng là điểm cốt yếu: một vi phạm an toàn xảy ra tại một thời điểm cụ thể và không thể hoàn tác; còn một tính chất sống còn thì chưa thỏa ở thời điểm này vẫn có thể thỏa ở thời điểm sau. Vì thế Kleppmann đòi các thuật toán phải luôn an toàn trong mọi kịch bản lỗi, và chỉ cần sống còn dưới những giả định nhất định. Sự phân đôi này đến từ Alpern và Schneider20.

    Có hai kiểu lời hứa. Kiểu một: "sẽ không có chuyện xấu xảy ra" — như hứa không làm vỡ cái cốc. Nếu cốc vỡ rồi thì không sửa lại được. Kiểu hai: "rồi chuyện tốt sẽ tới" — như hứa xe buýt sẽ tới, chỉ là chưa biết lúc nào. Nhớ nhé: lời hứa kiểu một phải giữ bằng mọi giá, vì lỡ hỏng là hỏng luôn.

    The final pair gives us the language to state precisely what an algorithm promises. Safety means "nothing bad ever happens": for instance, two identical tokens are never issued. Liveness means "something good eventually happens": for instance, every request eventually gets a response — note the word "eventually"1. The asymmetry is the crux: a safety violation occurs at one specific point and cannot be undone; a liveness property unsatisfied now may still be satisfied later. So Kleppmann demands that algorithms be always safe under every failure scenario, and live only under stated assumptions. The distinction comes from Alpern and Schneider20.

    Cách dùng thực tếKhi đọc tài liệu một hệ dữ liệu, hãy tách bảo đảm của nó thành hai cột. Cột an toàn phải đúng kể cả khi mọi node sập; cột sống còn hầu như luôn kèm điều kiện kiểu "miễn là quá bán số node còn sống và mạng rốt cuộc hồi phục".
    Dùng saoKhi đọc lời quảng cáo của một hệ, hãy hỏi: điều gì luôn đúng, và điều gì chỉ đúng khi mọi thứ suôn sẻ?
    How to use itWhen reading a data system's docs, split its guarantees into two columns. The safety column must hold even if every node crashes; the liveness column nearly always carries a condition such as "provided a majority of nodes are up and the network eventually recovers".

    Vì sao không thể đòi sống còn vô điều kiện

    Nếu mạng bị cắt hoàn toàn và vĩnh viễn, không thuật toán nào có thể hứa mọi yêu cầu rồi sẽ được trả lời. Do đó sống còn bắt buộc phải kèm giả định — thường là dạng bán đồng bộ: mạng rốt cuộc sẽ hồi phục19. Đây chính là cây cầu dẫn sang định lý FLP và các giao thức đồng thuận của Chương 9.

    Từ chứng minh sang hiện thực

    Kleppmann khép chương bằng một lưu ý khiêm tốn: mô hình toán học là công cụ hữu ích để chưng cất giả định, nhưng chứng minh trên giấy không loại trừ được lỗi hiện thực, cấu hình sai, hay những dạng hỏng nằm ngoài mô hình1. Lý thuyết và kiểm thử thực nghiệm — kiểu các bài phân tích Jepsen12 — bổ sung cho nhau chứ không thay thế nhau.

Ba ngộ nhận thường gặp về hệ phân tánThree common misreadings about distributed systems

“Timeout hết hạn nghĩa là node đã chết”"Timeout expired means the node is dead"

Sai: timeout chỉ nói rằng bạn đã ngừng chờ. Vì mạng bất đồng bộ không có chặn trên, không tồn tại ngưỡng nào chứng minh được cái chết. Tuyên bố một node đã chết luôn là một quyết định của quorum, không phải một quan sát — và phải được toàn hệ công nhận cùng lúc, kèm fencing token, nếu không sẽ có hai bản chính cùng ghi.

Wrong: a timeout only says you stopped waiting. Since an asynchronous network has no upper bound, no threshold can prove death. Declaring a node dead is always a quorum decision, not an observation — and it must be recognised system-wide at once, backed by fencing tokens, or two leaders will write at the same time.

“Đồng hồ máy chủ đủ chính xác để xếp thứ tự”"Server clocks are accurate enough to order events"

Sai: NTP chỉ đưa đồng hồ về gần đúng, với sai số phụ thuộc mạng và chu kỳ hỏi9. Dùng dấu thời gian tường để quyết định "lần ghi nào thắng" khiến lần ghi xảy ra sau bị vứt bỏ trong im lặng12. Muốn xếp thứ tự thì dùng đồng hồ logic; muốn dùng đồng hồ vật lý thì phải mang theo khoảng bất định như TrueTime8.

Wrong: NTP only brings clocks close, with error depending on the network and poll interval9. Using wall-clock timestamps to decide "which write wins" silently discards the write that happened later12. To order events, use logical clocks; to use physical clocks, carry the uncertainty interval, as TrueTime does8.

“Cứ có khóa phân tán là an toàn”"A distributed lock makes it safe"

Sai: khóa có thời hạn, còn tiến trình giữ khóa thì có thể bị dừng lâu hơn thời hạn ấy mà không hay biết. Kiểm tra hạn khóa ngay trước khi ghi cũng không cứu được, vì quãng dừng có thể rơi vào đúng khe hở đó6. Chỉ khi tài nguyên tự từ chối những token nhỏ hơn token cao nhất đã thấy thì khóa mới thật sự an toàn.

Wrong: leases expire, and the process holding one can be paused past the expiry without noticing. Checking the lease just before writing does not save you, because the pause can land in exactly that gap6. The lock is only truly safe when the resource itself rejects any token lower than the highest it has seen.

Tự đọc mìnhRead yourself

Soi những giả định bạn đang ngầm tinAudit the assumptions you silently make

Chương 8 không cho bạn công cụ mới; nó lấy đi những giả định cũ. Cách áp dụng thiết thực nhất là lôi các giả định ẩn trong hệ của bạn ra ánh sáng. Mỗi câu dưới đây nhắm vào một giả định mà phần mềm thường tin mà không hề tuyên bố.

Chương này không dạy mẹo mới, mà chỉ ra những điều mình tưởng là đúng. Thử soi hệ bạn đang làm bằng mấy câu hỏi dưới đây xem sao.

Chapter 8 gives you no new tools; it takes away old assumptions. The most practical way to apply it is to drag your system's hidden assumptions into the light. Each question below targets an assumption software usually believes without ever stating it.

Khi một node của bạn không trả lời, hệ kết luận điều gì — và dựa vào đâu?Hãy truy tới tận cùng: có phải chỉ là một timeout cố định trong tệp cấu hình? Nếu vậy, con số ấy được chọn thế nào, và ai đã kiểm tra nó dưới tải đỉnh? Nhớ rằng timeout không bao giờ chứng minh được cái chết — nó chỉ nói rằng bạn đã ngừng chờ.
Nếu bạn tuyên bố nhầm một node đang sống là đã chết, chuyện gì xảy ra?Liệt kê cụ thể: một thao tác bị thực hiện hai lần? hai node cùng nghĩ mình là bản chính? tải của node "chết" dồn sang các node khác đúng lúc hệ đang nghẽn? Kịch bản cuối cùng là con đường kinh điển dẫn tới sập dây chuyền.
Ở đâu trong mã của bạn có phép trừ hai mốc thời gian?Mỗi phép trừ ấy là một giả định rằng đồng hồ không nhảy lùi. Nó dùng đồng hồ thời-gian-ngày hay đồng hồ đơn điệu? Nếu là loại thứ nhất, hãy thử nghĩ xem điều gì xảy ra vào đêm có giây nhuận, hay khi NTP bước một nhát lớn.
Hệ của bạn có dùng dấu thời gian để quyết định "bản ghi nào thắng" không?Nếu có, thì độ lệch đồng hồ giữa các node đang là bao nhiêu, và bạn có đang theo dõi nó không? Với last-write-wins, một node chạy nhanh vài mili giây có thể âm thầm xóa các lần ghi hợp lệ — và sẽ không có dòng log nào báo cho bạn.
Quãng dừng dài nhất mà tiến trình của bạn từng chịu là bao lâu — bạn có số đo không?Không chỉ thu gom rác: còn di trú máy ảo, lỗi trang khi bị hoán đổi ra đĩa, tranh chấp CPU trên máy thuê chung. Nếu chưa có số đo, thì mọi thời hạn khóa trong hệ đang được chọn bằng cảm tính.
Nếu một client giữ khóa bị dừng quá hạn rồi tỉnh dậy và ghi, ai chặn nó?Câu trả lời đúng duy nhất là: chính tài nguyên chặn, bằng cách từ chối token nhỏ hơn token cao nhất nó đã thấy. Nếu câu trả lời của bạn là "client sẽ tự kiểm tra hạn khóa trước khi ghi", thì bạn vẫn đang để ngỏ khe hở giữa lần kiểm tra và lần ghi.
Hệ của bạn có cần chịu lỗi Byzantine không — và bạn đã trả lời câu này một cách có ý thức chưa?Với phần lớn hệ trong một trung tâm dữ liệu, câu trả lời là không, và đó là một quyết định hợp lý. Nhưng hãy nói ra thành lời, rồi hỏi tiếp: những dạng "nói dối" nhẹ — dữ liệu hỏng do lỗi bit, gói tin sai qua mặt kiểm tổng, cấu hình sai — bạn phòng bằng gì?
Hãy tách các bảo đảm của hệ bạn thành cột an toàn và cột sống còn.Cột an toàn phải đúng ngay cả khi mọi giả định về thời gian bị vi phạm. Cột sống còn được phép kèm điều kiện — nhưng điều kiện ấy phải được viết ra. Nếu bạn không tách được hai cột này, thì bạn chưa biết hệ của mình hứa gì.
Khi một máy im lặng, hệ của bạn kết luận nó chết hay chỉ đang bận?Im lặng không có nghĩa là đã chết. Đợi bao lâu là do mình chọn.
Nếu đoán nhầm là nó chết, thì chuyện gì hỏng?Có thể hai máy cùng tưởng mình là người phụ trách — và cùng ghi đè lên nhau.
Chương trình của bạn có chỗ nào lấy giờ hiện tại trừ đi giờ lúc nãy không?Nếu đồng hồ bị chỉnh lùi, phép trừ đó ra số âm và mọi thứ có thể vỡ.
Nếu máy của bạn "đứng hình" mười giây rồi tỉnh lại, nó có biết không?Thường là không. Nên đừng để nó tự tin rằng vẫn tới lượt mình.
Chỗ giữ dữ liệu của bạn có kiểm tra "vé" không, hay chỉ tin lời máy gửi tới?Nhớ nhé: phải kiểm tra vé, đừng chỉ nghe kể.
When a node stops answering, what does your system conclude — and on what basis?Chase it to the bottom: is it just a fixed timeout in a config file? How was that number chosen, and who tested it under peak load? A timeout never proves death; it only says you stopped waiting.
If you wrongly declare a live node dead, what breaks?Name them: an operation performed twice? two nodes each believing they lead? the "dead" node's load shifted onto others exactly while the system is congested? That last path is the classic route to cascading failure.
Where in your code do you subtract two timestamps?Every such subtraction assumes the clock never jumps backwards. Is it a time-of-day or a monotonic clock? If the former, think about leap-second night, or NTP taking a large step.
Does your system use timestamps to decide which write wins?If so, what is the current clock skew between nodes, and are you monitoring it? With last-write-wins, a node a few milliseconds fast can silently erase valid writes, with no log line to warn you.
What is the longest pause your process has ever suffered — do you have the measurement?Not just GC: VM migration, page faults after swapping, CPU contention on shared hosts. Without the measurement, every lease duration in your system was chosen by feel.
If a lock-holding client is paused past expiry, wakes and writes — who stops it?The only right answer is: the resource itself, by rejecting tokens lower than the highest it has seen. If your answer is "the client re-checks the lease before writing", the gap between check and write is still open.
Does your system need Byzantine fault tolerance — and have you answered that deliberately?For most systems inside one datacenter the answer is no, and that is reasonable. But say it out loud, then ask: what guards against the mild forms of lying — bit-flip corruption, bad packets slipping past checksums, misconfiguration?
Split your system's guarantees into a safety column and a liveness column.The safety column must hold even when every timing assumption is violated. The liveness column may carry conditions — but those conditions must be written down. If you can't split them, you don't yet know what your system promises.
Kết luận cô đọngThe conclusion, distilled
Trong hệ phân tán, điều khó nhất không phải là mọi thứ đều có thể hỏng, mà là bạn không biết thứ gì đã hỏng. Mạng không cho chặn trên, đồng hồ không cho thời điểm chắc chắn, tiến trình không cho tính liên tục — nên mọi phát biểu "node kia đã chết" hay "lần ghi này xảy ra sau" đều là suy diễn, không phải quan sát. Lối thoát của Kleppmann không phải một thuật toán khôn ngoan hơn, mà là đổi chỗ đặt niềm tin: để đa số định nghĩa sự thật, để tài nguyên tự kiểm chứng bằng token tăng ngặt, và viết rõ giả định của mình ra thành mô hình hệ thống cùng cặp an toàn – sống còn.1Nhiều máy làm việc chung thì kiểu gì cũng có lúc trục trặc — điều đó bình thường. Cái khó là mình không nhìn thấy chỗ nào đang hỏng. Vì thế: đừng tin một máy tự nói mình đúng, hãy hỏi cả nhóm; và chỗ giữ đồ thì phải kiểm tra vé, đừng chỉ nghe kể. Nhớ nhé: thành thật về chỗ mình không biết thì an toàn hơn là giả vờ biết.In a distributed system the hard part is not that anything can go wrong, but that you cannot tell what did. Networks give no upper bound, clocks give no certain instant, processes give no continuity — so every claim that "that node is dead" or "this write came later" is an inference, not an observation. Kleppmann's way out is not a cleverer algorithm but a relocation of trust: let the majority define truth, let the resource verify itself with strictly increasing tokens, and write your assumptions down as a system model with its safety and liveness properties.1