Daniel Nguyen
← Tất cả dự án

Cập nhật lô QC nhiều người dùng mà không mất dữ liệu

Row lock kết hợp version check để nhiều người kiểm định sửa cùng một lô, token đăng nhập an toàn hơn và API cập nhật hàng loạt nhanh hơn.

TTMI
~55% ít truy vấn hơnNhanh hơn ~60%, benchmark local ~320 dòng
Mảng
ERP bán lẻ
Thời gian
2026
Vai trò
Thiết kế và triển khai backend
  • Python
  • Django
  • Django REST Framework
  • PostgreSQL
  • pytest

Các phần ghép với nhau thế nào

Cập nhật lô QC nhiều người dùng mà không mất dữ liệuTwo inspectors update one batch: scoped tokens authenticate them, then narrow row locks and version checks decide which writes land.Inspector device Angười dùngInspector device Bngười dùngScoped token checkkiểm tra, cảnh báoBatch update APIdịch vụLock parent + itemskiểm tra, cảnh báoVersion checkkiểm tra, cảnh báoRelational DBkho dữ liệuapp + device tokenapp + device tokenauthenticated requestone txn, stable ordercompare client versionlock touched rows onlywrite, bump versionstale: conflict
Two inspectors update one batch: scoped tokens authenticate them, then narrow row locks and version checks decide which writes land.
  • Người dùng
  • Kiểm tra, cảnh báo
  • Dịch vụ
  • Kho dữ liệu

Vấn đề

TTMI vận hành một hệ thống ERP bán lẻ nội bộ cho 50 cửa hàng, ~500 nhân viên và 4 thương hiệu. Một module trong đó lo phần kiểm soát chất lượng: người kiểm định mở một lô hàng và ghi kết quả cho từng mục.

Thường có vài người cùng làm trên một lô, mỗi người một thiết bị. Luồng cập nhật cũ theo kiểu “ai lưu sau thì thắng”. Nếu hai người lưu gần như cùng lúc, hoặc một người lưu từ màn hình đã mở từ mấy phút trước, công sức của đồng nghiệp bị ghi đè mà không ai hay.

Cách sửa dễ nghĩ tới nhất là khóa cả lô mỗi lần sửa. Làm vậy thì mọi người phải xếp hàng chờ nhau và màn hình có cảm giác bị đơ.

Bên cạnh đó còn hai vấn đề liên quan. Token đăng nhập dùng chung và sống lâu, nên không gắn được với từng ứng dụng hay từng thiết bị, và hai lần đăng nhập cùng lúc có thể tranh nhau tạo ra token trùng. Ngoài ra, endpoint cập nhật hàng loạt của khu vực này chạy nhiều SQL hơn hẳn mức cần thiết.

Tôi đã làm gì

Row lock hẹp kết hợp version check

Trong một transaction, API khóa dòng của lô cha, rồi chỉ khóa những dòng mục mà request thật sự đụng tới. Người sửa các mục khác nhau không chặn nhau. Mỗi mục còn có số version. Client gửi version nó đã tải; nếu version đó đã cũ, API trả về conflict trước khi ghi bất cứ thứ gì.

Lock bảo vệ trước hai request chạy cùng một lúc. Version check bảo vệ trước người có màn hình đã cũ năm phút, điều mà lock không nhìn thấy được. Đổi lại, hệ thống có nhiều phần hơn so với dùng riêng một kỹ thuật, nhưng lock ngắn và màn hình cũ bị phát hiện rõ ràng.

Thứ tự khóa cố định

Hai request đụng vào các mục chồng nhau có thể deadlock nếu khóa theo thứ tự khác nhau. Tôi cho việc khóa các mục luôn theo một thứ tự ổn định. Cái giá là một lần sắp xếp nhỏ mỗi lần ghi, rẻ hơn nhiều so với một lần deadlock rồi phải thử lại.

Chỉ khóa các dòng chính

PostgreSQL không khóa được phía nullable của outer join, nên tôi giới hạn lock ở các dòng chính. Các dòng liên quan được bảo vệ bởi lock của dòng cha và bởi validation, không có lock riêng. Tôi chấp nhận điều này vì lock cha vốn đã buộc các lượt ghi trên cùng một lô phải đi lần lượt.

Token gắn với ứng dụng và thiết bị, xoay vòng nguyên tử

Tôi thêm loại token gắn với người dùng, ứng dụng và thiết bị. Việc cấp token mới diễn ra dưới row lock trong một transaction, và chỉ token của đúng ứng dụng và thiết bị đó bị thay. Hai lần đăng nhập đồng thời không còn tạo token trùng, và đăng nhập trên thiết bị này không làm người dùng bị đăng xuất ở nơi khác. Cái giá là thêm một lần đọc có khóa khi đăng nhập.

Ít query hơn trên đường cập nhật hàng loạt

Endpoint hàng loạt tải dữ liệu tham chiếu cho từng dòng và prefetch dữ liệu lồng nhau cho từng dòng. Tôi đổi sang đọc dữ liệu tham chiếu một lần mỗi request và bỏ prefetch lồng nhau theo dòng. Tôi cố ý giữ validation và locking theo dòng giống hệt đường sửa một mục, dùng chung một logic. Có thể viết một đường riêng nhanh hơn, nhưng hai bản quy tắc sẽ dần lệch nhau.

Kết quả

Lượt sửa từ màn hình cũ giờ nhận về một conflict rõ ràng, nhắc người kiểm định tải lại, và không ai bị mất dữ liệu trong im lặng. Những người sửa các mục khác nhau trong cùng một lô làm việc song song.

Đăng nhập đồng thời không tạo ra token trùng, và xoay token cho một thiết bị không ảnh hưởng các phiên khác của người dùng.

API lô QC hàng loạt chạy ít hơn ~55% số query SQL và nhanh hơn ~60% trong benchmark local với ~320 dòng. Các thay đổi đã được deploy lên production.

Nếu làm lại

Tôi mới đo mức cải thiện bằng benchmark local. Lần sau tôi sẽ thêm assertion về số query vào bộ test và đo thời gian trên production, để vừa giữ được kết quả vừa xác nhận nó dưới tải thật. Tôi cũng sẽ viết các quy tắc khóa thành một ghi chú thiết kế ngắn trước khi code, vì lý do đằng sau thứ tự khóa và giới hạn outer join rất dễ bị quên.