diff --git a/CLAUDE.md b/CLAUDE.md index 90d20194..6db06de6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -721,13 +721,19 @@ same event accepted on both nodes — which happens in every boot window, since docker-dev rig the site-a pair and site-b both run `AlarmHistorian__Enabled=true` against a deliberately unresolvable gateway endpoint, so the buffer is always a live, non-draining queue. -**The library pin is `0.2.1`** (`Directory.Packages.props`, both `ZB.MOM.WW.LocalDb` and -`.Replication`), bumped from `0.1.3` on 2026-08-15. It was a **pure pin bump — no code change**: -`RegisterReplicated(string)` is unchanged, everything 0.2.x added is additive, the wire protocol is -untouched (0.1.x peers still sync), and the on-disk bookkeeping schema v1 → v2 upgrades in place on -open, so no rig or volume action was needed. **Its two new APIs — `DeregisterReplicated` and -`RegisterReplicated(table, baselineExistingRows:)` — are now both used** (2026-08-18); the bullets -below are the shape that landed, not the bump itself. Two things are worth knowing: +**The library pin is `0.3.4`** (`Directory.Packages.props`, both `ZB.MOM.WW.LocalDb` and +`.Replication`), bumped from `0.2.1` on 2026-09-03 (was itself bumped from `0.1.3` on 2026-08-15). +The 0.2.1→0.3.4 bump is wire-compatible with 0.2.x, and the on-disk bookkeeping schema v2 → v3 +upgrades in place on open, so no rig or volume action was needed. `RegisterReplicated(string)` is +unchanged and everything 0.2.x/0.3.x added is additive, **except** that `RegisterReplicated` (0.3.3) +now throws `LocalDbRegistrationException` for a table that is the parent of a row-mutating ON +DELETE FK — none of this repo's four replicated tables have such an FK, so this did not fire. +0.3.0 made the snapshot-resync flag **per-table** (see the ⚠️ below, now corrected); 0.3.2 added +validated config `Replication:KeepAlivePingDelay`/`KeepAlivePingTimeout` (defaults 60s/20s, left +unset here) and a public `LocalDbOplogGapException`, neither yet wired to a call site. **Its two +new APIs — `DeregisterReplicated` and `RegisterReplicated(table, baselineExistingRows:)` — are now +both used** (2026-08-18); the bullets below are the shape that landed, not the bump itself. Two +things are worth knowing: - **`MaxBatchSize` no longer means what the rig's `16` was chosen to mean.** 0.2.0 made batching **byte-budgeted** — `LocalDb:Replication:MaxBatchBytes` (2 MB default, left unset here, sized @@ -753,10 +759,12 @@ below are the shape that landed, not the bump itself. Two things are worth knowi used to replicate only writes made after the restart. It seeds the ledger at the LWW floor (HLC `0`, this node's id) and flags a resync — a baselined row loses to any genuine remote write of the same key and wins only where the peer has no version at all. It is idempotent, so - it is free on every boot after the first. ⚠️ On the pinned **0.2.1** the snapshot flag is - per-**database**, not per-table, so baselining anything streams *every* registered table in - full, in both directions at once — trim `alarm_sf_events` before enabling replication on a node - that has been buffering alarm history for months, and expect one slow first sync. + it is free on every boot after the first. As of `0.3.0` (this repo pins `0.3.4`) the snapshot + flag is **per-table**, not per-database — baselining one table no longer streams every + registered table in both directions. (Prior to `0.3.0`, on the `0.2.1` pin this repo carried + until 2026-09-03, the flag was per-database and baselining anything streamed *every* + registered table in full; the old advice to trim `alarm_sf_events` before enabling replication + no longer applies at the current pin.) - **Not configured ⇒ `DeregisterReplicated(table)` per table**, an idempotent boot-time self-heal: it drops the capture triggers an earlier (unconditional) build left in the file and prunes that table's oplog + row-version rows. Merely declining to register would leave every diff --git a/Directory.Packages.props b/Directory.Packages.props index 60deb106..71f1681e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -175,18 +175,18 @@ package floors the native SQLitePCLRaw.lib.e_sqlite3 at 2.1.12, so consumers inherit the CVE-2025-6965-fixed native binary without needing the surgical pin below. --> - - + + - - + + - - - + + + diff --git a/docs/Configuration.md b/docs/Configuration.md index baeb1003..ceac456a 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -435,9 +435,9 @@ and syncs with nobody). A configured node registers all four tables with `baseli turning replication on converges the rows **already in the file** and not merely subsequent writes; an unconfigured node **deregisters** them at boot, dropping capture triggers an earlier build installed. ⚠️ The sync handshake compares registered-table digests **fail-closed**, so flipping either key is a -both-nodes-together change: set it on both and restart both. On LocalDb 0.2.1 baselining flags a -**per-database** snapshot resync (every registered table, in full, both directions), so trim a long-running -`alarm_sf_events` buffer before enabling replication late. +both-nodes-together change: set it on both and restart both. As of LocalDb 0.3.0 (this repo pins +0.3.4) baselining flags a **per-table** snapshot resync — enabling replication on one table no +longer forces a full-database resync of every registered table in both directions. Replication is **default-OFF** across the fleet; it is enabled per-pair as an opt-in. See [`docs/operations/2026-07-20-localdb-pair-replication.md`](operations/2026-07-20-localdb-pair-replication.md) diff --git a/docs/operations/2026-07-20-localdb-pair-replication.md b/docs/operations/2026-07-20-localdb-pair-replication.md index 51c401c8..2b30f253 100644 --- a/docs/operations/2026-07-20-localdb-pair-replication.md +++ b/docs/operations/2026-07-20-localdb-pair-replication.md @@ -77,12 +77,16 @@ Replication has two knobs, both under `LocalDb`: | `LocalDb:Replication:MaxBatchSize` | secondary row cap **and** the DB read-page size | `16` for the artifact cache. **Since LocalDb 0.2.0 this no longer bounds the wire message** — `MaxBatchBytes` does — so it is not the byte proxy the note here used to describe. Keep it at `16` anyway: `OplogStore`/`SnapshotStreamer` materialise a whole read page in memory *before* the byte-budget split runs, and 16 × ≈171 KB artifact chunk rows ≈ 2.7 MB bounds that transient allocation. Raising it no longer risks tripping the gRPC cap, only the allocation. | | `LocalDb:Replication:MaxBatchBytes` | primary cap on one outbound message (bytes) | Library default **2 MB**, sized under gRPC's 4 MB receive limit. Left unset on this rig. A single row over budget is sent alone rather than stalling the stream. | -> **Library version.** This repo pins **`ZB.MOM.WW.LocalDb` / `.Replication` 0.2.1** -> (`Directory.Packages.props`). The 0.1.3 → 0.2.1 move needed no config or code change here: the -> wire protocol is unchanged (0.1.x peers still sync), and the on-disk bookkeeping schema v1 → v2 -> is upgraded **in place on open**, so an existing node's database file needs no operator action. -> Two behaviours changed that an operator should know: batching is now byte-budgeted (the two rows -> above), and **both of 0.2.x's lifecycle APIs are now used** (2026-08-18) — see the box below. +> **Library version.** This repo pins **`ZB.MOM.WW.LocalDb` / `.Replication` 0.3.4** +> (`Directory.Packages.props`; bumped from `0.2.1` on 2026-09-03, itself bumped from `0.1.3` on +> 2026-08-15). Every move in that chain needed no config or code change here: the wire protocol is +> unchanged end to end (0.1.x/0.2.x/0.3.x peers all still sync), and the on-disk bookkeeping schema +> upgrades in place on open (now v3) with no operator action on an existing node's database file. +> Behaviours worth knowing at the current pin: batching is byte-budgeted (the two rows above); +> **both of 0.2.x's lifecycle APIs are now used** (2026-08-18, see the box below); the +> snapshot-resync flag introduced by baselining is **per-table**, not per-database, since `0.3.0`; +> and `0.3.2` added validated `Replication:KeepAlivePingDelay`/`KeepAlivePingTimeout` config +> (defaults 60s/20s, left unset on this rig). > **⚠️ Registration follows this config, and flipping it is a BOTH-NODES change.** Since > 2026-08-18 `LocalDbSetup.OnReady` installs the change-capture triggers only when the node is diff --git a/nuget-packages/ZB.MOM.WW.MxGateway.Client.0.2.1.nupkg b/nuget-packages/ZB.MOM.WW.MxGateway.Client.0.2.1.nupkg new file mode 100644 index 00000000..455f8e8e Binary files /dev/null and b/nuget-packages/ZB.MOM.WW.MxGateway.Client.0.2.1.nupkg differ diff --git a/nuget-packages/ZB.MOM.WW.MxGateway.Contracts.0.2.1.nupkg b/nuget-packages/ZB.MOM.WW.MxGateway.Contracts.0.2.1.nupkg new file mode 100644 index 00000000..815d32f2 Binary files /dev/null and b/nuget-packages/ZB.MOM.WW.MxGateway.Contracts.0.2.1.nupkg differ