Tác giả: Tùng David

Thông tin liên hệ:

Thấy mọi người chia sẻ nhiều nên cũng góp chút usecase thực tế mà mình từng trải qua. Usecase này mình gặp được cách đây khoảng 6 tháng khi cộng tác trong team DevOps của 1 doanh nghiệp FinTech bên Sing. Hiện tại mình vẫn đang cộng tác cho doanh nghiệp này nên cũng muốn private thông tin cá nhân. Chủ yếu là có thể chia sẻ giá trị và đóng góp được gì đó giúp cho mọi người có thêm góc nhìn về tech nước ngoài hay bài toán về machine learning có gì khác.

(Update: Cảm ơn Mạnh và devopsedu.vn đã làm thumbnail và format bài viết giúp mình)

Tối ưu hóa hạ tầng Kubernetes cho dịch vụ Machine Learning real-time xử lý hàng tỷ sự kiện/ngày

Bài toán

Doanh nghiệp FinTech này cung cấp dịch vụ phân tích dữ liệu tài chính real-time cho hàng triệu người dùng sử dụng một cụm Kubernetes on-premise để chạy mô hình Machine Learning. Dịch vụ này có lúc xử lý hơn 1 tỷ sự kiện/ngày, đầu vào yêu cầu lúc đó chính xác team nhận được để cải tiến là:

  1. Độ trễ thấp: Tất cả dữ liệu phải được xử lý dưới 50ms.
  2. Sử dụng tài nguyên hiệu quả: Vì chi phí đầu tư hạ tầng rất cao, tối ưu tài nguyên là ưu tiên hàng đầu.
  3. Đảm bảo độ tin cậy: Không được phép downtime vì dịch vụ liên quan đến tài chính và giao dịch quan trọng.

Hạ tầng hiện tại

  • Cluster Kubernetes: 500 nodes, mỗi node có GPU cho mô hình ML inference.
  • Kafka: Làm hệ thống message queue cho dữ liệu input.
  • Redis: Dùng làm caching layer để lưu trạng thái tạm thời của các mô hình.

Vấn đề gặp phải

  1. Hiệu suất hệ thống không đạt kỳ vọng:

    • GPU utilization chỉ ở mức 30-40%, trong khi tài nguyên rất đắt đỏ.
    • Các mô hình ML inference chỉ xử lý 2,000 requests/giây, trong khi yêu cầu SLA là 5,000 requests/giây.
  2. Độ trễ tăng cao:

    • Một số requests vượt ngưỡng 200ms thay vì dưới 50ms như yêu cầu.
    • Trong giờ cao điểm, số lượng requests bị timeout tăng 20%, dẫn đến khách hàng phàn nàn.
  3. Bottleneck ở Redis caching:

    • Redis server logs báo hiệu lỗi “OOM command not allowed”, tức là không đủ memory để lưu trữ các trạng thái tạm thời.
    • CPU usage của Redis tăng đột biến, chiếm 90-95%, gây nghẽn cục bộ.
  4. Nghẽn mạng:

    • Pods báo lỗi “connection reset by peer” khi kết nối tới Kafka trong giờ cao điểm.
    • Tổng throughput của network giảm mạnh từ 10Gbps xuống còn 4Gbps, gây chậm trễ trong việc truyền dữ liệu.

Nguyên nhân

  1. GPU bị underutilized:

    • Scheduling không tối ưu: Kubernetes mặc định chỉ quan tâm đến tài nguyên CPU/RAM khi schedule pods, không tối ưu hóa cho workloads nặng về GPU.
    • Requests không được batching: Mỗi request xử lý riêng lẻ, gây lãng phí tài nguyên GPU vì thời gian khởi động inference pipeline quá lâu.
  2. Redis không phù hợp với workload:

    • Redis được thiết kế chủ yếu để lưu trữ dữ liệu key-value nhỏ. Khi trạng thái mô hình lớn dần (ví dụ hàng GB), Redis không thể xử lý hiệu quả.
    • Không có sharding: Redis chạy trên một instance duy nhất, dễ bị quá tải khi traffic tăng cao.
  3. Networking overload:

    • Các pods trao đổi lượng lớn dữ liệu thô qua HTTP1.1.
    • Kafka cluster quá tải vì cả ingestion và processing đều sử dụng chung một lớp (tier).
  4. Thiếu autoscaling linh hoạt:

    • Cụm Kubernetes không tích hợp KEDA hoặc Horizontal Pod Autoscaler (HPA), dẫn đến không thể tự động scale khi workload tăng cao (phần này thật ra chưa đưa ra được con số thích hợp để cấu hình).

