Commit 310e278
perf(unstorage): stop downloading objects to discard them, and walk in one round trip
`open(path, "w")` is the most common write a mount ever sees — every shell `>`,
every `tar -x`, every editor save — and `acquire()` read the object
unconditionally, so overwriting a 1 GB object transferred 2 GB. It now passes
the empty value the `O_CREAT` branch already had a mechanism for; `truncate(p, 0)`
likewise. An already-open path still shares its one buffer, so a truncate
through one handle is seen by the others exactly as before.
The path walk did a point lookup *and* a full prefix listing per ancestor, so a
`stat` of a missing `/a/b/c/d/e` cost ten round trips, five of them listings. It
is now one point lookup per component issued together, then a single listing at
the deepest — equivalent because a missing component can never have a key below
it, so a shadowing component always precedes a missing one. Ten round trips
become seven, listings five become two. A per-call scope memoizes both kinds so
`readdir`, `rmdir`, `rename`'s emptiness checks and its directory-move loop
reuse the listing the walk already paid for. The memo lives for one driver
method call and is then dropped; nothing here reads a key back after mutating
it, so it cannot serve a stale answer.
Where it costs more, it is always point lookups and never listings: the shadow
check no longer short-circuits, so a path whose first component is a key costs
*d* lookups rather than one. That is the trade — one round trip of depth for
every path that resolves, which is nearly all of them.
`maxDepth` is **not** used, and not for the reason the review assumed. unstorage
forwards the option to every driver but only `fs`/`fs-lite` implement it, and
the two disagree on what depth means — `filterKeyByDepth` counts separators in
the absolute key, `readdirRecursive` counts levels below the base — so
`maxDepth: 1` on a base of depth 3 returns nothing on an S3-backed store and
everything relevant on an fs-backed one. It would have been a correctness bug,
not a no-op.
No listing got cheaper; `getKeys(prefix)` still enumerates the subtree. Every
saving here is a removed round trip.
Also guards the zero-length read at the `buffer.set`, matching `memory` — with
`validateRange` now correctly returning rather than throwing for that case, an
unguarded `set` threw a bare `RangeError` that `errnoOf` mapped to `EIO`.
Refs #1 (group I).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>1 parent e80c8ff commit 310e278
3 files changed
Lines changed: 441 additions & 54 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
114 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
115 | 123 | | |
116 | 124 | | |
117 | 125 | | |
| |||
0 commit comments