feat(preprocessing): process a batch's raw read files concurrently - #7190
Draft
corneliusroemer-agent wants to merge 1 commit into
Draft
corneliusroemer-agent wants to merge 1 commit into
corneliusroemer-agent wants to merge 1 commit into
Conversation
Preprocessing sent one blocking request per entry to the raw reads processing service and waited for each to come back before starting the next, so a batch cost the per-entry validation time times the batch size. Files of a whole batch are now submitted to the service up front from a thread pool, sized by the new raw_reads_processing_concurrency setting (default 4). The load on the single raw reads processing pod is that number times the number of preprocessing replicas that use it, which is what has to stay within the pod's memory headroom, so the knob is deliberately conservative and per-organism configurable via Helm. Both mates of a paired submission are also downloaded from S3 at the same time in raw-reads-processing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Submitting raw reads is slow in batches, and the reason is that preprocessing talks to the raw reads processing service strictly one entry at a time.
process_allloops over the batch and each iteration makes a blocking HTTP call that has to come back before the next entry starts. Onpreview-raw-reads-feature.pathoplexus.orga 100-entry batch ran at about 5.2 s per entry with no overlap at all, which is just the per-entry cost multiplied by the batch size. Nextclade alignment is already batched across the whole batch before this loop, so file processing is the one step that is still serialised per entry.This sends the files of the whole batch to the service up front from a thread pool and then resolves each entry's result inside the existing per-entry loop, so entries still get processed and returned in the same order as before. Only the waiting is overlapped; metadata processing, nextclade handling and error handling are untouched. Both mates of a paired submission are also now downloaded from S3 at the same time inside the service, which was a sequential loop over the two files.
The thing worth knowing that is not visible in the diff:
process_single_unalignedcalls the file processing service too, andConfig.finalizeforcesalignment_requirementtoNONEwhenever an organism has no segments or no nextclade dataset — which is plausible for an organism that only takes raw reads. Parallelising only the aligned branch would have been a no-op for exactly the case this is meant to speed up, so the prefetch sits above the branch and both arms use it.What I'd like a decision on
The default concurrency of 4 is a guess on the conservative side, and it is the number I would most like a second opinion on. The load that actually reaches the single raw reads processing pod is this number times the number of preprocessing replicas across all organisms pointing at it. Each concurrent request forks its own
java -jar readtools.jaron top of the roughly 4.9 GB that the deacon index occupies in an 8 Gi pod, so the ceiling is memory, not CPU. Happy to raise it, but I would want it load-tested against a preview first rather than reasoned about.There is also a failure mode that this change makes more likely, and I deliberately did not fix it here.
raw_reads_processing_service_timeout_seconds(default 600) is a socket timeout on each request. With concurrent requests, a request can sit queued inside the pod — in FastAPI's thread pool, and then behind deacon, which serves filter requests one at a time — and burn its timeout while waiting rather than working. If that timeout fires, the client closes the socket mid-request, and a client that disconnects part-way through is what drives the deacon server into a busy loop it never recovers from: every later filter hangs, the health probe does not notice, and the pod needs restarting. At a concurrency of 4 with a 600 s budget this is unlikely, but the clean fix is a semaphore insideraw-reads-processingso that excess requests wait in FastAPI with no socket at risk yet, instead of waiting inside a half-served deacon call. That felt like a separate change; I would do it before raising the concurrency much.On the numbers
I did not measure this against a preview. Locally, driving the real client code against a stub service that sleeps 3 s per request (the readtools plus deacon cost measured on the preview), a batch of 20 goes from 60.1 s at concurrency 1 to 15.0 s at 4 and 6.1 s at 16. That only demonstrates that the requests really do overlap and that the setting bounds them — the stub has no server-side cost, so it scales perfectly and the real curve will flatten much earlier, bounded by deacon's serial handling and by how many JVMs fit in the pod. Please don't read those numbers as expected production speedups.
Both new test files use a
threading.Barrierin the fake service so that they would time out rather than quietly pass if the calls were still sequential, which a wall-clock assertion would not catch reliably in CI.🚀 Preview: https://rawreads-parallel-file-pr.loculus.org