> ## Documentation Index
> Fetch the complete documentation index at: https://docs.archil.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox storage

> One Node script and a recipe per sandbox provider to rerun the storage numbers on the Archil compare pages yourself.

The storage numbers on our compare pages for [Modal](https://archil.com/compare/modal),
[Vercel Sandbox](https://archil.com/compare/vercel-sandbox), [Daytona](https://archil.com/compare/daytona), and
[sandbox providers](https://archil.com/compare/sandbox-providers), including Fly.io Sprites, come from the script on
this page. In each provider's sandbox, we mounted the provider's own storage and an Archil disk side by side and ran the same script against both, so the compute
is identical and only the storage differs. You can run it on your own accounts with the recipes below.

## What it measures

| Metric | How | Runs |
| - | - | - |
| Create a 4 KiB file and fsync it | `open`, `write`, `fsync`, `close` of a new file | 100 in a row; p50 and p90 |
| Write 1 GiB sequentially and fsync | 1 MiB writes of random data, then one `fsync` | 3; median |
| `npm ci`, 10,538 files | `npm ci --offline` from a local npm cache, so the registry isn't timed | 3; median |
| Delete that tree | `rm -rf node_modules` | 3; median |
| Read the 1 GiB back from a new sandbox | `read` mode in a sandbox that didn't write the file; sha256 checked | 1 in each of 3 new sandboxes; median |
| Another sandbox sees a new file | `watch` in one sandbox while `mark` writes the file in another; 60 s limit | 5 rounds; median |
| Create the storage | The provider's create call, from your machine, until the store can be attached | 5; median |
| Fork | Checkpoint the Archil disk, branch it, mount the branch, read the 1 GiB file | 3; median |

The recipes run the visibility, provisioning, and fork steps once; we repeated them for the counts above. Daytona
visibility and provisioning used 10 rounds.

Everything except provisioning, the branch call, and the branch mount is timed inside the sandbox, so the provider's
API round trips aren't counted. Setup is untimed: the script writes a 1 GiB random source file to local disk, resolves the lockfile,
and fills the npm cache, which is its only network use. Pinned to a registry date, `npm` resolves the same 501 packages
every time.

Some stores acknowledge `fsync` before data leaves the host. An Archil `fsync` returns once the write is durable on
Archil's replicated storage. Our compare pages mark the rows where the other store's write was not durable when
`fsync` returned, and [Vercel Drive fsync durability](https://docs.archil.com/reference/benchmarks/vercel-drive-durability.md) tests it directly.

## The script

Save it as `bench.mjs`. It needs Node 18 or later and npm, and nothing else.

```javascript bench.mjs theme={null}
// Sandbox storage benchmark: https://docs.archil.com/reference/benchmarks/sandbox-storage
// Pure Node (18+) and npm, no other dependencies. Every mode prints one JSON object.
//   node bench.mjs <dir>                 4 KiB create + fsync, 1 GiB write + fsync, npm ci, rm -rf
//   node bench.mjs <dir> read            read the 1 GiB file back and check it (run in a new sandbox)
//   node bench.mjs <dir> mark <name>     write a small file, for the visibility test
//   node bench.mjs <dir> watch <name>    wait up to --timeout seconds for that file to appear
// Options: --runs 3  --creates 100  --timeout 60  --commit "<shell command run inside every timed write>"
import { execFileSync } from "node:child_process"
import crypto from "node:crypto"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"

const args = process.argv.slice(2)
const opt = (name, def) => { const i = args.indexOf(`--${name}`); return i < 0 ? def : args.splice(i, 2)[1] }
const RUNS = Number(opt("runs", 3)), CREATES = Number(opt("creates", 100)), TIMEOUT = Number(opt("timeout", 60))
const COMMIT = opt("commit", "")
const [dir, mode = "write", name = "visible.txt"] = args
if (!dir) { console.error("usage: node bench.mjs <dir> [write | read | mark <name> | watch <name>]"); process.exit(2) }

// Pinned to this registry date, npm resolves the same 501 packages (10,538 files) every time.
const PACKAGES = { "@babel/core": "7.26.0", "@babel/preset-env": "7.26.0", eslint: "8.57.1", jest: "29.7.0", lodash: "4.17.21",
  react: "18.3.1", "react-dom": "18.3.1", typescript: "5.6.3", webpack: "5.96.1", "webpack-cli": "5.1.4" }
const NPM_BEFORE = "2026-11-02T17:10:00Z"

const MiB = 1 << 20, GiB = 1 << 30
const work = path.join(dir, "archil-bench"), seqFile = path.join(work, "seq.bin"), shaFile = path.join(work, "seq.sha256")
const tmp = path.join(os.tmpdir(), "archil-bench")
const bin = path.dirname(process.execPath), env = { ...process.env, PATH: `${bin}:${process.env.PATH}` }
const NPM = fs.existsSync(path.join(bin, "npm")) ? path.join(bin, "npm") : "npm"
const now = () => performance.now()
const tag = () => crypto.randomBytes(4).toString("hex")
const sorted = (xs) => [...xs].sort((a, b) => a - b)
const median = (xs) => { const s = sorted(xs), m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2 }
const round = (x, d = 2) => Number(x.toFixed(d))
const log = (msg) => console.error(`[bench] ${msg}`)
// Some stores persist only on an explicit commit (a Modal Volume: `sync <mount>`), so it runs inside the timing.
const commit = () => { if (COMMIT) execFileSync("sh", ["-c", COMMIT], { stdio: "inherit" }) }
const npm = (cwd, argv, timeout) => execFileSync(NPM, [...argv, "--cache", path.join(tmp, "npm-cache"), "--ignore-scripts",
  "--no-audit", "--no-fund", "--loglevel=error"], { cwd, env, stdio: "pipe", timeout })
const writeDurable = (file, data) => { const fd = fs.openSync(file, "w"); fs.writeSync(fd, data); fs.fsyncSync(fd); fs.closeSync(fd); commit() }

function readAll(file, onChunk) {
  const fd = fs.openSync(file, "r"), buf = Buffer.alloc(MiB)
  let total = 0
  for (let n; (n = fs.readSync(fd, buf, 0, MiB, null)) > 0; total += n) onChunk?.(buf.subarray(0, n))
  fs.closeSync(fd)
  return total
}

// Untimed: a 1 GiB random source on local disk, and a lockfile plus a filled npm cache (the only network use).
function setup() {
  fs.mkdirSync(tmp, { recursive: true })
  const src = path.join(tmp, "rand.bin"), app = path.join(tmp, "app")
  if (!fs.existsSync(src) || fs.statSync(src).size !== GiB) {
    log("writing a 1 GiB random source file to local disk")
    const fd = fs.openSync(src, "w")
    for (let i = 0; i < 1024; i++) fs.writeSync(fd, crypto.randomBytes(MiB))
    fs.closeSync(fd)
  }
  if (!fs.existsSync(path.join(app, "package-lock.json"))) {
    log("resolving the lockfile and filling the npm cache")
    fs.mkdirSync(app, { recursive: true })
    fs.writeFileSync(path.join(app, "package.json"), JSON.stringify({ name: "bench-app", version: "1.0.0", private: true, dependencies: PACKAGES }))
    npm(app, ["install", `--before=${NPM_BEFORE}`])
    fs.rmSync(path.join(app, "node_modules"), { recursive: true, force: true })
  }
  const hash = crypto.createHash("sha256")
  readAll(src, (b) => hash.update(b))
  return { src, app, sha: hash.digest("hex") }
}

const result = { dir, mode, node: process.version }
if (mode === "write" || mode === "mark") fs.mkdirSync(work, { recursive: true })

if (mode === "write") {
  const { src, app, sha } = setup()

  log(`creating ${CREATES} files of 4 KiB, each with fsync`)
  const createDir = path.join(work, `create-${tag()}`), page = crypto.randomBytes(4096), ms = []
  fs.mkdirSync(createDir)
  for (let i = 0; i < CREATES; i++) {
    const t = now()
    const fd = fs.openSync(path.join(createDir, `f${i}`), "wx")
    fs.writeSync(fd, page); fs.fsyncSync(fd); fs.closeSync(fd); commit()
    ms.push(now() - t)
  }
  result.create_4k_fsync_ms = { n: CREATES, p50: round(median(ms), 3), p90: round(sorted(ms)[Math.floor(CREATES * 0.9)], 3) }

  const mbps = []
  for (let r = 0; r < RUNS; r++) {
    log(`writing 1 GiB with fsync, run ${r + 1} of ${RUNS}`)
    const t = now(), fd = fs.openSync(seqFile, "w")
    const total = readAll(src, (b) => fs.writeSync(fd, b))
    fs.fsyncSync(fd); fs.closeSync(fd); commit()
    mbps.push(total / 1e6 / ((now() - t) / 1000))
  }
  writeDurable(shaFile, sha)
  result.write_1gib_fsync_mb_s = { runs: mbps.map((x) => round(x, 0)), median: round(median(mbps), 0) }

  const installs = [], removes = []
  for (let r = 0; r < RUNS; r++) {
    log(`npm ci from the local cache, run ${r + 1} of ${RUNS}`)
    const d = path.join(work, `app-${tag()}`)
    fs.mkdirSync(d)
    // Plain read and write: some FUSE stores reject copy_file_range, which copyFileSync uses.
    for (const f of ["package.json", "package-lock.json"]) fs.writeFileSync(path.join(d, f), fs.readFileSync(path.join(app, f)))
    let t = now()
    try {
      npm(d, ["ci", "--offline"], Number(process.env.NPM_TIMEOUT_S || 900) * 1000)
    } catch (e) {
      result.npm_ci_s = { failed: true, timed_out: e.signal === "SIGTERM", seconds: round((now() - t) / 1000), error: String(e.stderr || e.message).slice(0, 400) }
      break
    }
    commit()
    installs.push((now() - t) / 1000)
    result.npm_files = Number(execFileSync("sh", ["-c", `find "${d}/node_modules" -type f | wc -l`]).toString().trim())
    t = now()
    fs.rmSync(path.join(d, "node_modules"), { recursive: true, force: true }); commit()
    removes.push((now() - t) / 1000)
    fs.rmSync(d, { recursive: true, force: true })
  }
  if (installs.length === RUNS) {
    result.npm_ci_s = { runs: installs.map((x) => round(x)), median: round(median(installs)) }
    result.rm_rf_s = { runs: removes.map((x) => round(x)), median: round(median(removes)) }
  }
  fs.rmSync(createDir, { recursive: true, force: true })
} else if (mode === "read") {
  const want = fs.readFileSync(shaFile, "utf8").trim()
  const t = now(), total = readAll(seqFile)
  const s = (now() - t) / 1000
  const hash = crypto.createHash("sha256")
  readAll(seqFile, (b) => hash.update(b))
  result.read_1gib_mb_s = round(total / 1e6 / s, 0)
  result.sha_ok = hash.digest("hex") === want
} else if (mode === "mark") {
  writeDurable(path.join(work, name), String(Date.now()))
  result.written_at_ms = Date.now()
} else if (mode === "watch") {
  const file = path.join(work, name), limit = Date.now() + TIMEOUT * 1000
  for (;;) {
    const body = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : ""
    // Lag uses both sandboxes' wall clocks, so it carries their clock skew (usually a few ms).
    if (body) { result.visible_after_ms = Date.now() - Number(body); break }
    if (Date.now() > limit) { result.visible_after_ms = null; result.not_seen_within_s = TIMEOUT; break }
    await new Promise((r) => setTimeout(r, 50))
  }
} else {
  console.error(`unknown mode ${mode}`); process.exit(2)
}
console.log(JSON.stringify(result))
```

```bash theme={null}
node bench.mjs /vol                 # the provider's storage
node bench.mjs /mnt/archil          # an Archil disk in the same sandbox
node bench.mjs /mnt/archil read     # in a new sandbox: read the 1 GiB file back
```

It writes under `<dir>/archil-bench/` and prints one JSON object per run:

```json theme={null}
{
  "dir": "/mnt/archil",
  "mode": "write",
  "node": "v24.22.1",
  "create_4k_fsync_ms": {
    "n": 100,
    "p50": 6.463,
    "p90": 13.602
  },
  "write_1gib_fsync_mb_s": {
    "runs": [
      371,
      409,
      421
    ],
    "median": 409
  },
  "npm_files": 10538,
  "npm_ci_s": {
    "runs": [
      3.49,
      3.48,
      3.47
    ],
    "median": 3.48
  },
  "rm_rf_s": {
    "runs": [
      0.81,
      0.79,
      0.83
    ],
    "median": 0.81
  }
}
```

If a store persists only on an explicit commit, pass the commit command with `--commit`; it runs inside every timed
write. A Modal Volume needs `--commit 'sync /vol'`.

## Mount an Archil disk

Create the disk from your own machine, so your API key stays there. The sandbox only gets the disk's
[mount token](https://docs.archil.com/concepts/authorization.md#token-based-disk-authorization), which grants access to that disk alone:

```bash theme={null}
export ARCHIL_API_KEY=key-...
export ARCHIL_REGION=aws-us-west-2
npx disk create bench   # prints the disk ID and a mount token
```

Then, in the sandbox, as root:

```bash theme={null}
curl -fsSL https://archil.com/install | sh
mkdir -p /mnt/archil
ARCHIL_MOUNT_TOKEN=adt_... archil mount dsk-... /mnt/archil --region aws-us-west-2
```

Put the disk in the [Archil region](https://docs.archil.com/reference/regions.md) closest to the sandbox. We publish Archil side by side only
where a TCP connection from the sandbox to Archil took about 20 ms or less; Daytona's `us` region and Fly.io Sprites were
farther away, so those pages show the provider's storage alone.

## Recipes

Each recipe creates the stores, runs the script, and deletes everything it created.

### Vercel Sandbox and Vercel Drive

Reproduces every row on [archil.com/compare/vercel-sandbox](https://archil.com/compare/vercel-sandbox): one sandbox
with 2 vCPUs in `pdx1` (AWS us-west-2), a Vercel Drive in `pdx1`, and an Archil disk in `aws-us-west-2`.

```bash theme={null}
npm init -y && npm pkg set type=module
npm install disk@^1.11.0 @vercel/sandbox tsx
vercel link && vercel env pull
export ARCHIL_API_KEY=key-... ARCHIL_REGION=aws-us-west-2
npx tsx --env-file=.env.local vercel.ts
```

```typescript vercel.ts theme={null}
import { readFileSync } from "node:fs";
import { Drive, Sandbox } from "@vercel/sandbox";
import * as archil from "disk";

const REGION = "pdx1"; // AWS us-west-2, next to an Archil disk in aws-us-west-2
const ARCHIL_REGION = process.env.ARCHIL_REGION!; // aws-us-west-2
const script = readFileSync("bench.mjs");
const seconds = (t: number) => ((performance.now() - t) / 1000).toFixed(3);

// Provisioning, timed from your machine
let t = performance.now();
const drive = await Drive.getOrCreate({ name: `bench-${Date.now()}`, region: REGION });
console.log("create Drive s", seconds(t));
t = performance.now();
const { disk, token } = await archil.createDisk({ name: `bench-${Date.now()}` });
console.log("create Archil disk s", seconds(t));

async function run(sandbox: Sandbox, cmd: string, env: Record<string, string> = {}) {
  const result = await sandbox.runCommand({ cmd: "bash", args: ["-c", cmd], env, sudo: true });
  if (result.exitCode !== 0) throw new Error(await result.stderr());
  return (await result.stdout()).trim();
}

// A new sandbox with the Drive (or a snapshot of it) at /vol and the Archil disk at /mnt/archil
const sandboxes: Sandbox[] = [];
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// A Drive takes one read-write mount at a time, so wait until the previous sandbox has released it
const released = async () => { while ((await Drive.get({ name: drive.name })).currentSandboxName) await sleep(2000); };

async function start(vol: Drive | { drive: string; mode: "snapshot" }, flags = "") {
  if (vol instanceof Drive) await released();
  const sandbox = await Sandbox.create({ region: REGION, resources: { vcpus: 2 }, mounts: { "/vol": vol }, persistent: false, timeout: 45 * 60_000 });
  sandboxes.push(sandbox);
  await sandbox.writeFiles([{ path: "/tmp/bench.mjs", content: script }]);
  await run(sandbox, "curl -fsSL https://archil.com/install | sh > /dev/null");
  await run(sandbox, `mkdir -p /mnt/archil && archil mount ${disk.id} /mnt/archil --region ${ARCHIL_REGION} ${flags}`, {
    ARCHIL_MOUNT_TOKEN: token,
  });
  return sandbox;
}

try {
  // 4 KiB creates, 1 GiB write, npm ci and rm -rf on each store, in the same sandbox
  const a = await start(drive);
  console.log(await run(a, "node /tmp/bench.mjs /vol"));
  console.log(await run(a, "node /tmp/bench.mjs /mnt/archil"));

  // Visibility: a second sandbox watches while the first writes a file
  const b = await start(drive.snapshot(), "--shared");
  for (const dir of ["/vol", "/mnt/archil"]) {
    const name = `visible-${Date.now()}`;
    const watch = await b.runCommand({ cmd: "bash", args: ["-c", `node /tmp/bench.mjs ${dir} watch ${name}`], sudo: true, detached: true });
    await sleep(2000);
    await run(a, `node /tmp/bench.mjs ${dir} mark ${name}`);
    console.log((await (await watch.wait()).stdout()).trim());
  }
  await run(b, "archil unmount /mnt/archil");
  await b.stop();
  await run(a, "archil unmount /mnt/archil");
  await a.stop();

  // Cold reads: three new sandboxes each read the 1 GiB file back
  for (let i = 0; i < 3; i++) {
    const c = await start(drive);
    console.log(await run(c, "node /tmp/bench.mjs /vol read"));
    console.log(await run(c, "node /tmp/bench.mjs /mnt/archil read"));
    if (i === 2) {
      // Fork: checkpoint the disk, branch it from your machine, mount the branch, read the file
      const ms = await run(c, "s=$(date +%s%N); archil checkpoints create /mnt/archil cp-1 > /dev/null; echo $(( ($(date +%s%N) - s) / 1000000 ))");
      console.log("checkpoint s", Number(ms) / 1000);
      t = performance.now();
      await (await archil.getDisk(disk.id)).createBranch({ name: "br-1", fromCheckpoint: "cp-1" });
      console.log("branch s", seconds(t));
      t = performance.now();
      await run(c, `mkdir -p /mnt/branch && archil mount ${disk.id} /mnt/branch --region ${ARCHIL_REGION} --branch br-1`, {
        ARCHIL_MOUNT_TOKEN: token,
      });
      console.log("mount branch s", seconds(t));
      console.log(await run(c, "node /tmp/bench.mjs /mnt/branch read && archil unmount /mnt/branch"));
    }
    await run(c, "archil unmount /mnt/archil");
    await c.stop();
  }
} finally {
  for (const sandbox of sandboxes) await sandbox.stop().catch(() => {});
  await released();
  await drive.delete();
  await (await archil.getDisk(disk.id)).delete();
}
```

A Drive takes one read-write mount at a time. The visibility sandbox mounts a read-only snapshot of it, which is fixed
when the sandbox starts, so it doesn't see the new file. It mounts the Archil disk with `--shared`.

### Modal Volume

Reproduces [archil.com/compare/modal](https://archil.com/compare/modal): a VM sandbox (`runtime="vm"`) with 2 CPUs and
4 GiB in `us-west`, a Modal Volume (v2), and an Archil disk in `aws-us-west-2`. For the fork row, add the fork step from
the Vercel recipe.

```bash theme={null}
pip install archil modal && modal setup
export ARCHIL_API_KEY=key-... ARCHIL_REGION=aws-us-west-2
python modal_bench.py
```

```python modal_bench.py theme={null}
import time

import archil
import modal

ARCHIL_REGION = "aws-us-west-2"
NODE = "curl -fsSL https://nodejs.org/dist/v22.20.0/node-v22.20.0-linux-x64.tar.xz | tar xJ -C /usr/local --strip-components=1"
app = modal.App.lookup("archil-bench", create_if_missing=True)
image = (
    modal.Image.debian_slim()
    .apt_install("curl", "ca-certificates", "xz-utils", "fuse3")
    .run_commands(NODE, "curl -fsSL https://archil.com/install | sh")
    .add_local_file("bench.mjs", "/opt/bench.mjs")
)

# Provisioning, timed from your machine
name = f"bench-{int(time.time())}"
t = time.perf_counter()
modal.Volume.objects.create(name, version=2)
print("create Volume s", round(time.perf_counter() - t, 3))
vol = modal.Volume.from_name(name, version=2)
t = time.perf_counter()
created = archil.create_disk(name=name)
print("create Archil disk s", round(time.perf_counter() - t, 3))
disk = created.disk
token = modal.Secret.from_dict({"ARCHIL_MOUNT_TOKEN": created.token})
sandboxes = []


def run(sb, cmd):
    p = sb.exec("bash", "-c", cmd)
    p.wait()
    if p.returncode != 0:
        raise RuntimeError(p.stderr.read())
    return p.stdout.read().strip()


def start(flags=""):
    # The Volume at /vol and the Archil disk at /mnt/archil, side by side
    sb = modal.Sandbox.create(
        "sleep", "infinity", app=app, image=image, volumes={"/vol": vol}, secrets=[token],
        cpu=2, memory=4096, region="us-west", runtime="vm", timeout=3600,
    )
    sandboxes.append(sb)
    run(sb, f"mkdir -p /mnt/archil && archil mount {disk.id} /mnt/archil --region {ARCHIL_REGION} {flags}")
    return sb


try:
    # A Modal Volume persists on `sync <mount>`, so --commit runs it inside every timed write
    a = start()
    print(run(a, "node /opt/bench.mjs /vol --commit 'sync /vol'"))
    print(run(a, "node /opt/bench.mjs /mnt/archil"))

    # Visibility: a second sandbox watches while the first writes a file
    b = start("--shared")
    for path, commit in (("/vol", "--commit 'sync /vol'"), ("/mnt/archil", "")):
        mark = f"visible-{int(time.time())}"
        watch = b.exec("bash", "-c", f"node /opt/bench.mjs {path} watch {mark}")
        time.sleep(2)
        run(a, f"node /opt/bench.mjs {path} mark {mark} {commit}")
        watch.wait()
        print(watch.stdout.read().strip())
    run(b, "archil unmount /mnt/archil")
    run(a, "archil unmount /mnt/archil")

    # Cold reads: three new sandboxes each read the 1 GiB file back
    for i in range(3):
        c = start()
        print(run(c, "node /opt/bench.mjs /vol read"))
        print(run(c, "node /opt/bench.mjs /mnt/archil read"))
        run(c, "archil unmount /mnt/archil")
        c.terminate()
finally:
    for sb in sandboxes:
        sb.terminate()
    modal.Volume.objects.delete(name)
    archil.get_disk(disk.id).delete()
```

A running Modal sandbox sees another sandbox's Volume commit only after a reload, so the Volume visibility round is
expected to reach its 60 s limit.

### Daytona Volume

Reproduces [archil.com/compare/daytona](https://archil.com/compare/daytona): sandboxes from the default snapshot
(1 vCPU, 1 GiB) in Daytona's `us` region, each with a Daytona Volume at `/vol`. To add an Archil disk, mount it as in
the [Daytona guide](https://docs.archil.com/guides/platforms/daytona.md) and run the script against `/mnt/archil` too.

```bash theme={null}
pip install daytona
export DAYTONA_API_KEY=dtn_...
python daytona_bench.py
```

```python daytona_bench.py theme={null}
import shlex
import time

from daytona import CreateSandboxFromSnapshotParams, Daytona, VolumeMount

NODE = "curl -fsSL https://nodejs.org/dist/v22.20.0/node-v22.20.0-linux-x64.tar.xz | tar xJ -C /usr/local --strip-components=1"
daytona = Daytona()
script = open("bench.mjs", "rb").read()

# Provisioning, timed from your machine until the Volume is ready
name = f"bench-{int(time.time())}"
t = time.perf_counter()
vol = daytona.volume.create(name)
while vol.state != "ready":
    time.sleep(0.25)
    vol = daytona.volume.get(name)
print("create Volume s", round(time.perf_counter() - t, 3))
sandboxes = []


def run(sb, cmd, timeout=1800):
    r = sb.process.exec(f"sudo bash -c {shlex.quote(cmd)}", timeout=timeout)
    if r.exit_code != 0:
        raise RuntimeError(r.result)
    return r.result.strip()


def start():
    sb = daytona.create(CreateSandboxFromSnapshotParams(volumes=[VolumeMount(volume_id=vol.id, mount_path="/vol")]))
    sandboxes.append(sb)
    sb.fs.upload_file(script, "/tmp/bench.mjs")
    run(sb, NODE)
    return sb


try:
    a = start()
    print(run(a, "node /tmp/bench.mjs /vol", timeout=3600))

    # Visibility: a second sandbox watches while the first writes a file
    b = start()
    mark = f"visible-{int(time.time())}"
    run(b, f"nohup node /tmp/bench.mjs /vol watch {mark} > /tmp/watch.json 2> /dev/null &")
    time.sleep(2)
    run(a, f"node /tmp/bench.mjs /vol mark {mark}")
    print(run(b, "until [ -s /tmp/watch.json ]; do sleep 0.2; done; cat /tmp/watch.json", timeout=90))

    # Cold reads: three new sandboxes each read the 1 GiB file back
    for i in range(3):
        print(run(start(), "node /tmp/bench.mjs /vol read"))
finally:
    for sb in sandboxes:
        sb.delete()
    daytona.volume.delete(daytona.volume.get(name))
```

In our runs, `npm ci` on a Daytona Volume didn't finish within 15 minutes. The script stops it after 15 minutes
(`NPM_TIMEOUT_S` changes the limit) and reports `timed_out`.

### Fly.io Sprites

Reproduces the Fly.io Sprites results on [archil.com/compare/sandbox-providers](https://archil.com/compare/sandbox-providers): one Sprite measuring its own persistent
disk. A Sprite's disk can't attach to another Sprite, so the cold read drops the page cache in the same Sprite, and
the visibility row doesn't apply. To add an Archil disk, mount it as in the [Sprites guide](https://docs.archil.com/guides/platforms/sprites.md).

```bash theme={null}
time (sprite create bench-1 --skip-console && sprite exec --no-stdin -s bench-1 -- true)   # provisioning
sprite exec --no-stdin -s bench-1 --file bench.mjs:/home/sprite/bench.mjs -- mkdir -p /home/sprite/vol
sprite exec --no-stdin -s bench-1 -- node /home/sprite/bench.mjs /home/sprite/vol
for i in 1 2 3; do
  sprite exec --no-stdin -s bench-1 -- sudo sh -c 'sync && echo 3 > /proc/sys/vm/drop_caches'
  sprite exec --no-stdin -s bench-1 -- node /home/sprite/bench.mjs /home/sprite/vol read
done
time sprite checkpoint create -s bench-1   # checkpoint
sprite destroy -s bench-1 --force
```

## If your results differ

Email [support@archil.com](mailto:support@archil.com) with the script output, the provider, and the regions of the
sandbox and the disk.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.