Rất nhiều project chạy ổn khi chỉ có 1 instance.
Nhưng đến lúc cần scale thêm instance để chịu tải, rất nhiều bug bắt đầu lộ ra:
- cron chạy trùng
- cache nằm trong biến memory bị lệch giữa các máy
- upload file xong máy khác không thấy
- user hit qua instance khác thì session mất
Gốc của những lỗi này thường nằm ở chỗ code chưa được thiết kế rõ theo hướng stateless hay đang vô tình giữ quá nhiều state trong từng instance.
Kết luận nhanh
Nếu cần nhớ ngắn gọn:
statelessgiúp scale ngang dễ hơn vì mỗi instance có thể xử lý request gần như độc lậpstatefulkhông sai, nhưng nếu không kiểm soát tốt thì khi tăng instance rất dễ phát sinh bug khó đoán- những thứ dễ gây lỗi nhất khi scale là schedule, file local, session local và data giữ trong variable memory
- nếu muốn scale thêm instance an toàn, phải đẩy state quan trọng ra ngoài app process
Stateless là gì?
Hiểu thực dụng:
- một request đi vào instance A hay instance B thì kết quả vẫn phải nhất quán
- app không phụ thuộc vào dữ liệu tạm đang nằm riêng trong RAM của một instance cụ thể
Ví dụ gần với stateless:
- request API đọc dữ liệu từ database
- auth dựa vào token hoặc session store chung
- file nằm ở object storage hoặc shared storage
- cache nằm ở Redis
Ý chính là:
- instance chỉ xử lý logic
- state quan trọng không nằm cứng trong chính process đó
Stateful là gì?
Stateful nghĩa là app hoặc flow đang phụ thuộc vào trạng thái giữ bên trong một instance cụ thể.
Ví dụ:
- session user nằm trong memory của process
- app giữ một map dữ liệu runtime trong biến global
- file upload được ghi vào local disk của đúng instance đó
- cron job trong từng instance tự quyết định trạng thái xử lý
Những thứ này vẫn có thể chạy ổn khi chỉ có 1 máy.
Nhưng khi thêm instance thứ 2, thứ 3 thì bắt đầu rắc rối.
Vì sao hiểu stateless/stateful lại quan trọng khi scale?
Vì scale ngang gần như luôn giả định:
- nhiều instance có thể thay nhau xử lý request
- request nào cũng có thể rơi vào bất kỳ instance nào
- instance có thể bị kill, restart, replace bất cứ lúc nào
Nếu code đang ngầm giả định:
- “máy này giữ dữ liệu đó”
- “cron này chỉ chạy một lần”
- “biến này luôn còn trong RAM”
thì khi scale lên, bug sẽ xuất hiện rất nhanh.
Lỗi 1: giữ data trong variable memory
Đây là lỗi rất phổ biến.
Ví dụ:
const onlineUsers = new Map<string, string>();
Nếu bạn lưu state kiểu này trong app:
- instance A có
onlineUsersriêng - instance B có
onlineUsersriêng
Khi scale lên 2-3 instance:
- data giữa các instance không đồng bộ
- logic kiểm tra trạng thái user có thể sai
- request trước vào máy A, request sau vào máy B thì context biến mất
Nếu dữ liệu đó quan trọng cho business, nó không nên chỉ sống trong RAM của một process.
Nên thay bằng:
- Redis
- database
- message queue
- distributed cache
tùy bài toán.
Lỗi 2: session hoặc auth state nằm local trong app
Nếu session chỉ nằm trong memory local:
- user login qua instance A
- request tiếp theo đi vào instance B
- session không còn
Kết quả là:
- login chập chờn
- auth lúc được lúc mất
- scale lên xong tưởng load balancer lỗi
Nếu muốn scale nhiều instance ổn định, session nên nằm ở:
- Redis
- database
- hoặc dùng token stateless phù hợp
Lỗi 3: schedule chạy trùng khi tăng instance
Đây là pain point production rất hay gặp.
Ví dụ mỗi instance đều chạy:
- cron gửi email
- cron đồng bộ dữ liệu
- cron cleanup
- cron charge billing
Khi còn 1 instance:
- mọi thứ có vẻ ổn
Khi tăng lên 3 instance:
- cron chạy 3 lần
- email gửi trùng
- job cleanup đá nhau
- billing hoặc sync có thể bị chạy nhiều lần
Đây là ví dụ kinh điển của stateful behavior bị giấu trong app.
Nếu app có schedule quan trọng, cần nghĩ theo một trong các hướng:
- chỉ một worker chuyên chạy schedule
- dùng distributed lock
- dùng queue / job system
- để scheduler ngoài app chính điều phối
Nói ngắn gọn:
- đừng để mọi instance web cùng tự chạy schedule quan trọng nếu không có cơ chế coordination
Lỗi 4: lưu file ngay trong source hoặc local disk của instance
Đây là lỗi rất hay gặp ở app upload file hoặc export file.
Ví dụ:
- user upload avatar
- app ghi vào
./uploads
Khi có 1 instance:
- nhìn có vẻ ổn
Khi scale lên nhiều instance:
- upload vào máy A
- request đọc file lại rơi vào máy B
- file không thấy
Nếu còn deploy lại container hoặc replace EC2:
- file local còn có thể mất luôn
Đây là lý do file người dùng upload không nên giữ trong:
- source code folder
- local disk tạm của container
- local path chỉ gắn với một instance
Nên dùng:
- S3
- shared storage
- object storage tương đương
Lỗi 5: giữ progress hoặc trạng thái xử lý trong app instance
Ví dụ:
- job đang chạy tới bước nào
- ai đang xử lý item nào
- batch nào đang pending
Nếu chỉ giữ những state đó trong memory:
- restart là mất
- scale thêm instance là lệch
- failover rất khó
Những state dạng workflow nên được lưu ở nơi bền hơn:
- database
- Redis
- queue backend
Stateless có phải lúc nào cũng tốt hơn không?
Không phải mọi thứ đều phải “stateless tuyệt đối”.
Thực tế:
- hệ thống nào cũng có state
Vấn đề không phải là xoá hết state.
Vấn đề là:
- state đang nằm ở đâu
- có bền không
- có chia sẻ được giữa nhiều instance không
- có survive restart không
Nói cách khác:
- business vẫn có state
- nhưng app instance không nên là nơi giữ state quan trọng một cách âm thầm
Cách áp dụng để scale thêm instance an toàn hơn
Nếu bạn muốn codebase đỡ phát sinh bug khi scale, có thể đi theo checklist này:
1. Giả định request có thể rơi vào bất kỳ instance nào
Khi viết code, luôn nghĩ:
- request sau chưa chắc vào lại cùng máy
Chỉ mindset này thôi đã giúp bạn tránh rất nhiều lỗi local-state.
2. Đẩy state quan trọng ra ngoài app process
Ví dụ:
- session -> Redis / DB
- file -> S3 hoặc object storage
- cache shared -> Redis
- workflow state -> DB / queue backend
3. Tách web instance và worker / scheduler nếu cần
Đây là cách rất thực dụng.
Đừng bắt mọi instance web vừa:
- serve request
- vừa chạy cron
- vừa xử lý queue
Nếu system đã lớn, nên tách vai trò rõ hơn:
- web instances
- worker instances
- scheduler riêng nếu cần
4. Dùng idempotent design cho job quan trọng
Kể cả có lock hoặc queue, vẫn nên thiết kế job quan trọng theo hướng:
- chạy lại không phá data
- cùng một request không gây duplicate nguy hiểm
Điều này cực quan trọng khi scale và khi xử lý retry.
5. Xem local disk là tạm thời
Trong môi trường scale ngang hoặc container:
- local disk gần như nên được xem là chỗ tạm
Đừng dùng nó làm nơi giữ dữ liệu business lâu dài nếu không có lý do rất rõ.
Một cách tự kiểm tra code có dễ scale không
Trước khi tăng số instance, tự hỏi:
- nếu request tiếp theo sang máy khác thì có lỗi không?
- nếu instance restart thì có mất state quan trọng không?
- nếu scale từ 1 lên 3 instance thì schedule có chạy trùng không?
- nếu file upload nằm local thì máy khác có đọc được không?
- nếu một biến memory bị reset thì business có vỡ không?
Nếu câu trả lời là “có” ở nhiều chỗ, hệ thống chưa sẵn sàng để scale ngang an toàn.
Khi nào stateful vẫn chấp nhận được?
Stateful không phải lúc nào cũng xấu.
Một số thứ vẫn có thể giữ local nếu:
- chỉ là cache phụ
- mất cũng không sao
- không ảnh hưởng business correctness
Ví dụ:
- in-memory cache nhỏ để tối ưu read
- map tạm cho metric không quan trọng
Nhưng phải rất rõ ràng:
- đó là optimization
- không phải source of truth
Kết luận
Hiểu stateless và stateful không chỉ là kiến thức kiến trúc cho đẹp, mà là thứ giúp bạn tránh rất nhiều bug đau đầu khi hệ thống cần scale thêm instance.
Những chỗ dễ gây lỗi nhất thường là:
- schedule chạy trùng
- file lưu local
- session local
- data giữ trong variable memory
Muốn scale thêm instance an toàn hơn về sau, nguyên tắc rất quan trọng là:
- state quan trọng không nên nằm cứng trong app instance
App instance càng stateless bao nhiêu, việc scale ngang càng dễ và ít bất ngờ bấy nhiêu.