Building a Faster C# Driver for ScyllaDB…on our Self-Baked C#-Rust FFI Framework

Bài tóm tắt ScyllaDB 2026 3. Quy mô lớn Độ khó: Khó

ScyllaDB · 2026 · 3. Quy mô lớn · Độ khó: Khó — bài gốc

Phần 2/2: phần 1 là ScyllaDB 2026. Bài nhà cung cấp; benchmark do chính họ chạy trên một máy với cụm 3 node cục bộ, mọi con số là theo ScyllaDB. Dự án hiện tạm dừng để ưu tiên Node.js RS Driver.

Bối cảnh

  • Hiện đại hoá một codebase driver C# khoảng mười năm tuổi (fork của DataStax) bằng cách đặt nó lên lõi Rust driver, theo chiến lược một lõi cho mọi ngôn ngữ (xem phần 1).
  • Kiến trúc hai driver lệch nhau: C# có đối tượng Cluster quản lý session và kết nối điều khiển (cho phép đọc metadata trước khi có session), Rust thì không. Họ dựng một kiến trúc lai giữ API C# cũ.

Thay đổi chính

  • Đồng bộ build: lâu nay phải tự build và link thư viện Rust trước mỗi lần chạy test, dẫn tới nhiều buổi gỡ lỗi chỉ vì thư viện cũ; cuối cùng tự động hoá việc build/link ngay trong .csproj.
  • Callback thay cho dữ liệu trung gian: để không cấp phát tạm, bên giữ buffer (Rust) điều khiển còn bên giữ dữ liệu (C#) điền vào khi được hỏi. Serialization để lại ở C# (tham số là Object tổng quát, chuyển metadata kiểu sang Rust thì lợi bất cập hại), từng giá trị được chép vào builder mà Rust đặt trên stack của chính nó để tránh đuổi con trỏ. Các phương án nhanh hơn (một buffer liền, stackalloc lai) để dành nếu hiệu năng chưa đủ.
  • Race khi đóng session (TOCTOU): một luồng thấy session còn mở rồi chạy query, luồng khác đóng nó giữa chừng. Khoá phía C# không đủ vì trả về một Task không có nghĩa là Task đã xong: lệnh FFI vừa bắt đầu thì khoá đã nhả. Chuyển đồng bộ xuống Rust: RWLock trên session native, query dùng try_read() không chặn (lấy không được thì từ chối ngay), đóng session dùng write().
  • Lỗi thành exception: trait ErrorToException ánh xạ từng loại lỗi Rust sang exception C# tương ứng qua bảng con trỏ hàm tạo sẵn khi khởi động; quy tắc "không ném exception C# khi còn Rust trên stack"; panic của Rust đổi sang abort ngay thay vì unwind qua FFI.
  • Metadata: topology dùng cache đối tượng Host toàn cục và tự đếm tham chiếu kiểu Arc cho snapshot native, vì GC chỉ thấy một wrapper nhỏ chứ không thấy bộ nhớ thật phía Rust. Schema metadata trước đây nửa cache nửa lười nên dữ liệu lệch nhau; nay là snapshot tại thời điểm truy cập.
  • Log: log của Rust trước kẹt ở stdout, nay được chuyển tiếp từ tracing sang logger của C#, chỉnh mức và đích chung.

Kết quả (theo ScyllaDB)

  • Trong mọi kịch bản benchmark (ghi, đọc, (de)serialization kiểu phức tạp), driver mới ít nhất nhanh bằng bản fork DataStax; với tải đồng thời cao (100 worker) nhanh gần gấp đôi và không xa mức của driver Rust gốc.

Vì sao

  • Theo ScyllaDB: giao phần nặng (thực thi query, pool kết nối, định vị replica) cho Rust vừa dễ bảo trì hơn vừa nhanh hơn, nhất là khi tải đồng thời cao.

Bài học cho SWE

  • Khi bọc một lõi viết bằng ngôn ngữ khác, giữ API cũ cho người dùng và chấp nhận một lớp kiến trúc lai ở giữa.
  • Đồng bộ đặt ở phía có vòng đời thật của tài nguyên; khoá quanh một lời gọi trả về Task không bảo vệ được phần việc chạy sau đó.
  • GC không thấy bộ nhớ native nằm sau wrapper, nên tài nguyên lớn cần dọn chủ động.

Liên quan