ADR-0004. Trạng thái OTP trong Redis (theo TTL), không lưu PostgreSQL
| Trường | Giá trị |
|---|---|
| Trạng thái | Accepted |
| Ngày | 2026-02-12 |
| Người quyết định | identity-team |
| Thay thế | — |
Bối cảnh
- Flow OTP có trạng thái tạm thời với ngữ nghĩa TTL chặt chẽ:
- Mã đã hash (TTL 10 phút)
- Bộ đếm số lần thử (lockout sau 5 lần thử)
- Cooldown sau mỗi lần gửi (60s)
- Bộ đếm hạn ngạch hàng ngày
- Lưu trong PostgreSQL nghĩa là: TTL qua cleanup kiểu cron, lock khi tăng counter, bloat bảng từ hàng triệu bản ghi đã hết hạn.
Quyết định
Toàn bộ trạng thái OTP runtime nằm trong Redis với namespace key:
| Key | TTL | Mục đích |
|---|---|---|
{ns}:otp:{identifier} | 5–15 phút | Mã đã hash |
{ns}:lock:{identifier} | 10–15 phút | Khoá tài khoản |
{ns}:session:{token} | 1–24 giờ | Token phiên đã xác thực |
{ns}:cooldown:{identifier} | 60s | Cooldown gửi lại |
{ns}:daily:{identifier} | 24 giờ | Hạn ngạch hàng ngày |
Namespaces: verify-email, verify-phone, forgot-password, phone-auth, add-phone, add-email.
Mã được hash bằng Bun.password trước khi lưu.
Hệ quả
| Ưu | Nhược |
|---|---|
| Hết hạn theo TTL — không cần job cleanup | Flow OTP sập cứng nếu Redis down |
| INCR atomic cho counter | State là ephemeral — mất khi Redis flush |
| Đọc dưới mili-giây | Hash mã làm tăng CPU mỗi lần verify |
| Không bloat bảng PostgreSQL | Audit trail của các lần thử OTP không được lưu |
Chế độ thất bại
Nếu Redis không khả dụng:
- Yêu cầu OTP mới trả về
503 Service Unavailable - Việc xác minh JWT hiện hữu vẫn hoạt động (cache nằm phía sister)
- Token OTP đã phát trước đó vẫn xác minh được nếu cache phía client, nhưng xác minh phía server thất bại
Phương án đã xem xét
| Phương án | Ưu | Nhược | Lý do từ chối |
|---|---|---|---|
| PostgreSQL với cleanup cron | Audit trail | Lock contention trên counter; latency của cleanup; bloat | Sai trade-off |
| Chỉ trong-bộ-nhớ (per-instance) | Nhanh nhất | Không sống sót qua restart; không scale ra N pod | Vận hành mong manh |
| Hybrid (ghi DB + cache Redis) | Audit + tốc độ | Phối hợp double-write | Phức tạp không đáng |
Tham chiếu
BaseOTPBasedMFAService(lớp cơ sở abstract)EmailOtpService,PhoneOtpService(cụ thể)BindingKeys.APPLICATION_REDIS_BULLMQ