站內信不同於 Email,純資料庫操作即可實現。群發效能與儲存空間隨用戶量呈指數級挑戰,本文提出三階段架構演進策略。
第一階段:少量用戶 (< 500) — 空間換時間
Table: Message
ID, SendID, RecID (0=全體), Message, Status, PDate
- 群發:遍歷用戶表,逐筆 `INSERT` 重複內容。
- 查詢:`SELECT * FROM Message WHERE RecID=@uid OR RecID=0`。
- 優點:結構簡單、查詢快。缺點:空間浪費、群發慢。
第二階段:中量用戶 (千~萬) — 訊息內容正規化
Table: Message
ID, SendID, RecID, MessageID, Status
Table: MessageText
ID, Message, PDate
- 群發:`MessageText` 插 1 筆,`Message` 插 N 筆 (僅存 MessageID)。
- 空間節省:1 萬用戶 × 200 Bytes = 2 MB → 僅 200 Bytes + 1 萬 × 4 Bytes (ID)。
- 查詢需 `JOIN`,稍複雜但可接受。
第三階段:大量用戶 (百萬、活躍率低) — 延遲實例化 (Lazy Materialization)
- 群發:僅寫入 MessageText,完全不寫 Message 表。
- 用戶登入/進站內信頁:查詢 `MessageText` 中「該用戶在 Message 無對應記錄」的筆數 → 未讀數。
- 用戶點開閱讀:即時在 `Message` 插入該用戶的記錄 (標記已讀、記錄閱讀時間)。
- 不活躍用戶 (90%) 永遠不產生 Message 記錄,極大節省空間與群發時間。
架構比較總結
| 指標 | 階段一 | 階段二 | 階段三 |
| 群發 100 萬用戶耗時 | 極慢 (100 萬 INSERT) | 快 (1 INSERT + 100 萬輕量 INSERT) | 極快 (1 INSERT) |
| 儲存空間 (100 萬用戶、200 字元) | ~200 MB | ~4 MB + 200 Bytes | ~200 Bytes + 活躍用戶 × 4 Bytes |
| 查詢複雜度 | 低 | 中 (JOIN) | 中 (NOT EXISTS / LEFT JOIN) |
| 適用場景 | 內部系統、小型社群 | 中型論壇、電商 | 大型 SNS、入口網站 |
進階優化建議
- 分表分庫:`Message` 依 `RecID` hash 分 16/64 表。
- 非同步群發:訊息寫入 `MessageText` 後發佈事件至 Message Queue,背景 Worker 批次寫入 `Message` (階段二) 或等待用戶拉取 (階段三)。
- 未讀數快取:Redis `Bitmap` 或 `HyperLogLog` 維護未讀狀態,毫秒級查詢。
- 刪除策略:軟刪除 (`IsDeleted`),定期歸檔冷資料。
選對架構階段,站內信系統即能從容應對從創業初期到上市規模的增長挑戰。