2 điểm bởi GN⁺ 2023-12-14 | 1 bình luận | Chia sẻ qua WhatsApp
  • Knock đã xây dựng quy trình chuyển đổi kho lưu trữ cốt lõi của engine workflow thông báo là Postgres, từ AWS RDS Aurora 11.9 lên 15.3, mà không ảnh hưởng đến khách hàng
  • Nếu không hành động trước ngày Amazon RDS cho Postgres 11.9 ngừng hỗ trợ, 29/02/2024, họ sẽ phải chấp nhận nâng cấp bắt buộc và downtime
  • Nâng cấp tại chỗ và pg_dump/pg_restore bị loại vì cần thời gian gián đoạn dài; thay vào đó, họ chọn cách thiết lập logical replication dựa trên PUBLICATION/SUBSCRIPTION sang DB mới
  • Chiến lược replication được chia theo kích thước bảng và pattern ghi; bảng nhỏ được replicate trực tiếp, còn các bảng append-only lớn kết hợp copy_data = false với backfill từ snapshot
  • Cutover cuối cùng hoàn tất trong vài giây bằng cách giữ kết nối tới cả hai DB rồi đổi flag, cho các query đang chạy 500ms, sau đó dừng request tới DB mới trong 1 giây để giảm rủi ro stale read

Mục tiêu và ràng buộc của việc nâng cấp

  • Knock phụ thuộc vào Postgres cho engine workflow thông báo, dùng Postgres cho cấu hình workflow, template tin nhắn, thu thập hàng triệu log và queue job nền
  • Do đặc tính của cơ sở dữ liệu quan hệ, khi nâng cấp Postgres cần ít nhất một lần reboot; nâng cấp major version có thể cần shutdown hoàn toàn từ vài phút trở lên vì thay đổi cách lưu dữ liệu và index trên đĩa
  • Postgres 11.9 mà công ty dùng từ khi bắt đầu đã được lên lịch ngừng hỗ trợ trên Amazon RDS; nếu không có biện pháp riêng, có khả năng bị nâng cấp bắt buộc và downtime bắt buộc
  • Điều kiện nâng cấp được đặt theo hướng giảm rủi ro vận hành
    • Nhảy thẳng lên phiên bản mới nhất có thể, Postgres 15.3 cho Aurora
    • Không chấp nhận downtime quá 60 giây, lý tưởng là 0 downtime hệ thống
    • Hoàn tất trước hạn chót tháng 2/2024 của Amazon
    • Giảm thiểu ảnh hưởng tới khách hàng, ví dụ 0 phản hồi lỗi API
    • Runbook hóa quy trình để có thể tái sử dụng cho lần nâng cấp tiếp theo
  • Từ 11.9 lên 15.3 tương đương nâng cấp qua 4 major version, nên cách lặp lại nâng cấp tại chỗ 4 lần bị loại khỏi các lựa chọn

Chuẩn bị trước: giảm rủi ro và khả năng quan sát

  • Việc nâng cấp Postgres được tiếp cận bằng cách trước tiên lập danh sách rủi ro, rồi giảm các rủi ro có tác động lớn nhưng dễ loại bỏ trước
    • Downtime dài
    • Mất dữ liệu
    • Thay đổi hiệu năng DB đối với workload của ứng dụng
    • Thay đổi tần suất hoặc hành vi của VACUUM
    • Có cần di chuyển replication slot hay không
  • Họ kiểm tra các thay đổi giữa phiên bản qua Postgres release notes, xác định các rủi ro như thay đổi hành vi VACUUM hoặc nhu cầu reindex trong một số lần nâng cấp cụ thể
  • Trong quá trình nâng cấp, cần liên tục theo dõi chỉ số hệ thống và cơ sở dữ liệu
    • Max TXN ID để tránh transaction wraparound
    • Mức sử dụng CPU của DB
    • Session chờ trên writer instance
    • Độ trễ query
    • Độ trễ phản hồi API của ứng dụng
  • Knock cũng giám sát các chỉ số đặc thù của ứng dụng, như thời gian từ khi request API được chuyển thành thông báo
  • Nếu không có các chỉ số có thể kiểm tra kịp thời, trong quá trình nâng cấp sẽ giống như ở trong tình trạng bị bịt mắt

Các cách bị loại: nâng cấp tại chỗ và dump/restore

  • Nâng cấp tại chỗ của AWS RDS được chạy từ AWS console; AWS sẽ dừng DB, chạy script nâng cấp rồi đưa DB online trở lại
  • Quá trình này có thể mất từ vài phút đến vài giờ hoặc hơn, tùy lượng dữ liệu và mức độ thay đổi giữa các phiên bản
  • Ngay cả sau khi DB online trở lại, có thể vẫn cần các tác vụ bảo trì như VACUUM hoặc REINDEX, nên chưa chắc có thể sử dụng hoàn toàn ngay lập tức
  • Cách dùng pg_dumppg_restore đòi hỏi phải tách toàn bộ ứng dụng khỏi DB cũ để có được bản backup đáng tin cậy; với DB lớn, bản thân việc dump và restore cũng mất nhiều thời gian
  • Cả hai cách đều bị loại vì có khả năng vượt xa giới hạn downtime của Knock

