- Add ActionBar component with expandable toolbar for mobile - Add @mention functionality with autocomplete dropdown - Add browser notification system (push, sound, vibration) - Add NotificationSettings modal for user preferences - Add mention badges on room list cards - Add ReportPreview with Markdown rendering and copy/download - Add message copy functionality with hover actions - Add backend mentions field to messages with Alembic migration - Add lots field to rooms, remove templates - Optimize WebSocket database session handling - Various UX polish (animations, accessibility) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
131 lines
3.7 KiB
Markdown
131 lines
3.7 KiB
Markdown
# Design: Optimize WebSocket Database Sessions
|
||
|
||
## Context
|
||
|
||
Task Reporter 使用 WebSocket 進行即時通訊,目前實作在 WebSocket 連線期間持有單一資料庫 Session。這在少量用戶時可行,但隨著用戶增加會造成連線池耗盡。
|
||
|
||
**現況分析:**
|
||
- 連線池: 5 + 10 = 15 個連線
|
||
- WebSocket 持有 Session 直到斷線
|
||
- 50 用戶同時在線 = 需要 50 個 Session
|
||
- 結果: 連線池耗盡,後續請求阻塞
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:**
|
||
- 支援 100+ 並發 WebSocket 連線
|
||
- 資料庫操作即時寫入(不使用佇列)
|
||
- 修復 sequence_number 競爭條件
|
||
- 可配置的連線池參數
|
||
|
||
**Non-Goals:**
|
||
- 不改用 async SQLAlchemy(保持簡單)
|
||
- 不實作訊息佇列(維持即時寫入)
|
||
- 不改變 API 介面
|
||
|
||
## Decisions
|
||
|
||
### Decision 1: 短期 Session 模式
|
||
|
||
**選擇:** 每次 DB 操作使用獨立 Session,操作完成立即釋放。
|
||
|
||
**實作:**
|
||
```python
|
||
# database.py
|
||
@contextmanager
|
||
def get_db_context():
|
||
db = SessionLocal()
|
||
try:
|
||
yield db
|
||
finally:
|
||
db.close()
|
||
|
||
# router.py (改前)
|
||
db = next(get_db())
|
||
while True:
|
||
message = create_message(db, ...) # 共用 Session
|
||
|
||
# router.py (改後)
|
||
while True:
|
||
with get_db_context() as db:
|
||
message = create_message(db, ...) # 每次獨立
|
||
```
|
||
|
||
**替代方案考慮:**
|
||
1. **連線池擴大**: 只增加 pool_size 到 100+
|
||
- 優點: 最少改動
|
||
- 缺點: 浪費資源,MySQL 連線數有限
|
||
|
||
2. **Async SQLAlchemy**: 使用 aiomysql
|
||
- 優點: 真正非阻塞
|
||
- 缺點: 需要大幅重構,增加複雜度
|
||
|
||
3. **訊息佇列**: Redis/內存佇列 + 批次寫入
|
||
- 優點: 最高效能
|
||
- 缺點: 複雜度高,可能丟失資料
|
||
|
||
### Decision 2: Sequence Number 鎖定策略
|
||
|
||
**選擇:** 使用 `SELECT ... FOR UPDATE` 鎖定 + 重試機制
|
||
|
||
```python
|
||
def create_message(db, room_id, ...):
|
||
max_retries = 3
|
||
for attempt in range(max_retries):
|
||
try:
|
||
# 使用 FOR UPDATE 鎖定該房間的最大 sequence
|
||
max_seq = db.execute(
|
||
text("SELECT MAX(sequence_number) FROM tr_messages WHERE room_id = :room_id FOR UPDATE"),
|
||
{"room_id": room_id}
|
||
).scalar()
|
||
next_seq = (max_seq or 0) + 1
|
||
# ... create message ...
|
||
db.commit()
|
||
return message
|
||
except IntegrityError:
|
||
db.rollback()
|
||
if attempt == max_retries - 1:
|
||
raise
|
||
```
|
||
|
||
**替代方案:**
|
||
1. **AUTO_INCREMENT 子欄位**: 每個房間獨立計數器表
|
||
- 需要額外表,增加 JOIN 成本
|
||
|
||
2. **樂觀鎖**: 使用版本號重試
|
||
- 高並發時重試次數可能很高
|
||
|
||
### Decision 3: 連線池配置
|
||
|
||
**選擇:** 環境變數可配置
|
||
|
||
```python
|
||
# 生產環境建議值
|
||
DB_POOL_SIZE=20 # 常駐連線
|
||
DB_MAX_OVERFLOW=30 # 額外連線 (總共最多 50)
|
||
DB_POOL_TIMEOUT=10 # 等待連線秒數
|
||
DB_POOL_RECYCLE=1800 # 30 分鐘回收
|
||
```
|
||
|
||
## Risks / Trade-offs
|
||
|
||
| 風險 | 影響 | 緩解措施 |
|
||
|------|------|----------|
|
||
| 頻繁取得/釋放 Session 增加開銷 | 輕微效能下降 | 連線池的 `pool_pre_ping` 減少無效連線 |
|
||
| FOR UPDATE 可能造成鎖等待 | 高並發時延遲 | 設定合理的鎖等待超時 |
|
||
| 每次操作獨立事務 | 無法跨操作 rollback | 本系統每個操作獨立,無此需求 |
|
||
|
||
## Migration Plan
|
||
|
||
1. **Phase 1**: 部署新連線池配置(無風險)
|
||
2. **Phase 2**: 新增 context manager(無風險)
|
||
3. **Phase 3**: 修改 WebSocket router(需測試)
|
||
4. **Phase 4**: 修復 sequence 鎖定(需測試)
|
||
|
||
**Rollback:** 每個 Phase 獨立,可單獨回滾。
|
||
|
||
## Open Questions
|
||
|
||
1. 是否需要連線池監控指標(如 Prometheus metrics)?
|
||
2. 是否需要實作連線健康檢查端點?
|