SQLite to PostgreSQL migration 的核心責任是把 embedded single-file state 升級成 server SQL operational model。這條路線通常由 multi-user access、HA、central audit、permission、online schema governance、write concurrency 或 team handoff 壓力觸發。

本文的範圍是 SQLite 升級到 PostgreSQL 時的 migration driver、SQLite 到 PostgreSQL 的 diff audit、phase plan、data movement 與驗證、cutover、cutover 前要完成的 PostgreSQL operation gate,以及 no-go 條件。

Migration Drivers

Migration drivers 的核心責任是確認 PostgreSQL 真的承擔新增責任。SQLite 在 single-node、single-file、low-concurrency 場景很強;PostgreSQL 的價值出現在 server database governance。

Driver代表需求PostgreSQL 承擔的責任
Concurrent writers多 instance / 多使用者同時寫入MVCC、connection management、lock insight
HA / PITR需要時間點恢復與 managed backupWAL archiving、replica、restore drill
Central audit需要查詢與變更證據role、log、extension、SIEM integration
Permission boundaryapp / analyst / job 權限分離DB role、grant、row / schema boundary
Schema governancemigration 要 online 且可審查migration tool、lock review、rollback
Shared data platform多服務共用正式資料connection pool、capacity、ownership

Driver 要被量化。若問題只是單一 CLI 檔案變大,先改善 backup、VACUUM、index 與 WAL runbook;若問題是多 instance 同時寫、權限分離、audit 與 PITR,PostgreSQL 才是正確路由。

Diff Audit

Diff audit 的核心責任是把 SQLite 語意轉成 PostgreSQL 語意。SQLite 的 type affinity、date / time convention、auto-increment、foreign key、index、JSON、transaction 與 extension 都要逐項審查。

面向SQLite source 問題PostgreSQL target 決策
Typedynamic typing、STRICT usageinteger / bigint / numeric / timestamptz
Primary keyrowid、INTEGER PRIMARY KEYidentity、sequence、UUID
Date/timeTEXT / INTEGER conventiontimestamptz、timezone policy
JSONJSON text / function usagejsonb、GIN index、query rewrite
ConstraintFK pragma、check、unique collationenforced FK、deferrable、collation
Indexpartial / expression / covering indexequivalent index + explain
Transactionsingle writer、savepointisolation level、deadlock retry

Type mapping 要先保護 domain invariant。金額欄位用 integer cents 或 numeric、時間欄位用 timestamptz 或明確 UTC text、boolean 用 boolean;每個轉換都要有 invalid sample 與 round-trip test。

Index mapping 要用 production query 重跑 explain。SQLite 的 EXPLAIN QUERY PLAN 只能說明 SQLite planner;PostgreSQL 需要自己的 EXPLAIN (ANALYZE, BUFFERS),並使用接近真實分布的資料量。

Phase Plan

Phase plan 的核心責任是降低一次性 cutover 風險。SQLite to PostgreSQL migration 通常可以分成 schema 建模、資料匯出、adapter 切換、shadow read、freeze / cutover 與 cleanup。

Phase目的Evidence
Schema rewrite建立 PostgreSQL target schemamigration dry run、schema review
Data export從 SQLite 取出穩定 snapshotsource checksum、row count、export log
Data import寫入 PostgreSQLtarget checksum、constraint validation
Adapter layer將 repository 改為可切換dual test suite、error mapping
Shadow read比對新舊 query resultmismatch report、latency profile
Cutover切正式寫入freeze window、rollback snapshot
Cleanup退役 SQLite write pathretention、credential、runbook update

Adapter layer 是風險控制點。Repository 應把 SQLite 與 PostgreSQL driver 差異藏在 infrastructure layer,domain 不直接依賴 vendor-specific SQL exception 或 connection object。

Shadow read 適合先驗證 read contract。正式寫入仍留在 SQLite 時,background job 可以把相同 query 跑到 PostgreSQL mirror,記錄 row count、field diff、排序差異與 latency。

Data Movement

Data movement 的核心責任是讓搬遷結果可驗證。SQLite database file 可以透過 .dump、CSV export、application-level export 或 custom ETL 搬入 PostgreSQL;選擇取決於資料量、型別轉換、FK order 與 downtime window。

1# SQLite 端:依 id 排序,匯出成帶標題列的 CSV
2#   NULL 印成空欄位,空字串印成 "",含逗號或引號的值加引號跳脫
3sqlite3 app.db ".mode csv" ".headers on" ".once orders.csv" "SELECT * FROM orders ORDER BY id;"
4# PostgreSQL 端:orders 表要先在 Schema rewrite 階段建好,表不存在時回 relation "orders" does not exist
5#   沒寫欄位清單時依位置對應,HEADER 只負責跳過標題列、不比對欄名,
6#   所以 SELECT * 的欄位順序要與 PostgreSQL 表的欄位順序相同,順序不同時資料會靜默填進別的欄位
7#   CSV 格式下沒加引號的空欄位讀成 NULL,"" 讀成空字串,兩者搬過去仍分得開
8psql "$DATABASE_URL" -c "\\copy orders FROM 'orders.csv' CSV HEADER"

