Evaluating storageChapter 3 of 7

How fast is it on small files?

The short answer

Agent work writes thousands of small files, so its wall-clock time is set by how many network round trips each file costs, not by throughput. Storage that answers file operations on the machine finishes an install within seconds of a local disk. Storage that asks a server about every file takes several times as long.

Hunter LeathFounder & CEO, Archil

6 min readChecked November 2026Read as Markdown

Installing the dependencies of one Next.js app with npm ci writes 18,602 files. We ran that install on one EC2 instance with several file systems mounted side by side, and stopped the clock only when every write was durable. On the instance’s own EBS volume, it took 10.7 s. On Archil, it took 12.4 s. On Amazon S3 Files, which serves an S3 bucket over NFS, the same install took 71.4 s.

The host, the package cache, and the bytes written were the same each time. What differed was how often each file had to wait for a server. An agent that clones a repository, installs its dependencies, and runs its tests pays that difference at every step, and wall-clock time is what you and your users notice.

· npm ci, timed until durablePlayed 10 times faster than life. Each counter is the number of files written so far.

npm ci of a Next.js app writes 18,602 files. On one EC2 host, timed until every write was durable, it took 10.7 s on the instance’s own EBS volume, 12.4 s on Archil, and 71.4 s on Amazon S3 Files. The animation plays the three runs side by side, 10 times faster than they ran.

Two jobs, five file systems§

The pattern holds for git clone and for other designs. On the same host, a clone of the Django repository took 15.5 s on local disk, 19.8 s on Archil, and 107.5 s on S3 Files. JuiceFS, which keeps its metadata in a database (here Redis on ElastiCache, in the same Availability Zone), took 161.6 s for the install and 247.4 s for the clone with its defaults. Its write-back mode stages writes on local disk and uploads them later. Timed until the upload finished, it took 53.8 s and 29.2 s.

Seconds until durable · lower is better

npm ci, 18,602 files, until durable: Local disk (EBS gp3), for reference 10.7 s, Archil 12.4 s, JuiceFS, writeback mode 53.8 s, Amazon S3 Files 71.4 s, JuiceFS, default mode 161.6 s.

npm ci, 18,602 files, until durable, in seconds; lower is better
StorageSeconds
Local disk (EBS gp3), for referenceOne instance only: gp3 volumes can't attach to a second instance
10.7 s
Archil
12.4 s
JuiceFS, writeback modeStaged on local disk, then uploaded; timed until the upload finished
53.8 s
Amazon S3 Files
71.4 s
JuiceFS, default mode
161.6 s
How this was measured

One AWS c7gn.2xlarge (arm64) in us-east-1, every file system mounted on the same host. Archil client 0.10.1 with an exclusive mount of a disk in aws-us-east-1. JuiceFS Community Edition 1.4.2 with data in Amazon S3 and metadata in ElastiCache for Redis 7.1 in the same Availability Zone, mounted with its defaults (1 s attribute cache, local disk cache) and separately with --writeback. Amazon S3 Files through amazon-efs-utils 3.0.1. Local disk is EBS gp3. Inputs sat on local disk, so GitHub and the npm registry aren't in the times: git clone of the Django repository from a local mirror, and npm ci --prefer-offline --ignore-scripts of a Next.js 15 app (18,602 files) from a warm local cache. Each run is timed until its writes are durable: archil sync on Archil, sync -f elsewhere, and for JuiceFS writeback also until every block staged on local disk had uploaded to S3. Medians of 3 runs. Run it yourself.

· Two jobs, timed until durableChoose a job to compare. Local disk is drawn as an outline because only one instance can attach it.

Two developer jobs on one AWS c7gn.2xlarge in us-east-1, each timed until its writes were durable, medians of 3 runs. npm ci of a Next.js app (18,602 files): local EBS gp3 10.7 s, Archil 12.4 s, JuiceFS in write-back mode 53.8 s, Amazon S3 Files 71.4 s, JuiceFS with defaults 161.6 s. git clone of Django: local EBS gp3 15.5 s, Archil 19.8 s, JuiceFS in write-back mode 29.2 s, Amazon S3 Files 107.5 s, JuiceFS with defaults 247.4 s.

A separate benchmark against Amazon EFS shows the same shape. Cloning React (7,260 files) took 9.8 s on Archil and 81.5 s on EFS. npm ci of a create-next-app project took 7.6 s on Archil and 86.3 s on EFS. That benchmark stopped the clock when each command returned, rather than when its writes were durable.For the hosts, versions, and commands behind both benchmarks, see File systems on one host.

Where the time goes§

