Chương 25. Concurrency Trong Worker
Ở chương trước, ta nói worker là người xử lý việc nền.
Chương này trả lời câu hỏi tiếp theo:
> Worker xử lý được bao nhiêu việc cùng lúc?
Đó là câu hỏi về concurrency.
Nói đơn giản:
> Concurrency là số việc đang được xử lý cùng lúc.
Ví dụ:
Có 4 slot worker.
Mỗi slot xử lý 1 job.
=> concurrency = 4.
Nếu 4 job đang chấm bài AI cùng lúc, concurrency hiện tại là 4.
Điểm dễ nhầm:
> Concurrency không nhất thiết là thread.
Concurrency có thể đạt bằng:
- Process.
- Thread.
- Greenlet.
- Async coroutine.
- Nhiều machine chạy song song.
Thread chỉ là một cách.
Concurrency là ý tưởng lớn hơn:
> Có bao nhiêu việc đang tiến triển cùng lúc?
---
25.1. Ví dụ quán bánh: bao nhiêu thợ đang làm cùng lúc?
Quán bánh có 4 thợ.
Mỗi thợ làm một đơn.
Nếu có 20 đơn chờ, cùng lúc chỉ 4 đơn được làm.
Thợ 1: đơn A
Thợ 2: đơn B
Thợ 3: đơn C
Thợ 4: đơn D
16 đơn còn lại chờ.
Concurrency của bếp là 4.
Nếu quán thuê thêm 6 thợ, tổng 10 thợ:
Concurrency = 10.
Số đơn được làm cùng lúc tăng lên.
Nhưng nếu bếp chỉ có 4 lò nướng, thuê 10 thợ chưa chắc nhanh hơn.
Vì bottleneck có thể là lò nướng.
Trong hệ thống cũng vậy:
Tăng worker/concurrency chỉ tốt nếu bottleneck thật sự nằm ở số slot xử lý.
Nếu bottleneck là database, API quota, CPU, RAM, hoặc network, tăng concurrency có thể làm hệ thống tệ hơn.
---
25.2. Concurrency khác throughput thế nào?
Concurrency là số việc đang chạy cùng lúc.
Throughput là số việc hoàn thành trong một khoảng thời gian.
Ví dụ:
Concurrency = 10 job đang chạy.
Mỗi job mất 90 giây.
Throughput xấp xỉ:
10 job / 90 giây
= 6.67 job/phút
Nếu tăng concurrency lên 30:
30 job / 90 giây
= 20 job/phút
với điều kiện không có bottleneck khác.
Một câu nhớ:
> Concurrency là đang làm bao nhiêu việc cùng lúc. Throughput là mỗi phút làm xong bao nhiêu việc.
---
25.3. Công thức gần đúng
Công thức rất hữu ích:
concurrency cần thiết ≈ throughput mong muốn * latency mỗi job
Trong đó:
- Throughput tính theo job/giây.
- Latency tính theo giây.
Ví dụ:
Bạn muốn xử lý:
60 job/phút = 1 job/giây
Mỗi job mất:
30 giây
Concurrency cần:
1 * 30 = 30
Tức là cần khoảng 30 job đang chạy song song để đạt 60 job/phút.
Nếu mỗi job mất 90 giây và muốn 100 job/phút:
100 job/phút = 1.67 job/giây
1.67 * 90 ≈ 150
Bạn cần khoảng 150 job chạy song song.
Đây là lý do chỉ có 4 worker slot thì không thể tận dụng được quota 100 request/phút nếu mỗi request chờ 90 giây.
---
25.4. Ví dụ AI Judge: 4 worker là nghẽn thế nào?
Giả sử:
Mỗi bài chấm mất 90 giây.
Celery concurrency = 4.
Cùng lúc chỉ 4 bài được chấm.
Throughput:
4 / 90 giây = 0.044 job/giây
= 2.67 job/phút
Nếu Gemini API cho phép 100 request/phút, ta chưa chạm giới hạn Gemini.
Ta đang nghẽn ở worker concurrency của mình.
Nếu tăng concurrency lên 20:
20 / 90 giây = 13.33 job/phút
Nếu tăng lên 50:
50 / 90 giây = 33.33 job/phút
Nếu muốn gần 100 job/phút:
cần khoảng 150 job chạy song song
Nhưng không có nghĩa nên nhảy ngay từ 4 lên 150.
Phải tăng từng bước và đo:
- Gemini có trả 429 không?
- Timeout có tăng không?
- DB connection có đủ không?
- RAM có tăng quá không?
- Cost có vượt dự kiến không?
- Queue wait time có giảm không?
- Error rate có tăng không?
Concurrency là núm chỉnh rất mạnh.
Chỉnh mạnh mà không đo thì dễ làm vỡ thứ khác.
---
25.5. Concurrency khác parallelism thế nào?
Hai từ này hay bị dùng lẫn.
Concurrency:
> Nhiều việc đang tiến triển cùng lúc.
Parallelism:
> Nhiều việc thật sự chạy đồng thời trên nhiều CPU/core tại cùng thời điểm.
Ví dụ I/O-bound:
100 job gọi API bên ngoài.
Hầu hết thời gian là chờ network.
Một process async có thể quản lý nhiều job đang chờ.
Đó là concurrency cao.
Không nhất thiết cần 100 CPU core.
Ví dụ CPU-bound:
100 job render video.
Mỗi job ăn CPU thật.
Muốn chạy song song thật, cần nhiều core/process/machine.
Đây là parallelism.
Một câu nhớ:
> Concurrency giúp quản lý nhiều việc đang chờ. Parallelism giúp dùng nhiều CPU để chạy thật sự cùng lúc.
---
25.6. I/O-bound là gì?
I/O-bound là workload phần lớn thời gian chờ I/O.
I/O có thể là:
- Network.
- Database.
- File.
- External API.
- Object storage.
Ví dụ:
- Gọi Gemini API.
- Gửi email.
- Gọi payment provider.
- Upload file.
- Query database chờ kết quả.
Trong AI Judge, nếu worker gửi request đến Gemini rồi chờ 90 giây, phần lớn thời gian worker đang chờ network/provider.
CPU nội bộ có thể rảnh.
Với I/O-bound, concurrency cao thường có ích, vì trong lúc job A chờ, worker có thể cho job B cũng gửi request và chờ.
Nhưng vẫn bị giới hạn bởi:
- Rate limit.
- DB connection.
- Memory.
- Network.
- Provider quota.
- Cost.
---
25.7. CPU-bound là gì?
CPU-bound là workload ăn CPU thật.
Ví dụ:
- Render video.
- Encode ảnh.
- Nén file lớn.
- Tính toán ML local.
- Xử lý dữ liệu nặng.
- Mã hóa/giải mã nặng.
Với CPU-bound, tăng concurrency quá cao trên cùng máy có thể làm mọi thứ chậm hơn.
Vì các job tranh CPU.
Ví dụ máy có 4 core.
Nếu chạy 100 job render video cùng lúc, mỗi job không nhanh hơn.
Máy chỉ bị quá tải.
Với CPU-bound, thường cần:
- Process thay vì thread nếu runtime có GIL hoặc thread không chạy CPU song song tốt.
- Số process gần số core.
- Máy mạnh hơn.
- Scale nhiều machine.
- GPU nếu phù hợp.
Không nên dùng cùng cách tuning cho AI API và render video.
---
25.8. Database-bound là gì?
Database-bound là workload nghẽn ở database.
Ví dụ:
- Export 1 triệu dòng.
- Import ghi nhiều records.
- Report query nặng.
- Backfill dữ liệu.
- Update hàng loạt.
Nếu tăng worker từ 5 lên 100, database có thể sập.
Với database-bound, cần:
- Batch hợp lý.
- Index đúng.
- Query tối ưu.
- Giới hạn concurrency.
- Tách read replica nếu phù hợp.
- Chạy giờ thấp tải nếu được.
- Theo dõi lock, CPU, I/O của database.
Đừng nghĩ queue luôn nên chạy nhiều worker.
Đôi khi concurrency thấp mới là cách bảo vệ hệ thống.
---
25.9. Các cách đạt concurrency
Có nhiều cách để một hệ thống xử lý nhiều việc cùng lúc.
Process
Nhiều tiến trình riêng.
Mạnh, cô lập tốt, dùng nhiều CPU core tốt hơn.
Tốn RAM hơn.
Thread
Nhiều luồng trong cùng process.
Nhẹ hơn process, chia sẻ memory.
Cần cẩn thận thread-safety.
Greenlet
Luồng nhẹ kiểu cooperative, thường dùng với gevent/eventlet.
Hợp với I/O-bound nếu thư viện tương thích.
Async coroutine
Một event loop quản lý nhiều tác vụ bất đồng bộ.
Hợp với I/O-bound nếu code và thư viện async đúng cách.
Nhiều machine/container
Scale ngang.
Chạy thêm worker instance trên nhiều máy/container.
Đây cũng là tăng concurrency toàn hệ thống.
---
25.10. Process dễ hiểu thế nào?
Process giống nhiều người làm việc ở các bàn riêng.
Mỗi process có memory riêng.
Nếu một process chết, process khác có thể vẫn sống.
Trong Celery, pool mặc định thường là prefork.
Với prefork:
celery worker --concurrency=4
thường nghĩa là worker tạo 4 process con xử lý task.
Ưu điểm:
- Cô lập tốt.
- Hợp với CPU-bound hơn thread trong Python.
- Một process lỗi không nhất thiết kéo cả worker xuống.
Nhược điểm:
- Tốn RAM hơn.
- Khởi động process có chi phí.
- Chia sẻ connection/resource cần cẩn thận.
Process là lựa chọn mặc định an toàn trong nhiều hệ thống Python.
---
25.11. Thread dễ hiểu thế nào?
Thread giống nhiều người làm việc chung một bàn, dùng chung nhiều đồ.
Thread nhẹ hơn process.
Với I/O-bound, thread có thể hữu ích vì trong lúc thread A chờ network, thread B làm việc khác.
Nhưng thread có rủi ro:
- Shared memory dễ lỗi nếu code không thread-safe.
- Debug race condition khó.
- Trong Python, GIL làm CPU-bound không scale tốt bằng thread.
Thread phù hợp hơn với:
- I/O-bound.
- Thư viện không async nhưng thread-safe.
- Muốn ít tốn RAM hơn process.
Nhưng phải biết thư viện mình dùng có an toàn với thread không.
---
25.12. Greenlet/gevent là gì?
Greenlet là "luồng nhẹ" chạy cooperative.
Nói dễ hiểu:
> Nhiều việc cùng chia sẻ một thread/process, và tự nhường nhau khi chờ I/O.
Gevent/eventlet có thể giúp chạy rất nhiều I/O task cùng lúc với chi phí thấp hơn thread/process.
Ví dụ:
100 job cùng chờ HTTP response
gevent có thể phù hợp nếu toàn bộ thư viện network/database tương thích hoặc được monkey patch đúng.
Nhưng có bẫy:
- Không phải thư viện nào cũng hợp.
- Một đoạn code blocking có thể chặn cả event loop.
- Debug đôi khi khó.
- Monkey patch có thể gây hành vi bất ngờ.
Với Celery, gevent/eventlet có thể tốt cho I/O-bound, nhưng cần test kỹ.
Đừng đổi pool chỉ vì nghe concurrency cao.
---
25.13. Async là gì?
Async là cách viết code để một chương trình không đứng yên khi chờ I/O.
Ví dụ:
Gửi request đến API
Trong lúc chờ API trả lời
Chạy việc khác
Khi API trả lời thì quay lại xử lý
Async rất hợp với I/O-bound.
Ví dụ:
- Gọi nhiều HTTP API.
- WebSocket.
- SSE.
- Database driver async.
Nhưng async chỉ hiệu quả khi cả chuỗi dùng thư viện async.
Nếu trong async code có một hàm blocking lâu:
time.sleep(90)
hoặc HTTP client blocking, nó có thể chặn event loop.
Async không phải bột thần.
Nó cần ecosystem phù hợp.
---
25.14. Celery concurrency chính là gì?
Trong Celery, concurrency là số task worker pool có thể xử lý cùng lúc.
Nhưng nó là process, thread hay greenlet tùy pool.
Ví dụ:
celery -A app worker --concurrency=4
Nếu pool là prefork:
4 process con.
Nếu pool là threads:
4 thread.
Nếu pool là gevent:
4 greenlet slot, hoặc nhiều greenlet tùy cấu hình.
Điểm quan trọng:
> Celery concurrency không tự động có nghĩa là thread.
Nó có nghĩa:
> Worker có thể chạy bao nhiêu task cùng lúc trong pool đó.
Vì vậy khi hỏi:
Celery concurrency = 4 là gì?
Câu trả lời đúng là:
Nó là 4 slot xử lý song song theo kiểu pool đang dùng.
Mặc định thường là process.
---
25.15. Với AI Judge nên dùng kiểu nào?
AI Judge gọi Gemini API và chờ response.
Đây chủ yếu là I/O-bound.
Vì vậy vấn đề thường không phải CPU.
Vấn đề là:
- Có bao nhiêu request được gửi song song?
- Có vượt Gemini quota không?
- Có đủ connection không?
- Có đủ DB connection không?
- Có timeout/retry không?
- Có tốn cost quá không?
Các hướng:
Tăng prefork process
Dễ hiểu, an toàn hơn, nhưng tốn RAM.
Ví dụ:
concurrency 4 -> 10 -> 20
Dùng thread pool
Có thể tốt cho I/O, ít RAM hơn process.
Cần đảm bảo code/thread-safety.
Dùng gevent/eventlet
Có thể đạt concurrency cao cho I/O.
Cần đảm bảo HTTP client và thư viện tương thích.
Tách AI worker service async
Nếu workload rất lớn, có thể xây worker async riêng.
Nhưng đây là bước sau, không phải bước đầu bắt buộc.
Thực dụng nhất:
> Tăng từng bước, đo queue wait time, processing time, 429, timeout, DB load, RAM, cost.
---
25.16. Tại sao không tăng concurrency vô hạn?
Vì mỗi job đang chạy vẫn dùng tài nguyên.
Tăng concurrency làm tăng:
- Memory.
- Connection đến database.
- Connection đến external API.
- Network.
- CPU dùng để parse/serialize.
- Log volume.
- Cost.
- Áp lực lên provider.
Ví dụ:
concurrency = 150
nghĩa là có thể có 150 request Gemini đang bay.
Nếu mỗi request dùng nhiều memory hoặc response lớn, RAM tăng.
Nếu mỗi job mở database transaction, database connection có thể hết.
Nếu provider giới hạn 100 RPM, concurrency cao có thể tạo 429 nếu không có rate limiter.
Concurrency cao phải đi cùng kiểm soát.
---
25.17. Concurrency và rate limit khác nhau
Concurrency:
> Bao nhiêu request/job đang chạy cùng lúc?
Rate limit:
> Bao nhiêu request/job được bắt đầu trong một khoảng thời gian?
Ví dụ:
concurrency = 150
rate limit = 100 request/phút
Nghĩa là tối đa 150 request có thể đang bay, nhưng chỉ cho bắt đầu 100 request mỗi phút.
Nếu mỗi request mất 90 giây, để đạt 100 request/phút có thể cần concurrency khoảng 150.
Nhưng nếu mỗi request chỉ mất 1 giây, concurrency 150 có thể tạo throughput lớn hơn nhiều và vượt rate limit ngay.
Vì vậy cần cả hai:
- Concurrency limit.
- Rate limiter.
Một cái giới hạn số đang chạy.
Một cái giới hạn tốc độ bắt đầu.
---
25.18. Concurrency và database connection
Nhiều job chạy cùng lúc có thể cần nhiều database connection.
Ví dụ:
100 worker tasks
mỗi task giữ 1 DB connection
=> cần 100 DB connections
Nếu database pool chỉ có 30 connection, nhiều job sẽ chờ hoặc lỗi.
Với AI job, nên tránh giữ database connection trong lúc chờ Gemini 90 giây.
Cách tốt:
Load job data
đóng/nhả DB connection
gọi Gemini
mở DB connection lại để lưu kết quả
Không nên:
begin transaction
gọi Gemini 90 giây
commit
Giữ transaction/connection trong lúc chờ API ngoài là một lỗi rất phổ biến.
---
25.19. Concurrency và memory
Mỗi job có thể dùng memory.
Nếu một job dùng 100MB và concurrency là 50:
100MB * 50 = 5GB
Chưa tính overhead của process/runtime.
Với prefork process, mỗi process có memory riêng.
Với thread/async, memory có thể thấp hơn nhưng vẫn có payload, response, object trong RAM.
Vì vậy cần đo memory thật.
Không chỉ tính số worker trên giấy.
---
25.20. Concurrency và cost
Với external API tính tiền, concurrency cao có thể làm cost tăng rất nhanh.
Ví dụ:
Trước đây 4 job chạy cùng lúc.
Sau tăng lên 100 job chạy cùng lúc.
Nếu backlog lớn, hệ thống có thể tiêu tiền rất nhanh trong thời gian ngắn.
Concurrency không làm mỗi job rẻ hơn.
Nó chỉ làm nhiều job chạy cùng lúc hơn.
Với AI API, cần theo dõi:
- Số request/phút.
- Token usage.
- Cost theo ngày.
- Cost theo user/tenant.
- Job retry gây cost lặp.
Tăng concurrency phải đi kèm budget guardrail.
---
25.21. Concurrency và fairness
Nếu concurrency cao nhưng không có fairness, một nhóm user có thể chiếm toàn bộ worker.
Ví dụ:
Một lớp nộp 10.000 bài.
Tất cả worker AI xử lý lớp này.
Lớp khác phải chờ.
Cần cân nhắc:
- Giới hạn job pending mỗi lớp/user.
- Priority.
- Round-robin.
- Queue theo tenant lớn.
- Rate limit theo tenant.
Concurrency cao giải quyết năng lực tổng.
Fairness giải quyết phân phối năng lực.
Hai thứ khác nhau.
---
25.22. Concurrency và backpressure
Nếu job đến nhanh hơn khả năng xử lý, queue dài dần.
Tăng concurrency có thể giúp, nhưng không phải lúc nào cũng đủ.
Cần backpressure khi quá tải:
- Tạm ngừng nhận job mới.
- Trả 429/503.
- Giới hạn job pending/user.
- Hạ priority job phụ.
- Thông báo thời gian chờ dự kiến.
Nếu cứ nhận job vô hạn, concurrency cao mấy cũng có lúc backlog vượt kiểm soát.
Backpressure là cách hệ thống nói:
Tôi đang quá tải, đừng hứa thêm quá nhiều.
---
25.23. Concurrency và prefetch
Nhiều queue system cho worker lấy trước nhiều job.
Đó là prefetch.
Ví dụ:
Worker concurrency = 4
prefetch = 16
Worker có thể giữ 16 job, dù chỉ chạy 4 job cùng lúc.
Vấn đề:
- Job bị giữ ở worker này trong khi worker khác rảnh.
- Job priority thấp có thể bị giữ trước job priority cao.
- Nếu job dài, phân phối không đều.
Với job lâu như AI grading, prefetch quá cao có thể làm queue nhìn như đã bớt nhưng thực ra job đang nằm chờ trong worker.
Cần cấu hình prefetch phù hợp.
Trong Celery, hay cần chú ý worker_prefetch_multiplier.
Với long-running task, prefetch thấp thường dễ kiểm soát hơn.
---
25.24. Tính nhanh concurrency cần có
Bước 1: Biết thời gian mỗi job.
AI grading: 90 giây/job
Bước 2: Biết throughput mong muốn.
Muốn 30 job/phút
Bước 3: Đổi throughput sang job/giây.
30 job/phút = 0.5 job/giây
Bước 4: Nhân với latency.
0.5 * 90 = 45
Concurrency cần khoảng 45.
Bước 5: Thêm vùng an toàn và kiểm tra bottleneck.
Thử 20 -> 30 -> 45
Đo lỗi, RAM, DB, rate limit
Không nên chỉ tính xong là set ngay.
Công thức cho ta hướng đi.
Đo đạc cho ta sự thật.
---
25.25. Làm sao tăng concurrency an toàn?
Một cách thực dụng:
1. Đo hiện tại.
queue length
oldest job age
processing time
error rate
timeout
RAM/CPU
DB connections
external API 429
2. Tăng một bước nhỏ.
4 -> 8 -> 16 -> 32
3. Chạy dưới tải thật hoặc tải thử.
4. Xem bottleneck mới xuất hiện ở đâu.
5. Đặt giới hạn.
rate limit
max concurrency
max pending jobs
budget limit
6. Có rollback.
Nếu tăng xong:
- Queue wait time giảm.
- Error không tăng.
- DB ổn.
- Cost trong kiểm soát.
thì tốt.
Nếu error/timeout/429 tăng, concurrency đã vượt điểm phù hợp hoặc thiếu rate limit.
---
25.26. Khi nào scale ngang thay vì tăng concurrency trong một worker?
Có hai cách tăng năng lực:
Tăng concurrency mỗi worker.
Tăng số worker instances.
Tăng concurrency trong một worker có thể nhanh và đơn giản.
Nhưng có giới hạn:
- Một máy hết CPU/RAM.
- Một process quá lớn.
- Một instance lỗi ảnh hưởng nhiều job.
- Deployment khó hơn.
Scale ngang:
worker-ai x 1 -> worker-ai x 5 -> worker-ai x 20
Lợi ích:
- Phân tán tải.
- Dễ tận dụng nhiều máy.
- Dễ autoscale.
- Cô lập lỗi tốt hơn.
Thực tế thường kết hợp:
Mỗi worker instance concurrency 10.
Chạy 5 instances.
Tổng concurrency ≈ 50.
---
25.27. Concurrency trong monolith worker và service riêng
Nếu worker nằm trong monolith, vẫn có thể scale riêng:
web process
worker-ai process
worker-email process
Không cần tách microservice mới tăng concurrency AI.
Bạn có thể chạy nhiều process/container worker khác nhau từ cùng codebase.
Tách service riêng chỉ cần khi có lý do:
- Runtime khác.
- Team khác.
- Deploy riêng.
- Scale/cost/cô lập mạnh hơn.
- API dùng chung cho nhiều hệ thống.
Concurrency là vấn đề vận hành workload.
Microservice không phải câu trả lời bắt buộc.
---
25.28. Ví dụ cấu hình tăng dần AI worker
Giả sử hiện tại:
worker-ai concurrency = 4
processing time = 90s
throughput ≈ 2.67 job/phút
queue wait time = 20 phút
Gemini 429 = 0
DB CPU ổn
RAM ổn
Bước 1:
concurrency = 10
throughput lý thuyết ≈ 6.67 job/phút
Đo.
Bước 2:
concurrency = 20
throughput lý thuyết ≈ 13.33 job/phút
Đo.
Bước 3:
concurrency = 50
throughput lý thuyết ≈ 33.33 job/phút
Đo rất cẩn thận.
Nếu bắt đầu thấy 429:
- Thêm rate limiter.
- Giảm tốc độ start request.
- Backoff theo provider.
Nếu DB connection căng:
- Không giữ connection khi chờ Gemini.
- Tăng pool hợp lý.
- Giảm concurrency ghi DB.
Nếu RAM căng:
- Tối ưu payload/response.
- Scale ngang.
- Giảm concurrency mỗi instance.
---
25.29. Những lỗi phổ biến
Lỗi 1: Tưởng concurrency chính là thread
Thread chỉ là một cách đạt concurrency.
Celery mặc định thường dùng process.
Lỗi 2: Tăng concurrency mà không đo bottleneck
Queue giảm nhưng DB/API/cost có thể nổ.
Lỗi 3: Dùng cấu hình CPU-bound cho I/O-bound
AI API chờ network cần cách nghĩ khác render video.
Lỗi 4: Dùng cấu hình I/O-bound cho CPU-bound
Tạo hàng trăm concurrent task cho việc ăn CPU làm máy chậm hơn.
Lỗi 5: Giữ DB transaction trong lúc chờ API ngoài
Concurrency cao làm lỗi này nguy hiểm hơn nhiều.
Lỗi 6: Không có rate limiter
Concurrency cao vượt quota provider.
Lỗi 7: Prefetch quá cao với job dài
Job bị giữ trong worker, phân phối kém, khó quan sát.
Lỗi 8: Quên cost
Xử lý nhanh hơn cũng có thể tiêu tiền nhanh hơn.
---
25.30. Checklist chọn concurrency
Khi chọn concurrency cho worker, hãy hỏi:
- Job là I/O-bound, CPU-bound hay DB-bound?
- Mỗi job mất bao lâu?
- Throughput mong muốn là bao nhiêu?
- Concurrency lý thuyết cần khoảng bao nhiêu?
- External API có rate limit không?
- Có global rate limiter chưa?
- Mỗi job dùng bao nhiêu memory?
- Mỗi job giữ DB connection bao lâu?
- Có giữ transaction khi gọi API ngoài không?
- Queue wait time hiện tại là bao nhiêu?
- Error/timeout/429 hiện tại là bao nhiêu?
- Có cần scale ngang không?
- Prefetch có phù hợp không?
- Có giới hạn cost không?
- Có dashboard để đo sau khi tăng không?
Nếu không đo được các chỉ số này, tăng concurrency là đoán mò.
---
25.31. Tóm tắt bằng AI Judge
Với AI Judge:
Mỗi job gọi Gemini mất 90 giây.
Nếu concurrency = 4:
≈ 2.67 bài/phút
Nếu muốn 30 bài/phút:
30 bài/phút = 0.5 bài/giây
0.5 * 90 = 45 concurrency
Nếu muốn tận dụng gần 100 request/phút:
100/phút = 1.67/giây
1.67 * 90 ≈ 150 concurrency
Nhưng phải có:
- Rate limiter.
- Timeout.
- Retry đúng.
- Idempotency.
- DB connection discipline.
- Monitoring queue wait time.
- Monitoring 429/timeout/cost.
Concurrency cao giải quyết bài toán chờ I/O.
Nó không thay thế thiết kế job đúng.
---
25.32. Kết luận của chương
Concurrency là số việc đang được xử lý cùng lúc.
Nó không đồng nghĩa với thread.
Process, thread, greenlet, async coroutine, nhiều worker instance đều có thể tạo concurrency.
Điều quan trọng là hiểu workload:
- I/O-bound cần nhiều việc cùng chờ hiệu quả.
- CPU-bound cần CPU/process/machine thật.
- DB-bound cần bảo vệ database.
Với job gọi AI API, concurrency thấp có thể khiến hệ thống lãng phí quota bên ngoài.
Nhưng concurrency cao quá có thể làm vượt rate limit, hết DB connection, tăng timeout, tăng RAM, tăng cost.
Thông điệp quan trọng nhất:
> Đừng hỏi "nên set concurrency bao nhiêu?" trước. Hãy hỏi "mỗi job mất bao lâu, muốn xử lý bao nhiêu job/phút, bottleneck nằm ở đâu, và hệ thống chịu được bao nhiêu việc cùng lúc?".
Ở chương tiếp theo, ta sẽ nói về ba người bạn bắt buộc của job nền: retry, timeout và idempotency. Không có chúng, concurrency cao chỉ làm lỗi xảy ra nhanh hơn.