
# Tuning

**The defaults are already sensible. These are the knobs worth knowing about, in the order they matter.**

The two that matter most for speed are FUSE options, so through `mountx/auto` they go in `fuse: {…}` and are ignored on a host that mounts over NFS. Importing `mountx/fuse` directly takes them at the top level.

## Caching — this is where the speed is

```ts
await mount(driver, "/mnt/point", {
  fuse: {
    attrTimeout: 10, // seconds the kernel may cache file attributes (default: 10)
    entryTimeout: 10, // seconds it may cache name → file lookups (default: 10)
    keepCache: true, // keep page cache between opens (default: true)
    negativeTimeout: 0, // also cache "this file does not exist" (default: 0, off)
  },
});
```

These are worth far more than anything you can optimise in JavaScript. On the measured host, `attrTimeout`/`entryTimeout` are worth **8–15×**, and `keepCache` **~4.9×** on re-read. Nothing hand-optimised in the codebase comes close.

The trade-off is coherence, and it is entirely about _who else writes to your storage_:

- **Nothing else writes it** — raise the timeouts. The kernel answers `stat` from cache and your driver never hears about it.
- **Something else writes it** — lower them, or leave them and call [`notifyInvalInode()`](#telling-the-kernel-something-changed) when you know.
- **`negativeTimeout`** has the same caveat, sharper: a file created behind mountx's back stays _invisible_ for the whole timeout. It is a real saving for a build that stats hundreds of missing headers, and off by default for exactly that reason (libfuse's default too).

::note
The traffic these settings absorb never reaches your driver at all. That is why the measured peak request rate is _lower_ with the shipped defaults than with timeouts off — most of the work stopped happening.
::

## Concurrency

```ts
await mount(driver, "/mnt/point", { fuse: { readers: 2 } });
```

`readers` is how many reads are kept outstanding on `/dev/fuse` at once, and it is really a **threadpool** knob. `/dev/fuse` is a character device, so libuv classifies it `UV_FILE` and every read parks one of the four default (`UV_THREADPOOL_SIZE`) pool threads until a request arrives — threads shared with all other `fs`, `dns` and `zlib` work in the process, **including your driver's own I/O**.

Two readers on the default pool leaves two threads for a `node:fs`-backed driver. Four would deadlock it.

To go higher, raise both — and `UV_THREADPOOL_SIZE` must be set **before the process starts**, because libuv reads it once:

```sh
UV_THREADPOOL_SIZE=32 node serve.ts   # then readers: 8 is reasonable
```

Replies do not use the pool at all — they are written synchronously, for exactly this reason — so `readers` plus whatever your driver needs is the whole budget.

## Write size

Raising the kernel's `max_write` from the 128 KiB default to 1 MiB was worth ~20% on sequential writes on the measured host — read that as "15–40%", and expect more from a driver doing real I/O. mountx already asks for 1 MiB by default; `maxRead` caps the other direction and defaults to the kernel's own.

## Other mount options

```ts
await mount(driver, "/mnt/point", {
  // shared — the same meaning on both transports
  readOnly: true, // the driver never sees writes
  unmountTimeout: 10_000, // ms before unmount stops asking nicely
  signals: true, // unmount on SIGINT/SIGTERM (default: true)
  useDriverIno: true, // use the driver's own (dev, ino) identity (default: true)

  fuse: {
    fsname: "mydata", // what /proc/mounts shows as the device
    subtype: "myfs", // makes the type read `fuse.myfs`
    allowOther: false, // let other users in (default: false)
    defaultPermissions: true, // let the kernel enforce mode bits (default: true)
  },
  nfs: {
    exportPath: "/", // directory of the driver to export (default: "/")
    nobrowse: true, // macOS only, on by default: keeps Finder and Spotlight out
    hard: false, // retry forever instead of failing with EIO (default: false)
  },
});
```

Two of those deserve a sentence each:

- **`defaultPermissions`** (FUSE, on by default) hands mode-bit enforcement to the kernel, so your driver never has to make access decisions. Turning it off means your driver is answering for every process on the machine.
- **`nobrowse`** (macOS, on by default) hides the volume from the GUI. A visible one is a volume Finder and Spotlight will crawl — for a JavaScript driver that means a burst of traffic and a scattering of `.DS_Store` writes nobody asked for. Set it `false` if the point of the mount is for someone to open it in Finder.

## Telling the kernel something changed

If your storage changes behind mountx's back, drop the kernel's cached copy:

```ts
if (mounted.transport === "fuse") {
  mounted.notifyInvalInode(42n); // forget this file's cached data and attributes
  mounted.notifyInvalEntry(1n, "hello.txt"); // forget this name → file mapping
}
```

Both take inode numbers as `bigint`, and both are FUSE-only — which is what the `transport` check narrows to. With `mountx/fuse` imported directly they are just there.

This is the release valve that makes long cache timeouts safe: keep `attrTimeout` high, and invalidate precisely when you know something moved.

## How fast is it, actually

All numbers here and above come from [`.agents/benchmarks.md`](https://github.com/pithings/mountx/blob/main/.agents/benchmarks.md) and were taken on **one host, on one day**: Linux 6.12.96+deb13-amd64, 16 × Intel i7-10700K @ 3.80 GHz, Node v24.18.0, in-memory driver. They are not portable, and that file has the full tables and caveats.

- **Throughput** — sequential read **7,537 MiB/s** served from page cache, **1,526 MiB/s** with the cache off. Sequential write **796 MiB/s** over FUSE, **308 MiB/s** over NFS.
- **Request rate** — FUSE handles **41,500–50,300 requests/sec** with requests in flight and timeouts off (1/2/4 readers: 41,539 / 48,549 / 50,349), and 13,600/sec for a strictly one-at-a-time client. With the shipped defaults the measured peak is around **20,468 requests/sec** — lower because most of that traffic never reaches the driver.
- **Syscalls** — a single-threaded client sees 2,000–4,000/sec on uncached metadata, since one syscall is several FUSE requests.

## Next

- [Troubleshooting](/guide/troubleshooting) — what to do when it wedges.
- [FUSE](/transports/fuse) and [NFSv3](/transports/nfs) — per-transport detail.
- [Reference](/reference) — every option, in full.
