4 điểm bởi GN⁺ 2023-09-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Để truyền các thay đổi của Postgres sang hệ thống khác theo thời gian thực, cần có CDC (Change Data Capture); từ thông báo đơn giản đến sao chép dựa trên WAL, mỗi lựa chọn có độ tin cậy và gánh nặng vận hành rất khác nhau
  • Listen/Notify là cách nhẹ nhất để bắt đầu, nhưng do đặc tính truyền at-most-once, thông báo tạm thời và giới hạn payload 8000 byte, nó gần với tín hiệu bổ trợ hơn là CDC cốt lõi
  • Polling bảng và bảng audit (outbox pattern) có thể triển khai chỉ với bảng tiêu chuẩn và trigger, nhưng bạn phải tự giải quyết việc phát hiện xóa, diff, thứ tự commit, write amplification và backpressure
  • Logical replication là cách mạnh mẽ để stream insert/update/delete từ WAL, nhưng ứng dụng phải tự quản lý replication slot, ack, khởi động lại và khả năng xử lý lưu lượng
  • Sequin dựa trên logical replication của Postgres để chuyển thay đổi tới SQS, Kafka, Elasticsearch, Redis, HTTP endpoint và hơn thế nữa, giúp giảm gánh nặng phải trực tiếp xử lý replication slot

Khi nào cần Postgres CDC

  • Postgres rất mạnh trong việc xử lý dữ liệu được lưu trữ, nhưng nếu muốn kích hoạt workflow từ thay đổi bảng hoặc stream theo thời gian thực sang kho dữ liệu, hệ thống hay dịch vụ khác thì cần thiết kế riêng phần di chuyển dữ liệu
  • Change Data Capture (CDC) là cách xác định, ghi nhận các thay đổi trong cơ sở dữ liệu rồi truyền chúng theo thời gian thực tới các hệ thống downstream
  • Có nhiều cách để bắt thay đổi trong Postgres, và độ khó triển khai, độ ổn định cũng như gánh nặng vận hành của chúng rất khác nhau

Listen/Notify: pub-sub đơn giản nhất

  • Listen/Notify của Postgres là tính năng giao tiếp giữa các tiến trình, hoạt động theo mô hình publish-subscribe
  • Một session có thể listen trên một channel cụ thể, còn hoạt động trong cơ sở dữ liệu hoặc session khác có thể gửi notify tới channel đó
  • Có thể dùng nó cho việc bắt thay đổi bằng cách gắn trigger
    • Trigger ví dụ sẽ ở thời điểm after insert or update or delete, tạo JSON chứa table, id, action của record đã thay đổi và gọi pg_notify('table_changes', payload::text)
  • Các giới hạn của nó khá rõ ràng
    • Nó có ngữ nghĩa truyền at-most-once, và listener phải đang kết nối tại thời điểm thông báo được phát ra
    • Listener chỉ nhận được thông báo kể từ sau khi đăng ký, nên chỉ cần ngắt mạng trong chốc lát cũng có thể bỏ lỡ thông báo
    • Giới hạn kích thước payload là 8000 byte; vượt quá mức này thì lệnh notify sẽ thất bại
    • Kích thước payload cũng tính cả tên channel, và tên channel giống như identifier của Postgres, có thể dài tối đa 64 byte
  • Nó có thể hữu ích cho phát hiện thay đổi cơ bản hoặc tối ưu polling bảng, nhưng có thể không phù hợp với các yêu cầu CDC phức tạp

