1 điểm bởi GN⁺ 3 giờ trước | Chưa có bình luận nào. | Chia sẻ qua WhatsApp
  • Bài viết trình bày cách khắc phục điểm yếu mang tính cấu trúc trong các cụm PostgreSQL trên nền k8s, nơi độ trễ sao chép (replication lag) tích lũy khi xảy ra sự cố mạng khiến failover an toàn trở nên bất khả thi
  • Kiến trúc cũ ưu tiên tính sẵn sàng (availability) hơn độ bền dữ liệu (durability), nên trong khi primary vẫn tiếp tục nhận ghi, bản sao ngày càng tụt lại phía sau và không còn ứng viên nào có thể được promote mà không gây mất dữ liệu
  • Giải pháp là áp dụng sao chép đồng bộ (synchronous replication) cho các ứng viên failover và điều phối bằng trình quản lý HA mã nguồn mở Patroni
  • Với mô hình sao chép lai, chỉ các standby trong leader pool tham gia sao chép đồng bộ còn các read replica vẫn bất đồng bộ, từ đó cân bằng giữa độ bền và độ trễ
  • Dù có chi phí hiệu năng như độ trễ ghi tăng 53% khi dùng chế độ remote_apply, việc kiểm chứng qua 5 kịch bản sự cố đã giúp đạt được failover tự động an toàn

Vấn đề lộ ra trong game day

  • Datadog thường xuyên tổ chức game day để chủ động tìm ra lỗ hổng của hệ thống và quy trình, cố ý tạo tải lên nền tảng để quan sát phản ứng trong điều kiện thực tế
  • Trong một game day, họ mô phỏng sự cố vùng khả dụng (AZ) ở môi trường staging và gây ra độ trễ mạng, từ đó làm lộ điểm yếu trong kiến trúc PostgreSQL
    • Primary/writer node của nhiều cụm PostgreSQL chạy trên Kubernetes đang hoạt động trong AZ bị ảnh hưởng
    • Độ trễ mạng tăng vọt khiến primary không thể giao tiếp ổn định với replica, độ trễ sao chép tăng lên → ghi bị tắc nghẽn → ứng dụng trả về dữ liệu cũ
  • Không có replica nào đủ mới để failover an toàn, khiến cụm gần như bị đóng băng
  • Kiến trúc này hoạt động tốt trong điều kiện bình thường, nhưng trong một số tình huống sự cố mạng cụ thể lại ưu tiên availability hơn durability, nên không có đường khôi phục an toàn
    • Primary vẫn tiếp tục nhận ghi trong khi sao chép bị chậm, làm replication lag tăng và replica ngày càng tụt xa hơn
    • Kết quả là không thể promote một ứng viên failover mà không có nguy cơ mất dữ liệu, và lựa chọn duy nhất là chờ độ trễ giảm xuống để replica bắt kịp
  • Mục tiêu là biến failover thành tự động nhưng vẫn an toàn mà không làm tổn hại quá mức đến đặc tính hiệu năng của PostgreSQL

Kiến trúc cơ sở: PostgreSQL trên Kubernetes

  • Cụm PostgreSQL trên Kubernetes gồm hai pool là leader poolread replica pool, trong đó PostgreSQL là hệ thống single-writer
    • Việc tách đọc/ghi cho phép mở rộng đọc mà không tạo thêm gánh nặng cho leader, đồng thời giữ độ trễ ghi ổn định và dễ dự đoán
  • Leader pool gồm 1 writer node đang hoạt động duy nhất xử lý toàn bộ ghi và 2 standby node
    • Các standby không phục vụ traffic ứng dụng nhưng có thể được promote khi leader gặp sự cố
  • Read replica pool gồm nhiều node phục vụ traffic chỉ đọc, được tối ưu cho mở rộng đọc và cách ly truy vấn, đồng thời chủ động bị loại khỏi đối tượng failover

