From 921edab45434b0b61b7b9ddb0885e1695f5c938b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 20 Jul 2026 04:41:47 -0400 Subject: [PATCH] chore(docker): size the site-a oplog caps from the phase 2 soak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MaxBatchSize 500 -> 16. The default is a ROW count, not a byte budget, so the batch size in bytes is set by the widest replicated column — config_json, which Task 1 measured at up to ~60-70 KB in production. 70 KB x 500 is ~35 MB against gRPC's 4 MB default receive limit; 16 keeps a worst-case batch near 1.1 MB. MaxOplogRows 1,000,000 -> 250,000 and MaxOplogAge 7d -> 2d, sized from the soak's 0.80 sf_messages rows/sec (~69k rows/day). Tighter than default is correct here because exceeding a cap is not data loss: the oplog prunes to the ceiling and sets needs_snapshot, so the peer catches up by snapshot resync instead of incrementally. That trades a rare full resync for a bounded file. site-b and site-c stay unreplicated, so the default-OFF posture is still proven side by side on one rig. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts --- docker/site-a-node-a/appsettings.Site.json | 19 ++++++++++++++++++- docker/site-a-node-b/appsettings.Site.json | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/docker/site-a-node-a/appsettings.Site.json b/docker/site-a-node-a/appsettings.Site.json index f33407c3..3e54acb0 100644 --- a/docker/site-a-node-a/appsettings.Site.json +++ b/docker/site-a-node-a/appsettings.Site.json @@ -87,7 +87,24 @@ // pre-host secret expander. "Replication": { "PeerAddress": "http://scadabridge-site-a-b:8083", - "ApiKey": "dev-site-a-localdb-sync-key" + "ApiKey": "dev-site-a-localdb-sync-key", + // ---- Phase 2 sizing, from the Task 1 rig soak (not from the defaults) ---- + // + // MaxBatchSize (default 500) is a ROW count, not a byte budget, so the batch + // size in bytes is set by the widest replicated column. That is + // deployed_configurations.config_json: ~721 B on this rig, but up to ~60-70 KB + // in production (measured, Task 1) - and 70 KB x 500 is ~35 MB against gRPC's + // 4 MB default receive limit. 16 keeps a worst-case batch near 1.1 MB. + "MaxBatchSize": 16, + // Backlog caps bound the oplog while the peer is offline. Exceeding them is + // NOT data loss: the oplog is pruned to the ceiling and needs_snapshot is set, + // so the peer catches up by snapshot resync instead of incrementally. That + // makes tighter-than-default correct here - it trades a rare full resync for a + // bounded file. Sized from the soak's 0.80 sf_messages rows/sec (the only + // non-zero writer measured): ~69k rows/day, so 2 days is ~138k. 250,000 leaves + // room for burst without approaching the 1,000,000 default. + "MaxOplogRows": 250000, + "MaxOplogAge": "2.00:00:00" } } } diff --git a/docker/site-a-node-b/appsettings.Site.json b/docker/site-a-node-b/appsettings.Site.json index 78062879..376d2b44 100644 --- a/docker/site-a-node-b/appsettings.Site.json +++ b/docker/site-a-node-b/appsettings.Site.json @@ -80,7 +80,24 @@ // fail-closed, so a typo here does not degrade to unauthenticated replication; // it rejects every stream and the pair silently stops converging. "Replication": { - "ApiKey": "dev-site-a-localdb-sync-key" + "ApiKey": "dev-site-a-localdb-sync-key", + // ---- Phase 2 sizing, from the Task 1 rig soak (not from the defaults) ---- + // + // MaxBatchSize (default 500) is a ROW count, not a byte budget, so the batch + // size in bytes is set by the widest replicated column. That is + // deployed_configurations.config_json: ~721 B on this rig, but up to ~60-70 KB + // in production (measured, Task 1) - and 70 KB x 500 is ~35 MB against gRPC's + // 4 MB default receive limit. 16 keeps a worst-case batch near 1.1 MB. + "MaxBatchSize": 16, + // Backlog caps bound the oplog while the peer is offline. Exceeding them is + // NOT data loss: the oplog is pruned to the ceiling and needs_snapshot is set, + // so the peer catches up by snapshot resync instead of incrementally. That + // makes tighter-than-default correct here - it trades a rare full resync for a + // bounded file. Sized from the soak's 0.80 sf_messages rows/sec (the only + // non-zero writer measured): ~69k rows/day, so 2 days is ~138k. 250,000 leaves + // room for burst without approaching the 1,000,000 default. + "MaxOplogRows": 250000, + "MaxOplogAge": "2.00:00:00" } } }