3. 数据湖与Delta Lake集成基础

在 Databricks 机器学习平台上,数据湖是承载原始数据、特征数据与训练样本的底层存储基础。传统数据湖通常基于对象存储(如 S3、ADLS、GCS)保存 Parquet、ORC、JSON 等文件,具备容量大、成本低、格式开放的优势,但缺少事务、并发控制和一致性保障。Delta Lake 在数据湖之上引入事务日志与表协议,使数据湖具备 ACID 能力,从而成为机器学习特征工程、样本管理和模型训练数据准备的可靠底座。

数据湖与 Delta Lake 的关系

数据湖解决“数据放在哪里”的问题,Delta Lake 解决“数据如何被可靠地读写和演进”的问题。Delta 表在物理上仍然是对象存储中的一组 Parquet 数据文件,外加一个 _delta_log 目录。所有对表的修改不会直接覆盖原文件,而是以追加方式写入新的日志条目,记录本次事务新增、删除或修改了哪些文件。读取时,Delta 通过回放事务日志确定当前有效的数据文件集合,因此同一份底层文件可以支持快照读、时间旅行和并发写入。

Delta Lake 表结构:Parquet 数据文件 + _delta_log 事务日志 对象存储路径 /mnt/lake/features/user_profile part-00000-xxx.snappy.parquet 数据文件(不可变) part-00001-yyy.snappy.parquet 数据文件(不可变) part-00002-zzz.snappy.parquet 数据文件(不可变) _delta_log/ 00000000000000000000.json 00000000000000000001.json … 事务日志回放 → 当前表快照 版本 0:CREATE TABLE,定义 schema 版本 1:INSERT,新增 part-00000 版本 2:MERGE,新增 part-00001 版本 3:ADD COLUMN,模式演化 读取结果 有效文件 = 日志中未被 REMOVE 的文件 可指定版本或时间戳做时间旅行

ACID 特性在 Delta 表上的体现

原子性(Atomicity)意味着一次写入要么全部生效,要么完全不生效。Delta 通过“先写数据文件,再提交日志”的顺序保证:如果日志提交失败,新增的 Parquet 文件不会被任何快照引用,读取方看不到半成品数据。一致性(Consistency)由表协议和 schema 约束保证,写入必须符合当前表结构。隔离性(Isolation)依赖乐观并发控制:多个写入者可以同时尝试提交,提交时检查是否与已提交版本冲突,冲突则重试或失败。持久性(Durability)由对象存储的多副本机制承担,日志一旦提交即持久保存。

在机器学习场景中,ACID 的价值非常直接。例如特征回填任务与在线推理特征写入可能同时发生,如果没有隔离性,读取方可能看到部分回填的数据,导致训练样本与线上特征分布不一致。Delta 的可串行化隔离让读取方始终看到某个完整版本,从而保证特征一致性。

表创建与读写

在 Databricks 中创建 Delta 表通常使用 SQL 或 PySpark。以下示例创建一个用户画像特征表,并写入两条示例数据:

CREATE TABLE IF NOT EXISTS ml_features.user_profile (
  user_id      BIGINT,
  age          INT,
  city         STRING,
  is_premium   BOOLEAN,
  updated_at   TIMESTAMP
)
USING DELTA
LOCATION '/mnt/lake/features/user_profile';

INSERT INTO ml_features.user_profile VALUES
  (1001, 28, 'Shanghai', true,  '2024-06-01 10:00:00'),
  (1002, 35, 'Beijing',  false, '2024-06-01 10:05:00');

读取时可以直接查询表名,也可以读取路径。Delta 会自动合并小文件并利用统计信息做数据跳过。对于机器学习训练,常用写法是读取最新快照并筛选时间窗口:

SELECT user_id, age, city, is_premium
FROM ml_features.user_profile
WHERE updated_at >= '2024-06-01';

写入方面,INSERT 适合追加,MERGE INTO 适合按主键做 upsert。例如将每日增量用户画像合并进主表:

MERGE INTO ml_features.user_profile AS t
USING ml_features.user_profile_daily AS s
ON t.user_id = s.user_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;

执行后可通过 DESCRIBE HISTORY ml_features.user_profile 查看版本、操作类型、时间戳和操作指标,确认写入是否成功以及影响行数。

模式演化

机器学习特征经常新增字段,例如新增 lifecycle_stage 或 embedding_version。Delta 支持安全的模式演化。默认情况下,写入时如果出现新列会报错,需要显式开启:

ALTER TABLE ml_features.user_profile
ADD COLUMN lifecycle_stage STRING;

-- 或在写入时自动合并 schema
SET spark.databricks.delta.schema.autoMerge.enabled = true;

模式演化分为两类:追加新列是向后兼容的,旧数据该列为 null;删除列或修改列类型属于破坏性变更,需要重建表或使用 overwriteSchema。在特征平台中,建议只做追加式演化,并在特征注册表中记录 schema 版本,避免训练与推理特征错位。

时间旅行查询

时间旅行让读取方可以按版本号或时间戳查询历史快照,这对复现训练数据集、排查特征异常非常关键。语法如下:

-- 按版本号
SELECT * FROM ml_features.user_profile VERSION AS OF 2;

-- 按时间戳
SELECT * FROM ml_features.user_profile
TIMESTAMP AS OF '2024-06-01 10:00:00';

-- 查看历史
DESCRIBE HISTORY ml_features.user_profile;

假设某次训练使用了版本 5 的特征,但线上效果下降,可以分别读取版本 5 和版本 6 做对比,定位是哪个字段或哪批数据发生了变化。需要注意,时间旅行依赖日志和数据文件同时存在;如果执行了 VACUUM 删除旧文件,超过保留期的历史版本将无法读取。默认保留期为 7 天,生产环境应根据合规和复现需求调整。

实践建议:在特征表上同时维护 updated_at 和 data_version 字段,训练样本生成时记录所用 Delta 版本号,便于后续审计与复现。
常见误区:把 Delta 表当成普通 Parquet 目录直接删除文件,会破坏事务日志与数据文件的一致性,导致表不可读。所有清理操作应通过 VACUUM 或 DELETE 完成。

综合来看,Delta Lake 让数据湖从“能存”升级为“能可靠读写、能演进、能追溯”。在 Databricks 机器学习平台中,特征表、训练样本表和推理日志表都建议使用 Delta 格式,以 ACID、模式演化和时间旅行支撑从特征工程到模型上线的完整链路。