Microsoft open source pg_durable: Đưa workflow vào lòng Postgres

8 phút đọc English
Featured image for Microsoft open source pg_durable: Đưa workflow vào lòng Postgres

Hồi xưa tôi từng mất cả ba ngày cuối tuần chỉ để dựng một cái hàng đợi (job queue) thủ công. Tôi viết một cái cron job chạy mỗi phút, lấy mấy dòng dữ liệu có status = 'pending', gọi API bên ngoài, rồi cập nhật status thành running, xong rồi completed. Để xử lý retry khi lỗi, tôi thêm một cột đếm số lần chạy lại. Mọi thứ chạy mượt mà cho đến tối thứ Hai, database server đột ngột restart. Một nửa số job bị kẹt ở trạng thái running vĩnh viễn. Nửa còn lại thì chạy trùng hai lần vì API bị timeout đúng lúc server sập.

Năm tôi hai mươi mấy tuổi, tôi tưởng mình thông minh lắm khi viết được một hệ thống tự động chạy bằng cron job và bash script. Sau này server sập, dữ liệu mất sạch, tôi mới nhận ra ba má tôi dạy đúng: ai cũng có thể làm sai, quan trọng là làm sao để không ai biết mình sai.

Đó là loại vết sẹo nghề nghiệp khiến bạn nhìn vào một dự án như pg_durable với một cảm giác vừa nhẹ nhõm vừa… lo sợ.

Hôm qua, thành viên coffeemug gửi một link GitHub lên Hacker News với tiêu đề ngắn gọn: pg_durable: Microsoft open sources in-database durable execution. Chỉ trong vài giờ, bài viết đã nhận được 341 upvotes và 79 bình luận. Đây là dự án của Microsoft, nhưng không phải kiểu đóng hộp trong Azure; nó hoàn toàn open-source, viết bằng Rust, chạy trực tiếp trên Postgres 17 hoặc 18.

Câu chuyện trong một câu

pg_durable là một extension của PostgreSQL giúp bạn định nghĩa và chạy các workflow dài hơi, chống chịu lỗi ngay trong lòng database. Nó tự động lưu checkpoint trạng thái sau mỗi bước để nếu server sập hoặc restart, hệ thống sẽ chạy tiếp đúng chỗ bị đứt mà không cần bất kỳ hàng đợi bên ngoài nào như Redis, Celery, hay Temporal.

Tại sao bài này lên trang nhất?

Cộng đồng Hacker News vốn có một tình yêu cực kỳ nồng nhiệt nhưng cũng đầy trắc trở với PostgreSQL. Mấy năm gần đây, xu hướng chung là chia nhỏ mọi thứ: frontend, backend, hàng đợi (queues), worker độc lập. Database chỉ nên làm đúng việc của nó là lưu trữ dữ liệu.

Nhưng cái giá phải trả cho sự sạch sẽ đó là sự phức tạp của hạ tầng. Muốn lấy vài dòng dữ liệu, gọi một cái API rồi cập nhật lại DB, bạn phải viết code ứng dụng, cấu hình Redis, chạy Celery, loay hoay với cơ chế retry và xử lý timeout.

pg_durable đánh trúng tâm lý của những người muốn dọn dẹp đống lộn xộn đó. Nếu dữ liệu đằng nào cũng nằm trong database, tại sao không chạy luôn control flow ở đó cho rảnh nợ? Nó là một phần của làn sóng mà một commenter gọi là “năm của hàng đợi trên Postgres” (the year of the Postgres queue) - một sự phản kháng âm thầm chống lại sự phức tạp của kiến trúc cloud hiện đại.

Bên dưới nắp capo: pg_durable hoạt động thế nào?

Không giống như mấy cái script PL/pgSQL truyền thống chạy khóa chặt connection của bạn, pg_durable chạy như một background worker thực thụ của PostgreSQL. Nó cung cấp một ngôn ngữ SQL DSL riêng với các toán tử như ~> (chạy tuần tự) và |=> (chạy song song/fan-out).

Về mặt kỹ thuật, pg_durable được xây dựng trên hai thư viện Rust:

  1. duroxide - framework quản lý task bền vững giúp định hình replay, lưu checkpoint và quản lý timer.
  2. duroxide-pg - state provider ghi nhận trạng thái vào schema duroxide.* trong Postgres.

Một ví dụ chạy workflow sẽ như thế này:

SELECT df.start(
    'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
    ~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);

Background worker sẽ chạy query, lưu kết quả vào schema trạng thái của duroxide, rồi lên lịch cho bước tiếp theo. Nếu Postgres đột ngột restart giữa chừng, worker sẽ đọc lịch sử trong duroxide, xem bước nào đã xong để chạy tiếp. Nó cũng hỗ trợ gọi API ngoài thông qua hàm df.http(), giúp bạn không phải lo lắng giao dịch bị kẹt do lỗi mạng.

