File count was the cost, not bytes
RIPTON Desktop has always packaged a directory as a single bundle before moving it. Browser uploads through Web Edge did not: each uploaded file landed on the node as its own file, in a staging area, and finalize moved each one into place. That is fine for a hundred files. We tested with a folder of about 420,000 small files, the shape of a source-asset tree or a tiled raster, and it was not fine.
- Finalizing the upload took around five minutes on the test node: one create, write, close and rename per file, serialised on the filesystem's metadata path.
- Building an archive for download had the same shape: 420,000 opens and reads, each a separate syscall path, each competing for IOPS with the uploads in flight.
- Every session-scoped request hydrated the whole session, so a per-batch origin check on a session with 420,000 paths was itself expensive.
None of this was about network speed. The bytes were a few gigabytes. The work was in the count.
One object, one manifest, fixed offsets
A bundle is a single file with a short header, a manifest that lists every path with its size and payload-relative offset, and then the payload. Desktop already produced and consumed this format, so the node already knew how to read a byte range out of one.
RIPTBUNDLE\n
<20-digit manifest length>
<manifest: path, size, offset for every entry>
<payload: entry bytes, back to back, at the manifest's offsets>The change for browser uploads was to assign every file its final offset at setup time, when the control plane already knows the file list and sizes, and to have the node pre-create the bundle with the manifest in place. Each upload batch then writes its files straight into the bundle at those offsets with a positioned writer. Batches can arrive in any order and in parallel; they never overlap because the offsets were fixed before the first byte arrived. Finalize is a rename from the staging name to the final name.
What it did on the test node
| Step | Before | After |
|---|---|---|
| Finalize a 420,000-file upload | About 5 minutes | Under 10 seconds |
| Cold-cache archive build for download | About 1,000 files/s | About 2,800 files/s, reading the bundle sequentially with bounded read-ahead |
| Per-request session check | Loaded the full path list | Reads a session summary |
The second-order effects were the ones we cared about most. Downloads of file-heavy packages now read one object sequentially instead of seeking through hundreds of thousands of inodes. Storage cleanup deals with one object per package. And the node's filesystem is no longer the thing that decides how large a folder a browser can send.