docs: benchmark search and document scaling
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- Added production configuration validation for secrets, encryption keys, and the public HTTPS URL, plus an administrator-facing status check.
|
- Added production configuration validation for secrets, encryption keys, and the public HTTPS URL, plus an administrator-facing status check.
|
||||||
- Added Prometheus-compatible metrics and distinct liveness/readiness health probes.
|
- Added Prometheus-compatible metrics and distinct liveness/readiness health probes.
|
||||||
- Added SQLite FTS5 indexing for public-content search, maintained automatically as posts change.
|
- Added SQLite FTS5 indexing for public-content search, maintained automatically as posts change.
|
||||||
|
- Added a repeatable FTS search benchmark and documented PostgreSQL migration decision criteria.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# 擴展與資料庫評估
|
||||||
|
|
||||||
|
## 搜尋基準
|
||||||
|
|
||||||
|
FTS5 索引由 `posts_fts` 與 SQLite trigger 維護。可在部署前後以相同關鍵字比較查詢計畫與平均時間:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/benchmark-search.sh "關鍵字" 100
|
||||||
|
```
|
||||||
|
|
||||||
|
輸出中的 `queryPlan` 應包含 FTS 虛擬表掃描,而不是 `posts` 的全表 `LIKE` 掃描。請記錄貼文數量、硬體、SQLite 版本與平均查詢時間,作為升級決策依據。
|
||||||
|
|
||||||
|
## 何時由 SQLite 遷移到 PostgreSQL
|
||||||
|
|
||||||
|
SQLite 仍適合單一主機、單一磁碟與低至中等寫入量。當出現以下任一情況時,先在 staging 驗證 PostgreSQL:
|
||||||
|
|
||||||
|
- 需要跨多台主機同時執行 Web/Worker,或需要跨區高可用。
|
||||||
|
- 寫入鎖定持續造成同步佇列延遲,或 WAL 檔案/備份窗口已難以控制。
|
||||||
|
- FTS、稽核或貼文資料量使查詢基準無法滿足服務目標。
|
||||||
|
|
||||||
|
遷移步驟:停止寫入、使用 v0.7 以後的 JSON 匯出建立內容快照、以 migration 建立 PostgreSQL schema、匯入 users/sources/posts/互動與同步工作、在 staging 驗證計數與抽樣內容、切換唯讀短暫維護窗口、最後更新 `DATABASE_URL` 及備份/監控設定。SQLite 與 PostgreSQL 的雙寫不列為預設策略;除非有完整一致性驗證,避免長期雙寫。
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Usage: ./scripts/benchmark-search.sh "搜尋詞" [iterations]
|
||||||
|
query="${1:-test}"
|
||||||
|
iterations="${2:-100}"
|
||||||
|
[[ "$iterations" =~ ^[1-9][0-9]*$ ]] || { echo "iterations must be a positive integer" >&2; exit 2; }
|
||||||
|
docker compose exec -T -e SEARCH_QUERY="$query" -e SEARCH_ITERATIONS="$iterations" web node - <<'NODE'
|
||||||
|
const { performance } = require("node:perf_hooks"); const Database = require("better-sqlite3");
|
||||||
|
const db = new Database(process.env.DATABASE_PATH, { readonly: true }); const query = process.env.SEARCH_QUERY.split(/\s+/).filter(Boolean).map((term) => `"${term.replaceAll('"','""')}"`).join(" AND "); const iterations = Number(process.env.SEARCH_ITERATIONS);
|
||||||
|
const plan = db.prepare("EXPLAIN QUERY PLAN SELECT p.id FROM posts p JOIN posts_fts ON posts_fts.rowid=p.id WHERE p.visibility='PUBLIC' AND p.hidden=0 AND posts_fts MATCH ? ORDER BY COALESCE(p.remote_created_at,p.created_at) DESC LIMIT 20").all(query);
|
||||||
|
const statement = db.prepare("SELECT p.id FROM posts p JOIN posts_fts ON posts_fts.rowid=p.id WHERE p.visibility='PUBLIC' AND p.hidden=0 AND posts_fts MATCH ? ORDER BY COALESCE(p.remote_created_at,p.created_at) DESC LIMIT 20"); const start = performance.now(); let resultCount = 0; for (let index = 0; index < iterations; index++) resultCount = statement.all(query).length; const elapsed = performance.now() - start;
|
||||||
|
console.log(JSON.stringify({ query: process.env.SEARCH_QUERY, iterations, resultCount, totalMs: Number(elapsed.toFixed(2)), averageMs: Number((elapsed / iterations).toFixed(3)), queryPlan: plan.map((row) => row.detail) }, null, 2)); db.close();
|
||||||
|
NODE
|
||||||
Reference in New Issue
Block a user