NEWVectors or files. Pick a path.Start →
    Infrastructure
    15 min read
    Updated 2026-08-24

    Write-Ahead Logs on Object Storage: How S3 Becomes a Database's Durability Layer

    A write-ahead log acknowledges a write the moment it is durable, before any index work happens. Move that log onto S3 or GCS and the bucket becomes the durability layer: recovery, read replicas and change feeds all become readers of one chain of sequenced objects. Covers what a WAL is, why object storage changes the design, the two coordination problems it creates (writer fencing and garbage collection that could delete the only copy of a write), how conditional writes solve them, and the shipped write path in Mixpeek MVS.

    Write-Ahead Log
    Object Storage
    S3
    Durability
    Infrastructure
    Vector Databases
    Diskless Architecture
    Conditional Writes

    The Short Answer



    A write-ahead log records every change in an append-only sequence and makes that sequence durable before the system does anything expensive with it. The write is acknowledged as soon as it is in the log. Indexes, snapshots and query structures get built afterwards by reading the log back, which is why a crash costs replay time instead of data.

    Putting the log on object storage means the segments of that sequence are objects in an S3 or GCS bucket rather than files on an attached disk. The bucket's durability becomes the database's durability. The node holding the log stops being special, because anything that can read the bucket can rebuild the state. Recovery reads the chain. Read replicas tail the same chain. A change feed reads it a third time.

    You pay for this at the acknowledgement. A PUT to object storage costs tens of milliseconds where a local fsync costs tens of microseconds, so systems that take the trade batch many writes into one segment and ship on a cadence. The hard part is coordination: exactly one writer may append, a deposed writer has to be fenced off before it forks the sequence, and garbage collection must never delete a segment that still holds the only copy of a write.

    What A Write-Ahead Log Actually Is



    The technique is older than every system you associate it with. Postgres, InnoDB, SQLite, RocksDB, Kafka's partition log and every LSM-tree engine ever shipped run some version of it, because they all face the same question: what happens when the machine dies halfway through applying a change? The short definition lives in the glossary under write-ahead log; what follows is why the shape is what it is.

    Applying a change is usually expensive. Inserting a vector into an HNSW graph means walking the graph and rewiring neighbour lists. Adding a term to an inverted index means merging posting lists. If every write had to finish that work and fsync the result before returning, write latency would be index-maintenance latency.

    A write-ahead log splits the problem in half. The log takes the raw fact of the write, appends it to the end of a sequence, and makes the sequence durable. Appending is fast because it is sequential and involves no structure more complicated than a byte offset. Only then does the caller get an acknowledgement. The index update happens later, in memory, in the background, at whatever cadence the system prefers.

    Recovery becomes replay. The system loads its most recent snapshot, reads every log record written after that snapshot, and applies them in order. Whatever reached the log survives. Whatever did not was never acknowledged, so nobody was promised it.

    Two properties carry the whole guarantee.

    Ordering. Records have a sequence and replay respects it. A delete applied before the insert it removes leaves you in a different state than the other order, so the sequence number is part of the data.

    Durability before acknowledgement. The acknowledgement is a promise about bytes that already exist somewhere that survives power loss. Acknowledge before the log write lands and you have built a system that silently loses acknowledged writes, which is the worst class of storage bug there is, because no layer downstream can detect it.

    That second property is easier to break than it sounds. We shipped a fix for exactly this shape: an MVS shard that crashed after sealing a segment but before shipping it dropped those writes on boot, quietly, despite having already told the caller they were safe. Boot now replays sealed-but-unshipped segments. The lesson generalizes past our shard: every handoff between "the write is in this buffer" and "the write is in the durable thing" is a place where an acknowledgement can outrun the data.

    Why Put The Log On Object Storage?



    The conventional arrangement keeps the log on local NVMe and replicates it to two followers. Durability comes from the count of surviving copies, so you run a replication protocol, you manage stateful volumes, and a dead node is a data event that needs a rebuild from a peer. Storage and compute are welded together, which means holding more data requires machines that also bring CPU you may not need.

    Object storage moves durability underneath you. An S3 or GCS bucket already provides eleven nines, versioning, cross-region replication and lifecycle rules for cents per GB-month, and it does that whether or not your process is running. Once the log lives there, several things stop being your problem:

  1. A node failure is no longer a data event. A shard that dies holds nothing that matters, because the log is in the bucket. Replacement is scheduling, not recovery of a lost copy.
  2. Replication becomes a read. A replica tails the object chain. There is no consensus protocol to operate, no follower to catch up by hand, and no split-brain risk from the replication path itself.
  3. Storage and compute scale independently. Ten billion vectors sitting quiet cost storage and nothing else, which is the same economic argument that drove tiered vector storage and object-storage-backed vector search.
  4. The bucket can belong to the customer. When durability lives in object storage, the storage can sit inside someone else's account under their IAM policy and their retention rules, and the vendor operates compute against it. That posture is what bring your own object storage means in practice.


  5. The industry name for the result is diskless, or zero-disk architecture. Diskless Kafka designs took the same step for event streaming, and object-storage-native databases took it for tables. Vector search arrived at it late, for the reason that vector workloads are read-heavy and the read path was the obvious thing to optimize first.

    What It Costs: The Latency Floor At The Acknowledgement



    A local fsync to NVMe lands in tens of microseconds. A PUT to S3 lands in tens of milliseconds, sometimes worse at the tail. Acknowledging every individual write only after its own PUT completes would make a write-heavy workload unusable.

    Every system that takes this trade solves it the same way: batch. Writes accumulate in an in-memory segment, the segment seals on a size or time threshold, and the sealed segment ships to object storage as one object. Hundreds or thousands of writes share the cost of one PUT, so per-write overhead falls to something reasonable.

    That batching decision is also a durability decision, and it deserves to be explicit rather than inherited. Acknowledge on the in-memory append and your latency is excellent while an unshipped segment is a window of loss. Acknowledge only after the segment ships and every write waits for the batch. Real systems land somewhere in between, usually by making the local write durable enough to survive a process crash and treating the object-storage ship as the point where it survives losing the machine.

    Ship cadence sets the size of that window, so it is worth knowing your own number before an incident instead of during one. It is a contract question as much as an implementation detail. If an acknowledgement is going to mean "this write is in the durable chain", the ship has to happen before the ack returns, and the caller pays latency up to the cadence. The other shape keeps the ack fast and hands back a token the caller can wait on when it needs the stronger promise, so the cost falls only on the writes that want it. Any system doing this has picked one, and a system that has not picked yet is worth asking about.

    Coordination Problem One: Two Writers, One Log



    Move the log to a shared bucket and you inherit a distributed systems problem that local disks hid from you. A local file has an owner by construction. An object in a bucket can be written by anything holding credentials.

    The dangerous case is a writer that believes it is still the leader when it is not. Its process paused for a long garbage collection, or the network partitioned it, or its lease expired while it was busy. The cluster elected a replacement. The old writer wakes up and continues appending at sequence 41, which the new leader already wrote. Now the chain forks. Two different objects claim the same position, and every reader of the chain, meaning recovery, replicas and change feeds alike, resolves the same history differently depending on which one they read.

    Fencing is the fix, and it takes two pieces working together:

    A leader lease held as an object. Becoming the writer means taking the lease through a compare-and-swap: write the lease only if it still holds the value you read. A writer that lost its lease fails that CAS and learns it is deposed at the moment it tries to act.

    An identity fence on every segment. The lease alone is not enough, because a writer can lose it between checking and shipping. Each ship therefore carries the writer's boot identity and a fencing epoch, and segment PUTs are create-only, so a stale writer's attempt to occupy a sequence number that already exists fails at the object store instead of succeeding and corrupting the chain.

    The create-only PUT is the part that makes this airtight. Fencing that depends on the writer voluntarily checking a lease fails whenever the check and the write are not atomic, and they never are. Fencing enforced by the storage layer rejecting a duplicate sequence number holds regardless of what the deposed writer believes about itself.

    The obvious worry about routing every append through a compare-and-swap is throughput, and it is worth measuring before you design around it. In the MVS write protocol the ship rate works out to roughly 0.05 operations per second per chain, against provider compare-and-swap ceilings between 5 and 98 operations per second. The chains are partitioned per namespace and shard, so that ceiling applies to one chain and not to the fleet. Checking that ratio early is the step worth copying, because the limit is per object, and a design that funnels every shard through a single lease object will find it.

    Coordination Problem Two: Garbage Collecting Without Deleting The Only Copy



    Segments accumulate forever unless something deletes them. Once a snapshot exists that already contains a segment's writes, replay no longer needs that segment, so it can go.

    The word "already contains" is where systems get this wrong. Deleting a segment whose writes are only in a snapshot that has not yet committed to object storage destroys the only durable copy of acknowledged data. The failure is invisible at the time and shows up much later, during a recovery that was supposed to work, which is the worst possible moment to discover it.

    A safe GC gate needs three things, and each one is a place where a plausible implementation goes wrong:

  6. A deletion bound that takes the minimum of two positions, what compaction has consumed and what has been durably confirmed in object storage. Using compaction progress alone deletes segments whose snapshot is still in flight.
  7. Per-segment coverage checks that fail closed. When the system cannot prove a segment's writes are captured, it keeps the segment. Retaining a segment you did not need wastes storage. Deleting one you did need loses data, so the asymmetry decides the default.
  8. Counters that make refusals visible. A snapshot pipeline that has stalled deletes nothing, correctly, and therefore reports nothing. Without a retention counter, "GC is safe" and "GC has been silently doing no work for a week while storage grows" look identical from the outside.


  9. That third point generalizes to any fail-closed safety gate. The gate protects you by refusing, and refusals are silent unless you count them, so instrument the refusal rather than the success.

    The Primitive That Made This Practical: Conditional Writes



    None of the fencing above works on a storage API that only offers unconditional PUT. For years that was the situation, and object-storage-native systems compensated with an external lock service such as DynamoDB, etcd or ZooKeeper, which reintroduced the stateful dependency the design existed to remove.

    That changed when the object stores themselves grew compare-and-swap. Google Cloud Storage has had generation and metageneration preconditions for a long time. Azure Blob Storage has ETag conditions. S3 added conditional writes, first create-only through If-None-Match, then compare-and-swap on ETag. With those primitives the bucket itself arbitrates who owns the log, and the whole architecture collapses to one dependency.

    Two operational limits are worth carrying into a design review. Conditional operations on a single object are rate-limited well below normal object throughput, which is why per-chain partitioning matters and why measuring your actual ship rate against the ceiling is a real design step. And conditional-write semantics differ across providers in ways that matter for correctness, so a system that targets S3, GCS and R2 alike either implements against the weakest guarantee or maintains a per-provider path.

    How MVS Does It, And What Is Still In Flight



    MVS keeps vectors, payloads and index snapshots in object storage you own, and the write-ahead log is part of that same substrate rather than a separate stateful tier. Which parts of the design are running and which are specified matters more than a tidy diagram, so both are marked below.

    A three-band architecture diagram of the Mixpeek Vector Store write path on object storage. The top band runs left to right: a client write of an upsert or delete, into a write-ahead log where the append happens and the acknowledgement returns, then a segment sealing as immutable, then shipping to object storage. A callout under the log reads that the acknowledgement happens there and not after indexing, and a dashed box spanning the seal and ship steps marks a leader fence on the append as planned rather than shipped. The middle band is your object storage, an S3 or GCS bucket holding one sequenced object per sealed segment at the path wal slash namespace slash shard slash sequence dot wal, drawn as a row of numbered segments with the two oldest greyed. Beside them, snapshots compact the chain and commit to object storage, and a garbage-collection gate states that a segment is deleted only once its writes are provably inside a committed snapshot. The bottom band fans out to three readers of the same chain: recovery, which takes the latest snapshot plus a replay of shipped segments; replicas, which tail the chain to stay in sync; and change data capture, which reads the chain as an event feed.
    A three-band architecture diagram of the Mixpeek Vector Store write path on object storage. The top band runs left to right: a client write of an upsert or delete, into a write-ahead log where the append happens and the acknowledgement returns, then a segment sealing as immutable, then shipping to object storage. A callout under the log reads that the acknowledgement happens there and not after indexing, and a dashed box spanning the seal and ship steps marks a leader fence on the append as planned rather than shipped. The middle band is your object storage, an S3 or GCS bucket holding one sequenced object per sealed segment at the path wal slash namespace slash shard slash sequence dot wal, drawn as a row of numbered segments with the two oldest greyed. Beside them, snapshots compact the chain and commit to object storage, and a garbage-collection gate states that a segment is deleted only once its writes are provably inside a committed snapshot. The bottom band fans out to three readers of the same chain: recovery, which takes the latest snapshot plus a replay of shipped segments; replicas, which tail the chain to stay in sync; and change data capture, which reads the chain as an event feed.


    The dashed element in that diagram is the leader fence, drawn that way because it is planned and not yet running. The full diagram page has the longer write-up.

    Running: the acknowledgement happens at the log. A write lands in the shard's WAL and is acknowledged there, before any index work. This is the beat most people get wrong on first reading, because it looks like the ack ought to wait until the vector is searchable. It does not, and that gap is what makes the write fast. Shipping the sealed segment to object storage is a separate step from the acknowledgement, which is the distinction the batching section above is about.

    Running: segments seal and ship as sequenced objects. The chain is laid out as wal/{namespace}/{shard}/{sequence}.wal, one object per sealed segment, ordered by sequence number. A monotonic filename is its own conflict detector, so ordering within a shard is a property of the naming.

    Running: restore replays the chain on top of the last snapshot, and read replicas tail the same shipped objects to stay current. Two of the three readers are live today.

    Running: garbage collection sits behind a verified-capture gate. A segment is deleted only once its writes are provably inside a snapshot whose object-store commit succeeded. The bound is the minimum of what compaction has consumed and what has been durably confirmed, and the per-segment coverage check fails closed, so a straddling segment and the newest segment always survive. The rule runs today. Its weak point was visibility: the gate's refusals were silent, which meant a stalled snapshot frontier would hold segments indefinitely and report nothing at all. The newest addition, counters that make each pass legible by recording what it retains alongside what it deletes, is merged and arrives with the next shard release.

    In flight: the conditional-write fencing protocol. Segment PUTs and leader writes are unconditional today, so the fencing described earlier in this guide is a specified design and not yet a running guarantee. The protocol covers a fencing epoch, a conditional leader claim and renewal, and create-only segment PUTs. Its standing changed when the chain was designated a source of truth: conditional writes stop being defense in depth and become the prerequisite, because a log that a deposed writer can fork cannot settle a disagreement between two stores, whatever else is true about it.

    That last entry is the honest state of a system mid-build, and it is worth stating plainly rather than smoothing over, because the gap between "we have a WAL on object storage" and "the chain is fork-proof" is exactly where the interesting engineering lives. The vector store overview covers how the read path sits on the same bucket.

    One Log, Three Readers



    The design's real payoff is that three separate capabilities turn out to be the same code path pointed at the same objects.

    Recovery loads the latest snapshot and replays every shipped segment after it. This is the reader the log was built for.

    Read replicas tail the shipped chain. A replica needs no replication protocol, no leader to stream from and no catch-up mechanism of its own, because staying in sync means reading objects that are already there. A replica that falls behind reads faster. A replica that dies starts over from a snapshot.

    Change data capture reads the chain as an event feed. Every mutation is already recorded in order, with sequence numbers, which is exactly the shape a downstream consumer wants. Building change data capture for a search index on top of an existing WAL costs a reader rather than a new subsystem, because the ordering guarantee the log already provides is the hard part of CDC.

    Systems that skip the log end up building three of these separately, and then reconciling them when they disagree.

    When This Architecture Is Wrong



    Three honest disqualifiers.

    Single-digit-millisecond write acknowledgement. If your workload needs an ack in under a few milliseconds, the object-storage PUT is in the way and no amount of batching removes it. Local NVMe with synchronous replication still wins there.

    Very low write volume with strict freshness. Batching amortizes the PUT across many writes, so a workload with a trickle of writes and a requirement that each be immediately durable in the bucket pays close to full PUT cost per write. The cadence that makes this design efficient needs volume to work with.

    Workloads where the coordination cost exceeds the savings. A single-node system that never fails over does not need fencing, a lease, or a verified-capture gate, and building them anyway buys complexity for a guarantee nobody asked for.

    For everything else, and particularly for multi-tenant systems where most tenants are idle most of the time, the log-on-object-storage shape is a better fit than a fleet of stateful nodes. Multi-tenant vector search is the clearest case, because per-tenant chains partition naturally and an idle tenant costs storage alone.

    Frequently Asked Questions



    What is a write-ahead log?



    A write-ahead log is an append-only sequence of every change a system makes, written and made durable before the change is applied to the system's data structures. The write is acknowledged once it reaches the log, so acknowledgement latency is the cost of a sequential append rather than the cost of updating an index. After a crash, the system loads its most recent snapshot and replays every log record written after it, which restores exactly the state that was acknowledged. Postgres, InnoDB, SQLite, RocksDB and Kafka all use a form of this, and so does every LSM-tree storage engine.

    Can object storage like S3 be a database's write-ahead log?



    Yes, and as of 2026 it is a shipping architecture rather than a research idea. The log's segments become sequenced objects in a bucket, and the bucket's durability becomes the database's durability, which removes the need to run your own replication protocol across stateful disks. What made this practical was object stores adding conditional writes: compare-and-swap on an object lets a bucket arbitrate which writer owns the log, so the design no longer needs an external lock service such as DynamoDB or etcd. The two problems you must solve are fencing a deposed writer so it cannot fork the sequence, and making sure garbage collection never deletes a segment that holds the only durable copy of an acknowledged write.

    What is a diskless or zero-disk architecture?



    Diskless describes a system whose durable state lives entirely in object storage, so its compute nodes hold nothing that needs to survive them. A node that dies is replaced by scheduling rather than by rebuilding data from a peer, because everything it had is already in the bucket. Diskless Kafka designs applied the idea to event streaming and object-storage-native databases applied it to tables; vector search adopted it later because vector workloads are read-heavy and the read path was the obvious thing to optimize first. The trade is write acknowledgement latency, since a PUT to object storage costs tens of milliseconds against tens of microseconds for a local fsync, which is why these systems batch writes into segments and ship on a cadence.

    How do vector databases achieve durability on object storage?



    By separating the durable record of a write from the index built out of it. The vector goes into a write-ahead log that ships to object storage as sequenced segments, and the acknowledgement happens at the log. Index structures, whether HNSW graphs, IVF partitions or BM25 posting lists, are derived state rebuilt from snapshots plus log replay, so losing a node loses no data. Mixpeek MVS does this with a chain laid out as wal/namespace/shard/sequence.wal, restore replaying shipped segments on top of the last snapshot, replicas tailing the same objects, and a garbage collection gate that deletes a segment only once its writes are provably inside a snapshot whose object-store commit succeeded.

    What are S3 conditional writes and why do they matter for this?



    Conditional writes let a PUT succeed only if a stated condition about the object still holds, either create-only through If-None-Match or compare-and-swap against a current ETag. They matter because they let the object store itself enforce single-writer ownership of a log. A deposed writer attempting to occupy a sequence number the real leader already wrote fails at the storage layer, rather than succeeding and forking the chain. Before object stores offered this, systems needed an external lock service, which reintroduced the stateful dependency the architecture existed to eliminate. Note that conditional operations on a single object are rate-limited far below ordinary object throughput, typically single-digit to low-double-digit operations per second, so chains are usually partitioned per tenant or shard.

    How is a write-ahead log different from a snapshot?



    A snapshot is the full state of the system at one moment; a log is the ordered set of changes between snapshots. Recovery needs both, because replaying every write since the beginning of time would be unbearably slow, and a snapshot alone would lose everything written after it was taken. The interaction between them is where the safety-critical logic lives: a segment can only be deleted once a snapshot that includes its writes has itself committed durably, so garbage collection has to reason about snapshot commit status rather than about snapshot existence.

    Does one write-ahead log really serve recovery, replicas and change feeds?



    It does, and that is a large part of why the design is worth the coordination cost. All three are readers of the same ordered chain of objects. Recovery reads a snapshot plus the segments after it. A read replica tails the chain to stay current, which means it needs no replication protocol of its own. A change data capture feed reads the same chain as an event stream, and gets ordering for free because the log already had to guarantee it. Systems without a shipped log tend to build these three capabilities separately and then spend real effort reconciling them when they disagree.
    Managed Mixpeek

    Put multimodal search to work

    Connect a bucket and Mixpeek runs the whole multimodal search pipeline for you: extraction, indexing, and search over your own objects. No models to wire up, nothing to host.

    Start with Managed
    MVS · bring your own

    Already have vectors?

    Keep your embeddings on your own cloud and run dense, sparse, and BM25 search directly on object storage. From $25/mo.

    Start with MVS

    Run this on your own data

    Point Mixpeek at the storage you already have and search your video, images, audio, and documents the way this guide describes. Build starts at $25/mo for up to 1M vectors.

    Search your own archiveRead Docs