基于流水线的执行:不可变性与物化(materialization)
在前两个概念中我们多次谈到数据可追溯性(data lineage)。但只有当工作流程中的每一步都可被重新执行时,数据沿袭才有意义——换句话说,即可复现。要让结果可复现,每个分析步骤都必须能在不改变底层数据(无副作用)的情况下被轻松重跑。
在由 immundata 驱动的管线中,一次分析是由不可变(immutable)的转换构成的流水线:
- 每个函数都会返回一个全新的
ImmunData对象。 - 底层数据不被修改。新对象 = 数据的新视图。
- 这一串
ImmunData对象记录了数据如何从原始链(chains)走到最终数字。
默认落盘,按需入内存
不可变性乍看并不方便,但它使我们能够处理大于内存(larger-than-RAM)的免疫受体数据集。immundata 使用了多种聪明的数据工程技巧:
- 免疫数据表以 Parquet 文件落盘(列式压缩格式,加速数据分析)。
- 只有在计算需要时才将数据物化(即加载到内存)。例如做子集或输出最终指标(如重叠指数)。
- 对于中等规模数据(如 ~10 GB),这几乎是“无感”的:
immundata使用的轻量数据库 DuckDB 会从文件流式读取,并给你一个常规的内存数据框。 - 而对大型数据(如 ~100 GB),同一套代码依然有效,因为 Parquet 与数据视图的组合让 DuckDB 能高效查询底层数据,避免把无关部分装入内存。
权衡很清楚:要么像典型数据框那样全部在内存中工作(内存成为瓶颈);要么构建不可变的转换,让 DuckDB 优化处理查询,从而实现对超内存数据的支持。
“流水线思维”在日常中的含义
以不可变流水线思考意味着两件事:
- 创建快照(snapshot): 当遇到昂贵步骤时,使用
immundata::write_immundata()保存一个中间的ImmunData对象。该函数会生成一个新的ImmunData,你随后就用这个新对象,获得大幅加速。示例:计算到某些模式或序列的编辑距离。如果在这一步后创建快照,这些距离会被落盘;如果不创建,之后每次在当前ImmunData上做转换或计算时,immundata都得重新计算距离——而你知道距离计算有多耗时。 - 默认假设可重跑: 任何人——同事,或未来的你在一台更强的机器上——都应该能从头到尾运行
pipeline.R并得到完全一致的结果。
面向开发者:隐藏“水管”细节,把注意力还给生物学
基于 immundata 的包应当暴露简单而高层的函数,比如 compute_diversity() 或 plot_overlap()。
用户无需关心 ImmunData、DuckDB 或 Parquet。理想情况下,他们甚至察觉不到有一个落盘数据库的存在。
把数据工程留给数据工程师(和生物信息学家)。把注意力放在生物学上——那已经足够复杂了。