Incident report: Runs list and logs degraded
Bài tóm tắt Trigger.dev 2026 3. Quy mô lớn Độ khó: Khó
Trigger.dev · 2026 · 3. Quy mô lớn · Độ khó: Khó — bài gốc
Bối cảnh
- Trigger.dev tách hai đường: thực thi run (Postgres là nguồn sự thật + Redis cho queue) và giao diện quan sát (runs list, logs, traces) chạy trên ClickHouse. ClickHouse chỉ trả danh sách run ID khớp bộ lọc; chi tiết run luôn đọc từ Postgres.
- Task
output,errorvà span attributes được lưu trong cộtJSONcủa ClickHouse. Mỗi task trả về một hình dạng khác nhau, nên tổng schema của cột rất lớn.
Chuỗi sự kiện
- 17/6/2026: ClickHouse (dịch vụ managed) tự nâng cấp 25.12 → 26.2. Bản mới áp giới hạn 1.000 node cho "type tree" của cột JSON, và với background merge thì giới hạn này hard-code (merge chạy không có query context nên luôn dùng mặc định). Không setting nào nâng được.
- Không part nào vượt giới hạn, nhưng hợp schema của nhiều part thì vượt: ví dụ sáu part có 504–766 path, hợp lại là 1.471. Mảng lẫn kiểu bị suy thành kiểu lồng sâu vì setting tạo
Array(Dynamic)đang tắt; cơ chế tương thích ngược của ClickHouse lẽ ra giữ hành vi cũ thì bị hỏng. - Bảng events không chặn ở lúc insert, nên part không merge được tích dần từ 17/6. Bảng runs vô tình được bảo vệ: đường insert đi qua cùng phép kiểm tra nên run phức tạp bị từ chối, thể hiện thành triệu chứng "run biến mất khỏi danh sách".
- Tối 29/6: để sửa triệu chứng đó, nhóm nâng setting độ phức tạp cho phía đọc. Đọc khỏi, nhưng gỡ luôn lớp bảo vệ, và part không merge được bắt đầu vào bảng runs.
- 30/6: part chồng chất. Query runs list dùng
FINAL(đối chiếu mọi part lúc query) nên càng nhiều part càng tốn RAM, merge càng chậm, part càng nhiều: một vòng phản hồi. Một khách poll API runs list dày đặc giữ vòng này quay. ClickHouse chạm trần RAM khoảng 180 GiB; bảng events chạm trần 3.000 part và từ chối insert.
Tác động
- Khoảng 09:00–15:30 UTC: runs list chậm hoặc lỗi; p99 từ khoảng 1,5 giây lên khoảng 30 giây; lúc 09:23 khoảng 88% request lỗi. Log/trace mới không được ghi từ khoảng 10:16 tới đầu giờ chiều.
- Run vẫn thực thi bình thường, không mất run nào. Khoảng 0,3% lần ghi run không vào ClickHouse; hầu hết tự đúng ở lần ghi sau.
Hồi phục
- Scale dọc database (hết OOM nhưng không chữa merge) → detach các part không merge được theo lô (không xoá, có thể gắn lại) → khoảng 15:35 bảng runs hồi mạnh, cùng lúc một replica bị kẹt (do một backup chạy lâu giữ lại) cuối cùng tắt và nhả lock. Nhóm thừa nhận không quy được hồi phục cho riêng một hành động.
- Nhờ ClickHouse nâng giới hạn ở tầng dịch vụ không có tác dụng (vì hard-code), đã revert.
Khắc phục (theo Trigger.dev)
- Bật setting để mảng lẫn kiểu suy thành
Array(Dynamic); cân nhắc bớt dùng cột JSON native ("không phải lần đầu nó gây chuyện"). - Cảnh báo trực tiếp trên số part, tỉ lệ merge lỗi, RAM; các tín hiệu này lẽ ra báo trước hơn 12 giờ. Cảnh báo tỉ lệ thành công của API có kêu nhưng là tín hiệu hạ nguồn.
- Tách instance ClickHouse chỉ đọc cho runs list và log, để query nặng không tranh RAM với merge.
- Partition nhỏ hơn, batch insert lớn hơn để tạo ít part hơn.
- Thay đổi ClickHouse production phải thử trên đường merge với dữ liệu giống production.
- Giới hạn chi phí query và rate limit trên runs list để một caller không khuếch đại sự cố cho mọi người.
Bài học cho SWE
- Tách đường thực thi khỏi đường quan sát là lý do sự cố này không mất dữ liệu. Nguồn sự thật nằm ở Postgres, ClickHouse chỉ là bản phân tích.
- Một bản sửa triệu chứng có thể gỡ một lớp bảo vệ không ai biết là đang chịu lực (Sự cố và postmortem).
- Nâng cấp managed tự động là một thay đổi production mà mình không kiểm soát.
- Query đọc nặng (
FINAL) tranh tài nguyên với việc dọn dẹp nền tạo ra vòng phản hồi; tách đọc ra replica riêng là cách cắt vòng (Scale dọc trước khi scale ngang). - Một khách có thể giữ vòng xoắn quay, nên cần giới hạn theo từng caller (Load shedding, rate limit và backpressure).
- Cột schema không giới hạn (JSON tuỳ ý) là rủi ro tiềm ẩn chờ một giới hạn mới lộ ra.
Liên quan
Được nhắc tới trong
Chủ đề (3)
Công ty (1)
Bài tóm tắt (1)