Chuyển đến nội dung chính

Production OMOP:面向越南的 Postgres tuning、partition 與安全性

Duy Tran14 分鐘
Production OMOP:面向越南的 Postgres tuning、partition 與安全性

Eunomia 範例 CDM 有 2,700 人。實際 CDM 可能有 1 百萬到 1 億人。技術差距非常大。本文整理越南規模 Postgres OMOP CDM 的 production 最佳實務。

1. 硬體規模

規模vCPURAMSSDRAM/資料比
Dev (Eunomia)416 GB100 GBn/a
10 萬病人832 GB500 GB1:5
100 萬病人1664 GB2 TB1:8
1000 萬病人32256 GB10 TB1:10
1 億病人(BHYT 規模)64+512 GB+50 TB+1:15

分區:

  • pg_data:SSD NVMe RAID 10
  • pg_wal:獨立 SSD(低延遲)
  • pg_temp:獨立 SSD(sort/hash join 溢位)

2. PostgreSQL 設定

# 64GB RAM 的 postgresql.conf
shared_buffers = 16GB              # 25% RAM
effective_cache_size = 48GB         # 75% RAM
work_mem = 256MB                    # 每查詢,須注意 concurrent
maintenance_work_mem = 4GB
wal_buffers = 64MB
max_wal_size = 16GB
min_wal_size = 4GB
checkpoint_timeout = 30min
default_statistics_target = 500     # OMOP skew 大故設高
random_page_cost = 1.1              # SSD
effective_io_concurrency = 200      # SSD NVMe
max_parallel_workers_per_gather = 4
max_parallel_workers = 16
jit = on                            # PG12+,對分析有利

注意 work_mem × concurrent connection 可能超過 RAM → 應採用 pgBouncer transaction pooling,connection 上限 50。

3. 索引策略

OHDSI 已發布標準 DDL:OMOPCDM_postgresql_5.4_indices.sql。可依 workload 補充:

-- 最熱的資料表:condition_occurrence、drug_exposure、measurement
CREATE INDEX idx_co_person ON condition_occurrence(person_id);
CREATE INDEX idx_co_concept ON condition_occurrence(condition_concept_id);
CREATE INDEX idx_co_date ON condition_occurrence USING BRIN(condition_start_date);  -- 時序資料用 BRIN

CREATE INDEX idx_co_concept_date ON condition_occurrence(condition_concept_id, condition_start_date);

-- Vocabulary
CREATE INDEX idx_concept_std ON concept(standard_concept) WHERE standard_concept = 'S';
CREATE INDEX idx_ca_anc_desc ON concept_ancestor(ancestor_concept_id, descendant_concept_id);

-- Cohort
CREATE INDEX idx_cohort_id_person ON cohort(cohort_definition_id, subject_id);

-- 局部統計值
ALTER TABLE condition_occurrence ALTER COLUMN condition_concept_id SET STATISTICS 1000;
ANALYZE condition_occurrence;

4. 大型資料集的 Partition

4. 大型資料集的 Partition

CREATE TABLE condition_occurrence (
  ...
) PARTITION BY RANGE (condition_start_date);

CREATE TABLE condition_occurrence_2026 
  PARTITION OF condition_occurrence
  FOR VALUES FROM ('2026-01-01') TO ('2027-01-01');

CREATE INDEX idx_co_2026_person ON condition_occurrence_2026(person_id);

優點:

  • 依日期 pruning → 查詢加速 10 倍
  • 刪除舊分區極快(資料保留 5 年)
  • 維護(VACUUM、REINDEX)可平行進行

替代方案:依 person_id HASH 分區,適合多樣化日期範圍的世代查詢。

5. Vacuum 與 bloat

OMOP 在重跑 ETL 時會有大量 UPDATE/DELETE → bloat 大。

-- Auto vacuum 調整
ALTER TABLE condition_occurrence SET (
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_analyze_scale_factor = 0.02,
  fillfactor = 90
);

-- 大型操作手動執行
VACUUM ANALYZE condition_occurrence;
REINDEX TABLE CONCURRENTLY condition_occurrence;

監控 bloat:

SELECT schemaname, relname, n_dead_tup, n_live_tup,
       round(n_dead_tup::numeric / NULLIF(n_live_tup, 0), 3) AS dead_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY dead_ratio DESC;

6. 備份與災難復原

6. 備份與災難復原

2026 年的常見模式:

  • 同 DC 串流複寫 1 個 standby(failover < 30 秒)
  • 至 DR 站點的非同步複寫
  • pgBackRest 每週完整 + 每日增量 + 持續 WAL → S3
  • RPO 5 分鐘,RTO < 1 小時

越南:醫療資料屬於敏感個人資料,依**越南個人資料保護法 2025(2026 年 1 月 1 日生效,取代第 13/2023 號政令)**規定 — 必須在境內儲存,不可使用境外 S3 region。請使用越南雲端供應商:VNG Cloud、Viettel IDC、FPT Cloud、CMC Cloud(均符合個資保護法合規認證)。

7. 依越南個人資料保護法 2025 的安全性

2026 年法規更新:越南個人資料保護法(2025 年 6 月國會通過,2026 年 1 月 1 日生效)取代並升級第 13/2023 號政令,並結合第 15/2023/QH15 號醫療法(2024 年 1 月 1 日生效) — 規範電子病歷與電子健康紀錄(HSDT)的保存。

