Snakemake 工作流基本概念

我第一次接触 Snakemake 的时候,心里想的是:这不就是个加强版 Make 吗?后来才发现,嗯,它远比我想象的聪明。说白了,Snakemake 就是一个能帮你把数据分析流程串起来的工具——你只需要告诉它“我要从 A 得到 B,中间用 C 这个脚本”,剩下的依赖分析和并行调度,它自己搞定。

2.1 工作流是什么

工作流(Workflow)就是一系列有先后依赖关系的任务集合。在生物信息学里,我们经常要跑这样的流程:原始测序数据 → 质控 → 比对 → 定量 → 差异分析。手动一个个跑,容易忘参数,也容易出错。

Snakemake 把整个流程写在一个叫 Snakefile 的文件里。你定义好每个步骤的输入、输出和要执行的命令,它自动帮你安排顺序。举个例子,我团队里有个项目,一次处理 96 个样本,每个样本要跑 8 个步骤。用传统脚本调度,光等依赖关系就得写半天。换成 Snakemake 后,我只定义了规则,它自己就并行跑起来了。

核心思想: 指定“想要什么”,而不是“先做什么后做什么”。Snakemake 根据输出文件名反推需要哪些输入和规则。

2.2 Snakefile:核心配置文件

Snakefile 是一个 Python 风格的文本文件,里面定义规则(rule)和全局配置。它的后缀名就是 Snakefile,也可以用 .smk。

一个最简单的 Snakefile 长这样:

rule all:
    input:
        "results/clean_reads.fq.gz"

rule fastqc:
    input:
        "data/raw_reads.fq.gz"
    output:
        "results/clean_reads.fq.gz"
    shell:
        "fastp -i {input} -o {output}"

你看,rule all 是默认目标,Snakemake 看到它要的 results/clean_reads.fq.gz 不存在,就去寻找能生成它的规则。找到 rule fastqc,发现它需要 data/raw_reads.fq.gz——如果这个文件存在,就直接执行 fastp 命令。

2.3 规则(rule)的构成

每个规则包含几个关键部分:

部分说明我的经验
input上游文件列表可以根据通配符动态指定,我经常用 lambda wildcards: 做复杂匹配
output本规则生成的文件列表Snakemake 严格校验输出是否生成,缺文件会报错
shell / run / script执行的命令、Python 代码或外部脚本推荐用 shell 配合通配符 {input} {output},清晰
params额外的参数可以放阈值、线程数等,方便后期修改
threads需要的 CPU 核数Snakemake 会根据这个分配资源
一个小技巧: 如果你不确定规则怎么写,可以用 snakemake --print-compilation 看 Snakemake 是怎么解析你的规则的。

2.4 输入与输出:依赖关系图

当工作流变大,规则之间会形成一张依赖图(Directed Acyclic Graph, DAG)。Snakemake 会自动构建这张图,保证每个任务都在其上游任务完成后才启动。

我个人习惯先画出 DAG 再写 Snakefile。看一眼 Snakemake 自动生成的图,心里就有底了。命令很简单:

snakemake --dag | dot -Tsvg > workflow.svg

生成出的 SVG 图里,每个规则是一个节点,箭头表示输入输出依赖。下面这张就是我根据上面示例手动画的简化版:

Snakemake 工作流简化依赖图 raw_reads.fq.gz rule fastqc clean_reads.fq.gz rule trim trimmed.fq.gz 箭头方向:输出 → 规则 → 输入?其实是反过来的:Snakemake 从 目标 开始向下查找依赖,但执行方向是正向的。
我曾经踩过的坑: 在 input 里直接写文件名但忘了写路径,Snakemake 报错说文件找不到。后来我养成了习惯,所有路径都用 config["data_dir"] 这样的变量统一管理。

2.5 通配符:让规则泛化

当你要处理多个样本时,总不能为每个样本写一条规则吧?通配符就是用来解决这个问题的。

看这个例子:

rule fastqc:
    input:
        "data/{sample}.fq.gz"
    output:
        "results/{sample}_fastqc.html"
    shell:
        "fastqc {input} -o results/"

这里的 {sample} 就是通配符。当你运行 snakemake results/sampleA_fastqc.html 时,Snakemake 自动把 {sample} 替换成 sampleA。如果有 100 个样本,它就能自动生成 100 条任务,并行执行。

通配符还可以在多个规则间传递。比如:

rule trim:
    input:
        "data/{sample}.fq.gz"
    output:
        "results/{sample}_trimmed.fq.gz"
    shell:
        "fastp -i {input} -o {output}"

如果你同时依赖 fastqc 的输出,可以用 rules.fastqc.output 引用。但要注意,通配符必须能在上下游之间匹配成功。

为什么这么设计? 因为生物信息学里样本量经常几百上千,手动写规则根本不可行。通配符让 Snakefile 真正实现了“一次编写,多次运行”。

2.6 工作流执行过程

当你敲下 snakemake 时,背后发生了什么?我简单拆解一下:

  1. 解析 Snakefile:读入所有规则,建立规则索引
  2. 确定目标:如果没有指定,就找第一个 rule all 的输入;也可以手动指定文件名
  3. 构建 DAG:从目标开始反向搜索,找到所有必须执行的规则和它们的依赖
  4. 拓扑排序:保证先执行没有上游依赖的任务
  5. 调度执行:按顺序或并行(根据 --cores)运行任务,并在完成后检查输出是否生成
  6. 报告完成:输出日志,生成统计信息

执行过程中,Snakemake 会把每次运行的参数记录在 .snakemake 目录下,方便下次增量更新。如果你改了上游某个步骤的参数,它只会重新跑受影响的部分——这正是它比纯脚本高效的地方。

我曾经用它处理一个 512 个样本的 RNA-seq 流程,第一次全跑完用了 12 小时。后来修改了比对参数,重新运行 snakemake --cores 20,只花了 2 小时就完成了增量更新。这体验,真的舒畅。