How we built a real-time service that handles 20,000 updates per second
Bài tóm tắt Trigger.dev 2025 3. Quy mô lớn Độ khó: Trung bình
Trigger.dev · 2025 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc
Bài của nhà cung cấp nền tảng background job; một phần là giới thiệu sản phẩm. Số liệu theo Trigger.dev.
Bối cảnh
- Task chạy nền (xử lý CSV, AI agent, video) cần cho người dùng thấy tiến độ theo thời gian thực. Dữ liệu run vốn đã nằm trong Postgres.
- Phản xạ đầu là WebSocket, nhưng ba vấn đề: (1) muốn subscribe cả run đã bắt đầu trước khi mở trang; (2) mỗi kết nối phải query DB lấy trạng thái đầu rồi giữ một kênh cập nhật riêng; (3) phải có hệ riêng để biết khi nào dữ liệu đổi (trigger, queue, polling), khó ở throughput cao.
Thay đổi chính
- Dùng replication slot của Postgres qua ElectricSQL: task cập nhật bảng
runs→ thay đổi vào WAL → replication slot giữ vị trí đã đọc → Electric stream thay đổi theo thứ tự. Client gửi query ("shape"), nhận dòng đầu kèm transaction ID làm con trỏ, rồi long poll (trả về khi có thay đổi hoặc sau 20 giây) chỉ lấy thay đổi mới của shape đó. - Xác thực bằng JWT có scope (đọc run nào, tag nào), ký bằng API key: không query DB mỗi request.
- Giới hạn kết nối theo gói (ví dụ 500 kết nối đồng thời) bằng sliding window trong Redis viết bằng Lua (sorted set, xoá mục hết hạn, thêm, đếm, vượt thì gỡ), chạy nguyên tử nên không race; mỗi mục hết hạn sau 5 phút.
- Cache bộ lọc thời gian theo shape ID: shape cần mệnh đề WHERE cố định; nếu "run tạo trong 1 giờ qua" tính lại mỗi lần render thì sinh vô hạn shape. Cache bộ lọc
createdAttheo shape (bộ nhớ + Redis, tươi 1 tuần, cũ 2 tuần). - Kết quả: 20.000 cập nhật run/giây lúc đỉnh, hơn 500 GB insert Postgres mỗi ngày, dưới 100 ms tới trình duyệt.
Vì sao
- Dữ liệu đã phải bền trong Postgres; dùng chính WAL làm luồng thay đổi giữ một nguồn sự thật, không cần hệ thông báo riêng, và cho phép subscribe sau sự kiện (Event-driven, message queue và Kafka).
- Postgres lo ghi, tầng sync lo fan-out tới trình duyệt (Realtime và fan-out).
Bài học cho SWE
- Realtime không đồng nghĩa với WebSocket: long poll trên luồng thay đổi có con trỏ đơn giản hơn và tự có khả năng bắt kịp.
- Mọi tham số của "subscription" phải ổn định (không phụ thuộc thời điểm render), nếu không phía server tạo trạng thái vô hạn.
- Đẩy kiểm quyền vào token ký sẵn để đường nóng không chạm DB.
Liên quan
#Realtime#Postgres logical replication#Replication slot#WAL#ElectricSQL#Shape#Long polling#JWT#Redis Lua#Sliding window rate limit
Được nhắc tới trong
Công ty (1)
Bài tóm tắt (2)