预处理流程(一):BWA-MEM比对与SortSam排序、MarkDuplicates去重

预处理这一步,说白了就是把原始的 FASTQ 文件,一步步加工成 GATK 能直接吃进去的干净 BAM。很多朋友一上来就急着跑 HaplotypeCaller,结果报错一堆,排查半天发现源头在比对这一步就埋了雷。

我个人习惯把预处理拆分三个阶段:比对(BWA-MEM)→ 排序(SortSam)→ 去重(MarkDuplicates)。这三个阶段环环相扣,任何一个环节的参数没调好,后面都得加倍还债。

核心管线流程

原始 FASTQ → BWA-MEM 比对 → SAM/BAM → SortSam 排序 → MarkDuplicates 去重 → 分析就绪 BAM

GATK 预处理流程核心步骤 原始 FASTQ BWA-MEM 比对 SortSam 排序 MarkDuplicates 就绪 BAM 关键参数备查 BWA-MEM: -K 1000000(提高稳定性), -M(标记短拆分read为次要), -t 线程数 SortSam: SORT_ORDER=coordinate, MAX_RECORDS_IN_RAM=5000000 MarkDuplicates: REMOVE_DUPLICATES=false, METRICS_FILE=metrics.txt, ASSUME_SORTED=true

1. BWA-MEM 比对:不只是跑个默认参数

BWA-MEM 是目前最主流的短读段比对工具,准确率和速度都很均衡。但我见过不少人直接 bwa mem ref.fa read1.fq read2.fq > out.sam 就完事了,嗯,这样 GATK 后面会很难受。

为什么?BWA 默认输出的 SAM 文件里,read group 信息是空的,而 GATK 的很多模块(比如 BaseRecalibrator)强制要求 ReadGroup。我之前有一次跑完整条流水线,结果在 BaseRecalibrator 报错,回头一看,好家伙,比对时的 RG 标签全丢了。

我的推荐命令

# 单样本 paired-end 示例
bwa mem -K 1000000 -M -t 8 \
    -R '@RG\tID:sample1\tLB:lib1\tPL:ILLUMINA\tSM:sample1' \
    reference.fasta \
    sample1_R1.fastq.gz \
    sample1_R2.fastq.gz \
    > sample1.raw.sam

参数说明:

  • -K 1000000:控制每次处理的碱基数,提高大内存场景下的稳定性
  • -M:将短拆分 read 标记为 次要(secondary) 而非 supplementary,避免 GATK 后续误判
  • -t 8:线程数,根据你的 CPU 核心调整,不是越多越好,我一般设 8-16
  • -R '@RG\tID:...':ReadGroup 字符串,必须包含 ID、LB、PL、SM 四个字段

关于 -M 参数, 我多聊两句。GATK 的 HaplotypeCaller 对 supplementary alignment 的处理比较敏感。不加 -M 的话,那些跨过断裂点的 read 会被标成 supplementary,导致后续变异检测时被跳过或权重降低。说白了,加 -M 是让 GATK 从数据中借力,而不是被 BWA 的原始标注带沟里去。

BWA-MEM 与 GATK 的兼容性参数

参数 作用 我在项目中踩过的坑
-K 控制批处理碱基数 默认值太小会导致内存开销不稳定,尤其在 WGS 数据上
-M 标记短拆分 read 为次要 不加的话,GATK 的 ReadBackedPhasing 可能会漏检
-R 添加 ReadGroup 信息 漏掉这个字段,BaseRecalibrator 直接罢工
-t 线程数 线程数多到超出物理核心时,速度反而下降

曾经踩过的坑:BWA 的 -p 参数

我以前在混合数据时用过 -p 参数(单端模式),结果比对结果里出现了大量异常拆分,GATK 跑 VariantFiltration 时过不去。后来发现,-p 会导致 BWA 把双端当作单端处理,read pair 结构打乱。建议 paired-end 数据一定同时输入 R1 和 R2 文件,不要图省事。

2. SortSam 排序:为什么要排两次?

BWA 输出的 SAM 是按 read name 排序的(或者随机顺序)。但 GATK 所有的分析工具都要求 BAM 按 coordinate(基因组坐标)排序。所以排序这一步不是可选项,是强制项。

你可能会问:直接用 Picard 的 SortSam 还是用 sambamba/samtools 更快?我的建议是:如果在 GATK 生态内,就老老实实走 Picard。虽然 sambamba 跑排序确实快一截,但 GATK 团队对 Picard 输出的 BAM 兼容性验证最充分。

推荐的 SortSam 命令

