Osprey: Open Sourcing our Rule Engine

Bài tóm tắt Discord 2026 3. Quy mô lớn Độ khó: Trung bình

Discord · 2026 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bài công bố mã nguồn mở công cụ safety; phần nghiệp vụ chống lạm dụng chỉ tóm tắt ngắn, giữ phần kiến trúc.

Bối cảnh

  • Discord cần một hệ đánh giá hoạt động của user theo thời gian thực để phát hiện lạm dụng: xử lý hàng nghìn event mỗi giây, cho team viết luật có hiệu lực trong vài phút, trả về phán quyết rõ ràng, cho thấy luật đã chạy thế nào, và nối kết quả điều tra trở lại thành luật mới.

Thay đổi chính

  • Mô hình: Action (event JSON có schema theo loại) đi vào engine, chạy qua Rules viết bằng SML (ngôn ngữ dựa trên cú pháp Python để người ít kỹ thuật viết được, có kiểm tra tĩnh tuỳ biến); UDF viết bằng Python thật làm thư viện chuẩn và gọi dịch vụ ngoài (ví dụ điểm spam của một model); Features là biến toàn cục được xuất ra để index; Entities (user, server, email) nhận Effects (nhãn, phân loại). Action đồng bộ nhận lại Verdict.
  • Kiến trúc:
    • Action vào đồng bộ qua gRPC hoặc bất đồng bộ qua message queue (Pub/Sub hoặc Kafka).
    • Osprey Coordinator đứng giữa và một fleet rules worker, cân bằng hai loại và ưu tiên action đồng bộ khi có đợt dâng action bất đồng bộ.
    • Rules worker giữ bản luật mount cục bộ; ở Discord luật được phân phối qua etcd nên đẩy luật mới lên production không cần deploy.
    • Kết quả đi ra các output sink tuỳ chọn; sink mặc định đẩy vào Kafka → Druid cho UI điều tra (luồng event, chuỗi thời gian, Top N, trang lịch sử của từng entity). Ở Discord còn ghi mọi kết quả vào BigQuery.
  • Quy mô (12/2025): khoảng 400 triệu action/ngày, 204 loại action, 2.288 luật, mỗi action trung bình kích hoạt khoảng 500 luật, 99 UDF (nhiều UDF gọi RPC sang dịch vụ nội bộ). Scale bằng cách thêm rules worker.
  • Tách để open source: bản gốc dùng thư viện nội bộ. Họ cắt dần đến bản tối thiểu: nhận event cả qua gRPC lẫn queue (không bắt buộc qua Coordinator), hỗ trợ thay thế mở cho phụ thuộc độc quyền (Kafka bên cạnh Pub/Sub), nạp luật từ file lúc build (etcd thành tuỳ chọn), UDF và output sink cắm qua thư viện Pluggy. Phần cấu hình nội bộ chuyển ra sau các plugin đó, thay từng phụ thuộc một và deploy dần vào production của chính Discord (không làm cùng lúc) để giữ tương đương. Năm kỹ sư mất vài tháng; ví như "sửa động cơ khi đang chạy trên cao tốc".
  • Không mở được: bộ UDF/luật đầy đủ (ví dụ dịch vụ Counter gắn chặt với Scylla nội bộ) và các sink như BigQuery.

Vì sao

  • Hai đường vào (đồng bộ cho caller cần phán quyết, bất đồng bộ cho phần còn lại) cho phép một engine phục vụ cả hai mà không để tải nền làm chậm đường đồng bộ (Event-driven, message queue và Kafka, Load shedding, rate limit và backpressure).
  • Luật là dữ liệu phân phối qua kho cấu hình, không phải code deploy, nên tốc độ phản ứng tính bằng phút.

Bài học cho SWE

  • Khi một hệ phục vụ cả request đồng bộ và luồng bất đồng bộ, cần một điểm điều phối biết ưu tiên; không có thì đợt dâng của luồng nền sẽ dồn lên latency của caller.
  • Logic thay đổi nhanh (luật) nên tách khỏi chu kỳ deploy, kèm kiểm tra tĩnh trước khi phát hành.
  • Muốn open source một hệ nội bộ thì biến mỗi phụ thuộc nội bộ thành một điểm cắm, và chạy chính bản đã tách ở production.
  • Xuất mọi biến trung gian sang kho phân tích làm cho "tại sao luật này bắn" trả lời được.

Liên quan