Quá trình nhận diện vấn đề

  1. Quan sát metrics trên Prometheus và Grafana:

    • GPU usage ở mức thấp (~35%), trong khi các nodes luôn báo đủ GPU resources.
    • Latency của Redis (thông qua Redis Exporter) tăng đều từ 10ms lên 150ms khi traffic tăng.
    • Throughput của Kafka giảm đột ngột khi message queue size vượt ngưỡng 1 triệu messages.
  2. Phân tích logs từ các thành phần:

    • Redis logs: “OOM command not allowed” và “blocked clients reached maxmemory policy”.
    • Pod logs: “connection reset by peer” và “HTTP request timeout”.
    • GPU logs: Mỗi inference job chỉ chiếm 10-15% năng lực của GPU trong một khoảng thời gian rất ngắn.
  3. Debug trực tiếp:

    • Dùng monitoringnvidia-smi để kiểm tra tải GPU theo thời gian thực.
    • Dùng công cụ như iperf để kiểm tra bandwidth giữa các pods.

Biện pháp

  1. Tối ưu hóa GPU utilization:

    • Custom Kubernetes Scheduler:
      • Viết một scheduler plugin sử dụng framework kube-scheduler để ưu tiên pods vào các nodes có GPU rảnh thay vì để mặc định.
      • Cấu hình device plugins để gán chính xác số lượng GPU cho mỗi container dựa trên workload thực tế.
    • Batching inference requests:
      • Gộp nhiều requests nhỏ thành một batch lớn trước khi gửi tới mô hình, giúp GPU xử lý hiệu quả hơn.
  2. Giảm bottleneck Redis:

    • Thay thế Redis bằng RocksDB hoặc ScyllaDB để xử lý khối lượng dữ liệu lớn hơn mà không bị quá tải.
    • Tích hợp cơ chế lazy loading trạng thái mô hình từ storage khi cần thay vì lưu toàn bộ trên memory của Redis.
  3. Giải quyết networking overload:

    • Sử dụng service mesh (Istio) để tối ưu traffic giữa các pods và Kafka:
      • Áp dụng gRPC thay vì HTTP để giảm overhead truyền tải dữ liệu.
    • Tách Kafka cluster thành 2 lớp (tiers):
      • Một lớp ingestion cho việc nhận dữ liệu raw.
      • Một lớp processing để chuyển dữ liệu đã được tiền xử lý tới mô hình ML.
  4. Giảm thiểu độ trễ:

    • Dùng NVIDIA Triton Inference Server để tối ưu inference pipeline.
    • Cấu hình NUMA-aware CPU scheduling cho các pods chứa mô hình để tăng tốc xử lý dữ liệu input/output.
  5. Đảm bảo độ tin cậy:

    • Tích hợp KEDA (Kubernetes Event-driven Autoscaler) để tự động scale các pods dựa trên lượng messages trong Kafka.
    • Cấu hình multi-AZ failover setup trong on-premise cluster để đảm bảo uptime.

Kết quả

  • GPU utilization tăng lên 85%: Nhờ batching và custom scheduling, tiết kiệm chi phí hạ tầng đáng kể.
  • Độ trễ giảm xuống 30ms: Vượt yêu cầu SLA, tăng hiệu suất xử lý gấp đôi.
  • Traffic ổn định: Networking không còn nghẽn ngay cả trong giờ cao điểm.
  • Tăng độ tin cậy: Không còn downtime đến hiện tại sau 6 tháng vận hành sau cải tiến hoạt động tốt.

Bài học từ góp ý

updating…