Skip to content

PATTERN Cited by 1 source

Two-phase tentative-then-complete

Intent

Separate receipt from materialisation in a consensus commit path so that:

  1. Abandoned requests can be deleted cleanly (cancellation path).
  2. Durable requests are never forgotten (completion path).
  3. Completion and cancellation are mutually exclusive — a request ends up exactly one way.

Shape

Client → Leader: write X

Leader → Followers: tentative(X)
Followers → Leader: ack(X)

   [Leader tallies acks. Once durability threshold is met, X is durable.]

Leader → Client: ack               (optional — see Early-ack-on-durability)

Leader → Followers: complete(X)
Followers: apply X

A request passes through three stages:

Stage Who knows Follower marker Cancelable?
Incomplete leader + responsive followers tentative yes
Durable leader (implicit) tentative (no change) no
Complete leader + all followers applied n/a

The transition from incomplete → durable is implicit (no new message); the transition from durable → complete is explicit (complete message to each follower).

Canonical formulation

Sugu Sougoumarane, consensus-algorithms-at-scale part 6: "The above requirements can be met by introducing a two-phase protocol. The leader first transmits the payload of the request as tentative to all the nodes. A tentative request is one that can later be completed or canceled. A follower that is responsible for a leader's durability should acknowledge receipt of tentative requests. Once the leader receives the necessary acknowledgements from its followers, the request has become durable and cannot be canceled. The leader can then issue messages to complete the tentative request."

(Source: sources/2026-04-21-planetscale-consensus-algorithms-at-scale-part-6-completing-requests)

The mutual-exclusion invariant

The load-bearing safety property:

"Completion and cancellation are mutually exclusive: A request that was completed will never be canceled, and a request that was canceled will never be completed."

Clients and higher-layer protocols (idempotency tokens, retry logic) can rely on this: a success ack is not going to be silently rolled back later.

Composition with other patterns

Not two-phase commit

Despite the name similarity, this is not two-phase commit (2PC). Both have a prepare-like phase and a commit-like phase, but the invariants are different: 2PC delivers cross-participant atomicity for distributed transactions; two-phase tentative-then-complete delivers single-write durability for consensus. A consensus system can of course run 2PC on top for cross-shard atomicity.

The canonical non-example: MySQL semi-sync

MySQL semi-sync replication lacks the tentative step — "a replica receives a request, it immediately applies it". It therefore also lacks a clean cancellation path, and a primary that restarts after a crash can complete writes that were never durable, producing concepts/mysql-semi-sync-split-brain. Vitess deployments on top of MySQL have to paper over the gap operationally through reparenting tooling, lameduck drain, and vtgate query buffering.

Applicability

Use when:

  • Consensus safety requires "never forget an ack'd request" plus "never apply a non-durable request" as simultaneous invariants.
  • Leadership changes are possible and the elector needs to reason about in-flight work cleanly.
  • The system will later add a read-path optimisation (lease, quorum read) that benefits from knowing whether a request is durable vs complete.

Skip when:

  • The system is single-node (no replication) — the tentative state buys nothing.
  • The system is eventually consistent with CRDT merge semantics (concepts/no-distributed-consensus) — there is no "the leader decides" altitude where this protocol operates.

Seen in

Last updated · 347 distilled / 1,201 read