Polling bảng: đơn giản nhưng yếu ở xóa và diff

  • Cách bắt thay đổi vững chắc đơn giản nhất là polling trực tiếp bảng
  • Mỗi bảng cần một cột như updated_at được cập nhật mỗi khi hàng thay đổi; nếu cần có thể tạo bằng trigger
  • Dùng kết hợp updated_atid làm cursor, còn logic ứng dụng sẽ lưu và quản lý cursor đó
  • Nếu kết hợp thêm đăng ký Notify thì có thể báo cho ứng dụng biết khi có bản ghi được thêm hoặc sửa, từ đó giảm tần suất polling
    • Vì thông báo của Postgres là tạm thời, nên phù hợp nhất là chỉ dùng như lớp tối ưu bên trên polling
  • Có ba nhược điểm chính
    • Hàng đã xóa không còn trong bảng nên không thể phát hiện xóa
    • Một cách khắc phục là để trigger delete lưu id và các cột cần thiết vào bảng riêng như deleted_contacts, rồi ứng dụng polling bảng đó
    • Bạn có thể biết rằng record đã được cập nhật, nhưng không biết cái gì đã thay đổi
    • Datetime và sequence của Postgres có thể không khớp với thứ tự commit, nên khi đọc theo block dựa trên updated_at, bạn có thể bỏ sót những hàng vẫn đang commit
  • Với các trường hợp theo dõi thay đổi đơn giản, nơi việc xóa, diff hay bỏ sót thỉnh thoảng không phải vấn đề lớn, đây là lựa chọn hợp lý

Bảng audit: lưu log thay đổi theo outbox pattern

  • Cách dùng bảng audit (audit table) là ghi lại thay đổi vào một bảng changelog riêng, còn được gọi là outbox pattern
  • changelog có thể chứa các cột liên quan tới thay đổi
    • action: là insert, update hay delete
    • old: jsonb của record trước khi thay đổi, để trống với insert
    • values: jsonb của các trường đã thay đổi, để trống với delete
    • inserted_at: thời điểm thay đổi xảy ra
  • Để triển khai, cần hàm trigger chèn vào changelog mỗi khi có thay đổi, cùng các trigger cho từng bảng cần theo dõi
  • Cũng có thể tiêu thụ changelog như một hàng đợi
    • Worker của ứng dụng lấy các thay đổi từ bảng
    • Có thể dùng for update skip locked của Postgres để xử lý gần mức exactly-once
    • Worker có thể mở transaction, khóa một lô bằng order by timestamp limit 100 for update skip locked, xử lý chúng, xóa các record đã xử lý rồi commit
  • Có một số nhược điểm về vận hành
    • Một lần ghi vào bảng gốc có thể tạo ra nhiều lần ghi vào bảng audit, gây write amplification
    • Thông thường sẽ có ít nhất ba lần ghi: insert ban đầu vào bảng audit, update trong lúc xử lý và delete sau khi xử lý xong
    • Cách fan-out bằng worker phải được tự thiết kế cho phù hợp với ứng dụng
    • Trước khi triển khai ở quy mô production, có thể phải tinh chỉnh hàm trigger và thiết kế bảng
    • Cũng có thể cần cân nhắc các chính sách chi tiết như giới hạn thời gian worker được giữ thay đổi đã checkout
    • Nếu worker không xử lý thành công thì bảng audit vẫn tiếp tục đầy lên, nên việc quản lý backpressure còn yếu