Vai trò của Patroni và ZooKeeper

  • Patroni quản lý sao chép, failover và bầu leader, còn ZooKeeper được dùng làm DCS
    • ZooKeeper lưu metadata như khóa/lock leader hiện tại, cấu hình cụm và trạng thái sao chép của từng thành viên (ví dụ LSN mới nhất)
    • Patroni dùng thông tin này để quyết định promote hoặc demote một cách thận trọng, ưu tiên tính nhất quán dữ liệu hơn failover quá quyết liệt
  • Khi một node mới tham gia cụm, trước tiên nó kiểm tra trên ZooKeeper xem leader đã tồn tại hay chưa
    • Nếu chưa có leader, nó sẽ thử tạo znode tạm thời để lấy leader key; ZooKeeper đảm bảo chỉ một node có thể lấy key nên ngăn việc hình thành nhiều primary
    • Nếu leader đã tồn tại, node đó sẽ tự cấu hình thành replica và bắt đầu streaming replication
  • Khi xảy ra network partition, replica mất kết nối tới leader hoặc ZooKeeper sẽ không thể xác nhận trạng thái cụm, nên Patroni sẽ tạm dừng hoặc demote node đó
  • Nếu leader mất kết nối, Patroni phối hợp với ZooKeeper để chỉ cho phép một standby đủ điều kiện lấy leader lock, đảm bảo failover có kiểm soát ngay cả khi lỗi mạng chỉ xảy ra một phần
    • Sau khi kết nối được khôi phục, leader cũ nếu không lấy lại được leader lock sẽ tự demote thành standby, ngăn split brain

Vì sao failover an toàn là điều bất khả thi

  • Trong mô hình single-writer, khi có sự cố Patroni sẽ chọn một standby khỏe mạnh làm leader mới
  • Để ngăn mất dữ liệu, Patroni thực hiện kiểm tra an toàn trước khi promote; trọng tâm là xác nhận replication lag có nằm trong ngưỡng maximum_lag_on_failover hay không
    • Nếu standby bị tụt lại phía sau leader, việc promote có thể gây thiếu dữ liệu hoặc sai lệch
  • Trong game day, khi primary mất kết nối, replication lag của mọi standby đều vượt ngưỡng nên Patroni đã từ chối failover một cách chính xác
    • Cụm bị bỏ lại mà không có primary an toàn để ghi không phải do Patroni, mà vì không có ứng viên nào đủ an toàn để promote

Hai chế độ của streaming replication

  • Trong streaming replication, leader liên tục gửi write-ahead log (WAL) chứa toàn bộ thay đổi tới replica, và replica áp dụng WAL cục bộ để giữ đồng bộ
  • Sao chép bất đồng bộ (mặc định)

    • Leader không chờ replica xác nhận trước khi commit transaction
    • Độ trễ ghi thấp nhất, hỗ trợ thông lượng cao
    • Tuy nhiên nếu leader gặp sự cố, các transaction đã commit trên primary nhưng chưa được sao chép có thể bị mất trong quá trình promote
  • Sao chép đồng bộ

    • Leader chờ ít nhất 1 replica xác nhận trước khi trả phản hồi cho client
    • Điều này giúp giảm đáng kể nguy cơ không có replica nào đủ mới, và chỉ trả phản hồi sau khi xác nhận transaction đã tồn tại trên node khác nên đảm bảo durability mạnh hơn
    • Ứng viên failover nhiều khả năng luôn ở trạng thái mới nhất, có thể được promote mà không tạo ra rủi ro phân nhánh dữ liệu

Cấu hình sao chép lai

  • Để cân bằng durability, độ trễ và thông lượng, Datadog áp dụng mô hình sao chép lai
    • Các standby node trong leader pool tham gia sao chép đồng bộ, nên leader chỉ commit ghi sau khi nhận xác nhận từ standby đồng bộ được chỉ định
    • Các read replica vẫn giữ sao chép bất đồng bộ, vì chúng chỉ phục vụ traffic đọc và không phải đối tượng failover, nhờ đó giới hạn gánh nặng sao chép trong leader pool
  • Cách này áp dụng đảm bảo độ bền nghiêm ngặt chỉ cho các ứng viên failover, mà không ép toàn bộ read replica phải chịu cùng mức chi phí độ trễ

