3. 规则执行机制:作业调度、资源分配、日志输出

Snakemake 的执行机制,说白了就是三件事:怎么把规则拆成作业、怎么给作业喂资源、怎么把作业干了啥记下来。这三件事搞不明白,DAG 画得再漂亮也跑不起来。

3.1 作业调度:从 DAG 到实际运行

Snakemake 不是一股脑把所有规则全跑上。它先解析 Snakefile,构建出 DAG,然后根据依赖关系一层层往下推。推到哪一层能跑?没有输入依赖的规则先跑,跑完了通知下一层。这就是所谓的“拓扑顺序”。

我个人习惯在跑大流程前先看一眼 DAG,用 snakemake --dag 生成图确认依赖没错。否则等跑起来了才发现箭头反了,那叫一个难受。

# 查看 DAG 但不执行
snakemake --dag | dot -Tpdf > pipeline.pdf

3.2 资源分配:别让你的集群打架

资源分配是 Snakemake 最难搞的部分,没有之一。你想想看,一个流程里可能有几十个规则,有的要 64GB 内存,有的只要 1 核。你要是不管,Snakemake 可能把 10 个内存大胃王同时丢到一台机器上——然后 OOM 给你看。

解决方法是给每个规则写清楚资源需求:

rule align:
    input:
        "data/{sample}.fastq"
    output:
        "results/{sample}.bam"
    resources:
        mem_mb=16000,
        runtime_min=120
    shell:
        "bwa mem -t {threads} ..."

注意,资源里的 threads 不是 resources 的子项,它是单独的关键字。我当初刚上手时搞混过,死活拿不到多核。

Snakemake 支持多种资源后端:本地、SLURM、PBS、SGE。每个后端的资源参数略有不同,但核心概念一样——告诉调度器这活儿需要多少东西。

后端配置方式常用资源参数
本地--cores Nmem_mb, disk_mb
SLURM--cluster "sbatch ..." 或配置文件mem_mb, runtime_min, partition
PBS--cluster "qsub ..."mem_mb, walltime
SGE--cluster "qsub ..."mem_mb, h_rt

注意:资源参数的名字和单位在不同后端里可能不一样。比如 SLURM 里的 mem_mb 是 MiB,而 PBS 的 mem 默认是 KB。我踩过这个坑:写了个 16000 以为够了,结果给了 16KB……作业秒退。

3.3 日志输出:审计的命根子

日志不只是给运维看的,也是给自己看的。Snakemake 的日志分三种:

  1. 终端标准输出——你在控制台看到的实时信息。
  2. .snakemake 目录里的运行日志——每次运行 Snakemake 都会生成一个带时间戳的日志文件。
  3. 规则内自定义日志——你自己的命令把 stdout/stderr 重定向到文件。

我最常用的是第三种。每个规则都写日志,万一跑崩了,我能直接去看那一个规则的日志,省得在海量输出里翻。

rule align:
    input:
        "data/{sample}.fastq"
    output:
        "results/{sample}.bam"
    log:
        "logs/{sample}.align.log"
    shell:
        "bwa mem -t {threads} {input} 2> {log} | samtools sort > {output}"

这里 2> 把 stderr 重定向到日志文件。如果命令正常,日志就干干净净;如果报错,日志里会写清楚哪一步挂了。

技巧:我喜欢在每条规则里加 log: "logs/{rule}_{wildcards}.log",然后用 2>> {log} 追加模式。这样日志文件会把每次运行的输出都累积下来。方便回头比对。

3.4 作业并行与限流

Snakemake 的作业调度可以指定最大并行数。本地用 --cores,集群用 --jobs。但要注意:并行数不是越大越好。我见过有人把 500 个作业同时扔到集群上,结果调度器自己先崩了。

# 本地最大用 8 核
snakemake --cores 8

# 集群最多同时提交 20 个作业
snakemake --cluster "sbatch --mem=8G" --jobs 20

另外,Snakemake 还支持“组调度”。你可以把几个规则打包成一个组,组内串行、组间并行。这在有些工具需要独占资源时特别好用。

localrules:
    - bwa_index

rule bwa_index:
    output: "ref/genome.bwt"
    group: "index_group"
    shell: "bwa index ref/genome.fa"

rule align:
    input: "ref/genome.bwt"
    output: "results/{sample}.bam"
    group: "alignment_group"
    shell: "..."

3.5 日志的审计价值

曾经有一次,我明明跑完了流程,结果 reviewer 说“你怎么证明你跑了正确的版本?” 从那天起,我养成了跑完流程后把 .snakemake 目录打包备份的习惯。

审计内容至少包括:

  • 运行的 Snakefile 版本(最好用 git commit)
  • 每条规则的实际命令和 exit code
  • 资源使用情况(内存、CPU)
  • 开始/结束时间
  • 输入输出文件的 checksum

Snakemake 的 .snakemake 目录里有个 log/ 子目录,里面存着每次运行的日志。日志文件名格式是 年-月-日T时:分:秒.log。打开看,里面有完整的调度信息和作业状态。

核心理解:作业调度决定“谁先跑”,资源分配决定“怎么跑”,日志输出决定“跑得怎么样”。三者缺一不可。

Snakemake 规则执行机制 DAG 构建 作业调度(拓扑顺序) 资源分配(mem/threads) 日志输出(stdout/stderr) 规则执行 调度器按 DAG 顺序派发作业,资源不足时排队等待,日志实时记录每步状态

3.6 实践建议

最后整理几条我自己的习惯:

  • 每条规则都写 log 字段。 哪怕你觉着不会死,等真死了你绝对会后悔没写。
  • 用 --restart-times 3,遇到临时 IO 错误会自动重试,不用手动跑第二遍。
  • 跑之前用 --dry-run 看一眼要执行的命令。 我曾经因为通配符写错了,差点把整个参考基因组删了……幸亏先 dry-run 了。
  • 存好 .snakemake 目录。 一来审计用,二来下次 --rerun-incomplete 时还要读里面的状态。

特别注意: 日志文件如果没重定向,Snakemake 默认只捕获 exit code。拿不到命令内部的错误详情。所以一定要在 shell 命令里主动 2> 或 > 到 log 路径。

好了,作业调度、资源分配、日志输出这三块你都吃透了。后面我们再深入看看 DAG 审计中怎么利用这些日志做回放和排查。