Foreign Data Wrapper: lựa chọn gần với đồng bộ giữa các Postgres cụ thể

  • Foreign Data Wrapper (FDW) là tính năng cho phép cơ sở dữ liệu Postgres đọc và ghi vào nguồn dữ liệu bên ngoài
  • Extension dựa trên FDW được hỗ trợ rộng rãi nhất là postgres_fdw
    • Nó cho phép kết nối hai cơ sở dữ liệu Postgres và tạo một cấu trúc giống view để một cơ sở dữ liệu tham chiếu bảng của cơ sở dữ liệu kia
    • Về bản chất, một cơ sở dữ liệu Postgres đóng vai trò client còn cơ sở dữ liệu kia là server
    • Khi truy vấn foreign table, cơ sở dữ liệu client sẽ gửi truy vấn tới cơ sở dữ liệu server qua wire protocol của Postgres
  • FDW không phải là cách phổ biến để bắt thay đổi, và ngoài một số tình huống rất đặc thù thì khó có thể khuyến nghị
  • Nếu bạn muốn ghi thay đổi từ một cơ sở dữ liệu Postgres sang một cơ sở dữ liệu Postgres khác, FDW có thể phù hợp
    • Ví dụ là khi dùng riêng một cơ sở dữ liệu cho kế toán và một cơ sở dữ liệu cho ứng dụng
    • Thay vì qua bước bắt thay đổi trung gian, có thể phản ánh trực tiếp giữa các cơ sở dữ liệu bằng postgres_fdw
  • Cũng có thể tự tạo FDW để POST thay đổi tới API nội bộ
    • Vì việc ghi vào API xảy ra trong commit, API có thể từ chối thay đổi và rollback commit
  • FDW rất mạnh, nhưng hiếm khi là lựa chọn tốt nhất cho CDC; còn tự viết FDW thì gần như là công việc lớn nhất trong số các cách bắt thay đổi
    • Việc tự viết FDW đã dễ hơn nhờ các công cụ như Supabase wrappers, nhưng vẫn là một việc lớn

Tự triển khai logical replication: CDC mạnh mẽ dựa trên WAL

  • Postgres có một giao thức dành cho sao chép cơ sở dữ liệu, và một trong số đó là logical replication
  • Logical replication được xây dựng trên WAL (write-ahead log) của Postgres
    • Mọi thao tác insert, update, delete trong cơ sở dữ liệu đều được theo dõi
    • Các thay đổi được stream tới subscriber
  • Người dùng trước tiên tạo replication slot trên primary
    • Dùng dạng pg_create_logical_replication_slot('<your_slot_name>', '<output_plugin>')
  • output_plugin chỉ định plugin dùng để giải mã các thay đổi trong WAL
    • pgoutput là plugin mặc định, xuất ra định dạng nhị phân mà server client mong đợi
    • test_decoding là plugin đầu ra đơn giản, cung cấp thay đổi WAL ở dạng con người có thể đọc được
    • Dù không phải plugin tích hợp sẵn của Postgres, wal2json là plugin phổ biến và JSON dễ dùng hơn định dạng nhị phân của Postgres như một điểm bắt đầu cho ứng dụng
  • Sau khi tạo replication slot, bạn có thể bắt đầu và tiêu thụ dữ liệu
    • Replication slot dùng một phần giao thức Postgres khác với truy vấn tiêu chuẩn
    • Nhiều thư viện client cung cấp các hàm hỗ trợ làm việc với replication slot
    • Ví dụ với psycopg2, có thể dùng cursor.start_replication(...)cursor.consume_stream(...) để tiêu thụ thông điệp WAL, rồi gửi ack bằng cursor.send_feedback(flush_lsn=msg.wal_end)
  • Client phải ack các thông điệp WAL đã nhận; replication slot hoạt động khá giống Kafka có offset
  • Logical replication là cách vững chắc được tạo ra dành cho CDC, nhưng nó phức tạp
    • Replication slot và replication protocol ít quen thuộc hơn với lập trình viên so với bảng và truy vấn thông thường
    • Cần chiến lược để không bỏ lỡ thông điệp khi khởi động lại
    • Hệ thống cũng phải được thiết kế để xử lý lượng lớn thông điệp phát ra từ Postgres