Divide each install by its 18,602 files. On local disk, 10.7 s is 0.58 ms per file. That’s npm’s own work: reading its cache, unpacking, and making system calls that the kernel answers from memory. On S3 Files, 71.4 s is 3.84 ms per file, so each file spent about 3.3 ms waiting on the network. On Archil, each file spent about 0.09 ms.

A round trip to a server in the same region is short. Our published 4 KiB read that misses the client’s cache is one round trip, and it takes 0.50 ms at the median. So each file on S3 Files waited for the equivalent of six or seven round trips.

The waits come from how a network file system creates a file. A program makes four calls: open with O_CREAT, write, fchmod, and close. A local file system answers all four in memory. An NFS client has to send three of them to the server, as CREATE, SETATTR, and WRITE followed by COMMIT, and wait for each answer.

· One file, three waysEach bar is one round trip. The middle lane waits four times for a single small file.

The same four calls (open, write, chmod, close) on three kinds of storage. A local disk answers all four in memory. An NFS client waits for the server on three of them, for four round trips: CREATE, SETATTR, and WRITE plus COMMIT on close. A client that has checked out the directory answers all four in memory and sends the change later in one message with many other files.

The client can’t skip the question, even when no one else is using the directory. If two machines both create hello.txt, only the server sees both requests, so only the server can decide which one gets the name. The client has no way to know that nobody else is looking, so it asks every time.

A client can answer from memory only once it holds the right to decide: the server has handed it the directory, and nobody else can change that directory until the client hands it back. We call that a checkout. NFS and our docs call it a delegation. By default, an Archil client checks out the whole disk when it mounts it. It answers open, write, chmod, and close from memory, and it sends the changes to our servers later, many files to a message, and whenever the program calls fsync(). For how this idea grew out of AFS callbacks, leases, and NFSv4 delegations, see Anatomy of a file create over a network.

A test you can run§

Run the job you care about on the same host, once on local disk and once on each storage you’re considering. Stop the clock when the writes are durable, not when the command returns. A design that stages writes on a local disk and uploads them later looks like local disk until you wait for the upload.

Terminal
# Set MNT to each mount in turn, local disk first.
time (git clone -q file:///data/repo.git "$MNT/repo" && sync -f "$MNT/repo")
time (cd "$MNT/app" && npm ci --prefer-offline --ignore-scripts && sync -f .)

sync -f flushes the whole file system that holds the path. On Archil, run archil sync <mountpoint> instead. Keep the inputs on local disk, as in the preceding commands, so the network to GitHub or the npm registry isn’t part of the time. Then divide by the number of files. The following calculator starts from our install and takes your numbers too.

files

npm ci in our test

s

the program’s own cost

s

until the writes were durable

ms

0.5 ms: a read from a server in the same region

Per file, local disk
0.58 ms
10.7 s ÷ 18,602
Extra waiting per file
3.26 ms
3.84 ms − 0.58 ms
Round trips’ worth per file
6.5
3.26 ms ÷ 0.50 ms

· From a timing to a cost per fileChoose a measurement or type your own. The local-disk time is the program’s own work, and the rest is waiting.

The calculator divides a job’s time by the files it writes. For our npm ci of 18,602 files, local disk is 0.58 ms per file. Amazon S3 Files adds 3.26 ms of waiting per file, about 6.5 round trips at 0.5 ms each. JuiceFS with its defaults adds 8.11 ms, about 16 round trips. Archil adds 0.09 ms, about a fifth of one round trip.

Where Archil is slower§

Small files aren’t the only workload, and on several others we lose. In the same EFS benchmark, EFS was faster on the following three tests.

TestArchilAmazon EFS
Sequential write, 4 GiB, then fsync()194 MB/s590 MB/s
4 KiB random writes for 30 s, then fsync()953 IOPS2,976 IOPS
SQLite, one insert of 1 million rows11.0 s1.7 s

We have more work to do on throughput.

A checkout makes one client fast by making the others ask first. In the default exclusive mode, other machines can mount the disk read-only, but they can’t write until the holder unmounts. To share writes, clients check out separate paths, or mount in conditional mode, where creates and renames run on the server as they do on NFS, at the speed of a remote file system. When many clients keep writing the same files, handing ownership back and forth can cost more than NFS’s round trips. Other clients see changes within about 30 seconds by default, or at once after archil invalidate-cache. For more information about sharing, see Can many machines use it at once?

Distance still counts for reads. A checkout removes the waits on files a client writes, but a file the client has never read has to cross the network. A read that also misses our servers’ cache goes to the object store, typically 10 to 30 ms on S3. From a Modal sandbox about 40 ms from our servers, Archil read a 1 GiB file into a new sandbox at 331 MB/s, and a Modal Volume read it at 624 MB/s. Keep compute in the same region as the disk, warm the cache with archil prefetch before a job reads a tree it hasn’t seen, or run the command next to the data with serverless execution.