Cách được chọn: nâng cấp dựa trên logical replication

  • Lựa chọn cuối cùng là logical replication của Postgres dùng PUBLICATIONSUBSCRIPTION
  • Luồng cơ bản như sau
    • Khởi tạo DB mới với phiên bản Postgres mục tiêu
    • Chuyển cấu hình, extension, cấu trúc bảng, user, v.v.
    • Tạo publication trên DB cũ và cấu hình subscription trên DB mới
    • Thêm bảng vào publication
    • Khi replication hoàn tất, thực hiện các bài test để kiểm tra rủi ro còn lại
    • Khi cấu hình DB mới đã được xác nhận đủ, chuyển ứng dụng sang DB mới
    • Xóa DB cũ
  • Họ có thể tiến hành theo các bước tăng dần thay vì chạy một lần nâng cấp lớn, đồng thời test DB mới bằng dữ liệu thật và workload thật
  • Sau khi DB mới sẵn sàng, bản thân cutover chỉ mất vài giây, giúp kiểm soát tốt hơn thời điểm và cách chuyển đổi

Điểm cốt lõi trong cấu hình replication

  • Logical replication của Postgres dùng các tham số cần thiết để thiết lập replication slot; với ứng dụng đơn giản, thay đổi chính có thể là đặt wal_level thành logical
  • Nếu đã dùng replication slot cho read replica, DB failover, đồng bộ data warehouse, v.v., cần điều chỉnh các tham số liên quan như max_replication_slots theo tài liệu
  • Cấu trúc bảng của DB mới phải giống hệt nhưng phải trống so với DB cũ
  • Snapshot schema có thể được tạo bằng pg_dumpall với các tùy chọn --schema-only, --no-role-passwords, rồi so sánh với SQL cho DB mới để sửa các khác biệt
  • Khi tạo publication trên DB cũ và subscription trên DB mới, cần đặt các tùy chọn chính
    • enabled = false: không bắt đầu đồng bộ ngay từ đầu
    • create_slot = true: để Postgres quản lý replication slot
    • copy_data = true: mặc định sao chép nội dung bảng
    • disable_on_error = true: dừng subscription khi có lỗi bất ngờ để có thể sửa vấn đề rồi tiếp tục
  • Nếu đưa tất cả bảng vào publication cùng lúc bằng FOR ALL TABLES, DB lớn có thể gặp vấn đề hiệu năng, nên Knock dùng ALTER PUBLICATION ... ADD TABLE để thêm từng bảng một

Phân loại bảng và chiến lược replication

  • Knock chia bảng theo kích thước trên đĩa và số tuple
    • Bảng nhỏ có thể đồng bộ trong vài phút
    • Bảng lớn nhưng gần như append-only
    • Bảng lớn và phần lớn row được update thường xuyên
  • Theo tiêu chí của Knock, bảng “nhỏ” là bảng dưới 50GBdưới 10 triệu tuple
  • Trong Postgres, tuple là đơn vị lưu trữ của insert hoặc update; dù số row ít, nếu có nhiều tuple chưa được dọn dẹp, thời gian replication có thể kéo dài
  • Chạy VACUUM trước khi replication có thể giúp giảm số tuple mà source DB phải sao chép sang target DB
  • Thời gian đồng bộ bảng liên quan trực tiếp đến kích thước trên đĩa và số tuple; đồng bộ kéo dài có thể cản trở VACUUM của primary DB, dẫn tới suy giảm hiệu năng và rủi ro transaction wraparound

Replicate bảng nhỏ

  • Bảng nhỏ được xử lý bằng cách thêm bảng vào publication trên DB cũ và refresh subscription trên DB mới
  • Postgres phụ trách việc sao chép bảng, đồng bộ và áp dụng các thay đổi sau đó
  • Bảng rất nhỏ có thể đồng bộ trong dưới 1 giây

Replicate bảng append-only lớn

  • Với các bảng lớn không có update hoặc chỉ update trên các row gần đây, có thể tạo publication/subscription riêng với copy_data = false
  • Knock thêm hậu tố _nocopy vào tên để phân biệt với replication thông thường
  • Trước tiên chỉ replicate các thay đổi mới, còn dữ liệu cũ được backfill riêng từ backup hoặc snapshot
  • Quy trình dùng trên AWS RDS Aurora như sau
    • Tạo snapshot DB production
    • Restore snapshot thành DB instance mới
    • Thêm hậu tố như _snapshot vào tên bảng trong DB snapshot cần replicate
    • Tạo bảng dùng cho snapshot có cùng schema trên target DB
    • Cấu hình publication/subscription từ DB snapshot sang target DB
    • Giám sát tiến trình replication
    • Khi replication bắt kịp, merge vào bảng đích thực bằng INSERT ... ON CONFLICT DO NOTHING
  • Với bảng rất lớn, quá trình này có thể mất vài ngày, nhưng vì diễn ra trong background nên không nên ảnh hưởng đến môi trường production
  • Sau khi merge, so sánh số row để xác nhận tính nhất quán, rồi xóa bảng snapshot trên target DB, snapshot subscription và DB instance snapshot

