Clone latency on FSx for OpenZFS: from 10 minutes to 16 seconds

aayush-kosh1 pts0 comments

Clone latency on FSx for OpenZFS: from 10 minutes to 16 seconds — stagDB EngineeringA ZFS clone does not copy data. zfs clone writes a bit of metadata pointing a new dataset<br>at an existing snapshot&rsquo;s blocks, marks them copy-on-write, and returns. The cost is the<br>same whether the snapshot is 10 MB or 10 TB — you only pay for blocks that later diverge.<br>That property is the reason we picked ZFS: it makes it plausible to give every developer<br>their own full-size copy of production data instead of a gutted 500-row fixture set.<br>AWS offers that property as a managed service. FSx for OpenZFS runs real OpenZFS, and its CSI<br>driver provisions a volume from a snapshot with CopyStrategy=CLONE, sharing blocks rather<br>than copying them. On paper: a developer asks for a clone of a production-size Postgres<br>database and gets one in seconds, no matter how big it is.<br>We built that, and the block sharing behaved exactly as documented. The latency did not match<br>the underlying primitive, and characterizing the gap is what the rest of this post is about.<br>Where the latency actually comes from<br>Our first milestone spike was deliberately small: EKS 1.30, one FSx for OpenZFS<br>SINGLE_AZ_1 filesystem (256 GiB, 128 MB/s), and a ~10 GB golden snapshot — pgbench at<br>scale 700, 10.98 GB logical. We cloned it into three developer namespaces at once, booted a<br>CloudNativePG Postgres on each, and measured clone-to-Ready.<br>Three concurrent clones came back at 110 s, 177 s, and 238 s — a p50 of 177 s against a<br>gate of 120 s, and a p95 of 238 s.<br>The storage half passed convincingly. Amplification for the golden plus three clones was<br>1.107× — 12.16 GB used where four independent full copies would have been about 44 GB.<br>The blocks were genuinely shared, the cloned data directories byte-identical to the golden,<br>and they replayed WAL cleanly after we killed the pods. Copy-on-write worked as advertised.<br>So the latency was not data movement, and we confirmed it was size-independent — nothing was<br>being read or written. What we were waiting on was the managed control plane in front of ZFS.<br>FSx&rsquo;s CreateVolume API takes roughly 75 s for a plain provisioned volume and about<br>2 minutes when creating from a snapshot . That is the floor, and it is independent of the<br>data involved.<br>The shape of those three numbers is the more consequential detail. 110, 177, 238 — rising<br>with concurrency, spaced about a minute apart. That is not three requests each taking two<br>minutes in parallel; it is the signature of a queue. We ran the test again at ten concurrent<br>clones to characterize how the queue behaves at higher fan-out:<br>Concurrent clonesClone-to-Ready (s)K=3110 / 177 / 238K=10129 / 198 / 269 / 319 / 381 / 428 / 490 / 550 / 611, plus one that had not finished at 10 minThe inter-completion gap is a near-constant ~60 s . FSx CreateVolume serializes at<br>roughly one clone per minute , so the Nth developer to ask for a database waits about N<br>minutes. At K=10 the p50 was 381 s and the p95 was 611 s .<br>That characteristic is a poor match for the demand pattern a development-environment tool<br>sees. Developer demand for clones is not uniform; it arrives in a burst when a team starts<br>work in the morning, which is precisely the regime in which a serialized API performs worst.<br>Ten concurrent developers is a small team rather than a stress test, and under this API the<br>last request in line waits about ten minutes for an operation the storage layer itself<br>performs in milliseconds.<br>An unplanned natural control<br>We built the fix (below), redeployed, and ran a time-to-first-clone benchmark on a separate<br>live cluster, against a smaller golden — prod-pg16-v3, 86 MB and 750k rows — measuring each<br>Instance&rsquo;s creationTimestamp to its Ready condition. The size difference does not matter<br>here, and that is the point: the claim path moves no data at all, so its latency is<br>independent of golden size, which is what makes these numbers comparable to the 10 GB<br>measurements above.<br>Nine of the ten claims were fast, and one was not: a single claim raced a momentarily-empty<br>pool, found nothing warm, and fell through to the on-demand path — a real CreateVolume<br>against FSx. It returned in 600.3 s .<br>We could not have designed a cleaner control. Same cluster, same golden snapshot, same<br>operator build, same measurement — one request took the old path and every other took the<br>new one, roughly 37× apart , with no confounding variable except which code path it took.<br>It also reproduced the spike&rsquo;s finding on a live cluster, months later, without our<br>intending it: 600.3 s is what &ldquo;serializes at ~1/min&rdquo; predicts for a request landing behind a<br>queue of pool refills. We excluded it from the percentiles — it has no pool-claim<br>timestamp pair, so it is not a sample of what we were measuring — but it is the most<br>informative number in the run.<br>The fix: moving the API call out of the hot path<br>If the bottleneck is a control-plane API you do not control, you cannot make it faster. What<br>you can do is...

clone latency from minutes data snapshot

Related Articles