這段命令是教學骨架。正式 migration 要處理 quoting、NULL、timezone、large object、FK order、batch size、transaction size、retry、import log 與 sensitive data handling。

Row count 是基本證據,checksum 是更強證據。逐列的 checksum 可以在兩端用同一段依主鍵排序的查詢輸出 CSV,再各算一次 hash:

1# SQLite 端:依 id 排序輸出 CSV,交給 shasum 算 SHA-256
2sqlite3 -csv app.db "SELECT id, total_cents FROM orders ORDER BY id;" | shasum -a 256
3# PostgreSQL 端:同一段查詢經 \copy 輸出成 CSV,再算一次
4psql "$DATABASE_URL" -c "\\copy (SELECT id, total_cents FROM orders ORDER BY id) TO STDOUT CSV" | shasum -a 256
5# 兩行印出同一個 hash,代表每一列每一欄印成的文字都相同
6# 搬遷時換過型別的欄位,兩端印出的文字不同,hash 就對不上:
7#   SQLite 的 REAL 1.5 印成 1.5,PostgreSQL 的 numeric(10,2) 印成 1.50

換過型別的欄位要在查詢裡先轉成同一種文字格式再算,或改在 application layer 對 domain key 與重要欄位做 checksum。

1SELECT COUNT(*) FROM orders;
2SELECT SUM(total_cents) FROM orders;

Aggregate checksum 適合快速抓大錯。正式驗證還要補抽樣 row diff、edge case row、foreign key check 與 business invariant。

Cutover

Cutover 的核心責任是控制最後一次寫入切換。SQLite source 在 cutover 前應進入 read-only 或 writer freeze,確保最後 snapshot、import 與 validation 對齊。

Cutover step操作Rollback 條件
Freeze writers停止背景 job、API write、admin toolsource 寫入仍持續或 freeze 失敗
Final snapshotSQLite backup / exportchecksum 失敗
Final importPostgreSQL transaction / batch importconstraint error、row mismatch
Smoke test核心 read/write workflowerror rate、latency、permission failure
Switch traffic更新 config / secret / deploymentapplication error rate 超過 tripwire
Monitorquery latency、lock、connection poolpool exhaustion、deadlock spike、data diff

Rollback 要保存 source snapshot。若 cutover 後發現 PostgreSQL error mapping、permission 或 performance 問題,可以切回 SQLite read/write snapshot;前提是 cutover window 內所有新寫入都能回放或被阻擋。

PostgreSQL Operation Gate

PostgreSQL operation gate 的核心責任是確認團隊準備好接手 server DB。Migration 成功要包含資料進入 target 與 operation readiness;PostgreSQL 需要 connection pool、backup / PITR、vacuum、index bloat、role、migration lock review 與 alert。

最小 operation checklist:

  1. Connection pool 設計:max connections、pool size、timeout、transaction pooling policy。
  2. Backup / PITR:restore drill、retention、RPO / RTO。
  3. Role / grant:application role、migration role、read-only role。
  4. Migration lock review:DDL impact、online migration strategy。
  5. Observability:slow query、lock wait、deadlock、replica lag、disk。
  6. Incident route:rollback、restore、read-only mode、on-call owner。

這個 gate 要在 cutover 前完成。SQLite 讓 operation surface 很小;PostgreSQL 擴大能力的同時,也擴大維護責任。

No-Go Conditions

No-go condition 的核心責任是阻止過早升級。若服務仍是 single-user、local-first、low-write、可用簡單 backup 解決,PostgreSQL 可能引入比問題更大的 operation cost。

No-go 訊號更合適路由
Single-user app 或 desktop app保留 SQLite + backup / migration runbook
主要壓力是備份Litestream / LiteFS
主要壓力是 edge localityD1 / Turso route
Team 尚未準備 server DB operation先補 observability / restore drill
Schema / query 還在快速探索先穩定 domain model,再做正式 migration

No-go 條件要轉成 tripwire。當 writer concurrency、audit、PITR、role 或 HA 需求跨過明確門檻,再啟動 migration。

下一步路由

SQLite to PostgreSQL migration 完成後,下一步要看 target operation。PostgreSQL 能力讀 PostgreSQL;migration 方法讀 Database Migration Playbook;若需求只是 edge platform,改讀 SQLite to D1 / Turso migration。