Lựa chọn công nghệ hàng đợi Postgres
(adriano.fyi)- Trước khi đưa vào một hệ thống hàng đợi riêng, nếu có thể xây dựng hàng đợi tác vụ nền chỉ bằng Postgres vốn đang dùng, bạn có thể tận dụng đáng kể sự đơn giản trong vận hành và mức độ quen thuộc của đội ngũ
NOTIFY/LISTENvàFOR UPDATE SKIP LOCKEDtừ Postgres 9.5 trở đi cùng giải quyết việc thông báo tác vụ mới và ngăn xử lý trùng lặp giữa các worker- Redis, Kafka, RabbitMQ, Amazon SQS là những lựa chọn mạnh, nhưng phụ thuộc mới sẽ bổ sung các chế độ lỗi và chi phí học tập vào môi trường phát triển, kiểm thử và vận hành
- Hàng đợi Postgres cũng không phải vạn năng: push/pop có thể chậm hơn Redis, và có thể phát sinh chi phí vận hành như cơ sở dữ liệu hoặc máy chủ hàng đợi riêng, hay
VACUUMthường xuyên hơn - Nên trừu tượng hóa mã tác vụ nền để không bị ràng buộc vào backend hàng đợi, và chỉ xem xét công nghệ hàng đợi khác khi công nghệ quen thuộc không đáp ứng được yêu cầu
Cách hàng đợi Postgres hoạt động
- Công nghệ hàng đợi Postgres gồm hai thành phần
- pub/sub để thông báo và nhận tác vụ mới
- khóa hàng để ngăn nhiều worker xử lý cùng một tác vụ
- Cả hai tính năng đều được cung cấp sẵn từ Postgres 9.5, phát hành năm 2016
- Dùng
NOTIFYcùng vớiLISTENcó thể gắn pub/sub vào ứng dụng FOR UPDATE SKIP LOCKEDsẽ lấy khóa hàng trên các bản ghi khớp điều kiện và bỏ qua những bản ghi đã bị khóa- Khi áp dụng cho bản ghi tác vụ, có thể tạo truy vấn xử lý hàng đợi như
SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1
- Khi áp dụng cho bản ghi tác vụ, có thể tạo truy vấn xử lý hàng đợi như
SKIP LOCKEDcung cấp một góc nhìn “không nhất quán” về dữ liệu, nhưng đặc tính này lại hữu ích trong hàng đợi- Các tác vụ đang được xử lý sẽ không hiển thị với worker khác do khóa hàng
- Hành vi này cho phép loại trừ lẫn nhau phân tán
- Khi dùng
NOTIFYđể báo tác vụ mới cho các worker đangLISTEN, có thể xây dựng cả xử lý theo lô định kỳ lẫn xử lý tác vụ thời gian thực
Vì sao Redis trở thành mặc định cho tác vụ nền
- Xử lý các tác vụ chạy lâu là một lựa chọn kỹ thuật có nhiều trade-off
- Các lựa chọn hàng đợi/message broker được dùng rộng rãi gồm
- Redis: kho dữ liệu in-memory và backend của nhiều thư viện tác vụ nền
- Apache Kafka: nền tảng streaming sự kiện phân tán do Apache Foundation quản lý
- RabbitMQ: được giới thiệu là message broker được triển khai rộng rãi
- Amazon SQS: SaaS của Amazon cung cấp hàng đợi có khả năng mở rộng cao
- Trong background jobs topic trên GitHub, 5 thư viện phổ biến nhất đều dựa trên Redis
- Redis lưu dữ liệu trong bộ nhớ nên chèn và truy vấn nhanh, đồng thời cung cấp API pub/sub và các cấu trúc dữ liệu
list,set, phù hợp để triển khai hàng đợi - Khả năng mở rộng của Redis khiến nó trở thành lựa chọn mặc định với nhiều developer, và mặc định có sức ảnh hưởng lớn trong lựa chọn công nghệ
Tiêu chí cần xem xét trước khả năng mở rộng
- Nỗi ám ảnh “scale” của ngành công nghệ có thể đẩy sự đơn giản, khả năng bảo trì và việc giảm tải nhận thức cho developer xuống phía sau
- Nhiều ứng dụng không cần mở rộng ở mức Google, Facebook hay Uber
- Khi bắt đầu một dự án hoặc doanh nghiệp, thay vì tối ưu khả năng mở rộng ngay từ đầu, cần xem xét trước các tiêu chí sau
- Công nghệ mà đội ngũ hiểu rõ
- Công nghệ đủ tốt để đáp ứng nhu cầu người dùng
- Giải pháp phù hợp với năng lực đội ngũ và tốn ít công sức nhất
- Postgres trên thực tế cũng mở rộng tốt, nhưng trong use case hàng đợi, nó không phải hệ thống hàng đợi chuyên dụng có khả năng mở rộng mạnh nhất
- Là phần mềm đa dụng, Postgres xử lý khá tốt nhiều việc, và hàng đợi cũng có thể được xem là một trong số đó
Đánh giá theo “công nghệ nhàm chán”
- Khi chọn công nghệ hàng đợi, câu hỏi quan trọng nhất là hiện bạn đang dùng và hiểu rõ công nghệ nào
- Công nghệ đã dùng và đã hiểu rõ sẽ tạo ít gánh nặng hơn khi thêm vào software stack
- Nếu bạn đã dùng cơ sở dữ liệu quan hệ và cơ sở dữ liệu đó là Postgres, có thể cân nhắc hàng đợi Postgres trước các phần mềm khác
- “Công nghệ nhàm chán” không phải là một danh sách cố định mà thay đổi tùy theo công nghệ hiện đang sử dụng
- Với ứng dụng tập trung vào truyền thông điệp, RabbitMQ có thể là công nghệ nhàm chán
- Với ứng dụng tập trung vào caching, Redis có thể là công nghệ nhàm chán
- Với ứng dụng có nhiều dữ liệu quan hệ, Postgres có thể là công nghệ nhàm chán
- Nếu đưa Redis, Kafka, RabbitMQ, SQS vào chỉ để phục vụ tác vụ nền, chi phí sẽ lớn
- Thêm phụ thuộc hệ thống mới vào môi trường phát triển, kiểm thử và production
- Trong tương lai, Developer, DBA, SRE phải biết các chế độ lỗi và cấu hình của hệ thống mới
- Cần kiến thức về khôi phục sự cố, chẩn đoán vấn đề và giám sát hiệu năng
- Những unknown unknowns mà đội ngũ chưa biết vẫn là rủi ro
Cái giá của hàng đợi Postgres và tiêu chí lựa chọn
- Công nghệ nhàm chán không phải vạn năng, và Postgres cũng không ngoại lệ
- Chọn hàng đợi Postgres mang lại các lợi ích như sự quen thuộc, các chế độ lỗi đã biết và phân tán chi phí, nhưng đổi lại có thể phải trả các cái giá sau
- Push/pop của hàng đợi Postgres chậm hơn Redis đáng kể
- Thay vì một cơ sở dữ liệu quan hệ duy nhất, có thể cần cơ sở dữ liệu ứng dụng và cơ sở dữ liệu hàng đợi riêng
- Có thể cần máy chủ cơ sở dữ liệu riêng để mở rộng tác vụ nền một cách độc lập
- Có thể cần
VACUUMthường xuyên hơn, tạo thêm gánh nặng hiệu năng
- Không nên để Postgres, Redis, Kafka, RabbitMQ hay SQS trở thành mặc định tự động
- Lựa chọn công nghệ là một trade-off liên tục, cần được đánh giá theo yêu cầu của đội ngũ và ứng dụng
- Khi còn nghi ngờ, có thể áp dụng tiêu chí “chỉ cân nhắc phương án thay thế khi đã chứng minh được rằng công nghệ nhàm chán không đáp ứng yêu cầu”
Kiến trúc tác vụ nền có thể thay đổi hàng đợi
- Mã xử lý tác vụ nền không nên phụ thuộc vào một công nghệ hàng đợi cụ thể
- Khi ứng dụng phát triển, các công nghệ như memcached hay Redis có thể được thêm vào khi cần, và theo thời gian chúng cũng có thể trở thành “công nghệ nhàm chán” quen thuộc với đội ngũ
- Nếu trừu tượng hóa công nghệ hàng đợi, người dùng có thể chọn hàng đợi phù hợp với tác vụ
- Trong các thư viện tác vụ nền phổ biến nhất trên GitHub, ngoại trừ Hangfire, các thư viện còn lại không cung cấp lối thoát sang công nghệ hàng đợi khác ngoài Redis
- Với cấu trúc như vậy, muốn đổi hàng đợi thì phải viết lại mã ứng dụng
- Neoq là thư viện Go được xây dựng theo triết lý này
- Backend hàng đợi có thể dùng in-memory, Postgres hoặc Redis
- Người dùng có thể khởi tạo bằng backend hàng đợi khác mà không cần đổi mã ứng dụng
- Triển khai in-memory và Postgres được cung cấp trực tiếp, còn triển khai Redis dùng asynq
- Redis, Kafka, RabbitMQ, SQS có thể là những công nghệ tuyệt vời, nhưng không phải lúc nào cũng là hàng đợi phù hợp cho công việc đó hoặc có mức độ phức tạp thích hợp
1 bình luận
Các ý kiến trên Hacker News
Đến nay tôi đã xây dựng hệ thống tác vụ phân tán ba lần, và kinh nghiệm thực tế mà tôi khuyên trong nhiều năm là “hãy thiết kế dựa trên quy mô gấp 10 lần hiện tại”
Nếu hiện tại là 70 request/giây thì lấy mốc 700 request/giây; nếu cần 20 máy chủ chạy batch job thì lấy mốc 200 máy. Ngay cả startup tăng trưởng 100% mỗi năm thì sau 3 năm cũng chỉ lớn hơn 8 lần, nên vẫn có thời gian để viết lại khi tăng trưởng
Hệ thống đầu tiên tôi làm đã tránh SQL vì “khả năng mở rộng”, nhưng rồi gặp rất nhiều tình huống ngoại lệ cần tính toàn vẹn giao dịch và khá khổ sở
Hai hệ thống tác vụ phân tán sau đó dùng PostgreSQL làm bộ điều phối, và đúng nghĩa là được xây quanh
SELECT FOR UPDATE SKIP LOCKED. Một hệ thống thường điều khiển 350 worker, hệ thống kia áp dụng mức ưu tiên phức tạp cho hàng nghìn tác vụCả hai sẽ trụ tốt cho đến khi đạt quy mô doanh thu hằng năm hàng triệu đô la, và chẳng hạn hệ thống 350 worker kia chỉ cần chỉnh một chút là có thể mở rộng tới khoảng 2.000 CPU
Công nghệ siêu quy mô trông có vẻ khá rẻ cho đến khi bạn nhận ra mình cần transaction. Nhưng từ khoảnh khắc đó, việc giả lập ngữ nghĩa transaction trên một kho lưu trữ eventual consistency trở thành ác mộng kỹ thuật
Vì vậy hãy ngồi xuống và tính thử. Nếu công ty kiếm 100 triệu đô la mỗi năm thì hệ thống phân tán cần lớn đến mức nào, liệu có dễ kiếm một instance PostgreSQL xử lý được tải đó không, hoặc shard theo từng client có đủ không; nếu được thì rất đáng cân nhắc PostgreSQL một cách nghiêm túc. Một trăm chuyện nhỏ sẽ trở nên dễ hơn
Giải pháp SQL thường đơn giản hơn, ăn ít tài nguyên tính toán hơn và dễ hỗ trợ vận hành hơn
Tuy nhiên để làm cho nó chạy được, bạn phải hiểu database đủ rõ để biết ngay từ đầu rằng có những tính năng như
SELECT FOR UPDATE SKIP LOCKED. Ngày nay ngày càng nhiều kỹ sư lớn lên trong môi trường các ORM nặng nề che khuất năng lực thật sự của DBMS, nên kiến thức kiểu này đang trở nên khá hiếmHệ thống phải chịu được tải đỉnh, và nếu không biết tải đỉnh là bao nhiêu thì phải chừa biên an toàn hoặc thêm cách để bỏ tác vụ hay trì hoãn khi cần
Mọi thứ đều là đánh đổi; chỉ tối ưu những gì cần tối ưu, và khả năng phán đoán đâu là thứ đó là đặc điểm của một kỹ sư giỏi
Nếu điều này còn mới với bạn, thì bạn vừa thu lại đủ giá trị cho một tuần đọc HN trong giờ làm rồi; đáng để viết ra giấy và dán lên thẻ ATM
Ở nhiều dự án, tôi chọn cách tiếp cận đơn giản hơn: dùng bảng thông thường và
SELECT FOR UPDATE SKIP LOCKED, thứ có sẵn ngay trong ORM/query DSL framework của mọi ngôn ngữhttps://www.pgcasts.com/episodes/the-skip-locked-feature-in-...
Không phải “web scale”, nhưng theo kinh nghiệm của tôi thì có thể dễ dàng mở rộng đến hàng nghìn background job
Tôi đã dùng cách này cho công việc ở một tổ chức lớn mà không gặp vấn đề. Không cần triển khai đặc biệt hay hạ tầng mới, chỉ cần chạy vài worker thread trong app. Cũng có thể có một thread đặt lại các tác vụ bị bỏ rơi
Tuy nhiên trong 3 năm thực tế chưa từng xảy ra tình huống đó; mọi thứ đều nằm trong
try/catch, nếu thất bại thì đưa lại vào queue, và ứng dụng Java cũng rất ổn địnhdelete from taskwhere task_id in( select task_idfrom taskorder by random() -- use tablesample for better performancefor updateskip lockedlimit 1)returning task_id, task_type, params::jsonb as params[1] https://taylor.town/pg-task
Bạn nói “hàng nghìn background job”, nhưng queue thường nên được nhìn theo định luật Little, tức là phải nói đến các tỷ lệ như tốc độ đưa tác vụ vào trung bình mỗi giây và thời lượng trung bình của tác vụ. Chỉ số lượng thô không có nhiều ý nghĩa
Ban đầu dùng
UPDATE ... SETngây thơ cũng được, nhưng nó giữ quá nhiều lock. Bạn có thể dùng subquerySELECTtrongUPDATE, hoặc dùngSELECT FOR UPDATE SKIP LOCKEDđể tối ưu lock khi lấy tác vụ, nhưng cuối cùng các truy vấn lấy tác vụ có thể chặn lock của nhau và làm queue dừng lạiKhi đó bạn có thể tắt luồng đưa tác vụ vào để DB thở được, nhưng các tác vụ đưa vào bị mất sẽ gây mất dữ liệu, và thường vấn đề cốt lõi là các thao tác lấy tác vụ chặn lẫn nhau
Cũng có thể vội vàng shard bảng tác vụ để tránh lock, nhưng việc phân phối trên nhiều worker rất mong manh và có thể dẫn đến mất dữ liệu. Bạn cũng có thể bỏ một phần tác vụ ngẫu nhiên, nhưng dĩ nhiên đó là mất dữ liệu
Những lựa chọn này cực kỳ căng thẳng khi đang vận hành, và nếu không thiết kế lại từ đầu thì cũng rất khó khôi phục. Trừ khi bạn chỉ có vài khách hàng và chắc chắn mãi mãi chỉ ở mức vài chục tác vụ mỗi giây, còn sau khi tự trải qua tình huống này, nếu có thể tôi sẽ chọn công nghệ queue thật sự thay vì database
Tôi thường thấy kiểu phê phán như “các kỹ sư bắt chước hạ tầng của FAANG để trông ngầu, dù nó hoàn toàn khác nhu cầu của họ”, nhưng tôi nghĩ phần lớn là vấn đề về kiến thức và tài liệu hóa
Nếu muốn làm theo FAANG hoặc các startup khác để xây dựng kiến trúc dựa trên hàng đợi có thể mở rộng vô hạn, có hàng chục hướng dẫn, tutorial và white paper chất lượng cao giúp bạn thiết lập các giải pháp có khả năng mở rộng như Redis, SQS trong vài giờ
Chi phí bảo trì cao hơn, nhưng bạn có thể bắt đầu bằng cách copy-paste lệnh, code và cấu hình từ các nguồn có uy tín
Ngược lại, nếu muốn dùng
NOTIFYcủa PostgreSQL và tìm “SQLALchemy notify listen postgres”, bạn chỉ thấy vài câu hỏi Stack Overflow không có lời đáp và vài GitHub Gist thiếu ngữ cảnhTôi rất muốn thử cách này trong side project, nhưng không có dư 2–3 ngày tự mày mò một mình
Nhìn bề ngoài, lựa chọn có vẻ là giữa “đơn giản nhưng không mở rộng được, tức chỉ dùng PostgreSQL” và “phức tạp nhưng mở rộng được, tức Redis/SQS, v.v.”; khi đó người ta sẽ nói tôi bị công nghệ hào nhoáng và FAANG làm mờ mắt nên chọn khả năng mở rộng không cần thiết
Nhưng nếu tính cả hướng dẫn và tài liệu, lựa chọn thực tế gần với “phức tạp và khả năng mở rộng cũng không dự đoán được, vì tôi không biết cách triển khai và các cạm bẫy” so với “đơn giản và có thể mở rộng, cách mà mọi người thực sự đang làm”. Khi đó lựa chọn của kỹ sư đi theo FAANG trông hợp lý hơn nhiều
NOTIFYthì chỉ cần đọc https://www.postgresql.org/docs/current/sql-notify.html, không cần kỹ năng tìm kiếmVí dụ, nếu dùng PostgreSQL với tải thấp, các việc như migration schema, thêm ràng buộc mới, phân tích gần như là chuyện nhỏ
Dùng SQS, Cassandra, v.v. thì có được khả năng mở rộng/tính sẵn sàng, nhưng khi nhận ra thiết kế ban đầu không phù hợp, việc thay đổi sẽ mất thời gian hơn nhiều. Chẳng hạn khi phía kinh doanh yêu cầu thêm ràng buộc kiểu “người dùng loại foo không được kết hợp đồng thời giá trị bar”
Không có PostgreSQL vẫn có thể triển khai, nhưng nhất là khi cần thay đổi thì không dễ hoặc đơn giản
Vì vậy để giữ tính linh hoạt, tôi nghĩ nên dùng PostgreSQL, và khi biết chắc điều gì sẽ không thay đổi thì hãy dùng thêm thứ khác trên nền PostgreSQL. Tất nhiên trường hợp này sẽ phát sinh overhead về hạ tầng và bảo trì
Rốt cuộc luôn là sự đánh đổi, và cần biết đánh đổi cái gì, khi nào, với cái gì
Tôi không nghĩ việc mọi người tự triển khai worker tác vụ nền tùy chỉnh trong ứng dụng, dù backend là PostgreSQL hay hàng đợi khác, là lý tưởng
Tác vụ nền có nhiều điểm tinh vi và chi tiết triển khai rất dễ sai, nên thường tốt hơn là để một thư viện hoặc framework chuyên dụng, bao quát hơn đảm nhận
Nếu mọi ứng dụng Rails đều không có Sidekiq/Active Jobs mà mỗi nơi tự triển khai worker nền tùy chỉnh, có lẽ ứng dụng Rails đã mang tiếng tệ hơn nhiều về độ tin cậy so với hiện nay
Chỉ cần chọn giải pháp mang lại lợi ích lớn nhất mà không đi quá xa mục tiêu chính
Tôi từng thấy một startup nơi các lập trình viên dùng khoảng 80% thời gian để vật lộn với công cụ và hạ tầng. Runway còn 3 tháng, và giờ ở cuối đường băng đó chỉ còn một cái hố lớn. Nghĩ lại vẫn còn rùng mình
Có vẻ chưa đủ sự tôn trọng đối với độ phức tạp
Nhược điểm lớn nhất khi dùng PostgreSQL làm bus publish/subscribe cùng
LISTEN/NOTIFYlàLISTENlà tính năng theo session, nên không phù hợp với connection pooling theo từng câu lệnhNếu dùng cách này, nên dùng advisory lock. Các loại khóa tường minh khác tạo gánh nặng lớn hơn cho database, còn advisory lock được thiết kế có chủ đích để rất nhẹ
Ví dụ triển khai tôi thích là que, đã được port sang nhiều ngôn ngữ
NOTIFY/LISTENlà khi có vấn đề thì khó chẩn đoánGần đây cũng có vấn đề là sau một thời gian, tất cả
NOTIFY/LISTENđều dừng, chỉ khởi động lại database mới giải quyết được, nên đã phải ngừng dùng https://dba.stackexchange.com/questions/325104/error-could-n...Skype nhiều năm trước đã dùng PostgreSQL làm hàng đợi cùng với một plugin nhỏ để xử lý toàn bộ CDR. Không biết bây giờ họ còn dùng không, nhưng theo chuẩn 10 năm trước thì đó là “quy mô web”, và trong khi mọi người trên Internet tranh luận rằng dùng database làm queue là anti-pattern, nó vẫn cứ chạy tốt
Có transaction thì khá tiện
https://wiki.postgresql.org/wiki/SkyTools
Vì đã dùng khá nhiều trong công việc nên tôi cũng từng vài lần thuyết trình về chủ đề này ở Sydpy. Nếu bạn đã vận hành và hỗ trợ PostgreSQL tốt rồi thì nó hữu ích
Tuy nhiên nếu là bây giờ thì có lẽ tôi sẽ dùng queue chuyên dụng, và riêng RabbitMQ thì sẽ tránh
Ngay cả khi lo bị khóa vào vendor, các thao tác cơ bản của queue thực ra nhỏ đến mức gần như chỉ là push/pop, nên viết theo cách dễ chuyển đi khi cần cũng tương đối dễ
Nếu vì đã vận hành database sẵn mà nhét cả queue vào database, thì lập luận đó là con dao hai lưỡi. Không có gì đáng chúc mừng, nhưng mớ hỗn độn của queue giờ có thể kéo sập cả kho dữ liệu chính
Một trong những lợi thế lớn nhất khi dùng PostgreSQL làm queue ứng dụng là mọi tác vụ bất đồng bộ đã lập lịch đều được hưởng lợi từ tính transaction
Ví dụ có một thay đổi backend phức tạp sau khi đăng ký thành viên để gửi email; nếu sau khi đưa tác vụ gửi email vào mà phần sau đó trong cùng transaction thất bại và rollback, thì tác vụ email đó ngay từ đầu sẽ không được đưa vào queue
Với email, nếu chờ xác nhận gửi rồi thất bại, bạn phải chọn giữa làm transaction thất bại và chấp nhận khả năng email bị gửi trùng, hoặc tiếp tục và chấp nhận khả năng mất email
Lợi thế lớn nằm ở các đường code sửa DB bất đồng bộ. Vì việc consume tác vụ và cập nhật DB nằm trong cùng một transaction, nên có thể xử lý hoàn toàn bằng transaction cho ngữ nghĩa đúng một lần
Trong PostgreSQL, tôi đặt một bảng log transaction chứa sự kiện được trigger và
pg_current_xact_id()của transaction đó. Cũng có thể dùngxmintích hợp của hàng, nhưng phải để ý transaction wraparound. Khi insert vào hàng này thìNOTIFYsẽ xảy raMột background process chạy lặp lại, chọn tất cả các hàng trong bảng transaction có transaction ID nằm giữa
xmincủa lần chạy trước vàpg_snapshot_xmin(pg_current_snapshot())hiện tại. Nó map các sự kiện đó thành tác vụ rồi gửi lên SQS, ghi lạixmin, sau đó dùngLISTENđể chờNOTIFYtiếp theoVới queue PostgreSQL, đúng là nếu sau khi đưa tác vụ gửi email vào mà transaction thất bại về sau, email sẽ không vào queue
Code database cần tách biệt hoàn toàn thì đáng được tách thành các transaction khác nhau. Ngược lại, nếu user chưa được tạo trong DB thì bạn có thể cũng không muốn gửi email đăng ký, nên còn tùy tình huống
Với queue dựa trên Redis hoặc gần như bất kỳ queue nào khác, bạn sẽ nhanh chóng gặp tình huống một tác vụ chạy với giả định rằng bản ghi database đã tồn tại trước khi transaction được commit. Code để sửa việc này thường gượng gạo và phức tạp
Điều tôi thích ở Kafka là nó đơn giản là một log chỉ append, và client về bản chất chỉ giữ offset
Về mặt khái niệm thì rất dễ hiểu, bền vững, và khá chống chịu lỗi, vì có thể quay lại bất kỳ offset nào để đọc lại
Đáng tiếc là do đặc tính phân tán, Kafka kéo theo đủ độ phức tạp, nên cuối cùng với hầu hết use case thì không đáng
Cá nhân tôi muốn có thứ gì đó tương tự nhưng dễ vận hành hơn. Trên một node đơn lẻ, nó có thể xử lý hàng trăm, thậm chí hàng nghìn sự kiện mỗi giây; nếu không có độ phức tạp phân tán thì sẽ khá tốt
Về lý thuyết PostgreSQL cũng có thể làm được, chỉ cần không bao giờ xóa hàng. Có lẽ đó cũng có thể là câu trả lời
Nếu không cần toàn bộ tính năng như consumer group, partition, thì cấu hình consumer cũng sẽ đơn giản hơn nhiều
Windmill đang dùng đúng cách này. Windmill là một lựa chọn mã nguồn mở thay thế Retool và là công cụ gần với một Airflow hiện đại; họ chạy benchmark hằng ngày
Khi chạy một worker Windmill và PostgreSQL bằng container trên một instance GitHub CI vừa phải, benchmark xử lý 1.200 tác vụ mỗi giây. Thêm worker thì mở rộng ổn định lên đến 5.000 tác vụ mỗi giây
Họ đang xem xét dùng Citus để vượt rào cản 5.000 tác vụ mỗi giây trên instance multi-tenant
https://github.com/windmill-labs/windmill/tree/benchmarks
Thời còn ở mức khoảng 10 thông điệp mỗi giây, một số hàng đợi từng dùng PostgreSQL và cũng mở rộng được khá tốt. Nhưng nói thật, việc thiết lập SQS hay các stack hàng đợi khác trên AWS, GCP, Azure quá dễ, chúng được tạo ra đúng cho mục đích đó, và những thứ như DLQ cũng được tích hợp sẵn
Tôi không thấy lý do gì để chọn con đường này, rồi phải lo hệ thống đó sụp đổ và ảnh hưởng đến cả trạng thái của phần DB còn lại
Tôi thích nguyên tắc “hãy dùng công cụ đơn giản nhất”, nhưng đôi khi có vẻ các kỹ sư đi quá xa, để rồi dù các lựa chọn thay thế phổ biến khá rẻ và đơn giản, cuối cùng chỉ còn lại công cụ ngớ ngẩn nhất kèm theo những lưu ý mơ hồ
Lưu trạng thái công việc trong DB giúp có thể truy vấn trạng thái một cách dễ nhìn. Dù không thể hiện chính xác trạng thái thực tế, nó vẫn hữu ích khi phân tích sự cố trong vận hành. Đặc biệt là hầu hết hàng đợi công việc chỉ đơn giản xóa các bản ghi đã xử lý
Và nếu biến mọi tác vụ nền thành idempotent, thì cách làm kiểu “gửi một thông điệp vào hàng đợi công việc để yêu cầu xử lý tác vụ này” gần như luôn an toàn
Nếu chỉ dựa vào message queue, khi có vấn đề hiệu năng thường rất khó biết chuyện gì đang xảy ra. Ví dụ RabbitMQ có thể cho biết kích thước hàng đợi, nhưng gần như không có, hoặc rất hạn chế, chức năng kiểm tra dữ liệu bên trong
Có những phần mềm có thể được dự đoán là sẽ không bao giờ vượt quá năng lực của PostgreSQL, và ngay cả nếu vượt quá thì cũng có thể thiết kế để dễ chuyển sang dịch vụ khác
Và cũng có thể đơn giản là muốn xây dựng một hệ thống công việc có transaction ngay trong PostgreSQL
Chúng tôi dùng Oban của Elixir để xử lý hàng trăm nghìn đến hàng triệu tác vụ mỗi ngày bên trong PostgreSQL
Việc có ngữ nghĩa transaction quanh các tác vụ nền, chẳng hạn chỉ lên lịch gửi email khi transaction thành công, cực kỳ tiện lợi
Cần tinh chỉnh
autovacuummột chút, nhưng sau khi dọn dẹp xong thì nó rất phù hợp với chúng tôi