Sequin: công cụ CDC bọc quanh logical replication

  • Sequin là công cụ CDC chuyển các thay đổi và hàng của Postgres tới hàng đợi, stream, search index, cache, HTTP endpoint và hơn thế nữa
  • Các đích bao gồm SQS, Kafka, Elasticsearch, Redis, HTTP endpoints...
  • Sequin dùng logical replication của Postgres ở bên trong, nhưng trừu tượng hóa độ phức tạp của giao thức low-level
  • Nó có thể bắt cả insert, update, delete; với update và delete, nó ghi nhận cả giá trị newold của hàng
  • Những điều kiện để cân nhắc Sequin gồm
    • Cần CDC theo thời gian thực
    • Muốn stream trực tiếp tới đích như SQS hoặc webhook mà không cần hệ thống trung gian
    • Cần các tính năng như backfill dữ liệu lịch sử và lọc thay đổi dựa trên mệnh đề SQL where
    • Cần một lựa chọn đơn giản hơn so với tự quản lý replication slot
    • Cần đảm bảo xử lý exactly-once
  • Nó cũng có nhược điểm
    • Sequin không phải extension bên trong Postgres mà là công cụ bên thứ ba chạy cạnh cơ sở dữ liệu
    • Vì không phải extension nên nó tương thích rộng với nhiều cơ sở dữ liệu Postgres, nhưng nếu không dùng Sequin Cloud thì bạn sẽ phải tự dựng thêm hạ tầng

Tiêu chí lựa chọn

  • Ở giai đoạn bắt đầu, Listen/Notify và polling bảng là phù hợp
    • Listen/Notify tốt cho việc bắt các sự kiện không quan trọng, tạo prototype và tối ưu polling
    • Polling là lời giải ổn, đơn giản và trực diện cho các trường hợp dùng cơ bản
  • Ở giai đoạn nghiêm túc hơn, bảng audit có thể là lựa chọn trung gian
    • Có thể bắt được payload newold của hàng
    • Nếu làm đúng, bạn có thể có một hệ thống xử lý exactly-once
    • Khi mở rộng, write amplification và thiếu backpressure sẽ trở thành vấn đề; ngoài ra cấu hình thủ công dễ làm rơi thông điệp
  • Ở giai đoạn mở rộng, logical replication là lựa chọn gần nhất với một lời giải vững chắc
    • Tuy vậy, nên dùng công cụ như Sequin thay vì tự đọc trực tiếp từ slot
  • FDW là một tính năng thú vị, nhưng khả năng giải quyết các nhu cầu CDC phổ biến là khá thấp

