Files
Task_Reporter/openspec/changes/archive/2025-12-07-optimize-websocket-db-sessions/design.md
egg 599802b818 feat: Add Chat UX improvements with notifications and @mention support
- 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>
2025-12-08 08:20:37 +08:00

3.7 KiB
Raw Blame History

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操作完成立即釋放。

實作:

# 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 鎖定 + 重試機制

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: 連線池配置

選擇: 環境變數可配置

# 生產環境建議值
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. 是否需要實作連線健康檢查端點?