Tinh chỉnh PostgreSQL và Patroni để failover an toàn

  • Để bật sao chép đồng bộ, cần điều chỉnh tham số ở cả PostgreSQL lẫn Patroni
  • Các tham số chính đã điều chỉnh

    • synchronous_mode: bật sao chép đồng bộ trong Patroni; khi là true, commit chỉ hoàn tất sau xác nhận từ synchronous standby theo synchronous_node_count (mặc định false → true, do Patroni quản lý, bắt buộc)
    • synchronous_node_count: số lượng synchronous standby, được dùng để tạo danh sách synchronous_standby_names (mặc định 1 → 1, do Patroni quản lý, tùy chọn)
    • synchronous_mode_strict: ép chế độ đồng bộ nghiêm ngặt; nếu là true và không có replica khả dụng thì chặn ghi thay vì chuyển sang bất đồng bộ (mặc định false → true, do Patroni quản lý, tùy chọn)
    • synchronous_commit: cấu hình độ bền commit của PostgreSQL (mặc định on → remote_apply, do PostgreSQL quản lý, tùy chọn)
  • Sau khi áp dụng, leader chỉ gửi phản hồi transaction cho client sau khi synchronous standby xác nhận đã nhận và áp dụng dữ liệu

Cân bằng giữa độ bền và độ trễ

  • Sao chép đồng bộ cải thiện durability nhưng làm tăng độ trễ ghi vì leader phải chờ synchronous standby xác nhận, và dưới tải liên tục có thể ảnh hưởng đến thông lượng
  • Mức tác động tới hiệu năng phụ thuộc vào số synchronous standby (synchronous_node_count) và mức durability được chọn qua synchronous_commit
  • Trade-off theo từng mức durability của synchronous_commit

    • remote_apply: chờ replica ghi WAL, flush WAL và replay WAL xong; nhất quán mạnh nhất nhưng độ trễ cao nhất
    • on (nội bộ là remote_flush): chờ replica flush WAL xuống đĩa; durability mạnh nhưng dữ liệu trên standby vẫn chưa thể đọc ngay
    • remote_write: chờ WAL tới OS cache của replica (chưa xuống đĩa); độ trễ thấp hơn nhưng dễ tổn thương khi OS crash
    • local: commit sau khi flush đĩa cục bộ, không cần chờ standby; không đảm bảo durability giữa các node
    • off: commit trước cả khi WAL cục bộ được flush; độ trễ thấp nhất nhưng rủi ro mất dữ liệu cao nhất

