fix(upload): reduce disk-cache file-descriptor use to avoid EMFILE#696
Merged
RapidPoseidon merged 1 commit intoJul 21, 2026
Merged
Conversation
Concurrent local-file uploads could crash with `OSError: [Errno 24] Too many open files` on a common `ulimit -n 1024`. The dominant descriptor consumer is the on-disk upload cache: `FanoutCache(shards=128)` opens ~384 file descriptors at construction and grows to ~640 under the default 25-worker pool (measured), so a single default-config process already sits close to a 1024 limit before open files and HTTP connections are counted. Lower the default `cacheShards` 128 -> 32. Cache shards only reduce SQLite write-lock contention, and cache writes are tiny key->filename strings written after the network upload — so at the default 25 workers, 32 shards (>= worker count) costs nothing measurable while cutting cache descriptors from ~640 to ~317, keeping even several concurrent processes under 1024. `cacheShards` stays decoupled from `maxWorkers`: deriving it from the worker count would reshuffle the key->shard mapping and silently invalidate the persistent cross-run upload cache whenever workers changed. Also make the failure discoverable: `FailedUploadException` now detects descriptor-exhaustion failures (EMFILE, or the message text as a fallback) and appends a hint pointing at `RAPIDATA_cacheShards` / `RAPIDATA_maxWorkers` / `ulimit -n` and the config docs, and both are documented under a new "Too many open files" section. The bounded worker pool already respected the post-#687 atomic upload-then-persist ordering (it lives entirely within the state machine's UPLOAD_DATAPOINTS step); this change tunes the cache below it and does not touch that control flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: lino <68745352+LinoGiger@users.noreply.github.com>
LinoGiger
marked this pull request as ready for review
July 21, 2026 11:27
LinoGiger
approved these changes
Jul 21, 2026
RapidPoseidon
deleted the
fix(upload)/reduce-disk-cache-file-descriptor-use
branch
July 21, 2026 11:28
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.
Problem
Submitting local-file jobs on a common
ulimit -n 1024can crash withOSError: [Errno 24] Too many open files. Reported from integration (SDK 3.16.5, Python 3.12, Linux, Compare task); the workaround wasulimit -n 8192+ sequential submission.Root cause (measured)
The upload worker pool is already bounded (
maxWorkers=25). The dominant descriptor consumer is the on-disk upload cache —diskcache.FanoutCache(shards=128):A single default-config process already holds ~640 descriptors for the cache alone, before open files + HTTP connections — right at the edge of a 1024 limit.
Change
cacheShardsdefault 128 → 32. Shards only reduce SQLite write-lock contention, and cache writes are tinykey → filenamestrings written after the network upload. At 25 workers, 32 shards (≥ worker count) costs nothing measurable while cutting cache descriptors ~640 → ~317 — enough headroom for several concurrent processes under 1024. Benchmarked: fewer shards was not slower.cacheShardsdecoupled frommaxWorkers. Deriving shards from the worker count would change thekey → shardmapping and silently invalidate the persistent cross-run upload cache every time workers changed (verified: 32→16 shards = ~57% cache miss). A stable static default reshuffles the on-disk cache exactly once (on upgrade).FailedUploadExceptionnow detects descriptor-exhaustion failures (EMFILE errno, with a message-text fallback) and appends a hint pointing atRAPIDATA_cacheShards/RAPIDATA_maxWorkers/ulimit -nand the config docs. New "Too many open files" sections indocs/config.mdanddocs/error_handling.md.max_upload_workersparam (the globalrapidata_config.upload.maxWorkerscovers all upload paths uniformly). Both were discussed and decided with the requester.Compatibility with #687
Lands cleanly on top of the sibling's merged #687 (atomic job creation). #687's only
upload_config.pyedit was additive (failureTolerance) — no conflict. All changes here sit below theJobDefinitionCreationMachine, inside itsUPLOAD_DATAPOINTSstep, so the concurrency bound already respects the atomic upload-then-persist ordering; no control flow was forked or reverted. #687 also gave the error hint a natural home —FailedUploadExceptionis now the single chokepoint where mass-EMFILE failures surface.One-time impact
Changing the shard count reshuffles the on-disk cache layout, so anyone with an existing warm cache re-uploads previously-cached assets once, then re-caches under the new layout. Not a correctness issue.
Testing
tests/rapidata_client/exceptions/test_failed_upload_exception.py(3 tests) covering the EMFILE hint (errno + message-fallback + negative case).pyright src/rapidata/rapidata_client— 0 errors (the CI gate).pytest tests/rapidata_client/job tests/rapidata_client/exceptions— 13 passed.mkdocs build— succeeds.🔗 Session: https://node-b899e465.poseidon.rapidata.internal/