Skip to content

perf: prevent bin QC block on all binner jobs finishing#1062

Merged
dialvarezs merged 13 commits into
nf-core:devfrom
dialvarezs:perf/decouple-seqkit-from-binners
Jul 2, 2026
Merged

perf: prevent bin QC block on all binner jobs finishing#1062
dialvarezs merged 13 commits into
nf-core:devfrom
dialvarezs:perf/decouple-seqkit-from-binners

Conversation

@dialvarezs

@dialvarezs dialvarezs commented Jun 27, 2026

Copy link
Copy Markdown
Member

Performance / QoL improvement

In the current implementation, bins are grouped by sample before being fed to SEQKIT_STATS, which computes their sizes for downstream filtering. Because groupTuple has no size, it can't emit until every binner has finished, blocking bin QC for bins that already finished.

This PR removes that grouping and feeds each binner's bins to SEQKIT_STATS as they complete. Functionality is preserved: downstream re-keys the stats by bin filename (splitCsv -> join), so the result is identical regardless of how the seqkit jobs are batched.

The only drawback is more seqkit jobs (from 1 per sample to 1 per sample+assembler+binner), but that's negligible compared to removing the global pipeline lock.

PR checklist

  • This comment contains a description of changes (with reason).
  • If you've fixed a bug or added code that should be tested, add tests!
  • If you've added a new tool - have you followed the pipeline conventions in the contribution docs
  • If necessary, also make a PR on the nf-core/mag branch on the nf-core/test-datasets repository.
  • Make sure your code lints (nf-core pipelines lint).
  • Ensure the test suite passes (nextflow run . -profile test,docker --outdir <OUTDIR>).
  • Check for unexpected warnings in debug mode (nextflow run . -profile debug,test,docker --outdir <OUTDIR>).
  • Usage Documentation in docs/usage.md is updated.
  • Output Documentation in docs/output.md is updated.
  • CHANGELOG.md is updated.
  • README.md is updated (including new tool citations and authors/contributors).

@dialvarezs dialvarezs changed the title perf: prevent bin QC block on slowest binner perf: prevent bin QC block on all binner jobs finishing Jun 27, 2026

@piplus2 piplus2 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the advantage of splitting the stat into many processes but still there's the risk of having thousands of them.

ch_binning_results_gzipped_final = ch_binning_results_gzipped_final.mix(ch_semibin_bins)
}

// group bins into per-sample process and not flood clusters with thousands of seqkit jobs

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the possible creation of thousands of jobs as reported here?

@dialvarezs dialvarezs Jun 27, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the drawback. In practice, the worst case (all binners and all assemblers enabled) works out to about samples * 30 jobs, and most realistic configurations should be around samples * 10 or less.
I think that's an acceptable tradeoff, and arguably better than the hard lock we have today: because some binners are way slower than others, the current behavior can force you to wait 10+ hours for the last binner to finish before the pipeline can continue. Grouping per-sample avoids that serial bottleneck.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's still going to generate hundreds of jobs... On a standard HPC most of them will go on queue. Since it's a big change, I'd suggest to perform some tests on real scenarios. Also, I'm not sure that the way the stats results are currently processed in the subsequent steps of the workflow are consistent with these changes. From my understanding they expect a single tsv per sample.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, ch_seqkitstats_results stores one item per bin (it splits the seqkit CSV per row), so the downstream result ends up exactly the same regardless of how many tsv files seqkit produces, nothing expects a single tsv per sample.

I think the numbers of jobs is a valid concern, but most HPC handle small jobs without issues, and also they will not be spawned at the same time, as the binning jobs finish instead.

As an additional note, the whole post-binning workflow already runs at the same level of granularity (sample * assembler * binner): BUSCO, CheckM(2), GUNC, GTDB-Tk, CATPACK, Tiara, etc. So in practice, seqkit will not dispatch a lot of jobs compared to other tools in the pipeline.