Benchmark hiệu năng của sao chép đồng bộ

  • Vì mỗi lần commit phải chờ xác nhận từ ít nhất một standby, sao chép đồng bộ chắc chắn làm tăng độ trễ; để định lượng tác động này, Datadog benchmark bằng pgbench, công cụ kiểm thử tải chuẩn của PostgreSQL (Patroni phiên bản 3.2.1)
  • Họ dùng bộ transaction TPC-B để mô phỏng hỗn hợp đọc/ghi đơn giản và đo hai chỉ số
    • Độ trễ trung bình: thời gian xử lý trung bình cho mỗi transaction (ms)
    • Số transaction mỗi giây (tps): số transaction hoàn tất mỗi giây, không tính thời gian thiết lập kết nối
  • Tham số thử nghiệm

    • Thay đổi scale factor (kích thước cơ sở dữ liệu), số client (traffic người dùng đồng thời), số thread (CPU và mức song song), và số transaction (cường độ workload) để mô phỏng điều kiện gần với vận hành thực tế
    • Chế độ synchronous replication với quorum commit không được thử trong benchmark này
  • Kết quả benchmark

    • Mức tăng độ trễ ghi: remote_apply 53%, on 46%, remote_write 38%, local 32%
    • Mức giảm thông lượng (tps): remote_apply 34%, on 31%, remote_write 27%, local 23%
    • remote_apply luôn cho độ trễ cao nhất và thông lượng thấp nhất vì phải chờ replay/applied WAL trên replica, nhưng đổi lại cho mức nhất quán mạnh nhất nên phù hợp với failover an toàn
  • Triển khai vào production

    • Sau benchmark, Datadog triển khai remote_apply lên nhiều cụm có cường độ ghi cao, và dưới tải production liên tục vẫn không thấy ảnh hưởng đáng kể ở mức độ trễ ghi hay thông lượng tại tầng ứng dụng
    • Để giảm rủi ro hiệu năng, họ rollout dần theo từng data center và từng lớp workload, với giai đoạn bake-in và giám sát liên tục giữa các đợt
    • Ví dụ, workload xử lý tài nguyên thông lượng cao vẫn tiếp tục chạy mà không phát sinh độ trễ xử lý hay backlog hạ nguồn, dù độ trễ ghi DB có tăng sau khi bật sao chép đồng bộ
    • synchronous_commit có thể được điều chỉnh ngay lập tức bằng patronictl edit-config mà không cần downtime, giúp linh hoạt hạ mức durability commit cho workload siêu thông lượng cao khi cần

Kiểm chứng failover qua các kịch bản sự cố

  • Datadog xác minh rằng sao chép đồng bộ và cơ chế kiểm soát failover chặt chẽ có thực sự bảo vệ tính toàn vẹn dữ liệu, ngăn split-brain và đảm bảo tự phục hồi hay không
  • Kịch bản 1: mất 1 synchronous standby

    • Khi mất một synchronous standby, Patroni sẽ cố gắng gán một standby đủ điều kiện khác để duy trì sao chép đồng bộ
    • Patroni trên leader node dùng pg_stat_replication để phát hiện kết nối streaming bị ngắt, đình trệ hoặc chậm, đồng thời theo dõi membership replica qua ZooKeeper
    • Nó tính lại danh sách streaming replica đang khỏe và đủ điều kiện, rồi cập nhật synchronous_standby_names theo synchronous_node_count để tiếp tục vận hành trong trạng thái sao chép đồng bộ
  • Kịch bản 2: mất tất cả synchronous standby

    • Hành vi sẽ phụ thuộc vào giá trị synchronous_mode_strict
    • Chế độ không nghiêm ngặt: ưu tiên khả năng ghi

      • Patroni xóa synchronous_standby_names để tạm thời vô hiệu hóa sao chép đồng bộ; cho đến khi replica khỏe mạnh quay lại, leader sẽ chuyển sang bất đồng bộ và tiếp tục cho phép ghi
    • Chế độ nghiêm ngặt: chặn ghi để đảm bảo an toàn

      • Patroni đặt synchronous_standby_names thành *; ngay cả khi không có synchronous standby cụ thể, PostgreSQL vẫn nhận transaction ghi và commit cục bộ nhưng sẽ giữ phản hồi client lại cho đến khi một replica xác nhận WAL
      • Khi một replica phù hợp có thể tham gia lại vào sao chép đồng bộ, Patroni sẽ gán vai trò synchronous standby cho nó
  • Kịch bản 3: mọi standby và replica đều không khả dụng

    • Nếu toàn bộ replica không khả dụng và synchronous_mode_strict = true, PostgreSQL sẽ giữ lại việc xác nhận transaction cho tới khi ít nhất một replica đủ điều kiện quay lại
    • Tính nhất quán dữ liệu vẫn được giữ, nhưng ở tầng ứng dụng sẽ xuất hiện trạng thái tạm thời không thể ghi
  • Kịch bản 4: leader lỗi giữa lúc synchronous commit

    • Đây là edge case khi leader đang chờ xác nhận từ synchronous standby nhưng bị dừng trước khi nhận được xác nhận
    • Nguyên nhân phổ biến: client hủy transaction trong lúc commit, tiến trình PostgreSQL trên leader bị crash hoặc kết thúc, hoặc network partition xuất hiện trong pha commit
    • Nếu PostgreSQL đã flush WAL cục bộ nhưng chưa sao chép thành công sang standby, transaction sẽ không xuất hiện trên replica vì chưa có xác nhận
    • Nếu leader crash trước khi WAL được sao chép sang synchronous standby và standby đó lại được promote, transaction có thể bị mất và lịch sử giữa leader cũ với primary mới sẽ bị phân nhánh
    • Leader cũ dùng pg_rewind để xác định điểm phân nhánh timeline và tua ngược data directory theo timeline của primary mới, loại bỏ các thay đổi cục bộ chưa được sao chép rồi gia nhập lại với vai trò standby
    • Hành vi này là kết quả từ xử lý nội bộ của synchronous commit trong PostgreSQL chứ không phải từ Patroni, nhấn mạnh nhu cầu phải tinh chỉnh và giám sát cẩn thận các thiết lập quorum và cơ chế xác nhận
  • Kịch bản 5: ZooKeeper không khả dụng

    • Khi ZooKeeper không khả dụng, Patroni không thể xác minh leadership hoặc bầu mới, nên sẽ chuyển sang hành vi bảo thủ để tránh dữ liệu không nhất quán
    • Khi failsafe mode tắt

      • Dù không truy cập được ZooKeeper, leader vẫn có thể tiếp tục ghi nếu leader còn truy cập được và mọi node đều khỏe, nhưng chỉ cho tới khi TTL của leader lock hết hạn
      • Khi qua thời gian vòng lặp gia hạn leader key mà vẫn không thể gia hạn lock, Patroni sẽ demote leader và chuyển cụm sang chế độ chỉ đọc
    • Khi failsafe mode bật

      • Nếu leader mất kết nối tới ZooKeeper, Patroni sẽ liên tục dùng REST API để kiểm tra xem có truy cập được mọi thành viên trong cụm hay không
      • Chỉ khi toàn bộ thành viên đều truy cập được thì cụm mới tiếp tục cho phép ghi; nếu không leader sẽ bị demote sang chỉ đọc

