03 資料模型與儲存
主要讀者:DBA、後端研發工程師、測試平台維護者。
1 範圍與責任
本章只描述 Crystal/Sentry/Northstar E2E 測試平台。受測 API 與其業務資料是測試對象,不納入本平台實體字典。Sentry 是共用核心 library,並非另一個獨立資料服務。
圖中的型別是 Java 映射型別。PK 表示映射識別鍵,FK 表示邏輯關聯;實體約束是否已部署尚未查驗。歷史建表雖有部分 PK/FK 宣告,但不足以證明現場 schema 與工作樹一致。完整欄位與歷史/現行差異見 04 資料字典。
1-1 實體對照與資料來源
| 圖中實體 | 真實資料表 | 權威內容與負責元件 |
|---|---|---|
| PROJECT | qa.qa_c001 |
Crystal 維護公司與專案 |
| ENVIRONMENT/CONFIG | qa.qa_c002/qa.qa_c012 |
Crystal 維護環境與設定;執行時由 Northstar 載入 |
| SUITE/CASE_DEF/STEP_DEF | qa.qa_c003/qa.qa_c004/qa.qa_c013 |
Crystal 維護測試群組、案例及六類步驟定義 |
| PLAN/PLAN_CASE | qa.qa_c005/qa.qa_c006 |
計畫選取哪些 Case;不等同選取整個 Suite |
| TRIGGER_DEF | qa.qa_c007 |
計畫、環境、cron、下次執行時間與通知群組 |
| RUN/RUN_SUITE/RUN_CASE/RUN_STEP | qa.qa_c008~qa.qa_c011 |
Northstar 透過 Sentry 執行,經 Crystal API 回寫本次結果 |
| COMPARE/COMPARE_DETAIL | qa.qa_r001/qa.qa_r002 |
既有 SQL 比對資料與報表契約;有寫入 API,不保證每條現行 handler 都填入 |
| ARTIFACT | qa.qa_u001 |
執行產物的檔名、來源與遠端位置;實際儲存位置及停機後可用性待環境核對 |
| INVOKE_TYPE | qa.qa_z000 |
參數型別及可選呼叫類別的清單;不是方法執行紀錄 |
1-2 定義、結果、業務資料分開
| 資料層 | 保存什麼 | 不代表什麼 |
|---|---|---|
| 定義 | 下次執行的案例與設定 | 不包含某次實際回應或最後一次結果 |
| 執行結果 | 每次 Run 的狀態、時間、設定記錄與步驟證據 | Run FINISHED 不等於全部 Case PASSED |
| 測試資料 | _in 基準來源、受測表、_qa 預期資料及 SQL 操作結果 |
不是每個 Run 都有獨立副本;不是平台 schema 固定欄位 |
| 執行變數 | 環境設定與 RUN/CASE 暫存值 | 不是持久工作佇列,也不是環境隔離 |
| 產物 | SQL 比對 JSON、報告與其他已上傳檔案 | 檔案路徑存在不等於檔案仍可下載 |
早上供同仁使用及半夜測試,使用同一套 EKS 環境和資料。時間分流本身沒有資料隔離、互斥或每次 Run 還原到同一資料狀態的保證。夜間固定會強制關閉,早上再次啟動後須能查到前一夜的線索;不因測試異常或 Run 尚未結束而持續保留執行環境。關閉時可能仍有 RUNNING 紀錄,須在早上核對最後回報。這是已確認需求,尚未驗證完整持久化及強制關閉實作;目前 17 張表未提供完整環境啟停生命週期欄位,不能將 Run 的完成狀態當作 EKS 已關閉的證據。
2 識別碼、hooks 與結果快照
2-1 必須保留每次執行的識別碼
| 層次 | 定義識別碼 | 當次結果識別碼 | 解讀方式 |
|---|---|---|---|
| Suite | suite_num | run_suite_num | 同一群組可出現在多次 Run |
| Case | case_num | run_case_num | 同一 Case/hook 在同一 Run 也可能被呼叫多次 |
| Step | step_num | run_step_num | 指向本次呼叫的實際結果,不只指向可重用定義 |
例如 BeforeEach 會在每個一般 Case 前執行,因此不能將 (runNum, caseNum) 設為所有結果的唯一鍵。結果查詢應沿 run_num → run_suite_num → run_case_num → run_step_num 取得證據。重跑會新增結果,不把第一次失敗改成第二次成功。
2-2 設定記錄的界限
| 結果層 | 保存的設定 | 限制 |
|---|---|---|
| Run | plan_record、env_record、trigger_record | 各物件序列化內容;不是全部定義在同一時間鎖定版本 |
| RunSuite | suite_record | 不能推論報表所有名稱都只讀此欄 |
| RunCase | case_record | hooks 重複呼叫會有多筆結果;部分例外欄位未保證填值 |
| RunStep | step_exec、step_name、step_type、step_order | 能追查當時步驟設定,不能完整還原受測系統及外部資料 |
結果列在執行時仍會更新狀態、時間與回應。部分報告仍 JOIN 現行 Case/Step 名稱;修改定義後,舊報告所有顯示內容並不保證不變。因此不能宣稱現況是不可變快照或完整可重播系統。
3 一致性與防呆
| 規則 | 現況 | 應如何驗證/改善 |
|---|---|---|
| Plan 選案 | 透過 qa_c006 關聯 Case | 核對 (plan_num, case_num) 重複資料及是否有組合唯一約束 |
| 案例與步驟排序 | case_order/step_order 決定執行順序 | 重複排序值、同時修改及完整清單保存需 round-trip 測試 |
| Case 相依 | 依 depends_on 取相依 Case 的結果 | 現況只攔截查得的 FAILED,查詢未限定本次 Run/RunSuite;ERROR/未執行與跨 Run 影響須修正及驗證 |
| hooks | BEFORE_ALL、BEFORE_EACH、AFTER_EACH、AFTER_ALL 也都是 Case | 不可另造固定一對一結果;after hooks 不因名稱就有 finally 清理保證 |
| 環境設定 | qa_c012 以 env_num、config_key 表達 | 未確認組合唯一,重複鍵及敏感內容保存須核對 |
| 同步快照 | 各層執行時保存自己的設定 | 需要統一定義版本是改善提案,並非現況 |
| RunSuite 狀態 | 存在 suite_status 欄位 | 目前新增 RunSuite 段未填此欄,不能用欄位存在推論完整狀態流 |
| 比對輸出 | 現行兩類 SQL handler 保存 JSON 產物 | qa_r001 報表資料與檔案內容是否同步需獨立核對 |
| 執行產物 | qa_u001 以 source_num 查詢檔案 | 查詢未限制 source_type;混用來源類型前先處理識別碰撞 |
| EKS 停機 | 夜間固定強制關閉,早上啟動後查線索 | 關閉前持續保存關聯識別、最後狀態與錯誤;停機結果與證據可讀性分別驗證,不以 run_status=3 代替 |
完整組合欄位規則見 字典第 6 節,整合錯誤規格見 06 整合與交易時序。
4 索引與並行控制
4-1 索引候選
以下來自現行查詢模式,是待評估的索引設計,不是已部署清單。先查現有索引並使用測試環境的查詢計畫評估,避免重複建立。
| 用途 | 表 | 候選欄位順序 | 注意事項 |
|---|---|---|---|
| 公司內找專案 | qa_c001 | (com_num, project_code) |
若要唯一,先清查重複與業務規則 |
| 群組案例排序 | qa_c004 | (suite_num, is_deleted, case_order) |
同時考慮 category 篩選,勿直接宣稱排序值唯一 |
| 案例步驟排序 | qa_c013 | (case_num, step_order) |
依案例讀完整步驟 |
| 計畫選案 | qa_c006 | (plan_num, case_num) |
集合語意可考慮 Unique;目前未證實已部署 |
| 排程領取 | qa_c007 | (is_enabled, next_run_time) |
索引只能加速查詢,不能防止兩個 worker 重複領取 |
| 單次結果階層 | qa_c009/qa_c010/qa_c011 | 分別 (run_num)/(run_suite_num, case_num)/(run_case_num, step_order) |
保留各層執行 PK,不以 case_num 覆蓋歷史結果 |
| Run 清單與保留查詢 | qa_c008 | (project_num, create_at) |
清理政策需另外確認,欄位存在不代表已排程清理 |
| 環境設定 | qa_c012 | (env_num, config_key) |
唯一性需先核對實際資料 |
| 比對報表 | qa_r001/qa_r002 | (run_num, case_num, step_num)/(compare_num, row_num, column_name) |
同一 hook/步驟多次執行的粒度需額外確認 |
| 產物查詢 | qa_u001 | (source_num);改善後評估 (source_type, source_num) |
查詢條件與索引一起調整 |
4-2 現況與改善邊界
| 控制項目 | 已知現況 | 不可宣稱/後續設計 |
|---|---|---|
| 樂觀鎖 | 本範圍沒有通用 version 欄位及 compare-and-set 證據 | 新增版本檢查屬改善,現在仍須測試兩人同時編輯的覆寫風險 |
| 悲觀鎖 | 本範圍未確認使用 SELECT FOR UPDATE 領取排程或鎖環境 | 若要跨 worker 互斥,需設計持久領取與原子狀態轉移 |
| Trigger 防重 | 現有流程依 triggerNum 使用快取旗標 | 不等於同環境不同 Trigger 互斥,也不等於持久佇列 |
| 狀態回寫 | Run/Case/Step 分別寫入 | 程序中斷時可能只保存部分結果;不得把缺筆當通過 |
| SQL 比對 JSON | 讀取現有檔案、加入步驟結果後覆寫 | 同一 Run 的並行寫入可能競爭;目前未做壓力與故障測試 |
| 資料準備 | PRE_SQL/SQL_TRANSFER 可修改測試資料 | 同環境其他使用者可能受影響;目前沒有每次 Run 的資料分區 |
| 重送冪等 | 有各層識別碼,但不等於所有寫 API 都去重 | 目標冪等合約見整合規格;不可盲目重送會有副作用的步驟 |
若後續建立環境使用權與固定版本,需新增相應模型及交易設計。這些提案不能填成目前資料庫已存在的表、欄位或狀態。
5 產物、報表與保留
5-1 現有產物與報表
SQL 比對 handler 目前將結果加入 SqlCompareReport_<runNum>.json。Crystal 同時保有 qa_r001/qa_r002 的寫入介面及報告查詢;兩套儲存的資料來源須分別驗證,不能假設每個新 Run 都自動填滿比對表。
Email 一般案例統計只包含已記錄的 NORMAL RunCase:PASSED 為通過,FAILED/ERROR 合併為失敗,其他狀態以餘數歸入略過。零案例現況回傳 100。必須另外核對原定案例與實際結果筆數,才能判斷測試是否完整。
5-2 固定強制關閉與隔日追查
已確認需求:夜間固定強制關閉,但要留下隔天早上啟動後可查的線索。 因此證據應在執行過程持續送出,不能只依靠正常完成、最後一個 after hook 或關閉時的 finally 才保存。
| 必須保留的線索 | 使用既有資料 | 目標保存與隔日檢查 |
|---|---|---|
| 本次執行範圍 | run_num、trigger_num、plan_num、env_num 及既有設定記錄 | 可定位前一夜的 Run 與執行設定,不靠記憶體 RunContext |
| 最後進度 | run_status、run_start_time/run_end_time、run_suite_num、run_case_num、run_step_num | 保留實際最後回報;沒有終態或結束時間就顯示未完成/待核對,不算通過 |
| 失敗原因 | step_error、step_result、post_proc_resp、run_log 及執行日誌 | 以同一組 Run/Case/Step ID 串起線索;資料尚未回寫時,外部日誌應能補充時間與最後動作 |
| 已產生證據 | qa_u001 的檔名、remote_file_path、來源識別及比對 JSON | 關閉後檔案內容仍可讀;只有 metadata 不足以證明檔案已保存 |
| 關閉事件 | 環境控制與平台日誌中可關聯該夜間 Run 的時間、原因與結果 | 不新增虛構資料表欄位;需核對實際環境控制提供的紀錄 |
持久化是目標驗收條件,尚未證實部署已達成。 結果資料庫、日誌及產物必須位於不會隨本次執行容器/節點關閉而遺失的儲存;目前只有寫入邏輯及路徑資訊,尚未查驗磁碟掛載、外部儲存、最後寫入完成或停機後讀取。證據未確認保存是需告警與追查的缺口,不將它解讀為可以取消固定強制關閉。
強制關閉可能來不及更新終態。早上先查前一夜 RUNNING 或缺少結束時間的紀錄,對照最後步驟、日誌與關閉事件,再依實際結果補註中斷原因;不能直接把所有殘留 RUNNING 判成正常完成。狀態整理是目標流程,尚未證實已有自動 reconciliation。保留原本結果與狀態修正線索;人工重跑另建新 Run,不用新結果覆寫前一夜的失敗或不完整證據。
5-3 保留政策
結果與產物保留期限若採既有需求的 90 天,應包含關聯結果、比對明細及檔案清理策略;目前未證實排程清理已實作。本文件不執行刪除,清理前應先驗證保留期間及下游引用。
6 DBA 唯讀核對
本次沒有連線查詢實際資料庫。以下 PostgreSQL metadata 查詢可用於確認測試環境的部署事實;不需讀取 config_value 或實際案例內容。
6-1 欄位與實體型別
SELECT table_name, ordinal_position, column_name,
data_type, udt_name, character_maximum_length,
numeric_precision, numeric_scale, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'qa'
AND table_name IN ('qa_c001','qa_c002','qa_c003','qa_c004','qa_c005',
'qa_c006','qa_c007','qa_c008','qa_c009','qa_c010',
'qa_c011','qa_c012','qa_c013','qa_r001','qa_r002',
'qa_u001','qa_z000')
ORDER BY table_name, ordinal_position;
6-2 完整約束與索引
SELECT t.relname AS table_name, c.conname, c.contype,
pg_get_constraintdef(c.oid) AS definition
FROM pg_constraint c
JOIN pg_class t ON t.oid = c.conrelid
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE n.nspname = 'qa'
AND t.relname IN ('qa_c001','qa_c002','qa_c003','qa_c004','qa_c005',
'qa_c006','qa_c007','qa_c008','qa_c009','qa_c010',
'qa_c011','qa_c012','qa_c013','qa_r001','qa_r002',
'qa_u001','qa_z000')
ORDER BY t.relname, c.conname;
SELECT tablename, indexname, indexdef
FROM pg_indexes
WHERE schemaname = 'qa'
AND tablename IN ('qa_c001','qa_c002','qa_c003','qa_c004','qa_c005',
'qa_c006','qa_c007','qa_c008','qa_c009','qa_c010',
'qa_c011','qa_c012','qa_c013','qa_r001','qa_r002',
'qa_u001','qa_z000')
ORDER BY tablename, indexname;
6-3 組合鍵重複檢查
SELECT plan_num, case_num, count(*) AS duplicate_count
FROM qa.qa_c006
GROUP BY plan_num, case_num
HAVING count(*) > 1;
SELECT env_num, config_key, count(*) AS duplicate_count
FROM qa.qa_c012
GROUP BY env_num, config_key
HAVING count(*) > 1;
不要對 RunCase 執行 (runNum, caseNum) 去重:BeforeEach/AfterEach 的重複結果可能是正確紀錄。物理約束、資料完整性與並行寫入尚待實測,驗收方式見 08 UAT 與驗證。