A lesson in creating and using niche business DSLs at scale

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

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

Bối cảnh

  • Mỗi lệnh đặt vào OMS (Order Management System) phải qua nhiều kiểm tra nghiệp vụ tốn CPU, và lệnh sau phải chờ lệnh trước. Để giảm latency, Zerodha tách một phần kiểm tra ra một thành phần đứng trước tên là Veto: một reverse proxy kiểm tra lệnh theo luật động do đội quản trị rủi ro (RMS) đặt, từ chối ngay tại chỗ hoặc chuyển tiếp lên OMS. Lệnh bị từ chối không bao giờ tới OMS, nên OMS nhẹ tải hẳn.
  • Veto v1: luật viết bằng biểu thức kiểu công thức Excel trên thư viện govaluate, có hot reload, quản lý qua dashboard Frappe/ERPNext. Đội vận hành quen cú pháp, luật đơn giản viết nhanh.

Thay đổi chính

  • Vấn đề của DSL (theo Zerodha, chủ yếu là vấn đề con người): quá một độ phức tạp thì biểu thức không đọc nổi; không có biến, không có rẽ nhánh, chỉ ngoặc và AND/OR; một luật phải chẻ thành nhiều luật nhỏ (ví dụ một luật spread thành bốn). Biểu thức chỉ trả boolean nên không có thông báo lỗi riêng. Muốn thêm hàm mới phải đóng gói vào engine, tức developer vẫn phải tham gia, mất ý nghĩa của DSL cho người làm nghiệp vụ.
  • Chọn hướng mới bằng benchmark trên một luật thật từ production: govaluate 907 ns/op, Go plugin gốc 503 ns/op, Yaegi (trình thông dịch Go) 11.091 ns/op, Rego (OPA) 13.796 ns/op; plugin RPC của HashiCorp bị loại vì chậm. Chọn Go plugin gốc: nhanh như code gốc sau khi nạp, có type safety, Veto vốn viết bằng Go.
  • Veto v2: đội vận hành viết luật bằng Go thuần theo khung ba hàm: Slug, Validate (trả kết quả kèm thông báo cho khách), và TestData (mỗi luật tự mang test case, chạy trước khi được publish).
  • Lách các giới hạn của Go plugin (phải cùng phiên bản Go, cùng dependency, cùng GOPATH với host; không unload được, không nạp lại cùng một plugin): build host và plugin trong cùng image Docker với đường dẫn giả lập; gộp nhiều luật vào một .so bằng cách sửa AST để thêm tiền tố tên hàm theo luật; chèn hằng timestamp vào mã nguồn để mỗi bản build là plugin mới, nạp lại được khi hot reload; nhúng hàm Description liệt kê luật trong plugin.
  • Quy trình: dashboard có editor; server validation build và chạy test của luật, trả log build đầy đủ nếu lỗi; server compiler đồng bộ luật, biên dịch, đẩy plugin lên Veto production và gọi hot reload. Cả hai chạy trên một cụm K8s nội bộ.

Vì sao

  • Theo Zerodha, DSL "thông minh" thành nút thắt về khả năng dùng khi luật nghiệp vụ phức tạp dần; một ngôn ngữ đơn giản như Go lại dễ học với người vận hành hơn một DSL tự chế, và developer mở rộng được thoải mái.
  • Test đi kèm từng luật thay cho việc developer duyệt từng thay đổi, nên đội nghiệp vụ tự phát hành luật mà vẫn có lưới an toàn.

Bài học cho SWE

  • Chặn sớm ở một lớp nhẹ phía trước giúp hệ đắt tiền phía sau chỉ nhận việc hợp lệ.
  • DSL cho người không phải lập trình viên dễ phình thành một ngôn ngữ dở. Cân nhắc dùng ngôn ngữ thật với khung chặt.
  • Chọn công nghệ bằng benchmark trên workload thật (một luật production), không trên ví dụ đồ chơi.
  • Luật/cấu hình động cũng là một bản phát hành: bắt test tự mang theo trước khi nạp nóng vào production.

Liên quan