Failover và switchover thủ công dưới sao chép đồng bộ

  • Ngoài failover và switchover tự động dựa trên health check và điều phối qua ZooKeeper, Patroni còn hỗ trợ thao tác thủ công bằng lệnh patronictl; khi bật sao chép đồng bộ, không phải mọi standby đều là ứng viên hợp lệ nên có thêm các guardrail để bảo vệ tính toàn vẹn dữ liệu
  • Failover sang asynchronous standby

    • patronictl failover: sẽ thất bại nếu node được chọn là asynchronous
    • patronictl switchover: sẽ thất bại nếu node được chọn là asynchronous
    • Việc ép failover sang node bất đồng bộ trong lúc sao chép đồng bộ đang bật sẽ lách qua đảm bảo durability và có thể gây mất dữ liệu
  • Khi nhắm tới synchronous standby

    • patronictl failover: thành công, leader chuyển sang synchronous standby
    • patronictl switchover: thành công, thực hiện handoff mềm giữa leader và synchronous standby
  • Sau khi kiểm chứng hành vi của nhiều chế độ synchronous_commit và các guardrail của Patroni, Datadog đã bật sao chép đồng bộ trên các cụm production có workload ghi cao, đọc cao và hỗn hợp, mà không ghi nhận tác động đo được lên độ trễ hay thông lượng
  • Nếu phát sinh vấn đề, có thể quay lại sao chép bất đồng bộ an toàn mà không cần downtime bằng cách đặt synchronous_mode: false