Bảng lớn và được update thường xuyên

  • Bảng lớn mà phần lớn row được update thường xuyên là khó nhất; replication kéo dài có thể cản trở AUTOVACUUM chạy
  • Các biện pháp có thể cân nhắc gồm
    • Kiểm tra liệu có thể giảm kích thước bảng bằng housekeeping hay không
    • Kiểm tra VACUUM gần đây đã được chạy chưa
    • Xem xét có thể partition bảng thành các phần nhỏ hơn hay không
    • Kiểm tra sau một khoảng thời gian row có ngừng được update không, để quyết định có thể xử lý như append-only hay không
  • Nếu source DB dưới PG 15, lựa chọn bị hạn chế; cần replicate theo cách của bảng nhỏ và dùng monitoring để kiểm tra liệu dịch vụ có suy giảm không
  • Nếu cần, có thể rollback bằng cách xóa bảng khỏi publication và refresh subscription
  • Với bảng quá lớn, có thể bắt đầu replication vào thời điểm traffic thấp để giảm tác động của tải và hoạt động ghi

Replicate bảng lớn theo phần có thể thực hiện từ PG 15 trở lên

  • Nếu source DB là PG 15 trở lên, có thể chia replication qua nhiều publication để chuyển bảng lớn theo các mảnh nhỏ hơn
  • Cách này hoạt động tương tự partitioning hoặc sharding, với cái giá là dùng nhiều replication slot hơn
  • Vì Knock chuyển từ 11.9 lên 15.3, họ không thể dùng cách này và cũng chưa trực tiếp test
  • Ví dụ là chia row vào nhiều publication bằng hash của primary key và mệnh đề WHERE
  • Kích thước mảnh mà Knock cho là có thể quản lý được là khoảng 100GB dữ liệu, không tính index

Kiểm tra trạng thái replication và dừng lại

  • Khi bảng được thêm vào subscription, có thể kiểm tra trạng thái trong pg_subscription_rel.srsubstate của target DB
    • i: khởi tạo
    • d: sao chép nội dung bảng
    • f: sao chép hoàn tất, chờ đồng bộ cuối
    • s: hoàn tất đồng bộ ban đầu
    • r: replication bình thường đang chạy
  • Giai đoạn d cần giữ transaction ID Postgres cũ, nên có thể chặn VACUUM một cách hiệu quả và dẫn tới vấn đề hiệu năng hoặc transaction ID wraparound
  • Nếu tiến gần đến wraparound, nên dừng migration và chia thành các mảnh nhỏ hơn
  • Để dừng replication của một bảng cụ thể, xóa bảng khỏi publication trên DB cũ và refresh subscription trên DB mới
  • Chỉ disable subscription có thể không giải quyết vấn đề hiệu năng, vì source DB vẫn giữ transaction ID cũ
  • Trong tình huống khẩn cấp, có thể xóa toàn bộ publication và subscription rồi bắt đầu lại từ đầu; Postgres sẽ dọn các replication slot liên quan

Ràng buộc khi di chuyển replication slot

  • Replication slot của Postgres lưu log hoạt động DB để DB hoặc ứng dụng khác có thể consume
  • Tiến trình của slot được theo dõi bằng Log Sequence Number, tức LSN; LSN là duy nhất đối với primary Postgres DB
  • Không thể sao chép nguyên trạng LSN của replication slot từ DB cũ sang DB mới
  • Các ứng dụng consume replication slot, như công cụ data warehouse, cần quyết định chiến lược di chuyển theo tài liệu của từng công cụ
  • Nếu ứng dụng tự phát triển dùng replication slot, cơ chế idempotency có thể giúp loại bỏ transaction trùng lặp giữa DB cũ và DB mới

Kiểm chứng cuối cùng

  • Sau khi thêm tất cả bảng vào publication và subscription bắt kịp, cần xác minh các bảng có khớp nhau không
  • Do độ trễ của logical replication, rất khó để DB cũ và DB mới khớp hoàn hảo tại cùng một thời điểm, nhưng có thể kiểm tra chúng đủ gần bằng cách so sánh số row
  • Knock viết script đếm số row của DB cũ và DB mới cho từng bảng
  • Với các bảng có cột inserted_at, họ chỉ so sánh các row cũ hơn 10 giây, xác minh dựa trên giả định rằng dữ liệu của 10 giây gần nhất sẽ sớm được replicate
  • Một số bảng được kiểm tra thêm bằng cách so sánh mẫu row ngẫu nhiên để xác nhận nội dung bảng khớp nhau

