108 lines
5.6 KiB
C#
108 lines
5.6 KiB
C#
namespace ZB.MOM.WW.LocalDb.Replication;
|
|
|
|
/// <summary>
|
|
/// Configuration for the bidirectional 2-node replication engine. All durations are wall-clock UTC.
|
|
/// </summary>
|
|
public sealed class ReplicationOptions
|
|
{
|
|
/// <summary>gRPC address of the peer to dial. Required for the initiator role (validated in the sync host).</summary>
|
|
public string PeerAddress { get; set; } = "";
|
|
|
|
/// <summary>API key presented to / expected from the peer; null disables key auth.</summary>
|
|
public string? ApiKey { get; set; }
|
|
|
|
/// <summary>How often the change pump flushes pending oplog deltas to the peer.</summary>
|
|
public TimeSpan FlushInterval { get; set; } = TimeSpan.FromMilliseconds(250);
|
|
|
|
/// <summary>
|
|
/// Secondary, row-count cap on an outbound batch (and the snapshot streamer's DB page size).
|
|
/// <see cref="MaxBatchBytes"/> is the primary cap.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Historically this was the ONLY cap, which made it a proxy for a byte budget that consumers
|
|
/// had to tune by hand against their widest row — ScadaBridge pinned it to 16 because a 70 KB
|
|
/// <c>config_json</c> times the 500 default is ~35 MB against a 4 MB gRPC limit. With
|
|
/// <see cref="MaxBatchBytes"/> doing that job directly, this is back to being what its name
|
|
/// says: a bound on how many rows one message may carry.
|
|
/// </remarks>
|
|
public int MaxBatchSize { get; set; } = 500;
|
|
|
|
/// <summary>
|
|
/// Primary cap on an outbound batch: the summed serialized size of the entries in one
|
|
/// <c>DeltaBatch</c> / <c>SnapshotBatch</c> message, in bytes. Default 2 MB.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Sized at half the default 4 MB gRPC receive limit, so the message framing, the oneof wrapper
|
|
/// and any per-entry overhead the estimate does not model all fit inside the margin.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>A single row larger than the budget is still sent, alone.</b> Skipping it would stall the
|
|
/// stream permanently on one oversized row; sending it gives the transport the chance to accept
|
|
/// it (gRPC limits are configurable) and, if it refuses, a loud transport error naming the size
|
|
/// rather than silent non-convergence.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Wire compatibility:</b> none required. Batch size is a sender-side choice the protocol
|
|
/// already leaves free — <c>DeltaBatch</c>/<c>SnapshotBatch</c> carry a repeated field with no
|
|
/// declared count — so a node on this version interoperates with a peer on any earlier one in
|
|
/// both directions, with no proto change and no <c>lib_schema_version</c> bump. (ScadaBridge
|
|
/// site pairs stop and start together regardless; this simply does not depend on it.)
|
|
/// </para>
|
|
/// </remarks>
|
|
public int MaxBatchBytes { get; set; } = 2 * 1024 * 1024;
|
|
|
|
/// <summary>Backlog ceiling: exceeding it flags a snapshot resync and prunes the oldest oplog rows.</summary>
|
|
public long MaxOplogRows { get; set; } = 1_000_000;
|
|
|
|
/// <summary>Age ceiling: an unpruned oplog entry older than this flags a snapshot resync.</summary>
|
|
public TimeSpan MaxOplogAge { get; set; } = TimeSpan.FromDays(7);
|
|
|
|
/// <summary>How long deleted-row tombstones are retained before pruning.</summary>
|
|
public TimeSpan TombstoneRetention { get; set; } = TimeSpan.FromDays(7);
|
|
|
|
/// <summary>Upper bound on the exponential reconnect backoff.</summary>
|
|
public TimeSpan ReconnectBackoffMax { get; set; } = TimeSpan.FromSeconds(60);
|
|
|
|
/// <summary>
|
|
/// How long the initiator's HTTP/2 connection may sit without a keepalive PING before one is
|
|
/// sent. Default 60s.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// A sync stream is long-lived and, on a converged pair, almost entirely silent — which is
|
|
/// exactly the shape a half-open TCP connection hides behind. Without pings, a hard crash or a
|
|
/// partition of one node leaves the other holding a stream that will never produce another byte
|
|
/// and never fail either: the initiator sits in a session it believes is healthy, and the
|
|
/// passive node's handler sits inside its single-stream guard, refusing the reconnect that would
|
|
/// heal the pair. Pings turn that into a detected failure on both ends.
|
|
/// </para>
|
|
/// <para>
|
|
/// Sent with <c>KeepAlivePingPolicy.Always</c>, so they flow on an idle connection too — an
|
|
/// active-streams-only policy would not fire in precisely the silent-pair case above. Safe over
|
|
/// h2c: a PING is an HTTP/2 frame, nothing to do with TLS.
|
|
/// </para>
|
|
/// </remarks>
|
|
public TimeSpan KeepAlivePingDelay { get; set; } = TimeSpan.FromSeconds(60);
|
|
|
|
/// <summary>
|
|
/// How long the initiator waits for a keepalive PING ack before tearing the connection down.
|
|
/// Default 20s. Detection therefore lands within roughly
|
|
/// <see cref="KeepAlivePingDelay"/> + this.
|
|
/// </summary>
|
|
public TimeSpan KeepAlivePingTimeout { get; set; } = TimeSpan.FromSeconds(20);
|
|
|
|
/// <summary>How often the maintenance service enforces the oplog caps and prunes expired tombstones.</summary>
|
|
public TimeSpan MaintenanceInterval { get; set; } = TimeSpan.FromSeconds(60);
|
|
|
|
/// <summary>Maximum tolerated HLC drift where a peer's clock runs ahead of ours.</summary>
|
|
public TimeSpan MaxHlcDriftAhead { get; set; } = TimeSpan.FromMinutes(5);
|
|
|
|
/// <summary>
|
|
/// When true, entries whose HLC physical time exceeds <see cref="MaxHlcDriftAhead"/> are
|
|
/// dead-lettered individually (the rest of the batch still applies); when false, drifted
|
|
/// entries are applied and a single warning is logged per session.
|
|
/// </summary>
|
|
public bool FailClosedOnDrift { get; set; }
|
|
}
|