java -jar picard.jar SortSam \
    I=sample1.raw.sam \
    O=sample1.sorted.bam \
    SORT_ORDER=coordinate \
    MAX_RECORDS_IN_RAM=5000000 \
    TMP_DIR=/path/to/tmp \
    VALIDATION_STRINGENCY=LENIENT
  • MAX_RECORDS_IN_RAM:控制内存中缓存的记录数,根据你的可用 RAM 调整,避免大量磁盘 I/O
  • TMP_DIR:指定临时目录,保证磁盘空间充足,我一般设到一个单独的 SSD 盘
  • VALIDATION_STRINGENCY=LENIENT:对某些非致命格式错误睁一只眼闭一只眼,避免卡死在排序阶段

MAX_RECORDS_IN_RAM 给多少合适? 我个人经验:如果你的服务器有 64GB RAM,设 5000000 比较保守。如果只有 16GB,我建议降到 2000000,否则 JVM 容易 OOM。记住,这个值不是越大越好——太大反而导致 GC 停顿变长。

3. MarkDuplicates 去重:分子条形码和 PCR 重复

MarkDuplicates 是预处理里的重头戏。PCR 扩增会产生大量重复 read,如果不标记,变异检测时会高估这些区域的覆盖度,导致假阳性 SNP 呼叫。

这里有一个关键点:REMOVE_DUPLICATES 参数。我建议 不要设为 true。GATK 团队官方推荐保留重复标记(用 0x400 flag 标记),而不是直接删除。为什么呢?因为某些高频变异检测(比如低频体细胞突变)需要原始覆盖信息,删除后数据就回不来了。

推荐的 MarkDuplicates 命令

java -jar picard.jar MarkDuplicates \
    I=sample1.sorted.bam \
    O=sample1.markdup.bam \
    M=sample1.markdup_metrics.txt \
    ASSUME_SORTED=true \
    REMOVE_DUPLICATES=false \
    OPTICAL_DUPLICATE_PIXEL_DISTANCE=2500 \
    TMP_DIR=/path/to/tmp
  • ASSUME_SORTED=true:既然已经用 SortSam 排好序,节省一次检查开销
  • OPTICAL_DUPLICATE_PIXEL_DISTANCE:光学重复判定距离,Illumina 数据默认 2500,实测够用
  • M=...:输出 metrics 文件,一定要保留,方便后续统计去重率

双端数据的去重逻辑:MarkDuplicates 默认使用 read pair endpoint 来判定重复,而不是单端。如果两个 read pair 两端比对位置完全一致,就标记为重复。这个逻辑在双端数据上很合理,但如果是单端数据,需要加 READ_NAME_REGEX=null 来关闭基于名字的匹配模式。

曾经踩过的坑:光学重复参数调太高

有一次我在 NovaSeq 数据上跑 MarkDuplicates,把 OPTICAL_DUPLICATE_PIXEL_DISTANCE 设成了 10000,结果去重率飙到 40%。后来一查文档,这个参数是针对不同测序平台的。NovaSeq 的 flow cell 密度更高,光学重复的距离更小,用 2500 就够了。设得过大,会把正常的 non-duplicate read 也误标记为光学重复。

性能优化技巧

MarkDuplicates 的处理速度,在 WGS 数据上是整个管线的瓶颈。我试过一些优化方法:

  • 多线程模式:Picard 2.0+ 支持 --ADD_MATE_CIGAR 并行处理,但注意这需要额外内存
  • 减少内存碎片:设置 -Xmx JVM 堆大小为物理内存的 75%,不要全占满
  • SSD 临时目录:把 TMP_DIR 放到 SSD 上,I/O 瓶颈明显改善

我的经验数据

用 32 核服务器跑一个 30x WGS 样本,整个过程(BWA + SortSam + MarkDuplicates)大约需要:

  • BWA-MEM(8 线程):4-6 小时
  • SortSam:1-2 小时
  • MarkDuplicates:2-3 小时

如果你发现 MarkDuplicates 跑了一整天没结束,先检查 MAX_RECORDS_IN_RAM 是否设得太低,导致大量磁盘 spill。

4. 生成 BAM 索引

最后一步,千万别忘了建索引。GATK 所有工具都要求 BAM 文件附带 .bai 索引。我记得有一次需要强制重新比对一个区域,结果发现索引文件丢失,当场重新跑了一遍……

# 用 samtools 建索引
samtools index sample1.markdup.bam

索引文件虽然只多了几十 KB,但作用巨大。没有它,GATK 在读取特定区间时只能暴力遍历整个 BAM,速度慢到令人崩溃。