Complete implementation of the production line incident response system (生產線異常即時反應系統) including: Backend (FastAPI): - User authentication with AD integration and session management - Chat room management (create, list, update, members, roles) - Real-time messaging via WebSocket (typing indicators, reactions) - File storage with MinIO (upload, download, image preview) Frontend (React + Vite): - Authentication flow with token management - Room list with filtering, search, and pagination - Real-time chat interface with WebSocket - File upload with drag-and-drop and image preview - Member management and room settings - Breadcrumb navigation - 53 unit tests (Vitest) Specifications: - authentication: AD auth, sessions, JWT tokens - chat-room: rooms, members, templates - realtime-messaging: WebSocket, messages, reactions - file-storage: MinIO integration, file management - frontend-core: React SPA structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
556 lines
18 KiB
Markdown
556 lines
18 KiB
Markdown
# Database Schema Documentation
|
||
|
||
## 概述
|
||
|
||
Chat Room 模組使用三個主要資料表來管理事件室、成員關係與範本。本文檔說明資料庫架構、關聯關係、索引策略與設計考量。
|
||
|
||
---
|
||
|
||
## ER Diagram (實體關聯圖)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ incident_rooms │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ PK │ room_id (VARCHAR(36), UUID) │
|
||
│ │ title (VARCHAR(255), NOT NULL) │
|
||
│ │ incident_type (ENUM, NOT NULL) │
|
||
│ │ severity (ENUM, NOT NULL) │
|
||
│ │ status (ENUM, DEFAULT 'ACTIVE') │
|
||
│ │ location (VARCHAR(255)) │
|
||
│ │ description (TEXT) │
|
||
│ │ resolution_notes (TEXT, NULLABLE) │
|
||
│ │ created_by (VARCHAR(255), NOT NULL) │
|
||
│ │ created_at (TIMESTAMP, DEFAULT NOW) │
|
||
│ │ resolved_at (TIMESTAMP, NULLABLE) │
|
||
│ │ archived_at (TIMESTAMP, NULLABLE) │
|
||
│ │ last_activity_at (TIMESTAMP, DEFAULT NOW) │
|
||
│ │ last_updated_at (TIMESTAMP, DEFAULT NOW) │
|
||
│ │ member_count (INTEGER, DEFAULT 0) │
|
||
│ │ ownership_transferred_at (TIMESTAMP, NULLABLE) ⭐ │
|
||
│ │ ownership_transferred_by (VARCHAR(255), NULLABLE) ⭐ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
│ 1:N
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ room_members │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ PK │ id (INTEGER, AUTO_INCREMENT) │
|
||
│ FK │ room_id (VARCHAR(36)) ──→ incident_rooms.room_id │
|
||
│ │ user_id (VARCHAR(255), NOT NULL) │
|
||
│ │ role (ENUM, NOT NULL) │
|
||
│ │ added_by (VARCHAR(255), NOT NULL) │
|
||
│ │ added_at (TIMESTAMP, DEFAULT NOW) │
|
||
│ │ removed_at (TIMESTAMP, NULLABLE) ← 軟刪除 │
|
||
│ │ │
|
||
│ │ UNIQUE (room_id, user_id) WHERE removed_at IS NULL │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
|
||
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ room_templates │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ PK │ template_id (INTEGER, AUTO_INCREMENT) │
|
||
│ │ name (VARCHAR(100), UNIQUE, NOT NULL) │
|
||
│ │ description (TEXT) │
|
||
│ │ incident_type (ENUM, NOT NULL) │
|
||
│ │ default_severity (ENUM, NOT NULL) │
|
||
│ │ default_members (JSON) │
|
||
│ │ metadata_fields (JSON, NULLABLE) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 資料表詳細說明
|
||
|
||
### 1. incident_rooms (事件室)
|
||
|
||
**用途**: 儲存生產線異常事件室的核心資訊
|
||
|
||
#### 欄位說明
|
||
|
||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||
|---------|---------|------|-----|
|
||
| `room_id` | VARCHAR(36) | PK, NOT NULL | UUID 格式的房間唯一識別碼 |
|
||
| `title` | VARCHAR(255) | NOT NULL | 事件標題 |
|
||
| `incident_type` | ENUM | NOT NULL | 事件類型:EQUIPMENT_FAILURE, MATERIAL_SHORTAGE, QUALITY_ISSUE, OTHER |
|
||
| `severity` | ENUM | NOT NULL | 嚴重程度:LOW, MEDIUM, HIGH, CRITICAL |
|
||
| `status` | ENUM | DEFAULT 'ACTIVE' | 房間狀態:ACTIVE, RESOLVED, ARCHIVED |
|
||
| `location` | VARCHAR(255) | | 事件發生地點 |
|
||
| `description` | TEXT | | 事件詳細描述 |
|
||
| `resolution_notes` | TEXT | NULLABLE | 解決方案說明(狀態為 RESOLVED 時填寫) |
|
||
| `created_by` | VARCHAR(255) | NOT NULL | 建立者的使用者 ID(信箱) |
|
||
| `created_at` | TIMESTAMP | DEFAULT NOW | 建立時間 |
|
||
| `resolved_at` | TIMESTAMP | NULLABLE | 標記為已解決的時間 |
|
||
| `archived_at` | TIMESTAMP | NULLABLE | 封存時間(軟刪除標記) |
|
||
| `last_activity_at` | TIMESTAMP | DEFAULT NOW | 最後活動時間(用於排序) |
|
||
| `last_updated_at` | TIMESTAMP | DEFAULT NOW | 最後更新時間 |
|
||
| `member_count` | INTEGER | DEFAULT 0 | 當前活躍成員數量 |
|
||
| `ownership_transferred_at` | TIMESTAMP | NULLABLE | **所有權轉移時間** ⭐ |
|
||
| `ownership_transferred_by` | VARCHAR(255) | NULLABLE | **執行轉移的使用者** ⭐ |
|
||
|
||
#### 索引策略
|
||
|
||
```sql
|
||
-- 主鍵索引
|
||
CREATE UNIQUE INDEX pk_incident_rooms ON incident_rooms(room_id);
|
||
|
||
-- 複合索引:用於按狀態與時間查詢
|
||
CREATE INDEX ix_incident_rooms_status_created
|
||
ON incident_rooms(status, created_at DESC);
|
||
|
||
-- 單欄索引:用於查詢特定使用者建立的房間
|
||
CREATE INDEX ix_incident_rooms_created_by
|
||
ON incident_rooms(created_by);
|
||
|
||
-- 用於查詢活躍房間(軟刪除)
|
||
CREATE INDEX ix_incident_rooms_archived
|
||
ON incident_rooms(archived_at)
|
||
WHERE archived_at IS NULL;
|
||
```
|
||
|
||
#### 設計考量
|
||
|
||
1. **UUID 主鍵**: 使用 UUID 而非自增 ID,避免 ID 可預測性問題
|
||
2. **軟刪除**: 使用 `archived_at` 進行軟刪除,保留歷史記錄
|
||
3. **冗餘欄位**: `member_count` 為冗餘欄位,提升查詢效能
|
||
4. **稽核欄位**: `ownership_transferred_at` 和 `ownership_transferred_by` 用於追蹤所有權變更
|
||
|
||
---
|
||
|
||
### 2. room_members (房間成員)
|
||
|
||
**用途**: 管理房間成員關係與權限
|
||
|
||
#### 欄位說明
|
||
|
||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||
|---------|---------|------|-----|
|
||
| `id` | INTEGER | PK, AUTO_INCREMENT | 自增主鍵 |
|
||
| `room_id` | VARCHAR(36) | FK, NOT NULL | 關聯到 incident_rooms.room_id |
|
||
| `user_id` | VARCHAR(255) | NOT NULL | 使用者 ID(信箱) |
|
||
| `role` | ENUM | NOT NULL | 角色:OWNER, EDITOR, VIEWER |
|
||
| `added_by` | VARCHAR(255) | NOT NULL | 新增此成員的使用者 |
|
||
| `added_at` | TIMESTAMP | DEFAULT NOW | 加入時間 |
|
||
| `removed_at` | TIMESTAMP | NULLABLE | 移除時間(軟刪除標記) |
|
||
|
||
#### 約束條件
|
||
|
||
```sql
|
||
-- 外鍵約束
|
||
ALTER TABLE room_members
|
||
ADD CONSTRAINT fk_room_members_room_id
|
||
FOREIGN KEY (room_id) REFERENCES incident_rooms(room_id)
|
||
ON DELETE CASCADE;
|
||
|
||
-- 唯一性約束:同一房間中,每個使用者只能有一個活躍成員記錄
|
||
-- SQLite
|
||
CREATE UNIQUE INDEX ix_room_members_unique_active
|
||
ON room_members(room_id, user_id)
|
||
WHERE removed_at IS NULL;
|
||
|
||
-- PostgreSQL
|
||
CREATE UNIQUE INDEX ix_room_members_unique_active
|
||
ON room_members(room_id, user_id)
|
||
WHERE removed_at IS NULL;
|
||
```
|
||
|
||
#### 索引策略
|
||
|
||
```sql
|
||
-- 複合索引:用於快速查詢房間的特定成員
|
||
CREATE INDEX ix_room_members_room_user
|
||
ON room_members(room_id, user_id);
|
||
|
||
-- 單欄索引:用於查詢使用者參與的所有房間
|
||
CREATE INDEX ix_room_members_user
|
||
ON room_members(user_id);
|
||
|
||
-- 用於查詢活躍成員
|
||
CREATE INDEX ix_room_members_active
|
||
ON room_members(removed_at)
|
||
WHERE removed_at IS NULL;
|
||
```
|
||
|
||
#### 設計考量
|
||
|
||
1. **軟刪除**: 使用 `removed_at` 而非實際刪除記錄
|
||
- 優點:保留成員歷史、支援稽核、可恢復誤刪
|
||
- 缺點:需要在所有查詢中加入 `WHERE removed_at IS NULL`
|
||
|
||
2. **角色設計**: 簡單的 ENUM 而非獨立的角色表
|
||
- 適合固定的少量角色
|
||
- 若未來需要動態角色,應改為關聯表設計
|
||
|
||
3. **級聯刪除**: 當房間刪除時,自動刪除所有成員記錄
|
||
- 配合軟刪除機制,實際不會觸發(房間只會被標記為 archived)
|
||
|
||
---
|
||
|
||
### 3. room_templates (房間範本)
|
||
|
||
**用途**: 儲存預定義的房間範本,用於快速建立標準化事件室
|
||
|
||
#### 欄位說明
|
||
|
||
| 欄位名稱 | 資料型別 | 約束 | 說明 |
|
||
|---------|---------|------|-----|
|
||
| `template_id` | INTEGER | PK, AUTO_INCREMENT | 範本唯一識別碼 |
|
||
| `name` | VARCHAR(100) | UNIQUE, NOT NULL | 範本名稱(如 "equipment_failure") |
|
||
| `description` | TEXT | | 範本說明 |
|
||
| `incident_type` | ENUM | NOT NULL | 預設事件類型 |
|
||
| `default_severity` | ENUM | NOT NULL | 預設嚴重程度 |
|
||
| `default_members` | JSON | | 預設成員列表(JSON 陣列) |
|
||
| `metadata_fields` | JSON | NULLABLE | 擴展用元資料欄位 |
|
||
|
||
#### JSON 欄位範例
|
||
|
||
```json
|
||
// default_members 範例
|
||
[
|
||
{
|
||
"user_id": "maintenance_team@panjit.com.tw",
|
||
"role": "editor"
|
||
},
|
||
{
|
||
"user_id": "engineering@panjit.com.tw",
|
||
"role": "viewer"
|
||
}
|
||
]
|
||
|
||
// metadata_fields 範例(未來擴展)
|
||
{
|
||
"required_fields": ["equipment_id", "line_number"],
|
||
"custom_fields": [
|
||
{
|
||
"name": "equipment_id",
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 索引策略
|
||
|
||
```sql
|
||
-- 唯一索引:範本名稱必須唯一
|
||
CREATE UNIQUE INDEX ix_room_templates_name
|
||
ON room_templates(name);
|
||
|
||
-- 單欄索引:用於按類型查詢範本
|
||
CREATE INDEX ix_room_templates_incident_type
|
||
ON room_templates(incident_type);
|
||
```
|
||
|
||
#### 設計考量
|
||
|
||
1. **JSON 欄位**: 使用 JSON 儲存結構化資料
|
||
- 優點:彈性高、易於擴展
|
||
- 缺點:無法建立索引、查詢效能較差
|
||
- 適用場景:讀取頻繁、寫入較少、資料結構可能變動
|
||
|
||
2. **預設範本**: 系統啟動時自動初始化三個預設範本
|
||
- equipment_failure(設備故障)
|
||
- material_shortage(物料短缺)
|
||
- quality_issue(品質問題)
|
||
|
||
---
|
||
|
||
## 關聯關係
|
||
|
||
### 1. incident_rooms ↔ room_members (1:N)
|
||
|
||
```sql
|
||
-- 一個房間可以有多個成員
|
||
SELECT r.*, m.user_id, m.role
|
||
FROM incident_rooms r
|
||
LEFT JOIN room_members m ON r.room_id = m.room_id
|
||
WHERE m.removed_at IS NULL
|
||
AND r.archived_at IS NULL;
|
||
```
|
||
|
||
### 2. 使用者 ↔ room_members ↔ incident_rooms (M:N)
|
||
|
||
```sql
|
||
-- 一個使用者可以參與多個房間
|
||
-- 透過 room_members 作為中介表實現多對多關係
|
||
SELECT r.*
|
||
FROM incident_rooms r
|
||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||
WHERE m.user_id = 'user@panjit.com.tw'
|
||
AND m.removed_at IS NULL
|
||
AND r.archived_at IS NULL
|
||
ORDER BY r.last_activity_at DESC;
|
||
```
|
||
|
||
---
|
||
|
||
## 常見查詢模式與優化
|
||
|
||
### 1. 列出使用者的所有活躍房間
|
||
|
||
```sql
|
||
-- 優化前(N+1 問題)
|
||
SELECT * FROM incident_rooms WHERE room_id IN (
|
||
SELECT room_id FROM room_members
|
||
WHERE user_id = ? AND removed_at IS NULL
|
||
);
|
||
|
||
-- 優化後(使用 JOIN)
|
||
SELECT r.*, m.role as user_role
|
||
FROM incident_rooms r
|
||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||
WHERE m.user_id = ?
|
||
AND m.removed_at IS NULL
|
||
AND r.archived_at IS NULL
|
||
ORDER BY r.last_activity_at DESC
|
||
LIMIT ? OFFSET ?;
|
||
|
||
-- 索引使用:
|
||
-- - ix_room_members_user (user_id)
|
||
-- - ix_incident_rooms_archived (archived_at)
|
||
```
|
||
|
||
### 2. 取得房間詳情與所有成員
|
||
|
||
```sql
|
||
-- 優化:使用單一查詢避免 N+1
|
||
SELECT
|
||
r.*,
|
||
json_group_array(
|
||
json_object(
|
||
'user_id', m.user_id,
|
||
'role', m.role,
|
||
'added_at', m.added_at
|
||
)
|
||
) as members
|
||
FROM incident_rooms r
|
||
LEFT JOIN room_members m ON r.room_id = m.room_id AND m.removed_at IS NULL
|
||
WHERE r.room_id = ?
|
||
GROUP BY r.room_id;
|
||
|
||
-- 索引使用:
|
||
-- - pk_incident_rooms (room_id)
|
||
-- - ix_room_members_room_user (room_id, user_id)
|
||
```
|
||
|
||
### 3. 搜尋房間(全文搜尋)
|
||
|
||
```sql
|
||
-- SQLite FTS5(全文搜尋)
|
||
CREATE VIRTUAL TABLE incident_rooms_fts USING fts5(
|
||
room_id UNINDEXED,
|
||
title,
|
||
description,
|
||
location
|
||
);
|
||
|
||
-- 搜尋查詢
|
||
SELECT r.*
|
||
FROM incident_rooms r
|
||
INNER JOIN incident_rooms_fts fts ON r.room_id = fts.room_id
|
||
WHERE fts MATCH '設備 OR 故障'
|
||
AND r.archived_at IS NULL;
|
||
|
||
-- PostgreSQL(使用 pg_trgm)
|
||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||
|
||
CREATE INDEX ix_incident_rooms_search
|
||
ON incident_rooms USING gin (
|
||
to_tsvector('chinese', title || ' ' || description || ' ' || location)
|
||
);
|
||
|
||
SELECT *
|
||
FROM incident_rooms
|
||
WHERE to_tsvector('chinese', title || ' ' || description || ' ' || location)
|
||
@@ to_tsquery('chinese', '設備 & 故障')
|
||
AND archived_at IS NULL;
|
||
```
|
||
|
||
---
|
||
|
||
## 資料完整性與約束
|
||
|
||
### 1. 業務規則約束
|
||
|
||
```python
|
||
# 在應用層實施的約束
|
||
|
||
# 1. 房間至少要有一個 OWNER
|
||
def validate_room_has_owner(db, room_id):
|
||
owner_count = db.query(RoomMember).filter(
|
||
RoomMember.room_id == room_id,
|
||
RoomMember.role == MemberRole.OWNER,
|
||
RoomMember.removed_at.is_(None)
|
||
).count()
|
||
|
||
if owner_count == 0:
|
||
raise ValueError("Room must have at least one OWNER")
|
||
|
||
# 2. 狀態轉換規則
|
||
VALID_TRANSITIONS = {
|
||
RoomStatus.ACTIVE: [RoomStatus.RESOLVED],
|
||
RoomStatus.RESOLVED: [RoomStatus.ARCHIVED],
|
||
RoomStatus.ARCHIVED: []
|
||
}
|
||
|
||
def validate_status_transition(current_status, new_status):
|
||
if new_status not in VALID_TRANSITIONS.get(current_status, []):
|
||
raise ValueError(f"Invalid transition: {current_status} -> {new_status}")
|
||
```
|
||
|
||
### 2. 資料庫級約束
|
||
|
||
```sql
|
||
-- CHECK 約束(僅 PostgreSQL 支援)
|
||
ALTER TABLE incident_rooms
|
||
ADD CONSTRAINT chk_severity CHECK (
|
||
severity IN ('LOW', 'MEDIUM', 'HIGH', 'CRITICAL')
|
||
);
|
||
|
||
ALTER TABLE room_members
|
||
ADD CONSTRAINT chk_role CHECK (
|
||
role IN ('OWNER', 'EDITOR', 'VIEWER')
|
||
);
|
||
|
||
-- 確保 resolved_at 晚於 created_at
|
||
ALTER TABLE incident_rooms
|
||
ADD CONSTRAINT chk_resolved_after_created CHECK (
|
||
resolved_at IS NULL OR resolved_at >= created_at
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 效能考量
|
||
|
||
### 1. 索引策略總結
|
||
|
||
| 資料表 | 索引 | 類型 | 用途 |
|
||
|-------|------|------|------|
|
||
| incident_rooms | room_id | PRIMARY KEY | 主鍵 |
|
||
| incident_rooms | (status, created_at) | COMPOSITE | 狀態篩選與排序 |
|
||
| incident_rooms | created_by | SINGLE | 建立者查詢 |
|
||
| room_members | (room_id, user_id) | COMPOSITE | 房間成員查詢 |
|
||
| room_members | user_id | SINGLE | 使用者房間列表 |
|
||
| room_members | (room_id, user_id) WHERE removed_at IS NULL | UNIQUE PARTIAL | 唯一性約束 |
|
||
| room_templates | name | UNIQUE | 範本名稱查詢 |
|
||
|
||
### 2. 查詢效能估算
|
||
|
||
假設資料量:
|
||
- 10,000 個房間
|
||
- 平均每個房間 5 個成員 = 50,000 筆成員記錄
|
||
|
||
```sql
|
||
-- 1. 查詢使用者的房間(有索引)
|
||
-- 預期:<10ms
|
||
EXPLAIN QUERY PLAN
|
||
SELECT r.* FROM incident_rooms r
|
||
INNER JOIN room_members m ON r.room_id = m.room_id
|
||
WHERE m.user_id = 'user@example.com' AND m.removed_at IS NULL;
|
||
-- 使用索引:ix_room_members_user
|
||
|
||
-- 2. 查詢房間成員(有索引)
|
||
-- 預期:<5ms
|
||
EXPLAIN QUERY PLAN
|
||
SELECT * FROM room_members
|
||
WHERE room_id = 'xxx' AND removed_at IS NULL;
|
||
-- 使用索引:ix_room_members_room_user
|
||
```
|
||
|
||
### 3. 冗餘欄位權衡
|
||
|
||
**member_count 冗餘欄位**:
|
||
```sql
|
||
-- 不使用冗餘(每次都計算)
|
||
SELECT COUNT(*) FROM room_members
|
||
WHERE room_id = ? AND removed_at IS NULL;
|
||
-- 成本:每次查詢都需要掃描 room_members
|
||
|
||
-- 使用冗餘(直接讀取)
|
||
SELECT member_count FROM incident_rooms
|
||
WHERE room_id = ?;
|
||
-- 成本:寫入時需要同步更新,但讀取極快
|
||
|
||
-- 權衡:讀取頻率 >> 寫入頻率 → 使用冗餘欄位
|
||
```
|
||
|
||
---
|
||
|
||
## 資料遷移策略
|
||
|
||
### 1. 初始化腳本
|
||
|
||
```python
|
||
# alembic/versions/001_create_chat_room_tables.py
|
||
|
||
def upgrade():
|
||
# 1. 建立 incident_rooms 表
|
||
op.create_table(
|
||
'incident_rooms',
|
||
sa.Column('room_id', sa.String(36), primary_key=True),
|
||
sa.Column('title', sa.String(255), nullable=False),
|
||
# ... 其他欄位
|
||
)
|
||
|
||
# 2. 建立 room_members 表
|
||
op.create_table(
|
||
'room_members',
|
||
sa.Column('id', sa.Integer, primary_key=True, autoincrement=True),
|
||
# ... 其他欄位
|
||
)
|
||
|
||
# 3. 建立索引
|
||
op.create_index('ix_incident_rooms_status_created',
|
||
'incident_rooms', ['status', 'created_at'])
|
||
# ... 其他索引
|
||
|
||
def downgrade():
|
||
op.drop_table('room_members')
|
||
op.drop_table('incident_rooms')
|
||
op.drop_table('room_templates')
|
||
```
|
||
|
||
### 2. 資料備份策略
|
||
|
||
```bash
|
||
# SQLite 備份
|
||
sqlite3 task_reporter.db ".backup task_reporter_backup.db"
|
||
|
||
# PostgreSQL 備份
|
||
pg_dump -U postgres -d task_reporter > backup.sql
|
||
|
||
# 恢復
|
||
psql -U postgres -d task_reporter < backup.sql
|
||
```
|
||
|
||
---
|
||
|
||
## 總結
|
||
|
||
### 架構優勢
|
||
|
||
✅ **正規化設計** - 避免資料重複,保持一致性
|
||
✅ **軟刪除機制** - 保留歷史記錄,支援稽核
|
||
✅ **索引優化** - 覆蓋常見查詢模式
|
||
✅ **彈性擴展** - JSON 欄位支援未來需求變更
|
||
✅ **稽核追蹤** - 記錄所有關鍵操作(包含所有權轉移)
|
||
|
||
### 架構限制
|
||
|
||
⚠️ **軟刪除開銷** - 所有查詢需加入 `removed_at IS NULL`
|
||
⚠️ **JSON 查詢限制** - default_members 無法有效索引
|
||
⚠️ **冗餘欄位同步** - member_count 需要應用層維護一致性
|
||
⚠️ **無分散式支援** - 目前設計未考慮分散式部署
|
||
|
||
### 未來優化建議
|
||
|
||
1. **分區表** - 當資料量達到百萬級時,考慮按時間分區
|
||
2. **讀寫分離** - 使用主從複製提升讀取效能
|
||
3. **快取層** - Redis 快取熱門房間資訊
|
||
4. **全文搜尋引擎** - Elasticsearch 提升搜尋效能
|