Đồng bộ thư mục: Bài toán khó về độ tin cậy và tốc độ
Đồng bộ thư mục (directory sync) tưởng chừng đơn giản nhưng lại ẩn chứa nhiều cạm bẫy khi triển khai thực tế. Bài viết phân tích vì sao chuẩn SCIM không đủ tốt, và cách tiếp cận kết hợp giữa full sync tối ưu với cập nhật thời gian thực có thể giải quyết bài toán này.

Nếu danh tính người dùng là yếu tố quan trọng trong ứng dụng của bạn, bạn cần một cách để quản lý nó. Thông thường điều này có nghĩa là tạo tài khoản người dùng khi ai đó đăng ký. Nhưng cho phép nhân viên tự do đăng ký mọi ứng dụng SaaS họ thích lại là cơn ác mộng quản trị.
"Ai là người dùng của ngươi?" — Master Control Program, TRON (1982)
Vì vậy các tổ chức muốn kiểm soát chính xác người dùng nào tồn tại trong ứng dụng. Điều đó kiểm soát ai có thể đăng nhập, nhưng còn quyền hạn thì sao? Để làm được điều đó bạn cần vai trò (roles) hoặc nhóm (groups), và những thứ này cũng đến từ nhà cung cấp danh tính của tổ chức - Entra, Google, Okta, v.v.
Đồng bộ thư mục là gì?
Thư mục (directory) của một tổ chức là danh sách ai làm việc ở đó và họ được phép truy cập những gì. Nó sống trong nhà cung cấp danh tính và gồm ba thành phần:
- Người dùng (Users): những người trong tổ chức, mỗi người có tên, email và trạng thái như active hoặc suspended
- Nhóm (Groups): tập hợp người dùng có tên, như Engineering hay Product, cho phép cấp quyền cho nhiều người cùng lúc
- Thành viên (Members): ai thuộc về đâu. Một thành viên của nhóm có thể là người dùng hoặc một nhóm khác
Đồng bộ thư mục là quá trình sao chép thư mục đó vào ứng dụng của bạn và giữ nó luôn cập nhật. Nhà cung cấp danh tính là nguồn chân lý, còn ứng dụng của bạn chỉ giữ một bản sao.
Cần phân biệt rõ: đồng bộ thư mục không phải là SSO. Các chuẩn xác thực như OpenID Connect không nói gì về cách đưa người dùng vào ứng dụng. Đồng bộ thư mục chỉ đề cập đến cơ chế đưa người dùng và nhóm vào, chứ không phải cách họ được xác thực.
Mô hình dữ liệu và vấn đề nhóm lồng nhau
Xét một thư mục ví dụ: nhóm Product có hai thành viên là Alice và nhóm Engineering. Nhóm Engineering có một thành viên là Bob. Vì Engineering nằm trong Product, Bob thuộc Product một cách gián tiếp.
Để trả lời câu hỏi truy cập phổ biến như "Bob có phải thành viên của Product không?", bạn phải xem thành viên của Product, rồi thành viên của các nhóm con, cứ thế cho đến khi tìm thấy Bob.
Giải pháp là làm phẳng nhóm (flattening): cấp cho mỗi người dùng một dòng cho mọi nhóm họ thuộc về, dù trực tiếp hay gián tiếp. Khi đó câu hỏi trên chỉ là một phép tra cứu đơn giản.
SCIM - Chuẩn tưởng chừng hoàn hảo
SCIM (System for Cross-domain Identity Management) là một giao thức dựa trên HTTP giúp quản lý danh tính trong các tình huống đa miền dễ dàng hơn thông qua một dịch vụ được chuẩn hóa.
Trên lý thuyết, bạn chỉ cần xây dựng một bộ endpoint REST trong ứng dụng, và nhà cung cấp danh tính sẽ gọi chúng để giữ thư mục đồng bộ. Điểm hay của SCIM là nhà cung cấp đẩy (push) thay đổi đến ứng dụng, nên độ trễ có thể thấp hơn nhiều so với cách tiếp cận kéo (pull) theo lịch.
Nhưng có những vấn đề lớn:
- Ứng dụng phải luôn sẵn sàng nhận cập nhật gần như mọi lúc. Chỉ cần gián đoạn vài giây là có thể bỏ lỡ cập nhật quan trọng
- SCIM chuẩn hóa giao thức nhưng không chuẩn hóa cách gọi các endpoint, sự kiện vòng đời tương ứng, hay loại dữ liệu trong mỗi request
- Các nhà cung cấp triển khai SCIM rất khác nhau
Ví dụ điển hình về sự khác biệt:
- Cập nhật thành viên nhóm: Okta có thể gửi thêm và xóa cùng lúc trong một PATCH, còn Entra yêu cầu tách riêng và chỉ cho phép xóa một thành viên mỗi PATCH
- Ngay cả kiểu dữ liệu cơ bản cũng khác: Entra từng gửi
active: "False"dạng chuỗi thay vì boolean theo chuẩn SCIM - Khi thu hồi quyền người dùng: Okta yêu cầu thứ tự cụ thể, Entra xóa nhóm không nhất thiết vô hiệu hóa người dùng, JumpCloud có thể theo nhiều cơ chế khác nhau, OneLogin xóa người dùng có thể kích hoạt xóa, tạm ngưng, hoặc không làm gì cả
Kết quả là bạn phải viết nhiều nhánh code riêng cho từng nhà cung cấp thay vì một triển khai nhất quán. Đi theo SCIM có thể phức tạp và dễ vỡ hơn so với tự xây hệ thống kéo (pull-based) gọi API riêng của từng nhà cung cấp.
Cách tiếp cận kéo (Pull-based sync)
Với đồng bộ kiểu kéo, máy chủ của bạn gọi API nhà cung cấp theo lịch, duyệt qua thư mục và đối chiếu với cơ sở dữ liệu. Thuật toán đơn giản:
- Lấy tất cả nhóm
- Lấy tất cả thành viên của các nhóm
- Với thành viên là người dùng, lấy bản ghi người dùng tương ứng
- Với thành viên là nhóm, quay lại bước 2
- Lặp lại mỗi N phút
Một số chi tiết cần lưu ý:
- Phân trang (Pagination): mỗi lần gọi trả về một trang kết quả, cần theo liên kết trang tiếp theo cho đến hết
- ID ổn định: mọi người dùng và nhóm có ID không bao giờ đổi. Khớp bản ghi theo ID thay vì email hay tên, để việc đổi tên cập nhật thay vì tạo bản ghi trùng
- Chu trình (Cycles): một nhóm có thể nằm trong chính nó (A trong B trong A). Cần ghi nhớ nhóm đã thăm để không lặp vô hạn
- Làm phẳng: khi tìm thấy Engineering trong Product, mọi thành viên của Engineering cũng có dòng cho Product. Việc xóa cần tính toán lại thay vì xóa đơn thuần
Đồng bộ thư mục lớn hơn
Thư mục lớn mất nhiều thời gian duyệt hơn, làm tăng nguy cơ mất trạng thái tích lũy trước khi ghi được vào cơ sở dữ liệu. Giải pháp là dùng epoch và checkpoint:
- Khởi tạo timestamp bắt đầu
- Lấy một trang và ghi vào cơ sở dữ liệu kèm timestamp
- Lặp lại cho các trang còn lại
- Cuối cùng, xóa mọi bản ghi cũ hơn timestamp bắt đầu
Các vấn đề khác khi thư mục lớn dần:
- Giới hạn tốc độ (Rate limits): API nhà cung cấp có thể chia sẻ với mọi ứng dụng khác của khách hàng. Gọi quá mạnh có thể làm chậm các công cụ khác của họ
- Phản hồi lỗi: đôi khi API trả về 200 trông bình thường nhưng thiếu một phần thư mục. Nếu coi "thiếu" là "đã xóa", bạn có thể xóa nhầm nhiều người dùng thật. Cần đặt giới hạn hoặc cầu dao (circuit breaker) cho số lượng bản ghi một lần đồng bộ được phép xóa
- Nhóm lồng nhau: các nhà cung cấp không thống nhất về việc ai làm phẳng, và một số trả về kết quả cũ
- Tác vụ chồng lấn: nếu hai lần đồng bộ cùng thư mục chạy đồng thời, chúng có thể ghi đè lẫn nhau. Cần đảm bảo chỉ một tác vụ chạy tại một thời điểm
- Lỗi tạm thời và vĩnh viễn: timeout hay 503 nên thử lại, còn thông tin xác thực bị thu hồi thì không. Hệ thống đồng bộ phải phân biệt được hai loại này
Yêu cầu ít dữ liệu hơn
Càng lấy ít dữ liệu, đồng bộ càng nhanh. Hầu hết thư mục chứa nhiều thứ không liên quan đến ứng dụng của bạn như nhà thầu, tài khoản dịch vụ, và hàng trăm nhóm cho danh sách email hay địa điểm văn phòng.
Ở đâu có thể, hãy để nhà cung cấp lọc giúp. Chỉ yêu cầu người dùng và nhóm được gán cho ứng dụng của bạn thay vì tất cả mọi thứ.
Vấn đề về tính nhất quán
Quá trình này mang tính nhất quán cuối cùng (eventually consistent): cuối cùng cơ sở dữ liệu sẽ phản ánh thư mục của tổ chức. Tùy vào giới hạn tốc độ và kích thước thư mục, một lần đồng bộ có thể mất vài giây hoặc hơn một giờ.
Trong khoảng thời gian đó, thư mục có thể đã thay đổi. Độ trễ khi nhân viên mới gia nhập có thể khiến họ không đăng nhập được - tuy phiền nhưng không nguy hiểm. Nhưng độ trễ khi nhân viên nghỉ việc lại rất rủi ro: cho đến khi bạn kéo thay đổi đó về, họ vẫn còn quyền truy cập.
Delta sync - Giải pháp tưởng chừng hợp lý
Một cách giảm độ trễ là ngừng duyệt toàn bộ thư mục. Ghi nhớ điểm dừng và lần sau chỉ hỏi nhà cung cấp những gì đã thay đổi. Một số nhà cung cấp có tính năng này (Entra gọi là delta query).
Nhưng tác giả đã quyết định không dùng vì những lý do sau:
- Không hỗ trợ nhóm bắc cầu: delta chỉ cho biết thành viên trực tiếp của một nhóm thay đổi. Nó không cho biết điều đó có nghĩa gì với các nhóm cấp trên. Nếu Bob được thêm vào Engineering, bạn phải tự tính ra rằng anh ta cũng thuộc mọi nhóm chứa Engineering
- Xóa bản ghi: với cách epoch, bản ghi bị xóa tự được xử lý vì những gì không thấy lần này là đã mất. Delta feed phải báo cáo xóa rõ ràng, và các nhà cung cấp làm khác nhau
- Token hết hạn: delta chỉ hoạt động từ token đã lưu, và token có thể hết hạn (token của Entra tối đa bảy ngày). Khi đó bạn lại cần full sync, dẫn đến hai nhánh code cần bảo trì
- Sai sót tồn tại lâu: full sync tự sửa lỗi trong lần chạy tiếp theo, còn delta bị bỏ lỡ sẽ sai mãi cho đến khi có người phát hiện
Theo tác giả, delta sync giải quyết sai vấn đề. Mục tiêu không phải là tránh full sync, mà là làm cho full sync nhanh và đáng tin cậy đến mức có thể tin tưởng.
Giải pháp kết hợp (Hybrid)
Các nhà cung cấp danh tính đều có bộ API thời gian thực riêng để nhận cập nhật thư mục theo cơ chế đẩy - không cần SCIM. Những API này thường gửi cập nhật nhanh hơn cả SCIM. Ví dụ, Entra ghi nhận độ trễ lên đến 40 phút cho cập nhật SCIM của mình.
Firezone dùng cách tiếp cận kết hợp giữa các API thời gian thực này với full sync tối ưu, mang lại lợi ích của cả hai:
- Đối chiếu định kỳ toàn bộ thư mục để đảm bảo không bỏ sót cập nhật
- Kích hoạt thời gian thực cung cấp cập nhật gần như tức thời cho các thay đổi quan trọng
Vì kích hoạt thời gian thực xử lý các thay đổi khẩn cấp, full sync có thể chạy ít thường xuyên hơn như một lưới an toàn, tiết kiệm hạn mức API. Một hàng đợi tuần tự đơn giản đảm bảo hai nguồn không ghi đè lẫn nhau.
Kết luận
Đồng bộ thư mục có rất nhiều chi tiết khi áp dụng vào một tổ chức thật: thư mục lớn, API chập chờn, các nhà cung cấp không thống nhất về ý nghĩa của "đã xóa", và các tác vụ giẫm lên nhau.
SCIM để lại phần lớn những vấn đề này cho ứng dụng của bạn xử lý, lặp lại cho từng nhà cung cấp, và lấy đi khả năng hỏi nhà cung cấp điều gì là đúng khi có gì đó sai.
Kéo dữ liệu từ API của nhà cung cấp giúp bạn kiểm soát khi nào nên xem, tin điều gì, và làm gì khi có gì đó bất thường. Thêm các hook thời gian thực của nhà cung cấp lên trên, bạn sẽ có phần lớn những gì SCIM hứa hẹn, với ít gánh nặng hơn nhiều.
Đối với các đội phát triển phần mềm tại Việt Nam đang xây dựng sản phẩm B2B hướng tới khách hàng doanh nghiệp, đây là bài học thực tế đáng tham khảo: đừng ngần ngại rời bỏ một chuẩn "có vẻ đúng" nếu nó khiến hệ thống của bạn phức tạp và khó kiểm soát hơn.
Bài viết liên quan

Công nghệ
Kỹ sư công nghệ bị ong đốt, bị cảnh sát Trung Quốc giữ qua đêm và mắc kẹt trong cuộc họp 13 tiếng
02 tháng 10, 2026

Công nghệ
Pi Coding Agent đảo ngược quyết định, bổ sung hỗ trợ MCP
02 tháng 10, 2026

Công nghệ
DeepSeek Harness Desktop: Biến AI thành nền tảng mở rộng được bằng plugin trên macOS và Windows
02 tháng 10, 2026