I'm running some mag benchmarks now and hit this: my mag run has been about ~15h just waiting for COMEbin to finish, after it failed once.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So @piplus2 is sort of fair in his worry of flooding a HPC with lots of jobs - while you are right @dialvarezs that it is sort of the same for all the others, the problem is the seqkitstats is a very short/fast job which is something HPC admins do not like (batch them! Use an array!) as it adds a bunch of unnecessary strain on the scheduler database etc (apparently anyway).

Even if I think that would probably not toooo terrible if Diego's estimate is correct (samples * 30), as a mid-ground we could either:

  • Just document/make people aware of maxForks
  • Have a dedicated parameter than controls maxForks for this one process? (this would also arguably be more visible than just document)

What do you think?

@piplus2 piplus2 Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your point. Since this is a major change in the workflow, I'd like to leave it to @jfy133, as from my understanding, he wrote the original part.

@piplus2 piplus2 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfy133 I feel you may know this better than me, I don't feel confident enough about the possible consequences of these changes.

ch_binning_results_gzipped_final = ch_binning_results_gzipped_final.mix(ch_semibin_bins)
}

// group bins into per-sample process and not flood clusters with thousands of seqkit jobs

@piplus2 piplus2 Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your point. Since this is a major change in the workflow, I'd like to leave it to @jfy133, as from my understanding, he wrote the original part.

Comment thread tests/test_assembly_input.nf.test.snap Outdated
"-profile assembly_input": {
"content": [
285,
291,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the additional files variable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are the additonal processes from "flattening" seqkit.

ch_binning_results_gzipped_final = ch_binning_results_gzipped_final.mix(ch_semibin_bins)
}

// group bins into per-sample process and not flood clusters with thousands of seqkit jobs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So @piplus2 is sort of fair in his worry of flooding a HPC with lots of jobs - while you are right @dialvarezs that it is sort of the same for all the others, the problem is the seqkitstats is a very short/fast job which is something HPC admins do not like (batch them! Use an array!) as it adds a bunch of unnecessary strain on the scheduler database etc (apparently anyway).

Even if I think that would probably not toooo terrible if Diego's estimate is correct (samples * 30), as a mid-ground we could either:

  • Just document/make people aware of maxForks
  • Have a dedicated parameter than controls maxForks for this one process? (this would also arguably be more visible than just document)

What do you think?

@piplus2

piplus2 commented Jun 30, 2026

Copy link
Copy Markdown

@jfy133 maxForks makes sense to me. At least we have full control of what's gonna happen.

@dialvarezs

dialvarezs commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

maxForks makes sense to me.
I'm not sure how we should handle the parameter, though. A single one for seqkit and also gunzip and prokka (these ones are the heavy offenders, the run per bin)?

@jfy133

jfy133 commented Jul 1, 2026

Copy link
Copy Markdown
Member

maxForks makes sense to me.
I'm not sure how we should handle the parameter, though. A single one for seqkit and also gunzip and prokka (these ones are the heavy offenders, the run per bin)?

In principle yes. Or if they are are all running per bin, you could share across all because the number of bins won't change much at each stage

…-from-binners

# Conflicts:
#	tests/test_assembly_input.nf.test.snap
#	tests/test_hybrid.nf.test.snap
#	tests/test_longreadonly.nf.test.snap
#	tests/test_single_end.nf.test.snap
@dialvarezs

Copy link
Copy Markdown
Member Author

I added a new parameter to control seqkit concurrency, --bin_seqkit_stats_max_forks. By deafult is disabled (no cap).
I'll add one for prokka too, but in another PR.

@dialvarezs dialvarezs requested a review from jfy133 July 2, 2026 12:20
Comment thread conf/modules.config Outdated

withName: SEQKIT_STATS {
ext.args = ""
maxForks = params.bin_seqkit_stats_max_forks ?: null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default should be set in nextflow.config, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we can remove the ?: null

@dialvarezs dialvarezs merged commit 03de338 into nf-core:dev Jul 2, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants