GVL (Global VM Lock) cho phép chỉ một thread chạy mã Ruby tại một thời điểm trong mỗi process. Nhưng GVL được nhả ra khi thread chờ I/O — query DB, gọi HTTP, đọc file. Đó là lý do multi-thread vẫn có ích cho web app.
- Worker = process riêng (Puma cluster mode) → chạy song song thật trên nhiều CPU core, mỗi process một GVL.
- Thread trong cùng worker → chỉ chồng lấn được ở phần chờ I/O.
Cách quyết định:
- Số worker bám theo số CPU core và RAM khả dụng (mỗi worker là một bản copy của app). Điểm khởi đầu thông dụng: worker ≈ số core.
- Số thread bám theo tỉ lệ thời gian chờ I/O trong một request. App Rails điển hình (nhiều query DB) chạy tốt ở 3–5 thread; app thuần CPU thì thêm thread gần như không giúp gì, chỉ làm latency dao động.
- DB pool phải ≥ số thread mỗi worker, nếu không thread sẽ đợi connection và ném ConnectionTimeoutError. Tổng connection = worker × thread, phải nằm trong max_connections của Postgres.
# config/puma.rb
workers Integer(ENV.fetch('WEB_CONCURRENCY', 2))
threads_count = Integer(ENV.fetch('RAILS_MAX_THREADS', 3))
threads threads_count, threads_count
preload_app! # enables copy-on-write memory sharing between workersKhông đoán mò: chỉnh theo số đo.
Nếu CPU đã bão hoà thì thêm thread chỉ làm p95 tệ hơn; nếu CPU rảnh mà request xếp hàng thì mới tăng thread hoặc worker. preload_app! bật copy-on-write giúp các worker chia sẻ bộ nhớ ban đầu.