Counter Service: How we rewrote it in Rust

Bài tóm tắt Grab 2025 4. Điều chỉnh Độ khó: Trung bình

Grab · 2025 · 4. Điều chỉnh · Độ khó: Trung bình — bài gốc

Bối cảnh

  • Phần lớn microservice của Grab viết bằng Go, nằm trong một monorepo. Rust vào dần: vài CLI, rồi một reverse proxy cho model serving (Catwalk). Câu hỏi tiếp theo là ROI của việc viết lại một service Go.

  • Tiêu chí chọn service (theo Grab):

    • đơn giản (một hai chức năng chính, viết lại được trong thời gian hợp lý);
    • traffic đủ lớn để tiết kiệm chi phí là đáng kể;
    • team sẵn lòng học Rust, để không phụ thuộc vào một người duy nhất.

    Viết lại "chỉ để dùng Rust" không phải lý do kinh doanh đủ mạnh.

  • Counter Service (team Integrity): đọc stream, đếm event, ghi vào ScyllaDB; endpoint gRPC BatchRead phục vụ tới hàng chục nghìn QPS lúc cao điểm.

Thay đổi chính

  • Không dịch từng dòng (Go viết theo kiểu Go chưa chắc là Rust tốt). Họ coi service là hộp đen: viết lại từ đầu theo contract gRPC và dữ liệu trong Scylla, nhắm tới output gần như giống hệt.
  • Kiểm tra thư viện: có đủ (tonic, scylla-rust-driver, fred.rs…), nhưng vài thư viện ít sao/không chính thức. Thư viện Redis "chính thức" không đáp ứng được nên đổi sang fred.rs.
  • Thư viện nội bộ chỉ có cho Go (config dựa trên Go template, chèn secret). Họ tự viết parser tương đương bằng nom.
  • Trở ngại:
    • borrow checker ít khó hơn dự kiến (lúc đầu cứ clone thoải mái, sau mới profile bằng flamegraph);
    • async là chỗ dễ vấp: lỡ gọi Redis bằng hàm đồng bộ (blocking) trong code async làm hiệu năng rất tệ, cho tới khi đổi sang bản non-blocking.
  • Kiểm chứng bằng shadow traffic cho cả hai service.

Kết quả (2025)

QPS chuẩn hoá Tài nguyên
Bản Go 1.000 20 core
Bản Rust 1.000 4,5 core

p99 tương đương, có khi còn hơi kém hơn bản Go. Tức là không nhanh hơn, nhưng rẻ hơn ~5 lần (Grab tóm tắt là tiết kiệm ~70–80%).

Vì sao

Theo Grab: hiệu quả tài nguyên ở một service nhiều traffic. Kết luận của họ:

  • "Rust nhanh hơn Go": sai (Go đủ nhanh);
  • "Rust hiệu quả hơn": đúng;
  • "Rust khó học": tuỳ, sync dễ, async khó.

Họ khuyến khích chọn Rust cho service mới, với điều kiện đầu tư đào tạo.

Bài học cho SWE

  • Chọn ứng viên viết lại bằng tiêu chí rõ ràng: đơn giản, traffic lớn, team sẵn sàng.
  • Viết lại theo contract (hộp đen) + shadow traffic, cùng tinh thần Grab 2016 (Grab 2016).
  • Lợi ích có thể nằm ở chi phí chứ không ở latency. Discord 2020 thì ngược lại, viết lại vì spike latency do GC (Discord 2020).
  • Hệ sinh thái nội bộ (thư viện config, secret) là chi phí ẩn khi rời ngôn ngữ chính của công ty.

Liên quan