Khám và chữa bệnh...chậm mãn tính trên hệ thống Rails (P1)

Từ ngày xửa ngày xưa, ông trời sinh ra Ruby nhưng quên bổ sung nước tăng lực cho ngôn ngữ này. Cũng vì vậy, đứa con Ruby on Rails hoạt động với tốc độ khá chill.

Tuy nhiên, người dùng hệ thống của cháu thì không được “chill” cho lắm. Tưởng tượng các bác có 45 phút để làm bài thi mà mất 2 phút quay quay có tức không. Nhân dịp người dùng phàn nàn quá nhiều về hiệu năng, cháu quyết định khám bệnh cho hệ thống này

Tình trạng bệnh lý

Hồ sơ bệnh án:

  • Sử dụng combo Passenger + Nginx
  • Ngày thường vắng khách, cuối tuần vỡ trận
  • Mặc dù đã scale cấu hình hết cỡ nhưng vẫn chậm, chỉ thấy hết tiền nhanh hơn

Ngoài ra, trang quan trọng nhất - trang làm bài tập online có cây DOM siêu to khổng lồ

Tốc độ load trang này luôn dao động 30-50s, gây trầm cảm cho người dùng

hay thỉnh thoảng toang luôn 😢

Hướng điều trị

Bệnh 1: Tràn queue

Thiếu gì thì tăng cái đấy đúng không cả nhà. Tính thử một chút thì trang nặng nhất của cháu là khoảng 100 request, một buổi thi là 300 người, vị chi request queue khoảng 100x300=30000 là xả láng rồi ha.

server {
  ...
  add_header X-Frame-Options ALLOWALL;
  passenger_enabled on;
  passenger_app_env production;
  passenger_max_request_queue_size 30000;
  ...
}

Cháu sử dụng thuộc tính passenger_max_request_queue_size cho config nginx

Bệnh 2: Scale hệ thống nhưng không thấy nhanh hơn

Bệnh này khi theo dõi một buổi thi cháu mới thấy, mặc dù tốc độ load đề chậm nhưng CPU Usage và RAM không tăng theo. Chắc chắn có nút thắt cổ chai ở đây rồi.

Giờ cháu sẽ tăng số lượng process và thread của cả Web App lẫn Database

database.yml

production:
  adapter: postgresql
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 30 } %>
  timeout: 5000
  database: ******

Ở đây cháu đã tăng pool của DB lên 30 connections.

nginx conf

passenger_max_pool_size <SỐ TỰ NHIÊN>;
server {
  ...
  passenger_max_request_queue_size 30000;
  passenger_min_instances <SỐ TỰ NHIÊN>;
}

passenger_max_pool_size là số lượng process tối đa mà Passenger sẽ tạo ra. passenger_min_instances là số lượng tối thiểu process sẽ sinh ra.

Việc tăng giá trị 2 cấu hình có mục đích tăng số tiến trình cùng chạy để tận dụng tối đa sức mạnh của CPU. Vì nếu số tiến trình quá thấp, khi gặp các tác vụ blocking I/O hệ thống sẽ phải chờ các tác vụ đó xử lí xong, dẫn đến giảm hiệu quả sử dụng

Công thức passenger_max_pool_size là:

passenger_max_pool_size = (RAM * 0.75) / Dung lượng một app
passenger_min_instances = passenger_max_pool_size

Để đo thử một app các bác hãy dùng lệnh passenger-status và đo xem khi chạy app của các bác sẽ chiếm bao nhiêu RAM. Như của cháu trung bình là 110MB, bình thường sử dụng cấu hình t3a.small (2GB RAM)

VD: passenger_max_pool_size = (2048*0.75)/110 ~ 14

Ở phần tiếp theo, cháu sẽ khám bệnh chuyên sâu ở tầng Rails (Queries & Cache & Non blocking-UI)