Tranh luận trên diễn đàn: Logic nên nằm ở đâu?

Phần bình luận trên HN ngay lập tức chia thành hai phe rõ rệt. Phe kỹ sư ứng dụng, đại diện bởi junto, lập tức lên tiếng cảnh báo:

“Cái này sặc mùi stored procedures. Không thể unit test. Không thể version control. Mang business logic vào database là tự chôn mình (hidden brain problem), khó cô lập tải, không có observability, áp lực scale đè nặng lên Postgres, thiếu hụt IO, đặc biệt là khi gọi API ngoài.”

Đây là nỗi ám ảnh kinh điển của bất kỳ ai từng phải bảo trì những hệ thống cũ với hàng chục ngàn dòng code PL/SQL rối rắm. Khi logic nằm trong DB, nó biến mất khỏi lịch sử Git, khỏi các công cụ linter và unit test thông thường.

Nhưng phe thực dụng lại nghĩ khác. Commenter dpark phản pháo rất chi tiết:

“Bạn hoàn toàn có thể test stored procedures y hệt như SQL khác. Đúng là phải dựng DB lên để test. Nhưng nếu bạn không test được stored procedures, tức là bạn chẳng biết cách test SQL của mình, đó mới là vấn đề thật sự… Stored procedures thực ra giúp giảm đáng kể IO nếu dùng đúng cách, từ đó tăng khả năng scale.”

Ý kiến của moomoo11 có vẻ là một giải pháp trung hòa khá hay: giữ business logic ở app layer, nhưng dùng pg_durable cho các tác vụ bảo trì ở mức database:

“Tôi luôn phải viết các script bảo trì kiểu này. Nếu có thể đóng gói chúng chạy ngay bên cạnh database thì quá tiện.”

Một mối lo ngại thực tế khác là CPU. Chạy workflow trên Postgres đồng nghĩa với việc bạn đổi tài nguyên CPU rẻ và dễ scale ở tầng app lấy tài nguyên CPU đắt đỏ và cực kỳ khó scale của database chính. Nếu workflow chạy nặng, CPU database nhảy lên 100% thì ứng dụng của bạn cũng đi tong.

Bảng cân nhắc trước khi dùng

Hãy tự hỏi các tác vụ của bạn thuộc nhóm nào trước khi cài đặt:

Dùng pg_durable nếu…Bỏ qua pg_durable nếu…
Các tác vụ của bạn xoay quanh database (ETL, cập nhật vector embedding cho pgvector, dọn dẹp dữ liệu).Workflow của bạn đòi hỏi kết nối phức tạp với nhiều dịch vụ bên ngoài (ngoài giao thức HTTP).
Bạn muốn đơn giản hóa hạ tầng, xóa bớt Celery, Redis hoặc Airflow.Bạn đang dùng các dịch vụ DB quản lý (như AWS RDS) không cho phép cài extension ngoài hoặc chạy background worker.
Bạn muốn các runbooks tự động phục hồi sau khi database bị restart.Database của bạn đã quá tải CPU và không thể gánh thêm các tiến trình background worker.

Bắt đầu với pg_durable

Để chạy thử pg_durable trên máy cá nhân, bạn cần PostgreSQL 17 hoặc 18, Rust và công cụ cargo-pgrx. Dù Microsoft có cung cấp package Debian, cách nhanh nhất cho môi trường phát triển là build từ source:

# Clone repo
git clone https://github.com/microsoft/pg_durable.git
cd pg_durable

# Build và chạy extension trong Postgres do pgrx quản lý
cargo pgrx run pg17

Sau đó, trong terminal của Postgres, bạn khởi tạo extension:

CREATE EXTENSION pg_durable;

Một lưu ý nhỏ trong tài liệu: role của background worker (pg_durable.worker_role, mặc định là postgres) phải là superuser, vì nó cần vượt qua các chính sách Row-Level Security (RLS) để quản lý trạng thái workflow của mọi user khác nhau.

pg_durable hiện tại vẫn đang ở chế độ preview, nhưng Microsoft đã tích hợp nó vào dịch vụ cloud mới của họ là Azure HorizonDB. Đây là một nước đi khá thú vị. Trong khi cả thế giới mất mười năm để đẩy phần tính toán càng xa dữ liệu càng tốt, Microsoft lại cá cược rằng nhiều lập trình viên chỉ muốn viết vài dòng SQL rồi tắt máy đi về sớm.


Discussion on Hacker News · Source: github.com · Submitted by coffeemug

Hoang Yell

Một nhà phát triển phần mềm và là người kể chuyện kỹ thuật. Tôi đọc Hacker News mỗi ngày và kể lại những câu chuyện hay nhất ở đây — bằng tiếng Việt và tiếng Anh, cho người tò mò nhưng không có thời gian.