Cách chuyển đổi ứng dụng

  • Để cutover cuối cùng, có thể thay đổi để ứng dụng kết nối tới cả hai DB
  • Với DB có traffic thấp, việc di chuyển được thực hiện bằng cách đơn giản là đổi cấu hình sang DB mới và restart ứng dụng
  • Với ứng dụng có nhiều hoạt động đồng thời, cần tránh ghi xung đột giữa DB cũ và DB mới
  • Script cutover của Knock hoạt động theo thứ tự sau
    • Chỉ thị tất cả instance ứng dụng gửi query mới tới DB mới
    • Cho các DB query đang chạy 500ms để hoàn tất, sau đó hủy cưỡng bức
    • Trong 1 giây đầu sau khi đổi flag, tạm dừng nhân tạo các request tới DB mới để có thời gian replicate pending transaction sang DB mới
    • Sau đó đưa hoạt động DB về bình thường nhưng trỏ tới DB mới
    • Một số workload DB đặc thù được restart để ngắt rồi kết nối lại với DB mới
  • Knock xác nhận rằng 500ms dài hơn nhiều so với hầu hết DB query và không có lỗi do ngắt kết nối cưỡng bức

Xử lý sequence

  • Logical replication của Postgres không đồng bộ sequence
  • Ngay cả khi giá trị sequence được dùng trên DB cũ, giá trị sequence trên DB mới cũng không tăng
  • Knock chạy script kết nối tới cả hai DB ngay trước khi chuyển feature flag
    • Với mọi sequence trên DB cũ, lấy giá trị tiếp theo bằng SELECT nextval('sequence_name')
    • Trên DB mới, đẩy sequence lên trước bằng SELECT setval('sequence_name', value::int4 + 100000)
  • Cách này tạo gap trong sequence, nhưng sequence của Knock là bigint nên bỏ qua 100.000 giá trị gần như bằng 0% không gian sequence khả dụng
  • Cần điều chỉnh kích thước gap theo quy mô giá trị sequence sẽ dùng trong cutover thực tế

Những điều cần kiểm tra trước cutover

  • Các hạng mục cần kiểm tra trước chuyển đổi cuối cùng bao quát rộng trạng thái sẵn sàng vận hành
    • Số row của tất cả bảng có khớp như kỳ vọng không
    • Tất cả subscription có ở trạng thái enabled và đang chạy không lỗi không
    • Schema có khớp không, có thể đóng băng release migration không
    • DB mới đã được sizing phù hợp với workload chưa
    • Có cần read replica để khớp topology cluster giữa DB cũ và DB mới không
    • Đã chạy REINDEX và bảo trì VACUUM cơ bản trên DB mới chưa
    • Đã kiểm tra lại khả năng regression của ứng dụng trong Postgres release notes chưa
    • Đã thực hiện test tự động và thủ công trên staging DB phiên bản mới chưa
    • Đã load test các query nặng nhất bằng pg_bench chưa
    • Còn rủi ro nào có thể giảm thêm không
    • Đã luyện tập quy trình cutover nhiều lần trong môi trường staging hoặc test chưa
    • Đã tạo backup DB ngay trước cutover chưa

Kết quả chuyển đổi thực tế

  • Knock replicate từng bảng một trong vài tuần, chủ yếu sau giờ làm việc và vào các khung giờ traffic thấp nhất
  • Họ luyện tập cutover nhiều lần trong môi trường staging và tinh chỉnh quy trình để hoạt động mà không cần quá nhiều can thiệp của operator
  • Sau khi PG 15 replica và code chuyển đổi ứng dụng đã sẵn sàng, họ thực hiện kiểm tra cuối cùng và chuyển flag
  • Cutover thực tế hoàn tất trong vài giây; ngoài một latency blip ngắn có chủ ý để chờ replication, ứng dụng vẫn tiếp tục hoạt động
  • Sau đó, họ hoàn nguyên các thay đổi tạm thời trong ứng dụng, chuyển vĩnh viễn mọi kết nối sang DB mới, rồi xóa subscription trên DB mới và DB cũ
  • Knock đã hoàn tất migration không gián đoạn Postgres từ 11.9 lên 15.3

Kết luận

  • Nhảy qua 4 major version Postgres cùng lúc là việc vất vả nhưng khả thi
  • Cách dùng logical replication có thể an toàn hơn downtime đã lên lịch, vì cho phép luyện tập, test và làm lại nhiều lần trước cutover thực tế
  • Nếu có vấn đề trong quá trình thực hiện, có thể xóa publication trên DB cũ và bắt đầu lại, nhờ đó có thể đảo ngược quy trình mà không làm suy giảm dịch vụ
  • Tính sẵn sàng 100% hoàn hảo là điều không thể về mặt kỹ thuật, nhưng migration không gián đoạn giúp hệ thống tiếp tục vận hành mà không gây gián đoạn dịch vụ lớn