醫療資料的主要要求:

  • 靜態與傳輸中加密
  • 所有 PII/PHI 存取均需稽核紀錄
  • 假名化身分證字號 / BHYT 號
  • 境內儲存
  • 由資料保護長(DPO)負責
  • 定期風險評估

7.1 加密

# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/postgres.crt'
ssl_key_file = '/etc/ssl/private/postgres.key'
ssl_ciphers = 'HIGH:!aNULL:!MD5'

靜態加密:採用 LUKS / ZFS 加密 / TDE(Postgres 18 原生)。

7.2 假名化 person_source_value

-- 不存原始身分證字號
person_source_value = encode(
  hmac(cccd::bytea, current_setting('app.pseudonym_secret')::bytea, 'sha256'),
  'hex'
);

金鑰存放於獨立的 Vault / KMS。

7.3 列級安全性(Row-level security)

ALTER TABLE person ENABLE ROW LEVEL SECURITY;

CREATE POLICY person_policy ON person
  FOR SELECT
  TO research_user
  USING (location_id IN (
    SELECT location_id FROM user_location_access 
    WHERE user_id = current_user
  ));

7.4 稽核紀錄

-- pg_audit 擴充
CREATE EXTENSION pgaudit;
ALTER SYSTEM SET pgaudit.log = 'read,write,role,ddl';

紀錄送至 SIEM(Wazuh、Elasticsearch)。

8. 使用者與角色模式

-- 角色分離
CREATE ROLE omop_owner;            -- DDL、ETL
CREATE ROLE omop_etl LOGIN;        -- ETL service account
CREATE ROLE omop_analyst LOGIN;    -- HADES、analytics
CREATE ROLE omop_atlas_webapi LOGIN;  -- WebAPI service
CREATE ROLE omop_readonly_external LOGIN;  -- DARWIN EU partner

-- Grant
GRANT USAGE ON SCHEMA cdm TO omop_analyst, omop_atlas_webapi;
GRANT SELECT ON ALL TABLES IN SCHEMA cdm TO omop_analyst, omop_atlas_webapi;

-- Cohort schema 開放寫入
GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA results TO omop_atlas_webapi;

9. 詞彙升級工作流程

9. 詞彙升級工作流程

頻率:每季。保留版本紀錄:

CREATE TABLE vocabulary_history (
  id SERIAL,
  vocabulary_version TEXT,
  installed_at TIMESTAMP,
  installed_by TEXT,
  rollback_available BOOLEAN
);

10. 監控堆疊

層次工具
Postgres 指標pg_stat_statements + Postgres Exporter + Prometheus + Grafana
慢查詢pg_stat_statements top 100
鎖pg_locks 查詢,idle_in_transaction > 10 分鐘告警
Replica lagstreaming_lag 查詢
ETL jobAirflow / Prefect dashboard
DQD trend每週排程 DQD + Grafana 顯示 pct_failed
安全性SIEM 對角色提權、大量 SELECT count 告警

11. 容量規劃

元件成長指標
condition_occurrence每病人每月 ~3-5 筆
drug_exposure每病人每月 ~5-10 筆
measurement每病人每月 ~10-20 筆(lab + vital)
Vocabulary每季升級 +500MB
Cohort 表每次世代產生 +50MB
稽核紀錄依流量每天 100MB-1GB

依 3-5 年的前瞻規劃磁碟容量。

12. Kubernetes / 託管 Postgres?

選項:

  • Bare metal Postgres:控制力強、成本低、運維重
  • Patroni + etcd cluster:自建 HA
  • CloudNativePG / Zalando 在 K8s:現代,擴展性好
  • 託管:AWS RDS / GCP Cloud SQL / Azure Database — 越南:Viettel IDC PaaS Postgres、VNG Cloud Database

OMOP 大規模情境 → bare metal 或 CloudNativePG 仍是效能最佳的選擇。

13. Schema migration 的 CI/CD

# 用 Liquibase / Flyway 取代手寫 SQL
flyway migrate -url=jdbc:postgresql://... -user=... -password=...

每個 migration 都有版本 + 腳本 + rollback。

詞彙升級採獨立工作流(非 schema migration)。

14. 經驗教訓

  • 將 condition/drug/measurement 依年份 partition,可讓查詢加速 5-10 倍
  • 在 date 欄位用 BRIN index 可節省磁碟
  • 並行連線超過 50 時必須使用 pgBouncer transaction pooling
  • 對熱表使用較積極的 vacuum 以避免 bloat
  • Vault 將 secret 與 Postgres config 分離
  • 每季測試備份還原 — 沒測試的備份等於沒備份
  • 詞彙升級若不採 schema swap 可能造成 1-2 小時停機

結論

Production OMOP 同時需要 Postgres + DevOps + 安全性技能。Day 1 就投入正確,可擴展至 1 億+ 事件而無需重構。越南尤其需依個人資料保護法 2025(2026 年 1 月 1 日生效)與第 15/2023 號醫療法進行假名化、稽核、境內儲存,DPO 為強制要求,任何研究專案都必須做 DPIA。

下一篇:FHIR ↔ OMOP — 營運與分析層之間的橋樑。