3. 医疗数据湖存储层设计

存储层是医疗数据湖仓的地基。它要同时承载三类差异极大的数据:影像与波形等大体量非结构化文件、检验检查等结构化明细、以及日志与设备流等半结构化记录。设计目标不是“选一个最便宜的桶”,而是在成本、吞吐、格式效率、分区可维护性、合规保留五个约束下取得平衡。本节按对象存储选型、文件格式、分区策略、生命周期管理、保留合规五条线索展开,并给出可落地的参数示例。

一、对象存储选型:先看访问模式,再看接口与合规

医疗湖仓的底座通常是对象存储(S3 兼容、OSS、COS、OBS 等),而非 HDFS。原因是对象存储具备近乎无限的扩展性、按量计费、多副本或纠删码持久性,并且能被 Spark、Flink、Trino、Hive 等计算引擎通过 S3A/OSS 连接器直接访问。选型时建议按以下维度打分:

  • 访问模式:影像归档属于“写一次、极少读”,适合低频/归档层;而临床指标宽表被 BI 与科研反复扫描,需要标准层与高并发读。
  • 一致性模型:现代对象存储已提供强一致读后写,这对“写入即查询”的湖仓入湖任务很关键,避免出现文件已提交但列表看不到的经典问题。
  • 接口兼容:优先选择 S3 API 兼容,便于引擎无改造迁移;同时确认是否支持分段上传、多版本、对象锁(Object Lock)。
  • 合规与地域:患者数据须落在境内指定地域,跨域复制需评估是否触发数据出境合规审查。

一个常见的分层做法是:标准存储放近 90 天热数据,低频存储放 90 天至 2 年温数据,归档/冷归档放 2 年以上历史影像与出院病案。分层不是简单改存储类别,而要配合生命周期规则自动迁移,否则人工搬运会迅速失控。

二、文件格式:Parquet、ORC、Avro 各司其职

三种格式并非互相替代,而是对应不同阶段:

格式存储模型典型用途注意点
Parquet列式,带页级统计与字典编码湖仓明细表、宽表、BI 与即席分析嵌套 schema 演进需谨慎;小文件会放大元数据开销
ORC列式,含索引与谓词下推Hive 生态、需要强过滤的聚合表与 Parquet 功能重叠,团队应统一一种为主
Avro行式,schema 随文件写入Kafka 落地、CDC 缓冲、需保留完整变更记录不适合大范围列扫描,通常作为入湖前的暂存层

推荐链路是:设备与 HIS 变更经 Kafka 以 Avro 落地到“原始层”,保留逐条变更与 schema;再由 Spark 清洗、去重、合并为 Parquet 写入“明细层”与“汇总层”。这样既保留了可追溯的原始记录,又让分析层获得列裁剪与压缩收益。Parquet 常用参数示例:

spark.sql.parquet.compression.codec = zstd
parquet.block.size = 134217728      # 128MB
parquet.page.size = 1048576         # 1MB
spark.sql.files.maxRecordsPerFile = 2000000

压缩建议用 zstd:相比 snappy 通常有更高压缩比,且解压速度可接受,适合医学文本与编码字段混合的场景。若表以数值型检验结果为主,可对比 zstd 与 gzip 的实际落盘大小后再定。

三、分区策略:按时间 + 业务维度,控制分区数量

分区决定扫描裁剪效率,也决定元数据压力。医疗数据最自然的首选分区键是业务日期(如 dt=2024-06-01),因为绝大多数查询都带时间范围。其次可按机构编码或数据域(检验、影像、医嘱)二级分区。但分区不是越多越好:若按患者 ID 分区,会产生海量小分区,元数据服务很快成为瓶颈。

经验规则:单表分区总数控制在数万以内,单分区文件大小目标 128MB~512MB。对于高基数维度(患者、就诊号),应使用分桶(bucketing)或 Z-Order/聚簇,而不是分区。例如按 dt 分区、按 patient_id 分 64 桶,可在点查与范围扫描间取得平衡。

医疗数据湖存储层:格式—分区—生命周期协同 HIS / LIS 变更 CDC 事件流 PACS 影像 DICOM 大文件 监护设备 波形 / 时序 原始层 Raw Avro / 原始文件 保留完整变更 schema 随写随存 明细层 Detail Parquet + zstd 分区 dt / 机构 汇总层 Summary Parquet / ORC 指标宽表 · 分桶 生命周期 0–90 天 标准 90 天–2 年 低频 2 年以上 归档 到期转删除/冻结 受保留策略约束 Object Lock 防篡改 分区与文件控制要点 · 一级分区 dt=YYYY-MM-DD,二级分区 org_code / domain,分区总数控制在数万级 · 单文件目标 128–512MB;高基数维度用分桶(如 patient_id 64 桶)替代分区 · 影像等非结构化对象按 study_uid 前缀散列,避免单前缀热点;元数据入 Parquet 索引表

四、数据生命周期管理:从“存得下”到“管得住”

生命周期管理要回答三个问题:数据何时从热转冷、何时可以删除、谁有权提前删除。工程上通常用对象存储的生命周期规则 + 表级 TTL 双轨实现。例如:

规则1: prefix=raw/avro/  → 30 天后转低频,365 天后转归档
规则2: prefix=detail/dt= → 90 天后转低频,730 天后转归档
规则3: prefix=tmp/       → 7 天后直接过期删除
规则4: prefix=archive/   → 启用 Object Lock,合规保留期内禁止删除

需要特别注意的是,小文件合并本身就是生命周期的一部分。流式入湖若每分钟产生一个文件,一天就是 1440 个小文件,查询时元数据开销远超数据读取。应安排每日或每小时执行 compaction,把目标分区重写为 128MB 左右的大文件,并同步更新 Iceberg/Hudi 的元数据快照。

五、医疗数据保留合规要求

医疗数据的保留期限由法规与业务双重决定,常见要求包括:门诊病历通常不少于 15 年,住院病历不少于 30 年,部分影像与病理资料要求长期甚至永久保存;同时《个人信息保护法》《数据安全法》要求最小必要、目的限定,超期后应删除或匿名化。这意味着“归档”不等于“可随意删除”,删除动作必须可审计。

落地建议:

  • 为每类数据打上 retention_class 标签(如 outpatient_15y、inpatient_30y、imaging_permanent),由策略引擎统一驱动。
  • 启用对象锁与版本控制,防止勒索软件或误操作删除合规数据;删除请求走审批流并记录操作人、时间、依据。
  • 科研与 AI 训练使用的数据应去标识化后再进入分析区,原始标识信息单独隔离存放。
  • 定期做“保留策略符合性检查”:抽样核对对象标签、存储类别、到期时间是否与策略一致,输出差异清单并修复。
合规红线:不得为了节省成本而提前删除仍在法定保留期内的病历数据;也不得将含患者标识的数据复制到未授权的区域或测试环境。生命周期规则中的“过期删除”必须与保留策略联动,而不是简单按天数一刀切。

综合来看,存储层设计的核心是把“格式—分区—生命周期—合规”当成一个整体来调优:Avro 保原始、Parquet 供分析、分区控裁剪、分桶解高基数、生命周期降成本、保留策略守底线。任何一环单独优化,都可能在另一环产生新的瓶颈。