Đồng thời phía máy chủ: Đảm bảo tính đúng đắn khi các request chồng chéo
Lập luận về công việc phía máy chủ chồng lấn bằng cách xác định bất biến, điều kiện tranh đua, ranh giới nguyên tử, vùng tới hạn, phạm vi phối hợp, chiến lược lạc quan và bi quan, tranh chấp tài nguyên và bế tắc.
Bản đồ học tập phát triển phần mềm bởi Tran Trong Thuc · Về dự án Atlas · Cập nhật lần cuối: 5 thg 11, 2026
Đồng thời phía máy chủ: Đảm bảo tính đúng đắn khi các request chồng chéo
Ví dụ giả định về xử lý đồng thời (không phải sự cố tài chính có thật): Một tài khoản bắt đầu với số dư 100 USD. Hai yêu cầu rút 50 USD được xử lý ở hai bản sao ứng dụng; cả hai cùng đọc balance = 100, cùng chấp nhận yêu cầu và sau đó ghi giá trị đã tính trước là 50. Nếu cả hai khoản tiền đều thực sự được chi trả, tài khoản đã chi 100 USD nhưng số dư lưu trong cơ sở dữ liệu lại là 50 USD thay vì 0 USD. Hệ thống ghi thiếu 50 USD đã bị trừ, chứ không phải tiền tự nhiên “bốc hơi”. Ví dụ này giả định cả hai giao dịch được chấp nhận, không có bảo vệ idempotency xuyên request và cơ sở dữ liệu cho phép hai phép ghi dùng giá trị cũ cùng commit (chẳng hạn PostgreSQL READ COMMITTED). Đây không phải báo cáo sự cố thật và cũng không mô tả mọi mức cô lập giao dịch.
Lỗi đồng thời xuất hiện khi các thao tác chồng lấn cùng đọc trạng thái, ra quyết định và ghi hiệu ứng nhưng thiếu cơ chế bảo vệ bất biến nghiệp vụ. Chỉ thêm BEGIN/COMMIT không làm chuỗi đọc–kiểm tra–ghi tự động có tính tuần tự; mức cô lập, ràng buộc, phép ghi có điều kiện nguyên tử hoặc điều phối dùng chung mới quyết định các thao tác nào được phép thành công. Ngay cả tác vụ bất đồng bộ trên một lõi CPU cũng có thể gặp race; nhiều replica làm phạm vi điều phối rộng hơn.
TL;DR
💡 Quy tắc bỏ túi: Khóa chỉ nằm trong một tiến trình không bảo vệ được bất biến xuyên nhiều bản sao. Ưu tiên phép ghi có điều kiện nguyên tử hoặc ràng buộc tại kho dữ liệu, hoặc giao dịch với mức cô lập và cơ chế khóa phù hợp. Nếu workflow còn gọi dịch vụ bên ngoài, phải thiết kế riêng trạng thái ý định bền vững, retry và chống xử lý trùng.
- Bất biến xác định ranh giới phối hợp: Lập trình đồng thời không chỉ là hai luồng CPU chạy cùng tích tắc, mà là bảo đảm các thao tác xen kẽ không phá vỡ bất biến nghiệp vụ (như số dư không âm, tồn kho flash sale không bán vượt số lượng thực tế).
- Khóa nội bộ dừng lại ở ranh giới tiến trình: Mutex trong bộ nhớ (
sync.Mutex,lock) vô dụng khi ứng dụng chạy trên nhiều pod/container hoặc worker; ranh giới bảo vệ bắt buộc phải đặt tại kho dữ liệu dùng chung. - Lựa chọn chiến lược điều phối theo độ tranh chấp: Ưu tiên phép ghi có điều kiện nguyên tử (
UPDATE ... WHERE balance >= 50) cho số đếm đơn giản, khóa lạc quan (version = version + 1) khi tranh chấp thấp, hoặc khóa bi quan (SELECT ... FOR UPDATE) khi xung đột cao và chi phí thử lại (retry) quá đắt. - Tranh chấp tài nguyên bản chất là một hàng đợi ngầm: Khi hàng trăm request cùng giành một vùng tới hạn, thời gian chờ khóa tăng vọt và làm cạn kiệt connection pool; hãy thu nhỏ vùng tới hạn thay vì nới lỏng bất biến để đánh đổi thông lượng ảo.
- Lỗi nguy hiểm: Chuỗi đọc–kiểm tra–ghi bằng giá trị đã tính trước (
SELECT balance→ kiểm tra trong ứng dụng → ghi số dư cũ đã tính) có thể cùng chấp nhận hai yêu cầu và ghi đè một khoản trừ khi mức cô lập không đủ mạnh. Phải bảo vệ cả bất biến trong cơ sở dữ liệu lẫn các hiệu ứng nghiệp vụ được ghi nhận riêng.
Mục tiêu không phải là “dùng khóa”. Mục tiêu là làm cho cách xen kẽ không hợp lệ trở thành bất khả thi, hoặc phát hiện và từ chối nó trước khi được ghi nhận.
1. Bắt đầu từ bất biến
Lỗi đồng thời dễ phân tích hơn khi ta ngừng hỏi “hai dòng này có chạy cùng lúc không?” và thay bằng câu hỏi:
Điều gì phải vẫn đúng sau mọi cách chồng lấn được phép?
Ví dụ về bất biến:
- tồn kho không bao giờ âm;
- tên người dùng là duy nhất trong một tenant;
- mỗi lô quyết toán chỉ có tối đa một khoản chi trả đang hoạt động;
- số dư tài khoản không được thấp hơn ngưỡng cấu hình;
- tại một thời điểm chỉ một công việc lịch biểu sở hữu lease;
- đơn hàng đã
cancelledkhông thể quay lạipaid.
Bất biến quyết định ranh giới bảo vệ. Một mutex quanh Map trong bộ nhớ không thể thực thi quy tắc duy nhất nếu nguồn sự thật nằm trong cơ sở dữ liệu dùng chung. Một khóa hàng trong cơ sở dữ liệu cũng không thể hoàn tác khoản thanh toán đã thành công ở hệ thống bên ngoài.
2. Điều kiện tranh đua là vấn đề về cách xen kẽ
Xét một kiểm tra quota ngây thơ:
đọc remaining = 1
nếu remaining > 0:
ghi remaining = remaining - 1Hai yêu cầu có thể xen kẽ như sau:
Con số cuối có vẻ không bất thường, nhưng hiệu ứng nghiệp vụ đã sai: hai thao tác cùng được chấp nhận từ một đơn vị dung lượng.
Điều kiện tranh đua xảy ra khi tính đúng đắn phụ thuộc vào bên nào thắng cuộc đua thời gian. Mẫu nguy hiểm thường không phải một phép ghi bộ nhớ bị xé nhỏ ở mức thấp. Nó thường là quyết định nhiều bước kiểu đọc → kiểm tra → hành động.
Một lỗi liên quan là mất cập nhật:
A đọc phiên bản 7
B đọc phiên bản 7
A ghi thay đổi X thành phiên bản 8
B ghi thay đổi Y thành phiên bản 8Nếu B ghi đè A mà không phát hiện xung đột, cập nhật thành công của A đã bị mất.
3. Tính nguyên tử đóng khoảng hở đọc-kiểm tra-ghi
Một thao tác có tính nguyên tử đối với một phía quan sát khi nó hành xử như một chuyển trạng thái không thể chia nhỏ: công việc xung đột không thể quan sát hoặc ghi nhận một cách xen kẽ bị cấm ở giữa.
Thay vì:
đọc remaining
kiểm tra remaining > 0
ghi remaining - 1hãy ưu tiên chuyển trạng thái có điều kiện và nguyên tử khi kho dữ liệu hỗ trợ:
UPDATE inventory
SET remaining = remaining - 1
WHERE sku = $1
AND remaining > 0
RETURNING remaining;Lúc này kho dữ liệu quyết định chuyển trạng thái có hợp lệ hay không ngay tại ranh giới nó thực hiện phép ghi. Phải kiểm tra số hàng được trả về: không có hàng nào nghĩa là phép trừ chưa được áp dụng (có thể không tồn tại SKU hoặc số lượng đã hết). Không báo thành công hay kích hoạt hiệu ứng bên ngoài chỉ vì câu SQL không ném lỗi.
Với ví dụ số dư tài khoản đầu bài, có thể dùng một phép chuyển trạng thái trên một hàng theo kiểu PostgreSQL:
UPDATE accounts
SET balance = balance - $1
WHERE id = $2
AND balance >= $1
RETURNING balance;Gán $1 là số tiền rút dương và $2 là ID tài khoản; việc phân quyền tài khoản vẫn phải kiểm tra riêng. Một hàng được trả về chỉ xác nhận số dư đã được cập nhật trong giao dịch, không chứng minh dịch vụ thanh toán ngoài hệ thống đã chi tiền. Khi có thể, ghi ý định rút tiền bền vững và công việc outbox cùng giao dịch với phép cập nhật số dư; không thể hoàn tác giao dịch ở dịch vụ thanh toán chỉ bằng rollback cơ sở dữ liệu.
Tính nguyên tử luôn gắn với một ranh giới. Một phép tăng nguyên tử ở mức ngôn ngữ có thể bảo vệ một ô nhớ nhưng không tự động bảo vệ:
- hai trường liên quan phải đổi cùng nhau;
- nhiều hàng cùng tạo thành một bất biến nghiệp vụ;
- trạng thái chia giữa cơ sở dữ liệu và API từ xa;
- nhiều bản sao dịch vụ, mỗi bản sao có bộ nhớ riêng.
4. Lựa chọn đầu tiên: loại bỏ trạng thái thay đổi dùng chung không cần thiết
Khóa rẻ nhất thường là khóa không cần tồn tại.
Trước khi phối hợp, hãy hỏi liệu có thể đơn giản hóa quyền sở hữu hay không:
- giữ dữ liệu riêng của từng yêu cầu ở dạng bất biến;
- phân vùng công việc để một chủ sở hữu xử lý một key tại một thời điểm;
- dùng nhật ký thêm mới thay vì sửa tại chỗ khi mô hình phù hợp;
- tính toán thuần túy bên ngoài vùng tới hạn;
- đưa quy tắc duy nhất hoặc giới hạn vào ràng buộc của kho dữ liệu;
- tránh registry toàn cục trong bộ nhớ cho trạng thái thật sự thuộc về người dùng, tenant hoặc job.
Ví dụ, nếu mỗi worker sở hữu đúng một partition, thao tác trên hai partition khác nhau không cần mutex toàn cục. Nếu tính duy nhất là quy tắc của cơ sở dữ liệu, unique constraint là cơ chế đồng thời mạnh và đơn giản hơn kiểu “kiểm tra trước, chèn sau”.
Đừng coi “không khóa” là mặc định tốt hơn. Thiết kế loại bỏ trạng thái dùng chung có giá trị vì nó vừa giảm phối hợp vừa làm mô hình đúng đắn rõ hơn.
5. Đồng thời lạc quan: phát hiện xung đột rồi thử lại hoặc từ chối
Đồng thời lạc quan giả định xung đột có thể xảy ra nhưng chưa thường xuyên đến mức đáng phải chặn mọi thao tác từ đầu. Phía ghi phải chứng minh trạng thái dùng làm cơ sở quyết định vẫn còn mới.
Một kiểm tra phiên bản điển hình:
UPDATE documents
SET body = $1,
version = version + 1
WHERE id = $2
AND version = $3;Nếu không có hàng nào được cập nhật thì phiên bản không khớp hoặc tài liệu không tồn tại; cần phân biệt hai trường hợp trước khi trả mã 404 hoặc phản hồi xung đột theo hợp đồng API. Ứng dụng có thể:
- đọc lại và thử lại nếu thao tác có thể tính lại an toàn;
- trả xung đột để người dùng hòa giải thay đổi;
- chỉ merge nếu miền nghiệp vụ có quy tắc merge đúng đắn.
Cùng ý tưởng này xuất hiện dưới các dạng compare-and-swap, conditional write, cột phiên bản hoặc điều kiện HTTP như If-Match.
Đồng thời lạc quan phù hợp khi:
- xung đột tương đối hiếm;
- giữ khóa trong thời gian người dùng suy nghĩ sẽ tốn kém;
- việc tính lại hoặc xử lý xung đột được định nghĩa rõ;
- mục tiêu ghi có phiên bản hoặc điều kiện đáng tin cậy.
Nó không phù hợp khi tranh chấp nóng làm nhiều phía ghi liên tục thất bại và thử lại. Khi đó retry làm khuếch đại tranh chấp thay vì tăng tính đồng thời hữu ích.
6. Đồng thời bi quan: chủ động chặn công việc xung đột
Đồng thời bi quan lấy khóa hoặc quyền sở hữu độc quyền trước khi thực hiện quyết định được bảo vệ. Cách này hữu ích khi xung đột được dự đoán là thường xuyên, tài nguyên bảo vệ cụ thể và chặn đợi rẻ hơn nhiều lần thất bại rồi thử lại.
Ví dụ:
- mutex trong một tiến trình bảo vệ cấu trúc bộ nhớ;
- khóa hàng cơ sở dữ liệu (chẳng hạn
SELECT ... FOR UPDATE) trong lúc giao dịch cập nhật trạng thái liên quan; - một chủ sở hữu đơn luồng xử lý thao tác cho một key;
- lease khi quyền sở hữu độc quyền phải kéo dài qua nhiều tiến trình.
Các câu hỏi cần trả lời:
- Khóa này thực sự đại diện hoặc sở hữu cái gì?
- Những ai có thể tranh chấp nó?
- Được giữ trong bao lâu?
- Nếu chủ sở hữu crash thì điều gì xảy ra?
- Bên chờ có thể timeout hoặc hủy không?
- Khóa có bảo vệ đúng nguồn sự thật không?
Tên “lock” không tự tạo ra correctness. Nó chỉ hoạt động khi mọi đường thực thi có thể xung đột cùng tham gia một giao thức phối hợp.
7. Khóa nội bộ tiến trình dừng ở ranh giới tiến trình
Đây là lỗi phía máy chủ rất phổ biến:
yêu cầu tới
lấy mutex nội bộ tiến trình cho account 42
kiểm tra trạng thái tài khoản bền vững
ghi trạng thái tài khoản bền vững
nhả mutexMutex đó có thể phối hợp các luồng hoặc tác vụ bất đồng bộ trong một tiến trình máy chủ. Nó không phối hợp bản sao khác có không gian bộ nhớ khác.
Cả MA và MB có thể được lấy cùng lúc vì chúng là hai khóa khác nhau.
Chỉ dùng khóa nội bộ tiến trình khi bất biến được bảo vệ thật sự cũng chỉ thuộc một tiến trình, hoặc dùng nó như tối ưu hóa đặt bên trên một cơ chế correctness dùng chung.
Đối với correctness qua nhiều bản sao, hãy ưu tiên primitive do hệ thống sở hữu trạng thái dùng chung cung cấp khi có thể:
- phép ghi có điều kiện nguyên tử;
- unique/check constraint;
- giao dịch và mức cô lập phù hợp;
- compare-and-swap hoặc kiểm tra phiên bản do kho dữ liệu cung cấp;
- hàng đợi bền vững hoặc một chủ sở hữu cho mỗi partition.
Chỉ dùng khóa phân tán khi quyền sở hữu độc quyền bản thân nó là yêu cầu của miền nghiệp vụ và primitive gắn với trạng thái đơn giản hơn không biểu diễn tốt. Khóa phân tán kéo theo thời hạn lease, fencing, clock/failure và chủ sở hữu cũ — các vấn đề cần một bài sâu riêng.
8. Giao dịch là một công cụ đồng thời, không phải lá chắn thần kỳ
Giao dịch cơ sở dữ liệu phù hợp khi bất biến thuộc trạng thái cơ sở dữ liệu và nhiều lần đọc/ghi phải tạo thành một quyết định logic.
Nhưng chỉ có BEGIN/COMMIT không chứng minh an toàn. Vẫn phải phân tích:
- snapshot nào các giao dịch đồng thời có thể nhìn thấy;
- liệu hai giao dịch có thể cùng validate rồi cùng commit quyết định xung đột không;
- có cần khóa hàng hoặc predicate không;
- mức
Serializablecó thể abort cách xen kẽ không an toàn không; - ứng dụng có thử lại toàn bộ giao dịch khi cần không.
Tài liệu PostgreSQL 18 mô tả việc READ COMMITTED có thể cho phép quyết định đọc–kiểm tra–ghi dựa trên dữ liệu cũ, trong khi mức cô lập mạnh hơn có thể hủy giao dịch khi xung đột. SQLSTATE 40001 là serialization_failure, còn 40P01 là deadlock_detected. Khi retry, phải chạy lại toàn bộ giao dịch và logic ra quyết định với số lần thử có giới hạn, không chỉ thử lại một câu SQL bị lỗi. Không tự động lặp hiệu ứng ở dịch vụ ngoài nếu hiệu ứng đó có thể đã thành công. PostgreSQL cũng khuyến nghị lấy nhiều khóa theo cùng thứ tự để hạn chế bế tắc.
Vì vậy bài này tập trung vào việc chọn phạm vi phối hợp, còn Database Transactions & Isolation đi sâu vào semantics của giao dịch trong kho dữ liệu.
9. Bế tắc: khóa đúng vẫn có thể làm hệ thống ngừng tiến triển
Bế tắc xảy ra khi các thao tác đồng thời tạo thành chu trình chờ nhau.
Các biện pháp thường dùng:
- lấy nhiều tài nguyên theo một thứ tự xác định;
- giữ vùng tới hạn ngắn;
- tránh gọi mạng từ xa trong khi đang giữ khóa;
- dùng timeout cho khóa hoặc giao dịch;
- khi hệ thống nền phát hiện bế tắc, xử lý và thử lại nạn nhân theo chính sách giới hạn;
- giảm số tài nguyên một thao tác phải giữ cùng lúc.
Đừng “sửa” bế tắc bằng cách bỏ khóa mà không khôi phục bất biến theo cách khác. Cách đó đổi lỗi khả dụng dễ thấy thành hỏng dữ liệu âm thầm.
10. Tranh chấp chính là một hàng đợi, dù sơ đồ của bạn có vẽ nó hay không
Nếu 500 yêu cầu đến cùng lúc và đều cần một vùng tới hạn độc quyền, chỉ một yêu cầu giữ được nó tại một thời điểm. Tối đa 499 yêu cầu còn lại phải chờ, bị từ chối hoặc chịu cơ chế điều tiết tải ở đâu đó: trong ứng dụng, runtime, connection pool, cơ sở dữ liệu hoặc lock manager.
Vì vậy thiết kế đồng thời cũng là thiết kế năng lực.
Các tín hiệu hữu ích:
- thời gian chờ khóa;
- thời lượng giao dịch;
- tỷ lệ xung đột và retry;
- độ sâu hàng đợi;
- tỷ lệ timeout/hủy;
- phân bố hot key;
- lượng công việc hoàn thành trên mỗi lần lấy vùng tới hạn.
Khi tranh chấp cao, có thể:
- thu nhỏ vùng tới hạn;
- phân vùng quyền sở hữu theo key;
- gom nhóm các thao tác tương thích;
- đưa tính toán thuần túy ra trước lúc lấy khóa;
- thay khóa bi quan bằng phép ghi có điều kiện nguyên tử nếu vẫn đúng;
- chủ động tuần tự hóa công việc của hot key thay vì để retry không giới hạn tạo bão.
Tối ưu sai là nới lỏng bất biến với lý do “race hiếm khi xảy ra”.
Tình huống vận hành giả định: xuất báo cáo trùng trên nhiều bản sao
Ví dụ minh họa, không phải sự cố được công bố: API xuất báo cáo chạy trên sáu bản sao và chỉ cho phép một bản xuất đang hoạt động trên mỗi (tenant_id, report_id). Để tránh tạo job tốn tài nguyên trùng nhau, mỗi tiến trình ứng dụng giữ một mutex trong bộ nhớ theo cặp khóa này.
Trong lúc lưu lượng tăng, hai yêu cầu giống nhau đến gần như cùng lúc. Bộ cân bằng tải đưa chúng tới hai bản sao khác nhau.
Mỗi bản sao:
- lấy mutex nội bộ của chính nó;
- truy vấn và đều thấy chưa có bản xuất đang hoạt động;
- tạo hàng export và lên lịch công việc nền;
- trả
202 Accepted.
Cả hai đều thành công vì hai mutex chưa bao giờ phối hợp với nhau.
Hậu quả: hai bản xuất cùng tiêu tốn tài nguyên cơ sở dữ liệu và worker, người dùng nhận nhiều thông báo hoàn tất, còn logic dọn dẹp tranh chấp xem artifact nào là bản chính.
Nguyên nhân cốt lõi: bất biến “tối đa một bản xuất đang hoạt động cho mỗi tenant/report” là bền vững và xuyên nhiều bản sao, nhưng primitive phối hợp chỉ nằm trong một tiến trình. Ứng dụng bảo vệ sai phạm vi. Chuỗi đọc-rồi-tạo cũng không có ranh giới duy nhất nguyên tử dùng chung.
Cách khắc phục chuẩn: thực thi bất biến tại nơi trạng thái dùng chung được sở hữu, chẳng hạn bằng ràng buộc duy nhất hoặc conditional create trong kho dữ liệu cho export đang hoạt động, hoặc một bản ghi quyền sở hữu có tính nguyên tử tương đương. Chỉ xem mutex cục bộ là tối ưu hóa tranh chấp tùy chọn. Nếu client có thể gửi lặp, cấp định danh idempotency bền vững cho yêu cầu/job. Lên lịch công việc nền từ trạng thái bền vững đã commit để retry không tạo một job thứ hai không được theo dõi.
Với PostgreSQL, có thể dùng partial unique index để bảo đảm mỗi cặp chỉ có một bản xuất đang hoạt động, với điều kiện việc chuyển trạng thái job diễn ra trong giao dịch phù hợp:
CREATE UNIQUE INDEX one_active_export_per_report
ON exports (tenant_id, report_id)
WHERE status IN ('queued', 'running');Nếu một thao tác chèn hoặc chuyển trạng thái làm trùng hàng đang hoạt động, cơ sở dữ liệu trả lỗi duy nhất; API cần xử lý thành kết quả job đã tồn tại hoặc xung đột, không báo rằng một job thứ hai đã tạo thành công. Partial unique index là khả năng riêng của PostgreSQL, không phải bảo đảm của mọi hệ SQL. Cần định nghĩa chính xác trạng thái nào là đang hoạt động và lên lịch worker từ hàng đã commit (chẳng hạn qua transactional outbox) để tránh làm mất việc khi worker crash.
Bài học tổng quát là: phạm vi phối hợp phải ít nhất rộng bằng phạm vi của bất biến mà nó bảo vệ.
Các lỗi thường gặp
“JavaScript hoặc code async là đơn luồng nên không có race”
Một await tạo điểm xen kẽ. Yêu cầu khác có thể thay đổi trạng thái dùng chung hoặc trạng thái bền vững trước khi phần tiếp tục của yêu cầu đầu chạy lại. Đồng thời không đòi hỏi CPU thực thi thật sự cùng một khoảnh khắc.
“Chúng tôi đã đặt mutex quanh nó”
Hãy hỏi mutex sống ở đâu. Khóa trong một tiến trình không phối hợp bản sao khác, worker process, cron job hoặc công cụ quản trị nếu chúng không dùng cùng một giao thức.
“Có giao dịch nên workflow an toàn”
Commit nguyên tử và isolation là hai thuộc tính khác nhau. Hãy trích bất biến rồi chứng minh chiến lược transaction/isolation/constraint đã chọn từ chối mọi cách chồng lấn không an toàn.
“Retry lạc quan luôn mở rộng tốt hơn”
Ở mức tranh chấp cao, xung đột lặp lại có thể tạo bão retry. Hãy đo tỷ lệ xung đột và chọn cách tuần tự hóa phù hợp cho đường nóng.
“Có thể giữ khóa trong lúc gọi dịch vụ khác”
I/O từ xa khiến thời gian giữ khóa phụ thuộc một miền lỗi khác. Ưu tiên commit quyền sở hữu/trạng thái cục bộ nhanh rồi phối hợp hiệu ứng từ xa bằng workflow bền vững.
“Có deadlock nghĩa là khóa là ý tưởng sai”
Bế tắc nghĩa là đồ thị chờ có thể tạo chu trình. Sửa thứ tự, phạm vi, timeout hoặc mô hình quyền sở hữu nhưng vẫn phải giữ correctness.
Tự kiểm tra
Một dịch vụ có bốn bản sao. Mỗi bản sao giữ một Set trong bộ nhớ chứa tên người dùng đang được đăng ký. Yêu cầu kiểm tra Set, kiểm tra cơ sở dữ liệu xem tên còn trống, rồi mới chèn người dùng. Hai yêu cầu cùng một tên có thể rơi vào hai bản sao khác nhau.
Cách sửa mạnh nhất là gì?
Xem giải thích chi tiết
Set trong bộ nhớ chỉ bảo vệ một tiến trình nên không thể thực thi tính duy nhất xuyên nhiều bản sao. Bất biến bền vững phải nằm ở kho dữ liệu dùng chung.
Dùng unique constraint hoặc phép chèn có điều kiện nguyên tử tương đương làm ranh giới correctness. Set cục bộ có thể giữ lại như tối ưu hóa để giảm công việc trùng trong một bản sao, nhưng correctness không được phụ thuộc vào nó. Nếu chèn xung đột, hãy chuyển xung đột thành phản hồi đúng theo hợp đồng API thay vì cố “kiểm tra kỹ hơn” trước khi chèn.
Checklist rà soát đồng thời
- Bất biến: Tôi có nêu chính xác điều phải còn đúng sau mọi thao tác chồng lấn thành công không?
- Chủ sở hữu trạng thái: Hệ thống nào sở hữu trạng thái quyết định bất biến đó?
- Race: Hai yêu cầu có thể cùng vượt qua bước đọc/kiểm tra trước khi phép ghi xung đột của bên kia trở nên nhìn thấy không?
- Tính nguyên tử: Có thể biểu diễn quy tắc bằng một conditional write nguyên tử hoặc constraint của kho dữ liệu không?
- Vùng tới hạn: Vùng bảo vệ có phải nhỏ nhất nhưng vẫn thật sự quyết định correctness không?
- Phạm vi: Primitive phối hợp có bao phủ mọi tiến trình, bản sao, worker và công cụ có thể phá bất biến không?
- Nhánh lạc quan: Nếu dùng version/compare-and-swap, phát hiện xung đột có đáng tin và retry/hòa giải có giới hạn không?
- Nhánh bi quan: Nếu chặn đợi, quyền sở hữu khóa, thứ tự, timeout, hủy và hành vi khi crash đã rõ chưa?
- Giao dịch: Nếu có transaction cơ sở dữ liệu, isolation/locking đã chọn có bảo vệ đúng bất biến không?
- Bế tắc: Việc lấy nhiều tài nguyên có thể tạo chu trình chờ không, và thứ tự lấy có xác định không?
- Tranh chấp: Telemetry production có cho thấy thời gian chờ, hot key, tỷ lệ conflict, retry và thời lượng vùng tới hạn không?
- Tác dụng phụ bên ngoài: Hiệu ứng từ xa có được tách khỏi giả định rằng tính nguyên tử cục bộ có thể hoàn tác chúng không?
Quy tắc cho agent
Khi rà soát code phía máy chủ có đồng thời, không được duyệt mutex, transaction, retry loop hoặc distributed lock chỉ dựa vào tên primitive. Trước hết phải trích bất biến và toàn bộ tập tác nhân có thể phá bất biến. Sau đó xác minh primitive phối hợp bao phủ đúng phạm vi, cách xen kẽ không an toàn bị ngăn hoặc từ chối, và hành vi khi xung đột, timeout, crash, retry, tranh chấp tài nguyên và bế tắc đều được nêu rõ.
Khái niệm liên quan
- Database Transactions & Isolation — bảo vệ bất biến thuộc cơ sở dữ liệu qua nhiều câu lệnh và định nghĩa hành vi serialization/locking.
- Idempotency — gửi lặp không được vô tình lặp hiệu ứng bền vững.
- Distributed Locks — phối hợp quyền sở hữu độc quyền qua nhiều tiến trình nhưng thêm failure mode về lease và chủ sở hữu cũ.
- Background Jobs — worker chạy đồng thời cần quy tắc rõ về ownership, retry và chống trùng.
- Message Queues — có thể chủ động tuần tự hóa hoặc phân vùng công việc thay vì cho chồng lấn không kiểm soát.
Tiếp tục theo lộ trình Backend Systems để học background jobs, caching, queue, partial failure và resilience.
Nguồn
Các nguồn chính được rà soát ngày 2026-11-05 (PostgreSQL 18/current):
- Go Memory Model — mô hình bộ nhớ của một ngôn ngữ cụ thể, minh họa vì sao truy cập đồng thời vào trạng thái thay đổi dùng chung cần cơ chế đồng bộ.
- PostgreSQL documentation — Transaction Isolation
- PostgreSQL documentation — Explicit Locking
- PostgreSQL documentation — Serialization Failure Handling
- PostgreSQL documentation — UPDATE / RETURNING
- PostgreSQL documentation — Partial Indexes
Bài này ở trạng thái evolving với chu kỳ rà soát mục tiêu 180 ngày. Mô hình lập luận bắt đầu từ bất biến là bền vững, còn primitive runtime, hành vi kho dữ liệu và hướng dẫn vận hành vẫn tiếp tục thay đổi.
Cẩm nang Debug Auth: 401, 403, CORS, Redirect, Issuer, Audience & ClaimNew
Debug lỗi browser, OIDC, Keycloak, token validation, CORS, session và authorization bằng cách xác định boundary hỏng trước khi sửa cấu hình.
Tác vụ nền: Xử lý bền vững ngoài vòng đời requestNew
Lập luận về công việc nền bền vững qua vòng đời rõ ràng, quyền xử lý, xác nhận, giao lại, retry có giới hạn, hiệu ứng lặp lại an toàn, hủy, công việc không thể xử lý và tín hiệu tồn đọng vận hành.