Est.

Write Consistency Semantics When Multiple Nodes Mount the Same Bucket

· 9 min read
Cover illustration for “Write Consistency Semantics When Multiple Nodes Mount the Same Bucket”
POSIX Filesystems · October 16, 2026 · 9 min read · 1,964 words

Read-after-write consistency on S3, GCS, and R2 is real, but it solves only half the problem. Once a write is acknowledged, every later read returns that version, anywhere in the world. But that guarantee says nothing about what happens when multiple nodes write to the same object at the same moment. Conflating the two is how multi-node setups lose data silently. This piece walks exactly where the guarantee ends, and what you build on top of it when you mount a shared bucket across many nodes.

What read-after-write consistency actually guarantees on S3, GCS, and R2

All three major object stores now give you strong read-after-write consistency for object data. That means a straightforward promise: once a write is acknowledged, every subsequent read anywhere in the world returns that version, not an older one. S3 made this change in 2020, covering all GET, PUT, and LIST operations, plus tag, ACL, and metadata changes. R2 defines it explicitly: the effect of any write, update, or delete is observed globally, immediately, by all clients. GCS offers the same strong global consistency for uploads, updates, deletes, and object metadata changes.

You get this at no extra charge and with no performance penalty on S3. The guarantee is real, and it's been tested hard by workloads that used to need workarounds. Before 2020, S3 customers ran EMRFS Consistent View or S3Guard to paper over the eventual-consistency window. Those tools are now unnecessary for read-your-writes correctness. GCS has one carve-out worth noting: IAM and ACL access changes are still eventually consistent. S3 keeps its own carve-out for bucket-level configuration changes. If you enable versioning for the first time, the change itself propagates eventually, and AWS recommends waiting 15 minutes before issuing writes. That's a config-time exception, not a data-path exception.

The thing to hold onto here is the shape of the promise. It's about what a reader sees after a write completes. It is not about what happens while two writes are in flight. The store guarantees that the winning write becomes visible everywhere immediately. It makes no claim about which write should win. That second question is a coordination problem, and consistency does not touch it.

What read-after-write consistency does not cover: concurrent writers on the same key

Here's the crux. Strong read-after-write consistency is a guarantee about visibility. Concurrent-write safety is a guarantee about arbitration. They are different properties, and the object stores give you only the first one by default. The second property does not exist on any of the three stores without additional mechanisms.

The default resolution for concurrent writes to the same key is last-writer-wins. Whichever PUT request completes last overwrites the other, silently, with no error returned to the losing writer. R2's docs state this plainly: "In the event two clients are writing (PUT or DELETE) to the same key, the last writer to complete 'wins'." S3 uses the same semantics internally, and network latency means the order in which S3 receives requests and the order in which applications receive acknowledgments cannot be predicted. A racing writer has no way to know whether its write will survive. You can issue two writes from two nodes, get two acknowledgments, and still have only one of them on disk.

That's the first gap. The second is related but separate. The consistency guarantee is strictly per-key. There is no built-in mechanism on any of the three stores to make an update to one object depend on, or happen atomically with, an update to a different object. If your application needs "these two objects must change together," the object store cannot enforce that invariant for you. Consistency makes each individual write visible. It does not group writes.

Why teams conflate the two guarantees, and the real-world scenarios where it hurts

Teams run into this because the phrase "strongly consistent" sounds like a complete safety story. It isn't. The moment more than one writer touches the same key, the guarantee has nothing left to say. And the systems where this bites hardest are the ones you're already running.

The biggest gap is with filesystem-style access. Agents and multi-node jobs use tools like open(), read_csv(), rsync, and git, not the object API. When you mount a bucket as a filesystem, every POSIX-trained assumption comes with it: atomic rename, no partial reads, safe concurrent open. The object store does not honor those assumptions by default. When a bucket is mounted as a native filesystem, as systems like Archil do, applications automatically expect POSIX semantics. But those expectations rest on coordination guarantees the object store does not provide. Last-writer-wins silently discards one writer's data with no error, and mounting multiple nodes against the same bucket exposes that gap immediately.

The cache layer makes it worse. FUSE-based mounts and serverless filesystems add a local write-back cache on top of the object store to avoid round-tripping every operation. Each node maintains its own dirty view of the data. A second node can read stale data from a peer's cache before the write has reached the bucket. Strong read-after-write consistency at the object layer provides no protection if the write has not yet left the local system.

Multi-agent workflows are directly exposed. A framework like LangGraph saves workflow state at intervals or after each step so it can resume after errors. When multiple subgraph threads write checkpoint objects to the same bucket keys, last-writer-wins destroys whichever thread's checkpoint arrived first. The framework thinks it can resume. The state it needs is gone. And GCS adds a per-key write-rate limit of one write per second to the same object name. It's invisible in the consistency docs, but it throttles pipelines where several agents update a shared manifest or state file.

Conditional writes: what they fix, and where they stop

Conditional writes are the right first tool for preventing unguarded overwrites on a single key. They also have a hard ceiling: they scope to exactly one key at a time. Multi-key update patterns remain unprotected.

