Architecture
This page documents Orleans.Lattice.Replication.Grpc 9.9.0, in the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at architecture.md, and llms.txt lists every page.Orleans.Lattice.Replication.Grpc binds the replication package's public transport seams to ASP.NET Core gRPC. It does not change how mutations are captured, encoded, applied, deduplicated, or merged; those behaviours belong to Orleans.Lattice.Replication. This document describes the transport topology in behavioural terms.
Transport pipeline
A sender - the replication shipper - tails the local WAL partitions directly, packages the entries as a ReplicationBatchEnvelope, sends them through IReplicationTransport, and waits for a ReplicationAck. The receiver endpoint decodes the same envelope and drives IReplicationApplier.
flowchart LR
subgraph "Cluster A sender"
Feed[Local WAL partitions]
Batch[ReplicationBatchEnvelope]
Transport[IReplicationTransport]
Feed -->|batched records| Batch
Batch -->|SendAsync| Transport
end
subgraph "Cluster B receiver"
Endpoint[ASP.NET Core mapped endpoints]
Applier[IReplicationApplier]
Ack[ReplicationAck]
Endpoint -->|decoded records| Applier
Applier -->|high-water mark + hints| Ack
end
Transport -->|unary gRPC call over cached HTTP/2 channel| Endpoint
Ack -->|accepted + HighestAppliedHlc| Transport
The gRPC call boundary carries the public ReplicationBatchEnvelope bytes described in Wire Format. The receiver ack is the public ReplicationAck used by the shipper to advance progress and react to receiver flow-control hints.
Sender behaviour
- Peer resolution. The target cluster id is looked up in
LatticeReplicationGrpcOptions.Peers. Missing peers fail the send. - Channel construction. The first send to a peer creates a long-lived
GrpcChannel. HTTPS is required unlessAllowPlaintextEndpointsis enabled. - Channel customization.
ConfigureChannelruns during channel construction so the host can attach handlers, credentials, retry policy, keep-alive, and message-size settings. - Unary batch push. Each batch is sent as one unary call. HTTP/2 multiplexing lets concurrent calls share the peer channel.
- Ack handling. On an accepted ack the sender advances its durable per-peer cursor to
ReplicationAck.HighestAppliedHlc; when that frontier is at or below the current cursor (for example every entry was deduplicated), it advances to the last shipped entry's HLC instead so the same batch is not re-shipped. A rejected ack (Accepted = false) leaves the cursor in place and retries after a backoff.
The transport is safe for concurrent sends to different peer and tree pairs. Ordering, batching, cursor persistence, retry cadence, and adaptive throttling are owned by the replication shipper; see Replication Drivers and Receiver Flow Control.
Receiver behaviour
- Endpoint mapping.
MapLatticeReplicationGrpcmaps the receiver routes on an ASP.NET Core endpoint route builder. - Decode. The inbound body is decoded with the replication batch encoder, preserving the same envelope shape used by other transports.
- Validate. Before the service sees the call, the shared-secret check authenticates it and, by default, binds the presented secret to the origin header (see Security and
BindCredentialToOriginCluster). An envelope with an empty tree name or origin cluster id is then rejected asInvalidArgument, and a call that carries no origin header, or whose envelope declares an origin cluster id that differs from that header, is refused asPermissionDenied; neither reaches the applier. - Apply. The decoded records are passed to
IReplicationApplier, which handles duplicate suppression, causal buffering, dead-letter quarantine, and CRDT merge dispatch. - Acknowledge. The receiver returns
ReplicationAckwith accepted state, the highest applied HLC, and optional flow-control or compatibility hints. Every non-deferred outcome - applied, deduplicated, parked in the causal-apply buffer, dead-lettered, dropped at the receiver's enrollment gate because the tree resolves no merge mode here, or refused as local-origin - is acknowledgedAccepted = true. A batch the applier deferred because an in-flight coordinated restore holds the tree's inbound receive fence is acknowledgedAccepted = falsewith a 500 msPauseForMs, so the sender keeps its cursor and re-ships the batch once the fence lifts. The built-in shipper does not read flow-control hints from a rejected ack: it treats the rejection as a transient failure, retries on its ordinary ship backoff (ShipBackoffInitialtoShipBackoffMax), and counts it on the outboundpeer.consecutive_errorsgauge. An apply that throws fails the call with anInternalstatus instead of acknowledging.
Receiver idempotency is essential: a retry may redeliver a batch after the receiver applied it but before the sender observed the ack. The apply path turns a repeated record - an exact (origin, hlc, key, op) match - into a no-op.
Shared endpoint topology
The gRPC package also carries remote snapshot bootstrap, read-only anti-entropy probe traffic, and the cross-cluster saga control channel over the same peer endpoint map. Those protocols are documented by the replication package:
- Snapshot Bootstrap - point-in-time seeding before live incremental shipping.
- Automatic drift remediation - opt-in anti-entropy orchestration.
- Coordinated restore - the all-or-nothing cross-cluster restore saga driven over the saga control channel.
- Transport Security - shared-secret auth and HTTPS posture for every replication call.
Invariants preserved
- Payload semantics stay in replication. The transport moves envelopes and acks; it does not decide merge order, conflict resolution, or dead-letter policy.
- Origin metadata is preserved and checked. Outbound calls stamp the local origin header from
LocalClusterIdor the cluster-wideLatticeReplicationOptions.ClusterId; records still carry their source origin inside the envelope, and the receiver refuses a push, content-manifest exchange, or peer high-water-mark probe that carries no origin header or whose request names an origin - the sending tree's ownClusterId; for a push, the batch envelope's - that differs from it. While receiver authentication is on, the default credential-to-origin binding also refuses a call whose secret is not bound to the header's cluster. - Progress is ack-driven. The sender advances its cursor only on an accepted ack from the receiver - to the acked high-water mark, or to the last shipped entry when the receiver deduplicated the whole batch.
- Peer endpoints are explicit. A batch never falls back to discovery or broadcast when a peer id is missing from
Peers. - Security fails closed by default. Non-HTTPS peer endpoints are rejected unless
AllowPlaintextEndpointsopts in.
The chaos suite summarized in Chaos Tests validates the retry and idempotency side of these invariants under channel faults.