1 bình luận

 
GN⁺ 2023-12-14
Các ý kiến trên Hacker News
  • Cách sao chép toàn bộ nội dung từng bảng một gây tải I/O quá lớn, và không hiệu quả với các bảng rất lớn
    Cách tốt hơn là tạo replication slot, chụp snapshot rồi khôi phục vào instance mới, đẩy LSN tiến lên, sau đó replication từ đó trở đi. Như vậy sẽ có một logical replica chứa đầy đủ dữ liệu, rồi chỉ cần nâng cấp replica đó
    Bài của Instacart có trình bày cách làm: https://archive.ph/K5ZuJ
    Nếu tôi nhớ không nhầm thì bài viết có vài lỗi nhỏ, nhưng quy trình tổng thể hoạt động được và tôi đã nâng cấp nhiều instance cỡ TB theo cách này

    • Đây là một công thức tốt, nhưng cần một chỉnh sửa nhỏ mà quan trọng về thứ tự chèn pg_upgrade vào quy trình
      Nếu bắt đầu logical replication trước rồi mới chạy pg_upgrade thì có nguy cơ hỏng dữ liệu. Thảo luận liên quan có trên pgsql-hackers: https://www.postgresql.org/message-id/flat/20230217075433.u5...
      Để giải quyết, trước tiên hãy tạo logical slot, đẩy cluster mới tiến tới vị trí LSN của slot nhưng chưa bắt đầu logical replication, sau đó chạy pg_upgrade, và sau khi cluster đã chạy lên trên phiên bản PostgreSQL mới thì mới bắt đầu logical replication
      Gần đây Postgres.ai đã dùng chính xác cách này khi nâng cấp không downtime nhiều cluster multi-TiB của GitLab trong trạng thái tải cao, đồng thời dùng cả PAUSE/RESUME của PgBouncer. Alexander Sosna dự kiến sẽ có bài trình bày vào cuối tuần này: https://www.postgresql.eu/events/pgconfeu2023/schedule/sessi...
    • Với tư cách OP, tôi cũng đã xem xét cách này, nhưng không đủ tự tin với việc đẩy LSN thủ công như phương án được đề xuất, và cũng không chắc có thể phát hiện sai lệch nếu bỏ lỡ replication
      Tiến hành theo từng bảng thì phiền hơn nhiều, nhưng có vẻ đáng tin cậy hơn
    • Bài viết đã được cập nhật: https://tech.instacart.com/zero-downtime-postgresql-cutovers...
    • Bài đó nói về nền tảng của cách Instacart nâng cấp, nhưng đã khá cũ; bài bên dưới thể hiện quy trình hiện tại tốt hơn
      Chúng tôi đã nâng cấp thành công nhiều cơ sở dữ liệu rất lớn và hoạt động sôi động bằng cách này
      https://www.instacart.com/company/how-its-made/zero-downtime...
  • Cách tiếp cận thú vị và được tài liệu hóa tốt, nhưng câu “khách hàng hiện đại kỳ vọng 100% availability” khiến tôi hơi vướng
    Đó không phải là sở thích của tôi với tư cách khách hàng, cũng không phải trải nghiệm của tôi với tư cách nhà cung cấp. Với nhiều workload, tính nhất quán quan trọng hơn availability rất nhiều
    Khi nhà cung cấp thông báo một khoảng downtime, nhiều khi điều đó lại khiến tôi yên tâm vì trông như một tín hiệu rằng họ đang xử lý dữ liệu của tôi một cách thận trọng

    • Với tư cách OP, đây là góp ý hay
      Tôi muốn tạo niềm tin cả vào độ tin cậy của sản phẩm lẫn tính nhất quán của workload. Tất nhiên, thay vì giả vờ là nhất quán trong khi không ổn định, thì quản lý kỳ vọng của khách hàng và chủ động có downtime để đạt uptime tốt hơn về dài hạn là lựa chọn tốt hơn nhiều
      Việc khiến khách hàng dự liệu trước các khung bảo trì định kỳ cũng có thể dẫn tới một kiến trúc vững chắc hơn về tổng thể. Nếu khách hàng xây dựng các biện pháp an toàn để chịu được downtime thì khả năng phục hồi sẽ cao hơn, và khi đội ngũ có thể tin khách hàng theo cách đó, họ cũng có thời gian đầu tư cho một sản phẩm tốt hơn
      Có lẽ sau lần nâng cấp major version tiếp theo, tôi sẽ viết bài “thiết lập kỳ vọng về downtime là con đường dẫn tới uptime rất cao”
    • Còn tùy khách hàng là ai
      Với tư cách khách hàng của AWS, tôi kỳ vọng 100% availability. Vì khách hàng của tôi ở khắp thế giới và không có khung thời gian nào để đặt downtime
  • AWS hiện đã hỗ trợ blue/green deployment: https://aws.amazon.com/about-aws/whats-new/2023/10/amazon-rd...

    • Tôi đã tự thử vài tuần trước, và hiện tại tốt nhất vẫn chưa nên tin nó với PostgreSQL
      Sau vài lượt trao đổi với AWS, thử nghiệm bị kẹt trong vài giờ, và mãi sau AWS UI mới thừa nhận rằng việc chuyển đổi chưa được áp dụng. May là nó fail an toàn, nhưng tôi không tin có thể căn đúng thời điểm chuyển đổi thực tế trên dataset từ GB trở lên
    • Đúng vậy. Với tư cách OP, khi đó chúng tôi đang dùng Aurora 11.9 và không thuộc diện được hỗ trợ blue/green deployment
      Lần sau có thể sẽ dùng được
  • Cái này rất tuyệt
    Tôi đã tạo một công cụ tự động hóa phần lớn những việc đã trải qua; nếu thấy hữu ích hoặc muốn mở rộng bằng phản hồi/ý tưởng thì luôn hoan nghênh: https://github.com/shayonj/pg_easy_replicate

    • Công cụ hay đấy
      Những phát hiện từ các bảng lớn có thể sẽ rất thú vị với công cụ kiểu này. Nếu giúp áp dụng chiến lược phù hợp cho từng bảng dễ hơn, nó có thể trở thành công cụ bắt buộc phải có cho các đội thực hiện những migration như vậy trong tương lai
  • Việc nói rằng “một dịch vụ như Knock không thể chấp nhận bất kỳ downtime nào, dù có lên lịch trước hay không” nghe có vẻ đáng ngờ
    Hệ thống phức tạp thì sẽ có sự cố và downtime. 15 phút downtime đã được thông báo trước là chấp nhận được với gần như mọi doanh nghiệp SaaS. Đây đâu phải bệnh viện hay nhà máy điện
    Nhiều việc giả tạo phát sinh vì người ta nghĩ dịch vụ quan trọng hơn thực tế. Nếu thời gian kỹ thuật bỏ vào đây được dùng để cải thiện sản phẩm hoặc năng suất đội ngũ phát triển, có lẽ người dùng đã vui hơn. Đặc biệt là nếu có thể đưa thông báo vào queue rồi bắt kịp sau downtime
    Nếu có SLA doanh nghiệp với điều khoản bồi thường cho 15 phút downtime thì có thể biện minh được, nhưng đa số không phải vậy. Thực tế có khả năng đã từng có vài sự cố tương tự hoặc dài hơn
    Trong migration cơ sở dữ liệu, chênh lệch khối lượng công việc giữa “downtime ngắn” và “không gián đoạn” thường khá lớn nên điều này càng quan trọng. Với trường hợp một lần như lần này, và khi các phiên bản PostgreSQL mới nhất của RDS được hỗ trợ mặc định, tôi nghĩ càng khó biện minh

    • Với tư cách OP, đúng là mọi dịch vụ đều có downtime vì lý do nào đó
      Chúng tôi cũng đã thảo luận việc đặt maintenance window, nhưng điều tôi cứ băn khoăn là làm sao diễn tập nâng cấp bằng dữ liệu production. Một bản sao PG 15 đồng bộ với dữ liệu production là cực kỳ quan trọng để xác minh workload có hoạt động như dự kiến hay không
      Dùng bản sao thời gian thực cho phép diễn tập trong môi trường production với tác động tối thiểu
      Bài học lớn từ lần migration này là việc theo dõi và giảm thiểu mọi rủi ro có thể nghĩ tới trong các dự án như vậy hữu ích đến mức nào. Cuối cùng, rủi ro của nâng cấp tại chỗ có vẻ lớn hơn rủi ro của lộ trình đã chọn, và đó là đánh giá tách biệt với việc có maintenance window hay không
      Thêm nữa, nếu sau này cần cách tiếp cận này, bài blog này sẽ là điểm khởi đầu và tiết kiệm được vài tuần. Hy vọng nó cũng giúp ích cho các đội khác trong tình huống tương tự
    • Ở góc nhìn bác sĩ, tôi thấy thú vị khi “đâu phải bệnh viện” lại được nêu như ví dụ về hệ thống không chịu nổi downtime
      Epic, một trong những nhà cung cấp hồ sơ y tế điện tử lớn nhất ở Mỹ, cũng có downtime định kỳ để nâng cấp ít nhất mỗi tháng một lần, mỗi lần khoảng 30–60 phút
    • Vấn đề là trên RDS không có cách nâng cấp một instance PostgreSQL với 15 phút downtime đã lên lịch
      Bạn không thể kiểm soát thời điểm reboot. Khi bắt đầu quy trình, việc chuyển đổi có thể bắt đầu sau một giờ, hai giờ, ba giờ, và bạn không thể biết cũng không thể kiểm soát khi nào nó reboot
      Nếu có replica, chúng sẽ được nâng cấp song song và reboot vào thời điểm tùy ý, còn rắc rối hơn
      Vì vậy, nếu không thể chịu được tình trạng bất khả dụng ngẫu nhiên trong một khung thời gian có thể kéo dài tới vài giờ tùy kích thước cơ sở dữ liệu, thì replication logic gần như là cách duy nhất để nâng cấp RDS
      Instance càng lớn thì vấn đề càng khó
    • Vấn đề thật sự của downtime là khi tất cả hệ thống cùng sập một lúc
      Nếu Jira ngừng 15 phút mỗi ngày thì thường không ảnh hưởng lớn. Trong hàng đợi công việc còn việc khác, và tệ nhất là dù nhiều sự cố chồng lên nhau thì vẫn có tài liệu đã hứa với ai đó để làm
      Nhưng nếu toàn bộ bộ sản phẩm Atlassian cùng chết, sẽ khó hơn nhiều để duy trì các công việc đệm nhằm tiếp tục làm việc. Nếu mọi ứng dụng của doanh nghiệp đều dùng cùng một storage array, tổn thất năng suất có thể nhảy từ 5% lên 95%
    • Trái với câu “15 phút downtime đã thông báo trước là chấp nhận được với gần như mọi doanh nghiệp SaaS”, có thể có đối thủ cạnh tranh không có downtime hằng tháng
      Đối thủ như vậy đặt nhu cầu của tôi lên trước sự tiện lợi của họ
      Sự cố của bạn cũng chính là sự cố của tôi
  • Hiện hava.io đang trải qua quy trình này
    Chúng tôi đang nâng từ AWS RDS PostgreSQL 11.13 lên 15.5
    Cuối cùng, chúng tôi chọn cách tiếp cận tương đối đơn giản là replication một chiều bằng pglogical. Vì đã từng chuyển từ Google Cloud SQL sang AWS RDS không gián đoạn theo cùng cách, chúng tôi tự tin rằng nó sẽ hoạt động mà không có tác động người dùng nhìn thấy
    pglogical làm những migration kiểu này khá đơn giản. Không phải lúc nào cũng nhanh, nhưng nếu bạn có thể chờ vài ngày để toàn bộ cơ sở dữ liệu được replicate dần sang instance mới thì ổn
    Cách này cũng cho chúng tôi thêm tự do thay đổi loại và kích thước storage. Chúng tôi đang cấp phát storage quá mức để có IOPS, nên muốn đổi loại storage và giảm cả kích thước. Vì vậy, restore snapshot đơn giản là không được

  • Tôi tự hỏi có phải đang nói đến tính năng mà AWS đã hứa ở giai đoạn “sales engineering” không
    Thực tế là khi buộc phải thực hiện nâng cấp phiên bản major, họ đã không cung cấp được

  • Thật ngạc nhiên là không thể khởi tạo bản sao từ bản sao lưu
    Nếu làm được thì đã giảm bớt công sức phải stream nội dung cơ sở dữ liệu hiện có ổn định sang máy chủ mới
    Và đây không phải là “không gián đoạn”, vì vẫn có vài giây downtime khi chuyển dịch vụ sang máy chủ mới
    Bài viết bỏ sót cách họ bảo toàn tính nhất quán. Ví dụ, không thể cứ gắn ứng dụng vào cả hai máy chủ trong một khoảng thời gian. Việc đọc có thể phục vụ từ cả hai, nhưng như vậy cũng không hoàn hảo; còn ghi thì nhất định chỉ được đi vào một máy chủ
    Cuối cùng cũng không có tùy chọn rollback. Những lần di chuyển một khối dữ liệu lớn như thế này đôi khi trục trặc vào khuya muộn. Vì vậy luôn cần một kế hoạch để có thể quay lại bước trước đó và yên tâm đi ngủ với niềm tin rằng đến sáng dịch vụ vẫn sống
    Đặc biệt, nếu đã gửi transaction ghi sang máy chủ mới rồi mà vì lý do nào đó phải quay về máy chủ cũ thì sẽ khó, vì dữ liệu đã không còn nhất quán

    • Với tư cách OP, có thể khởi tạo bản sao từ bản sao lưu, nhưng sẽ không có được các lệnh ghi vẫn tiếp tục phát sinh trong lúc sao lưu
      Nếu không có một cơ chế replication nào đó, hoặc không đưa việc này lên tầng ứng dụng, hệ thống được khôi phục sẽ bị thiếu các lệnh ghi
      Ví dụ có thể sửa ứng dụng để áp dụng ghi kép. Tôi hiểu là các nhóm đã replatform toàn bộ ứng dụng từ RDBMS sang một cơ sở dữ liệu hoàn toàn khác như Apache Cassandra cũng đã làm như vậy
      Trong bối cảnh của chúng tôi, ghi kép trông rủi ro hơn so với việc thiết lập streaming replication bằng tính năng cơ bản của PostgreSQL. Nhưng với một số nhóm, đó có thể là lựa chọn tốt hơn
      Về phần “không phải không gián đoạn” và “thiếu chi tiết bảo toàn tính nhất quán”, bài viết đã trình bày khá kỹ cách chúng tôi giữ tính nhất quán và tránh downtime API. Ý chính là ứng dụng được kết nối với cả hai cơ sở dữ liệu, nhưng chưa dùng cơ sở dữ liệu mới làm mặc định
      Sau đó chúng tôi dùng LaunchDarkly để gửi tín hiệu chuyển đổi đến tất cả instance ứng dụng, và LaunchDarkly duy trì kết nối độ trễ thấp với tất cả các instance
      Trong 1 giây đầu sau tín hiệu, máy chủ đưa các request cơ sở dữ liệu vào hàng đợi để replication kịp bắt nhịp. Điều này gây một spike độ trễ ngắn, nhưng nằm trong phạm vi chấp nhận được đã được tính toán có chủ đích. Sau khoảng tạm dừng đó, request tiếp tục chảy như bình thường nhưng nhắm tới cơ sở dữ liệu mới, và quá trình chuyển đổi hoàn tất
      Với phần traffic còn lại tới cơ sở dữ liệu cũ, chúng tôi cũng thêm cơ chế buộc ngắt kết nối với timeout 500ms. Giá trị này lớn hơn nhiều so với thời gian truy vấn p99, nên các truy vấn đang chạy không bị buộc kết thúc. Nhờ đó traffic tới cơ sở dữ liệu cũ dừng lại và replication có đủ thời gian bắt kịp
      Tùy chọn rollback không được đưa vào bài blog, nhưng chúng tôi cũng đã xem xét phương án tạo một cơ sở dữ liệu thay thế ở PG 11.9 và replicate cơ sở dữ liệu 15.3 sang cơ sở dữ liệu thứ ba đó. Nếu phải dừng lại, chúng tôi có thể roll-forward sang cơ sở dữ liệu cùng phiên bản này
      Sau khi luyện tập quy trình nâng cấp nhiều lần ở staging và xác nhận khả năng thành công, chúng tôi quyết định không dùng phương án này. Vì đã rehearsal nhiều lần nên khi chuyển đổi thật chúng tôi khá tự tin. Trên production, chúng tôi cũng dùng canary deployment để xác minh một phần workload chỉ đọc trên instance 15.3, coi nó như một read replica
      Để tránh sự cố vào khuya muộn, chúng tôi cố ý thực hiện vào đầu buổi tối cuối tuần. Quá trình chuyển đổi được script hóa và rehearsal rất kỹ để giảm rủi ro lỗi con người
      Nếu xảy ra lỗi nghiêm trọng, hệ thống cũng đã sẵn sàng quay lại cơ sở dữ liệu cũ. Trong trường hợp đó sẽ có mất một phần dữ liệu đã đi vào cơ sở dữ liệu mới, và chúng tôi đã chuẩn bị để hòa giải các phần cốt lõi. Để giảm rủi ro mất dữ liệu, chúng tôi tạm dừng một số tác vụ nền trong lúc chuyển đổi nhằm giảm số lượng ghi
      Những chi tiết này không được đưa vào blog vì chúng tôi muốn tập trung vào các chi tiết liên quan đến PostgreSQL hơn là các cân nhắc đặc thù của Knock. Bất kỳ nhóm nào muốn áp dụng playbook này luôn cần lập danh sách rủi ro trong bối cảnh của chính mình và giảm thiểu chúng
  • Phần liên quan đến sequence thật sự thú vị
    Một thời gian rồi tôi hầu như không dùng sequence, chủ yếu dùng UUID tuần tự, UUID v7, hoặc các cách như HiLo
    https://en.wikipedia.org/wiki/Hi/Lo_algorithm

    • Với những ai muốn giữ trách nhiệm tạo UUID v7 bên trong cơ sở dữ liệu cho đến khi PostgreSQL hỗ trợ native, một hàm PL/pgSQL có thể hữu ích
      Cách làm là tạo sequence 12 bit dựa trên đặc tả bản nháp của IETF, rồi kết hợp mili-giây UNIX epoch hiện tại với 62 bit ngẫu nhiên để cấu thành UUID
      Cốt lõi là có uuidv7_seq và để hàm generate_uuidv7() dùng clock_timestamp(), NEXTVAL, RANDOM() để trả về giá trị dạng UUID v7
    • Với tư cách OP, do vấn đề dependency nên chúng tôi tránh sequence ngoại trừ một chỗ trong ứng dụng
      Chúng tôi dùng KSUID và UUID v4 ở nhiều nơi. “Cái bẫy” này áp dụng cho mọi sequence, nên đáng nêu ra như một lời khuyên chung khi làm kiểu migration này
      [1]: https://segment.com/blog/a-brief-history-of-the-uuid/
  • Không có ý hạ thấp khối lượng công việc khổng lồ mà họ đã hoàn thành thành công, nhưng tôi tò mò vì sao không nâng cấp từng bước nhỏ mỗi khi có phiên bản mới
    Đây là bài đọc rất hay, nhưng cảm giác giống câu chuyện về các thủy thủ, biết rằng có thể kết thúc trong bi kịch mà vẫn quyết định đi thẳng xuyên qua cơn bão lớn thay vì vòng tránh
    Trong trường hợp này, nâng cấp nhỏ có nằm ngoài lựa chọn không? Tôi thắc mắc có phải kiểu “mỗi lần nâng cấp nhỏ cũng tốn chi phí downtime như nâng cấp lớn, nên trì hoãn tối đa” hay không. Phần mở đầu có vẻ gợi ý như vậy, nhưng cũng có thể tôi đã diễn giải quá mức

    • Với tư cách OP, chúng tôi có lẽ cũng sẽ dùng cùng cách tiếp cận cho cả nâng cấp minor
      Không hẳn là “trì hoãn đến khi bị dồn vào chân tường”, mà gần với “nếu chưa hỏng thì đừng sửa”, dù vẫn biết một lúc nào đó phải nhảy phiên bản
    • Việc nâng N phiên bản, dù N là 1 hay 3, xét về đe dọa đến availability thì gần như giống nhau
    • Mỗi lần nâng cấp đều có downtime
      Dù câu trả lời thực tế là dưới 60 giây, trên đường lên 15 họ vẫn sẽ phải trải qua downtime đó nhiều lần