1 bình luận

 
GN⁺ 2023-09-24
Các ý kiến trên Hacker News
  • Trigger + bảng lịch sử (bảng audit) là đáp án đúng trong 98% trường hợp. Nếu chưa dùng thì có thể bắt đầu dùng từ hôm nay. Đây là kỹ thuật đã được kiểm chứng hơn 30 năm
    Một ví dụ đơn giản về cách triển khai generic có ở https://gist.github.com/slotrans/353952c4f383596e6fe8777db5d.... Cách này đánh đổi hiệu quả không gian để chọn “dễ triển khai”
    Nếu có thể lưu dữ liệu bất biến thì thật sự rất tốt, nhưng trong database có lẽ có rất nhiều dữ liệu khả biến, và khả năng cao là mỗi ngày bạn đang quên rất nhiều thứ. Đừng quên, hãy dùng bảng lịch sử
    Tham khảo: https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
    Không nên dùng các thư viện hay kỹ thuật theo dõi lịch sử ở tầng ứng dụng như Papertrail. Chúng chậm, dễ lỗi, và không bắt được các thay đổi DB đi vòng qua app stack. Việc cố gắng đóng dấu timestamp updated từ ứng dụng cũng sai về căn bản, vì mỗi web server có đồng hồ khác nhau. Phải dùng đồng hồ DB, đó là chiếc đồng hồ duy nhất đúng

    • Để đảm bảo tính nhất quán, không nên tạo thời gian ở client mà nên đưa các lời gọi như now() vào trong query để dùng đồng hồ DB
      Nhưng chỉ timestamp này thôi thì chưa đủ để đồng bộ. Lý do là timestamp được tạo tại thời điểm bắt đầu transaction, không phải thời điểm commit transaction
      Nếu polling bảng và lọc theo timestamp gần đây, bạn có thể bỏ sót một phần các transaction có thứ tự commit bị đảo lộn. Có thể đặt một khoảng đệm bằng cách truy vấn lùi thêm vài phút và loại bỏ trùng lặp, nhưng trong PostgreSQL thời gian transaction là không giới hạn, và truy vấn quá sâu về quá khứ sẽ rất lãng phí. Nếu độ chính xác và hiệu quả quan trọng thì cách này không phù hợp
    • Estuary(https://estuary.dev, tôi là CTO) tạo changelog data lake thời gian thực chứa toàn bộ thay đổi database trên cloud storage mà không cần cấu hình bổ sung trên DB vận hành
      Nếu dùng log sequence number, thời gian DB và REPLICA IDENTITY FULL, nó còn bao gồm cả trạng thái trước/sau thay đổi. Sau đó, khi materialize collection sang nơi như Snowflake, mặc định bạn sẽ có bảng đồng bộ bám theo các cập nhật của source DB
      Cũng có thể chuyển đổi hoặc materialize toàn bộ lịch sử bảng phục vụ mục đích audit từ cùng data lake nền tảng đó, nên không cần gắn thêm capture hay WAL reader vào source DB nữa
    • Nếu tham chiếu biến session trong trigger, có thể đưa thêm thông tin như comment về lý do thay đổi vào lịch sử. Tôi mới chỉ thử trong một dự án cá nhân nhỏ, nhưng đến giờ vẫn hoạt động tốt
    • Tôi đã port ví dụ sang SQLite và demo cách hoạt động: https://chat.openai.com/share/b5113cb1-10df-4a38-adde-5ec0e7...
      Tôi cũng giải thích riêng một cách SQLite triển khai pattern tương tự theo kiểu dựa trên cột thay vì JSON: https://simonwillison.net/2023/Apr/15/sqlite-history/
    • Cách tiếp cận này tốt, và thực tế chúng tôi cũng đang xây dựng activity feed của app theo cách này. Tuy nhiên, nó không tự giải quyết bài toán “push thay đổi ra bên ngoài”. Dĩ nhiên, nếu lắng nghe các thay đổi WAL của bảng audit thì có thể có cả hai lợi ích
  • Bài viết này tóm tắt ngắn gọn và tốt nhiều cách tiếp cận có thể làm bằng các tính năng cơ bản của Postgres
    Ở phần “capture thay đổi vào bảng audit”, tại công ty trước đây chúng tôi đã dùng tốt pattern Temporal Tables. Khác với các RDBMS lớn khác, Postgres không tích hợp sẵn tính năng này, nhưng có một pattern đơn giản có thể dùng bằng SQL function: https://github.com/nearform/temporal_tables
    Bạn có thể xem trạng thái của bảng tại một thời điểm cụ thể, nên trả lời được các câu hỏi như “cài đặt của người dùng này vào ngày 12/8 là gì”, “lúc 11:55 tối qua có bao nhiêu record chưa xử lý”, “hãy cho xem khác biệt về feature flag giữa hiện tại và một tuần trước”

  • Tôi từng tư vấn cho một công ty có một SQL Server nguyên khối rất lớn. Nó không phải Postgres, nhưng giả sử là Postgres thì cũng tương tự
    Nó đã vận hành suốt hàng chục năm, được dùng cho đủ mọi mục đích trong công ty, và về cơ bản mọi ứng dụng cùng quy trình nghiệp vụ của toàn công ty đều lưu dữ liệu vào cơ sở dữ liệu này
    Vấn đề là có rất nhiều ứng dụng truy vấn DB này, và cũng có vô số quy trình, thủ tục chèn/sửa dữ liệu, nên khi các quy trình chèn/sửa ở thượng nguồn thay đổi hoặc được thêm mới, chúng bắt đầu phá vỡ các bất biến ở cấp ứng dụng. Ngay cả các quy trình bình thường cũng hoạt động khác đi khi có dữ liệu xấu
    Việc truy vết nguyên nhân cực kỳ khó, vì những thứ cần xem thường được viết từ 10 năm trước và những nhân viên đó đã rời công ty từ lâu
    Tôi tự hỏi liệu có thể ghi nhận các thay đổi trong cơ sở dữ liệu Postgres dưới dạng một DAG nào đó, để biết quy trình nào chèn/sửa/xóa dữ liệu, lịch sử hành vi của chúng ra sao, nhiều ứng dụng truy vấn dữ liệu này như thế nào và thống kê truy vấn thay đổi ra sao theo thời gian hay không
    Tôi không rõ đã có tiền lệ nào như vậy chưa, hay cách tiếp cận nào có thể dùng để xây một công cụ như thế. Trước đây tôi từng nghĩ đến việc làm thứ tương tự, nhưng có vẻ đây là lĩnh vực đòi hỏi mức hiểu biết ngang kỹ sư lõi Postgres để đưa ra lựa chọn tốt

    • Logical replication của Postgres chứa đầy đủ các câu lệnh thay đổi, tức thông tin chèn/sửa/xóa, để tái tạo cùng một trạng thái về mặt logic trên cơ sở dữ liệu khác
      Với mỗi thay đổi, bạn sẽ không lấy được dữ liệu nguồn gốc ở cấp client
      Dù vậy vẫn có cách обход. Luồng logical replication cũng có thể bao gồm các thông điệp thông tin từ hàm pg_logical_emit_message, nên client có thể tự đưa metadata vào. Có lẽ có thể cấu hình để phát ra định danh client ở đầu mỗi transaction
    • Tôi không biết xử lý truy vấn thế nào, nhưng với chèn/sửa thì đặt một cột để theo dõi nguồn gốc sự kiện (last updated by). Có thể đây là anti-pattern, nên nếu có giải pháp vững chắc hơn thì tốt
    • Về mặt kỹ thuật, log replication chứa mọi thao tác do mọi tác nhân thực hiện, và nếu dùng trigger cẩn thận thì cũng có thể theo dõi toàn bộ bằng bảng ghi nhận DDL/DML. Nếu lo về DCL thì cũng có thể đưa vào
      Cách tiếp cận này hoạt động với hầu hết các giải pháp họ SQL dùng WAL hoặc trigger
      Trong SQL Server tôi đã dùng cách trigger nhiều lần, nhưng nếu log mọi truy vấn thì thường bị chậm. Thiết kế cơ chế insert không cản trở vận hành không phải chuyện hoàn hảo, và có thể cần sampling
    • Chỉ riêng việc để mỗi ứng dụng có người dùng DB riêng cũng đã cung cấp khá nhiều thông tin
    • Từng có ý tưởng rà soát mọi script và chương trình gửi truy vấn tới DB, rồi gắn chú thích ID duy nhất cho từng truy vấn để liên kết về script/chương trình đó. Nếu chú thích và ID đó còn lại trong query log thì có vẻ có thể truy vết nguồn gốc
  • Nếu đi theo hướng “bảng audit” thì cứ dùng pgaudit là được. Đây là extension đã được kiểm chứng trong thực tế, và nếu dùng AWS thì cũng dùng được trên RDS
    https://github.com/pgaudit/pgaudit/blob/master/README.md
    https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appen...

  • Không nhất thiết phải làm. Việc muốn thứ này nghĩa là biến các quan hệ của Postgres thành hợp đồng. Khi đó không dịch vụ nào có thể bền vững hóa trạng thái nội bộ nữa
    Nếu bạn thật sự dồn toàn lực vào thiết kế hướng miền thì có thể làm được, nhưng tốt hơn là dùng một hệ thống dựa trên sự kiện vừa nhẹ vừa thực tế

    • Các quan hệ của cơ sở dữ liệu, dù thích hay không, vốn đã là hợp đồng rồi
      Một thứ dựa trên sự kiện phức tạp hơn gấp 1000 lần
  • Cách polling cột updated_at ở dạng đơn giản nhất không vững chắc. Lý do là không có gì đảm bảo các transaction sẽ commit theo đúng thứ tự đó

    • Tôi là tác giả bài viết. Nhận xét hay. Ví dụ transaction A bắt đầu, before trigger chạy và updated_at của Row 1 được đặt thành 2023-09-22 12:00:01
      Một lát sau transaction B bắt đầu, updated_at của Row 2 được đặt thành 2023-09-22 12:00:02, và B commit trước
      Truy vấn polling chạy, coi Row 2 là thay đổi mới nhất và cập nhật con trỏ thành 2023-09-22 12:00:02, rồi sau đó A mới commit thì sẽ bỏ lỡ Row 1
      Cách đơn giản để tránh vấn đề này là đừng polling gần như theo thời gian thực. Thứ tự cuối cùng rồi sẽ nhất quán
      Một đề xuất vững chắc hơn có thể là dùng sequence. Ví dụ đặt một cột updated_at_idx tăng lên mỗi khi hàng thay đổi
    • Giờ tôi mới biết chuyện này. Nếu dùng trigger để cập nhật cột thì vẫn vậy à?
      Tôi thắc mắc liệu dùng before trigger đặt now() thì timestamp updated_at của hai hàng vẫn có thể khác với thứ tự commit transaction hay không. updated_at không cần giống commit timestamp, nhưng updated_at phải biểu thị chính xác thứ tự commit ở mức mili giây/micro giây
    • Khi polling, thay vì updated_at, dùng cột _txid do trigger đặt bằng transaction ID hiện tại. Sau đó khi polling, dùng txid_current() để kiểm tra transaction nào đã commit và transaction nào chưa
      Hơi chênh vênh và rất dễ mắc lỗi biên, nhưng đã chạy tốt trong production suốt vài năm
  • Bài viết rất hay
    Nếu dùng Elixir và Postgres, tôi đã làm một thư viện nhỏ theo cách tiếp cận tương tự để lắng nghe thay đổi WAL: https://github.com/cpursley/walex

  • Những cách này đều không ổn lắm; cá nhân tôi thấy polling là thực tế nhất
    Mong Postgres sẽ đổi mới trong mảng này

    • Đã từng có những nỗ lực đưa nhiều dạng tính thời gian vào tiêu chuẩn SQL như một tính năng hạng nhất
      Tôi nghĩ trước khi nó được đưa vào tiêu chuẩn SQL, sẽ khó có động lực trong không gian kernel của các DBMS quan hệ. Các lựa chọn thì nhiều và phức tạp, còn những giải pháp thành công ở không gian người dùng cũng không đến mức tạo gánh nặng quá lớn về hiệu năng
      Nhân tiện, những người nghiên cứu lĩnh vực này nhìn chung nghiêng về cách tiếp cận bảng audit. Vì nó duy trì các thuộc tính ACID nhất quán bên trong cơ sở dữ liệu, và giữ Postgres là điểm lỗi đơn thay vì thêm proxy hay tác vụ polling
    • Khoảng polling 1 giây có thực tế không?
  • Trong thế giới dữ liệu có một khoảng trống lớn. Thay vì hỏi kho dữ liệu về kết quả, sẽ tốt hơn nếu kết quả truy vấn được push dần dần
    Tôi làm khá nhiều phân tích thời gian thực/streaming; có thể dùng stream processing, và cũng có thể xử lý một phần bằng materialized view trong kho dữ liệu. Nhưng sau khi dữ liệu đã vào DB hoặc data lake, để downstream thấy thay đổi thì thực chất lại quay về polling
    Khi muốn phản ứng với một tình huống nào đó xảy ra trong dữ liệu, hoặc cập nhật màn hình mà không cần refresh trang, không có nhiều giải pháp gọn gàng. Các giải pháp trong bài này cũng có vẻ giống workaround hơn là tính năng hạng nhất
    Nếu muốn tạo báo cáo được cập nhật theo thời gian thực mà không refresh trang, thường sẽ là tải dữ liệu từ DB rồi đẩy thay đổi tới GUI bằng Kafka và WebSocket. Khi đó bạn vận hành một kiến trúc lambda kỳ lạ, trong đó một phần phân tích nằm trong code, một phần nằm trong DB
    Có đổi mới trong lĩnh vực này. KSQL và Kafka Streams có thể phát ra thay đổi, Materialize có subscription, còn ClickHouse có live view. Tuy nhiên nhiều tính năng còn mới hoặc ở giai đoạn preview và không thật sự khớp. Tôi đã thử tất cả, nhưng cảm thấy chúng đẩy quá nhiều việc sang cho lập trình viên
    Ước gì có một thư viện cho phép nhận ngay change feed bằng một tùy chọn kiểu [select * from orders with suscribe]. Đây là lĩnh vực đủ quan trọng nhưng vẫn chưa được chú ý đúng mức

  • Có một cạm bẫy lớn của replication mà bài không đề cập, và vì vậy tôi không dùng replication
    Postgres cố gắng đảm bảo rất mạnh rằng consumer của replication slot sẽ không bỏ lỡ dữ liệu. Vì thế nếu consumer không tiêu thụ dữ liệu từ slot, Postgres sẽ tử tế tiếp tục giữ dữ liệu bị lỡ, rồi cuối cùng đi đến mức đầy đĩa và DB sập. Tôi đã gặp chuyện này với hai DB SaaS khác nhau trong lúc prototyping, và cách duy nhất để khôi phục là gửi ticket hỗ trợ
    Nếu consumer của replication slot ngừng đọc, nhất định phải có cảnh báo
    Một lý do khác là code path để lấy snapshot ban đầu của bảng và code path để đọc thay đổi hoàn toàn khác nhau. Khởi tạo việc đọc replication slot sao cho không bỏ lỡ bất kỳ thay đổi nào không phải chuyện nhỏ
    Đáng tiếc là xét từ góc độ change capture, replication là giải pháp ít hacky nhất
    Tôi dùng polling, nhưng thay vì updated_at thì lưu txid

    • Có thể đặt giới hạn kích thước để slot bị đánh dấu vô hiệu khi vượt quá một kích thước nhất định, thay vì cứ giữ dung lượng mãi: https://www.postgresql.org/docs/current/runtime-config-repli...
      Tôi tò mò bạn muốn hành vi nào hơn
      Nếu xử lý khối lượng dữ liệu lớn, bạn sẽ muốn xử lý snapshot ban đầu và đọc thay đổi theo cách khác nhau. Vì cần có thể làm những việc như khởi tạo song song hoặc khởi tạo dựa trên backup vật lý. Tuy vậy tôi hiểu rằng một tính năng streaming có chọn lọc dữ liệu hiện có sau khi tạo slot có thể hữu ích
      Phần khởi tạo đọc replication slot sao cho không bỏ lỡ thay đổi lẽ ra không nên khó, tôi tò mò bạn bị vướng ở đâu
    • Một mẹo để xử lý vấn đề đầu tiên là gửi thông điệp logical decoding cho chính mình. Như vậy có thể giữ lượng WAL được lưu trữ ở mức thấp
      Khi không cần mọi thay đổi, replication slot tạm thời tự dọn dẹp khi kết nối bị ngắt cũng hữu ích. Cũng có cấu hình để đặt mức tối đa cho WAL được giữ lại nhằm tránh làm sập server
    • Tôi từng mắc cạm bẫy này. Nó thật sự tinh vi. Khi bỏ consumer đi, tưởng như DB chính sẽ không bị ảnh hưởng gì, nhưng thực tế lại tạo ra một quả bom hẹn giờ
      Bạn có thể giải thích thêm cách dùng txid thay cho updated_at được không