S3 added conditional writes in August 2024. The If-None-Match: * header prevents overwriting an existing key, giving you create-once semantics. If-Match came in November 2024. It compares the current ETag, so a write fails with 412 if another writer changed the object first. That gives you an optimistic check-and-set pattern without an external lock manager. Bucket owners can enforce these headers via bucket policy using the s3:if-none-match and s3:if-match condition keys. Requests without the required header return 403. That makes the guard mandatory, not optional. The one quirk: CopyObject requests with those headers originally returned 501 Not Implemented. Support was extended to CopyObject in October 2025.

R2 evaluates conditional write headers atomically, as part of the same write that commits the object. Competing conditional writes to the same key are serialized. When two clients concurrently issue PutObject with If-None-Match: * for a previously absent key, at most one succeeds and every other fails with 412. You can rely on that for create-once semantics. GCS has offered generation-match preconditions for longer. An operation only executes if the object's generation number matches the caller's expectation. Fullstaq Ruby's CI/CD system used GCS generation-match headers in production to build a distributed lock for safe concurrent package publishing. Strong consistency was a prerequisite for that pattern, but it was not sufficient on its own. The conditional writes built the lock. The consistency just made the lock's reads trustworthy.

The ceiling is the same on all three stores. Conditional writes protect a single key against a single race. There is no mechanism to make two keys update atomically. If your logic requires two objects to change together, that invariant must live at a layer above the object store.

What coordination looks like above the object-store layer

When per-key conditional writes aren't enough, teams reach for coordination mechanisms that sit above the object store. Each one fixes a piece of the problem and leaves a different failure mode exposed. Advisory locks via application-level protocols let agents coordinate before modifying a file. Lock-acquire and lock-release patterns in multi-agent MCP tooling are a common example. The version history that accompanies them preserves every edit as a safety net. But "advisory" means unenforced. A crashed or misbehaving agent leaves the lock held, and there's no automatic release mechanism to unblock peers. You trade silent data loss for a different failure: a stuck lock.

Namespace partitioning sidesteps the race entirely. Forked agents write to separate key namespaces and merge at a controlled point, so concurrent writes never target the same key. This is the pattern behind speculative execution in agent checkpointing research. A runtime forks from a current sandbox state, runs draft actions in parallel against isolated namespaces, then discards or commits the fork. No lock management, no contention, but the merge becomes the new hard problem.

Copy-on-write at the VM or container layer is a different vector. DeltaBox (arXiv 2605.22781, 2026), a Firecracker microVM-based agent sandbox, implements DeltaFS, which organizes change-based filesystem checkpoint and restore in layers. A checkpoint freezes the writable layer and inserts a new one, making rollback a layer switch. This sidesteps object-store consistency entirely for in-flight agent state. You never write the intermediate state to the object store at all.

The filesystem-layer approach goes further. Archil mounts an existing S3, GCS, R2, or Azure Blob bucket as a real POSIX filesystem with broad POSIX semantics, atomic rename, and flock/fcntl, so existing code runs unmodified. Its writes are replicated before returning and flushed asynchronously to the bucket, and the disk is mountable on many nodes simultaneously. That makes shared-bucket multi-agent access a supported pattern rather than a workaround. AWS S3 Files, launched on April 7, 2026, takes a parallel route with a filesystem-backed layer. Both address the assumptions that raw FUSE mounts leave broken.

Choosing the right mechanism for a given workload

The mechanism you need depends on your concurrency pattern: single-key, multi-key, or full filesystem semantics. Use a heavier mechanism where a lighter one suffices and you waste engineering effort. Use a lighter one where a heavier one is needed and you get silent data loss.

Single-key races are the simplest case. Leader election, write-once pipelines, deduplicated uploads. Conditional writes with If-None-Match: * or If-Match on S3 and R2, or generation preconditions on GCS, are sufficient and require no external infrastructure. Enforce them via bucket policy so the guard is mandatory rather than dependent on application code remembering to send the header.

Multi-key atomic updates are where the object store stops helping. Updating a manifest and a data file together, or multi-step workflow state, cannot be made atomic on any of the three stores. Route through a transactional store for the coordination record, or redesign the writes so each logical update lands on a single key. If you can't do either, you need a layer above the object store.

Shared mounts with POSIX-trained workloads are the most complex case. Agents using shell tools, frameworks that assume atomic rename. A filesystem layer that enforces POSIX semantics is the right abstraction, rather than a raw FUSE mount with close-to-open consistency. Archil's shared-mount model and AWS S3 Files' filesystem-backed layer both resolve the assumptions FUSE leaves broken. One caveat to keep in mind: if a CDN or cache sits in front of an R2 bucket, the strong-consistency model is relaxed for cached reads. Deleted or overwritten objects remain available until TTL expiry or an explicit cache purge. That's a separate consistency boundary.

The recurring principle is simple. Strong read-after-write consistency is the floor, not the ceiling. It makes the coordination mechanisms above it reliable, but it does not replace them. When you mount a shared bucket across many nodes, the consistency guarantee keeps every read accurate. It does not decide who wins the write. That decision is yours to make, and it's the one that keeps your data from vanishing.

Sources

  1. Consistency model · Cloudflare R2 docs
  2. Amazon S3 Update
  3. Amazon S3 Strong Consistency

More in POSIX Filesystems