第3章:参数配置文件(nextflow.config)

这一章我们聊聊nextflow.config。说实话,我刚开始接触Nextflow时,觉得配个文件而已,随便写两行就行。结果第一个正式项目就跑崩了——原因特别低级:缓存路径没配对,重跑时一直用旧缓存。你说冤不冤?

后来养成了习惯,每次新项目都先把配置文件搭好框架。这一章我就把这套框架拆开讲清楚,你顺着走一遍就能避开我当时踩的坑。

3.1 参数配置的层级结构

nextflow.config 里的参数不是平铺的,而是有明确的层级。我用一张图先让你看明白整体结构。

nextflow.config 参数层级结构 全局参数 (params.*) executor / workDir cache / resume process.scratchDir process.cache scope 'withLabel' / 'withName' 低层级参数会覆盖高层级(局部优先)

从上往下看,最顶层是全局 params.*,你可以把它当成整个管道的“公共变量区”。往下一层是执行器(executor)和工作目录(workDir),这些决定了任务跑在哪、缓存存哪。再往下走就是进程级别的独有配置,比如 process.scratchDir 和 process.cache。还有一个隐藏的“作用域配置”,通过 withLabel 或 withName 做精细化调整。

我自己项目里最常用的是这个组合:全局参数放输入输出路径,进程配置给内存和 CPU,缓存相关参数单独用 withLabel 去控制。不乱套,也容易复用到其他项目。

3.2 核心参数详解

这一节我挑三个最容易搞出事情的参数来聊。记住一句话:配置弄对了,缓存复用才可靠。否则你调 -resume 等于没调。

3.2.1 executor + workDir

executor 决定了任务调度方式。本地跑用 local,集群用 slurm / pbs / awsbatch。workDir 就是缓存地狱的根源——假设你改了代码但 workDir 没变,Nextflow 会认为任务已经完成,直接跳过。我曾经犯过一个低级错误:本地和服务器各跑了一遍,workDir 都用的默认 ./work,结果一合并就出乱。

// 建议显式指定唯一路径
workDir = '/vast/scratch/project_xyz/work'
// 或者用环境变量
workDir = System.getenv('WORK_DIR') ?: './work'
特别提醒: 如果你同时跑多个项目,一定给每个项目一个独立的 workDir。否则缓存会串,想哭都来不及。

3.2.2 process.cache

这个参数控制单个进程的缓存策略。默认是 deep,也就是对输入文件做完整哈希比较。如果你只改了参数没改文件,又想强制重跑某个进程,可以临时设成 false。不过一般不建议长期关缓存,否则 pipelines 会越来越慢。

process {
    withName: 'bwa_mem' {
        cache = false   // 测试阶段用
    }
}

我个人习惯:正式生产环境所有进程都保持默认 deep,只有调试某个进程时才局部关掉。跑完马上改回来。

3.2.3 resume 的依赖条件

很多人以为 -resume 是“万能后悔药”,其实不是。它依赖两个前提:1) 进程定义未变;2) 输入文件的哈希值未变。你改了个命令行参数但没改进程脚本,resume 就失效。这时候你得看日志里 Cached: skipped 的数量来判断缓存生效没。

# 启动时带上 -resume,并观察输出
nextflow run main.nf -resume -ansi-log false

3.3 配置文件组织与复用技巧

一个成熟项目通常会有多个 config 文件:基础配置、集群配置、调试配置。我推荐你用 -c 参数叠加配置文件,而不是在一个文件里写死所有东西。

# 基础参数配置(所有环境共通)
base.config:
  params.input = 'data/raw'
  params.outdir = 'results'
  workDir = '/scratch/work'

# 集群特定配置(比如用 slurm)
cluster.config:
  executor = 'slurm'
  queue = 'normal'
  cpus = 16
  memory = '64 GB'

# 调试专用配置(局部关缓存)
debug.config:
  process {
      withLabel: 'slow_step' { cache = false }
  }

运行时按需组合:

nextflow run main.nf -c base.config -c cluster.config
# 调试时再加一个 debug
nextflow run main.nf -c base.config -c debug.config -resume
小技巧: 把 base.config 加到你的项目模板仓库里,新项目直接 fork 改几条参数就行。省时省力。

3.4 实战案例:一个包含缓存复用的完整配置文件

下面是我自己项目里用的模板,你可以直接拿去改。重点看 cache 和 resume 相关的配置项。

// nextflow.config
params {
    input   = 'samples.csv'
    outdir  = 'results'
    genome  = 'hg38'
}

workDir = '/scratch/myproject/work'

executor {
    name    = 'slurm'
    queue   = 'compute'
    cpus    = 8
    memory  = '32 GB'
    time    = '48h'
}

process {
    cache = 'deep'
    scratch = true   // 每次任务使用临时目录,避免 workDir 膨胀
    // 针对特定标签做调整
    withLabel: 'no_cache' {
        cache = false
    }
}

// 特殊参数:控制 resume 的详细行为
resume {
    historySize = 50   // 保留最近 50 次运行缓存记录
    cacheStrategy = 'copymove'  // 避免大量硬链接失败
}

docker {
    enabled = true
    runOptions = '-u $(id -u):$(id -g)'
}

// 避免在 workDir 里生成大量 .command.* 文件
report {
    enabled = true
    trace.enabled = true
}

timeline {
    enabled = false
}

这个配置跑在生产上已经大半年没出过缓存相关的幺蛾子了。核心就两点:workDir 唯一 和 cache 分进程管理。嗯,记住这个就够用。

3.5 常见问题与避坑

  • 缓存明明是新的,但 resume 不认? —— 检查 workDir 是否被多个项目共用。我遇到过最离谱的一次,队友把两个项目 workDir 设成同一个,缓存互相污染。
  • 配置文件报错:Unrecognized property —— 看版本。Nextflow 22 以后有些参数名改了,比如 process.cache = 'lenient' 改成了 cache = 'lenient' 直接写在顶层。记得先 nextflow info 看版本。
  • 为什么我关掉了缓存,任务还是没重跑? —— 检查 cache 是否设置正确的作用域。有时候你在 process 块里写了 cache = false,但 withLabel 又覆盖了,没生效。建议用 -dump-hashes 参数打出哈希对比表定位问题。

一句话总结: 配置文件是你管道的“宪法”,参数覆盖规则不搞清楚,缓存复用就是空谈。我建议你新项目必做的第一件事,就是敲定 nextflow.config 的骨架,而不是急着写脚本。