Two Problems, Often Confused
"Resumable" and "parallel" get used interchangeably in upload discussions, but they solve different problems. Resumability is about surviving failure. Parallelism is about speed. You can have one without the other, but the best upload pipelines combine both — and understanding the distinction helps you configure each correctly.
Resumable.js supports both out of the box. The names aren't accidental: the library was designed for resumability first, with parallel chunk uploading as a throughput optimization layered on top.
Resumability: Surviving Failure
A resumable upload can continue from where it stopped after an interruption. The user's browser crashes, their train enters a tunnel, their laptop goes to sleep — when they come back, the upload picks up from the last successfully transferred chunk rather than restarting from zero.
Resumable.js implements this through the testChunks mechanism. When an upload starts (or restarts), the library sends a GET request for each chunk to ask the server: "Do you already have chunk N?" If the server responds with 200, that chunk is skipped. If it responds with 204 (or any non-200), the chunk gets uploaded.
const r = new Resumable({
target: '/api/upload',
testChunks: true, // Enable resume capability
chunkSize: 2 * 1024 * 1024,
});
The server's job is simple: check whether a chunk file exists and return the appropriate status code. The retries and resume guide covers the server-side implementation in detail.
Resumability matters most when:
- Files are large — a 2 GB video upload that fails at 90% is devastating without resume
- Connections are unreliable — mobile networks, trains, coffee shop WiFi, developing regions
- Uploads take a long time — anything over a few minutes has a meaningful chance of interruption
- Users have limited bandwidth — re-uploading isn't just slow, it costs money on metered connections
The overhead of resumability is the test-chunk requests. For a 100 MB file with 2 MB chunks, that's 50 GET requests on resume. For a fresh upload with no interruption, you can skip these entirely by detecting that the upload is new. The resume after refresh example shows how to persist upload state across browser sessions.
Parallelism: Maximizing Throughput
Parallel chunk uploading sends multiple chunks simultaneously rather than waiting for one to complete before starting the next. This overlaps network latency and fills the available bandwidth more effectively.
Without parallelism, uploading 50 chunks sequentially means each chunk's upload time includes:
- TCP connection setup (or reuse)
- TLS handshake (if new connection)
- Request transmission (the actual bytes)
- Server processing time
- Response receipt
The server processing and response time for one chunk is dead time where the client's upload bandwidth sits idle. Parallelism fills that gap by keeping multiple chunks in flight.
Resumable.js controls this with simultaneousUploads:
const r = new Resumable({
target: '/api/upload',
chunkSize: 2 * 1024 * 1024,
simultaneousUploads: 3, // Upload 3 chunks at once
});
The throughput improvement depends on the bottleneck. If your upload bandwidth is fully saturated by a single chunk stream, adding parallelism won't help — it just divides the same bandwidth across multiple connections. But if per-connection overhead (latency, server processing time) is the bottleneck, parallelism helps significantly.
How They Combine
Resumability and parallelism are orthogonal — you can enable either independently:
| Configuration | Behavior |
|---|---|
testChunks: false, simultaneousUploads: 1 | Sequential, non-resumable. Simple but fragile. |
testChunks: true, simultaneousUploads: 1 | Sequential, resumable. Safe but slower. |
testChunks: false, simultaneousUploads: 3 | Parallel, non-resumable. Fast but restarts on failure. |
testChunks: true, simultaneousUploads: 3 | Parallel and resumable. The default recommendation. |
Resumable.js handles the interaction cleanly. When resuming with parallelism enabled, it tests chunks and then uploads the missing ones in parallel. There's no special configuration needed — the two features compose naturally.
The configuration reference documents both options and their defaults.
When Parallelism Matters Most
Not every upload benefits equally from parallelism. The gains depend on specific conditions:
High-bandwidth connections with latency — a user on a 100 Mbps connection uploading to a server 200 ms away benefits significantly. Each round trip costs 200 ms of idle time; three parallel streams keep the pipe fuller.
Large files — with enough chunks in the queue, parallelism has room to work. A 5 MB file split into 1 MB chunks only has 5 chunks — the benefit of 3-way parallelism is marginal. A 500 MB file with 250 chunks sees meaningful improvement.
Server-side processing delays — if your upload endpoint writes to object storage (S3, R2) and each chunk write takes 50–100 ms of server processing, parallel uploads overlap those delays. The optimal chunk sizes guide discusses how chunk size and count interact with server processing.
CDN or edge environments — if your upload endpoint is behind a CDN that adds per-request overhead, parallelism helps amortize that cost.
Trade-Offs of High Parallelism
More isn't always better. Increasing simultaneousUploads has real costs:
Browser Connection Limits
Browsers enforce a limit of roughly 6 concurrent connections per origin over HTTP/1.1. If you set simultaneousUploads: 8, two of those requests will queue behind the others. Worse, those connections are shared with all other requests to the same origin — API calls, asset loading, WebSocket connections. Setting parallelism too high can starve your application's other network needs.
HTTP/2 multiplexes requests over a single connection, relaxing this constraint significantly. If your server supports HTTP/2, you have more headroom — but you're still sharing the connection's bandwidth.
Memory Pressure
Each in-flight chunk is held in memory as a Blob (from File.slice()). With 3 simultaneous uploads of 2 MB chunks, that's 6 MB of chunk data in memory at any time. With 10 simultaneous uploads of 5 MB chunks, that's 50 MB — noticeable on mobile devices.
Server Load
Each parallel chunk is a concurrent request hitting your server. With 100 users uploading 5 chunks in parallel, your server handles 500 concurrent upload requests. Size your upload infrastructure accordingly — worker processes, file handles, disk I/O, memory buffers.
The rate limits guide covers server-side controls for managing this load.
Ordering and Reassembly
Chunks arriving out of order is the normal case with parallel uploads. Your server must handle this gracefully: write each chunk to its designated slot (identified by chunk number) and detect completion when all slots are filled. Don't assume chunks arrive in order.
Server-Side Implications
Parallel chunk uploads require your server to handle concurrent writes for the same upload session. A few implementation patterns:
File-per-chunk: write each chunk to a separate temporary file (upload_abc123_chunk_001, upload_abc123_chunk_002). On completion, concatenate them in order. This is simple and avoids file-locking issues.
Sparse file with seeking: open a single file and seek to the correct offset for each chunk. This works but requires file locking or careful concurrency handling.
Direct-to-storage: write each chunk directly to object storage as a separate object. On completion, use the storage API's multipart completion or a server-side concatenation step.
The file-per-chunk approach is the most common and works well with all storage backends. The server receivers guide covers the implementation.
Practical Recommendations
Start with these defaults and adjust based on measurement:
const r = new Resumable({
target: '/api/upload',
chunkSize: 2 * 1024 * 1024, // 2 MB chunks
simultaneousUploads: 3, // 3 parallel streams
testChunks: true, // Enable resume
maxChunkRetries: 3, // Retry failed chunks
chunkRetryInterval: 2000, // 2s between retries
});
simultaneousUploads: 3 is a safe starting point. It provides meaningful parallelism without hitting browser connection limits or creating excessive server load. On fast, reliable connections, you might increase to 5. Going above 6 rarely helps with HTTP/1.1.
Measure before tuning. Use the upload events to track per-chunk and overall timing. Calculate effective throughput (bytes transferred / wall-clock time) and compare across different parallelism settings. If doubling simultaneousUploads doesn't improve throughput, you've hit a different bottleneck — likely bandwidth or server capacity.
Adapt to conditions. Some applications detect connection quality and adjust parallelism dynamically. On a fast WiFi connection, use 5 simultaneous uploads. On a mobile network, drop to 1 or 2 to avoid overwhelming a constrained connection. The chunking guide discusses adaptive strategies.
The bottom line: resumability protects your users from wasted effort. Parallelism respects their time. Use both, tune parallelism conservatively, and measure the results.