Vì sao không chọn DRBD

  • Trong quá trình đánh giá HA, Datadog cũng cân nhắc DRBD (Distributed Replicated Block Device), một hệ thống sao chép ở cấp block có thể mirror toàn bộ volume giữa các server, bao gồm PostgreSQL data directory và các file WAL, để tạo standby gần như theo thời gian thực
  • DRBD có thể cho độ trễ thấp hơn streaming replication tích hợp sẵn của PostgreSQL, nhưng đòi hỏi thay đổi kiến trúc đáng kể, bao gồm hạ tầng mới, giám sát mới và playbook vận hành mới
  • Xét tới thiết lập Kubernetes đã trưởng thành và khả năng kiểm soát chi tiết của sao chép đồng bộ trong PostgreSQL, Datadog chọn sao chép ở tầng cơ sở dữ liệu vì cho khả năng quan sát, độ linh hoạt và độ tin cậy vận hành tốt hơn

Giám sát sao chép đồng bộ

  • Sau khi bật sao chép đồng bộ, Datadog theo dõi sát trạng thái sao chép và mức sẵn sàng failover; đặc biệt hai tín hiệu sau giúp duy trì ổn định ở quy mô lớn
  • Sự kiện chờ SyncRep

    • Xảy ra khi primary đang chờ synchronous standby xác nhận trước khi hoàn tất commit và trả trạng thái; một phần là bình thường, nhưng nếu kéo dài hoặc quá thường xuyên thì cho thấy vấn đề hiệu năng ở replica hoặc độ trễ mạng giữa các node
    • Lý do quan trọng: thời gian chờ dài làm tăng độ trễ ghi và giảm thông lượng
    • Cần theo dõi: thời lượng và tần suất của các wait event SyncRepWalSenderWaitForReply, được thu thập từ metric Datadog postgresql.activity.waits lọc theo tag wait_event:SyncRep (nội bộ truy vấn bảng pg_stat_activity)
  • Không phát hiện synchronous standby

    • Nếu Patroni không thể phát hiện một synchronous standby khỏe mạnh trong thời gian dài, cụm sẽ mất khả năng failover an toàn
    • Lý do quan trọng: không có synchronous standby nghĩa là failover dễ gây mất dữ liệu
    • Ngưỡng cảnh báo: nếu patroni_sync_standby duy trì ở trạng thái rỗng thì sẽ phát cảnh báo sức khỏe HA; dữ liệu được lấy từ OpenMetrics và chưa có tích hợp Datadog gốc
  • Sao chép đồng bộ cải thiện durability nhưng làm giảm availability và hiệu năng khi replica bị lỗi hoặc không thể truy cập, nên giám sát thời gian chờ và mức sẵn sàng của standby là điều thiết yếu để giữ availability và hiệu năng dưới tải

Failover an toàn ngay từ giai đoạn thiết kế

  • Sự cố AZ được mô phỏng đã phơi bày điểm yếu nghiêm trọng trong kiến trúc PostgreSQL: replica tụt lại sau leader, buộc hệ thống phải chọn giữa chờ lỗi mạng qua đi hoặc chấp nhận phân nhánh dữ liệu, một trade-off không thể chấp nhận trong production
  • Bằng cách áp dụng sao chép đồng bộ dựa trên Patroni và tinh chỉnh giữa durability với độ trễ, Datadog đã biến failover thành thứ vừa khả thi vừa an toàn ngay cả trong điều kiện mạng suy giảm; benchmark và mô phỏng lỗi lặp lại xác nhận khả năng khôi phục có thể dự đoán được mà không làm tổn hại hiệu năng ở quy mô lớn
  • Việc chặn ghi trong lúc sao chép đồng bộ gặp sự cố giúp bộc lộ lỗi một cách rõ ràng lên các dịch vụ upstream, thay vì để ghi âm thầm bị mất như sao chép bất đồng bộ; nhờ đó hệ thống có thể phản ứng bằng retry, queueing và các biện pháp khác, khiến chế độ lỗi dễ quan sát và dễ phục hồi hơn
  • Trong tương lai, họ tiếp tục khám phá chế độ commit dựa trên quorum và khả năng quan sát sâu hơn đối với trạng thái sao chép

Chưa có bình luận nào.

Chưa có bình luận nào.