ADR-0001. WebSocket scale ngang qua một bus Redis pub/sub duy nhất
| Trường | Giá trị |
|---|---|
| Status | Accepted |
| Date | 2026-03-28 |
| Deciders | signal-team |
| Supersedes | — |
Bối cảnh
- Cập nhật real-time (order, payment) phải tới client POS/Admin bất kể instance Signal nào giữ socket.
- Kết nối WebSocket sticky với một process; trạng thái client in-memory không thể chia sẻ giữa replica.
- Service khác (Sale, Payment) phải push được tới client mà không host WebSocket server riêng.
- Chúng ta muốn một edge real-time duy nhất, không phải một WebSocket server nhúng trong mọi service.
Quyết định
Chúng ta sẽ chạy Signal là WebSocket server duy nhất và định tuyến mọi deliver xuyên instance / xuyên service qua một bus Redis pub/sub duy nhất.
- Signal host
WebSocketServerHelper(subscribe + publish); producer giữ mộtWebSocketEmitternhẹ (chỉ publish). sendToClientdeliver cục bộ nếu client trên instance này, không thì re-publish tới Redis cho instance sở hữu.broadcast/sendToRoomluôn publish tới Redis để mỗi instance deliver tới client khớp cục bộ của nó.- Redis chạy chế độ
singlehoặccluster(APP_ENV_WEBSOCKET_REDIS_*); mọi bên tham gia phải chia sẻ cùng instance.
Hệ quả
| Ưu | Nhược |
|---|---|
| Replica stateless — scale ngang sau Traefik | Redis trở thành phụ thuộc cứng / single point of failure cho fan-out |
| Producer giữ mỏng (chỉ emitter, không WS server) | Deliver xuyên instance là best-effort (bỏ nếu không instance nào subscribe) |
| Một edge real-time để bảo mật và vận hành | Mọi publisher phải cấu hình với Redis y hệt |
Phương án đã cân nhắc
| Phương án | Ưu | Nhược | Vì sao loại |
|---|---|---|---|
| WS server nhúng trong mỗi service | Không bus dùng chung | N server để bảo mật; client tung hứng N socket | Vận hành nặng, lặp auth/mã hoá |
| Sticky session, không bus | Đơn giản | Phá broadcast + deliver xuyên instance | Không đáp ứng yêu cầu "tới mọi client" |
| Kafka làm bus deliver | Đã có sẵn | Latency cao hơn, không dựng cho fan-out pub/sub ngắn ngủi | Redis pub/sub hợp deliver live hơn |
Tham chiếu
signal/src/components/websocket.component.ts(NxWebSocketComponent,bindEmitter)signal/src/services/signal-event.service.ts(định tuyến cục bộ/từ xasendToClient)- Operations — fan-out đa instance