docs(localdb): Phase 1 truth pass
Task 13. SiteEventLogger's XML doc said 'Not replicated to standby. On failover, the new active node starts a fresh log.' That is now false and was the exact behaviour Phase 1 set out to fix. OperationTrackingOptions.ConnectionString and SiteEventLogOptions.DatabasePath marked MIGRATION-ONLY: nothing reads them but SiteLocalDbLegacyMigrator, and they are safe to delete from a config once a node has migrated. ScadaBridge CLAUDE.md gains a consolidated-site-database entry covering the GUID/opaque-token contract change, the migration-only keys, the incidental CWD-outside-the-volume data-loss fix, the fail-closed default-OFF replication posture, and the fact that ZbTelemetryOptions.Meters is a silent allowlist. scadaproj CLAUDE.md's LocalDb row goes from 'no app adoption yet' to the honest current state, per the component-status-honesty convention: ScadaBridge Phase 1 only, on an UNMERGED branch, replication live-proven on the rig's site-a pair ONLY, default-OFF everywhere else, Phase 2 not started, no other app adopted, no production deployment replicating. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -120,6 +120,12 @@ Other peers in the `scadaproj` family (see `scadaproj/CLAUDE.md` for details): `
|
||||
- DCL write failures returned synchronously to calling script.
|
||||
- Tag path resolution retried periodically for devices still booting.
|
||||
- Static attribute writes persisted to local SQLite (survive restart/failover, reset on redeployment).
|
||||
- **Consolidated site database (LocalDb Phase 1, 2026-07-19).** `OperationTracking` and `site_events` now live in ONE `ZB.MOM.WW.LocalDb`-managed SQLite file, configured by the **required** `LocalDb:Path` (`/app/data/site-localdb.db` on the rig; validated with `ValidateOnStart`, so a site config missing it fails to boot). Both are `RegisterReplicated` tables. Consequences worth knowing:
|
||||
- `site_events.id` changed from autoincrement INTEGER to an application-minted **GUID**. Last-writer-wins keys on the primary key, so two nodes independently minting `id=1,2,3…` would destroy each other's events rather than merge them. The event-log read path uses a composite `(timestamp, id)` keyset cursor with an **opaque string** continuation token; `EventLogEntry.Id` and both `ContinuationToken`s are `string`/`string?` on the site↔central Akka DTOs.
|
||||
- `ScadaBridge:OperationTracking:ConnectionString` and `ScadaBridge:SiteEventLog:DatabasePath` are **migration-only** — nothing reads them but `SiteLocalDbLegacyMigrator`, which copies a pre-Phase-1 file in once (deterministic `mig-{NodeName}-{legacyId}` event ids, `INSERT OR IGNORE`, runs AFTER `RegisterReplicated` so migrated rows replicate) and renames it `.migrated`. Delete the keys once a node has migrated.
|
||||
- This incidentally fixes a data-loss bug: both legacy databases defaulted to CWD-relative paths **outside** the mounted volume and were discarded on every container recreate.
|
||||
- **Replication is default-OFF and opt-in** via `LocalDb:Replication:PeerAddress` + a matching `ApiKey` on both nodes. `LocalDbSyncAuthInterceptor` is **fail-closed**: no configured key means no sync stream is accepted at all, so a key typo does not degrade to unauthenticated replication — the pair simply stops converging. The sync endpoint shares the existing site gRPC h2c listener (8083); no new port. Rig posture: **site-a replicated, site-b/site-c deliberately not**, so both states are proven side-by-side. Status surfaces on the site health report as `LocalDbReplicationConnected` / `LocalDbOplogBacklog` (both nullable — null means "no data", NOT "disconnected with an empty backlog") and as `localdb_*` Prometheus series. Note `ZbTelemetryOptions.Meters` is an **allowlist** (`SiteServiceRegistration.ObservedMeters`); an unlisted meter exports nothing, silently.
|
||||
- Design: `docs/plans/2026-07-19-scadabridge-localdb-design.md`; Phase 1 plan `docs/plans/2026-07-19-localdb-adoption-phase1.md`. Phase 2 (config tables + `sf_messages`, deleting `SiteReplicationActor` + StoreAndForward `ReplicationService`) is NOT started and needs its own plan.
|
||||
- All timestamps are UTC throughout the system.
|
||||
- Inter-cluster communication uses two transports: ClusterClient for command/control (deployments, lifecycle, subscribe/unsubscribe handshake, snapshots) and gRPC server-streaming for real-time data (attribute values, alarm states). Both CentralCommunicationActor and SiteCommunicationActor registered with receptionist. Central creates one ClusterClient per site using NodeA/NodeB as contact points. Sites configure multiple central contact points for failover. Addresses cached in CentralCommunicationActor, refreshed periodically (60s) and on admin changes. Heartbeats serve health monitoring only.
|
||||
- gRPC streaming channel: SiteStreamGrpcServer on each site node (Kestrel HTTP/2, port 8083); central creates per-site SiteStreamGrpcClient via SiteStreamGrpcClientFactory. Site entity has GrpcNodeAAddress/GrpcNodeBAddress fields. Proto: sitestream.proto with SiteStreamService, SiteStreamEvent (oneof: AttributeValueUpdate, AlarmStateUpdate). DebugStreamEvent message removed (no longer flows through ClusterClient).
|
||||
|
||||
@@ -6,7 +6,14 @@ public class SiteEventLogOptions
|
||||
public int RetentionDays { get; set; } = 30;
|
||||
/// <summary>Maximum SQLite database size in megabytes before old entries are purged; default 1024 MB.</summary>
|
||||
public int MaxStorageMb { get; set; } = 1024;
|
||||
/// <summary>File path for the site event log SQLite database.</summary>
|
||||
/// <summary>
|
||||
/// MIGRATION-ONLY as of LocalDb Phase 1. <see cref="SiteEventLogger"/> no longer reads
|
||||
/// this: <c>site_events</c> lives in the consolidated site database at
|
||||
/// <c>LocalDb:Path</c>. The only remaining consumer is
|
||||
/// <c>SiteLocalDbLegacyMigrator</c>, which uses it to locate a pre-Phase-1
|
||||
/// <c>site_events.db</c> and copy it in once. Safe to delete from a config once that
|
||||
/// node's migration has run.
|
||||
/// </summary>
|
||||
public string DatabasePath { get; set; } = "site_events.db";
|
||||
/// <summary>Maximum number of rows returned per paginated query; default 500.</summary>
|
||||
public int QueryPageSize { get; set; } = 500;
|
||||
|
||||
@@ -7,9 +7,11 @@ using ZB.MOM.WW.LocalDb;
|
||||
namespace ZB.MOM.WW.ScadaBridge.SiteEventLogging;
|
||||
|
||||
/// <summary>
|
||||
/// Records operational events to a local SQLite database.
|
||||
/// Only the active node generates events. Not replicated to standby.
|
||||
/// On failover, the new active node starts a fresh log.
|
||||
/// Records operational events into the consolidated site database (LocalDb Phase 1).
|
||||
/// Only the active node generates events, but <c>site_events</c> is a replicated table:
|
||||
/// when the pair has a peer configured, the standby holds the same history and a failover
|
||||
/// no longer starts a fresh log. With no peer configured the database is simply local —
|
||||
/// replication is opt-in via <c>LocalDb:Replication:PeerAddress</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
|
||||
@@ -7,9 +7,12 @@ namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tracking;
|
||||
public class OperationTrackingOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Full ADO.NET connection string for the SQLite database (e.g.
|
||||
/// <c>"Data Source=site-tracking.db"</c>). Tests use the
|
||||
/// <c>Mode=Memory;Cache=Shared</c> form to keep the database in-memory.
|
||||
/// MIGRATION-ONLY as of LocalDb Phase 1. <see cref="OperationTrackingStore"/> no longer
|
||||
/// reads this: the tracking table lives in the consolidated site database at
|
||||
/// <c>LocalDb:Path</c>. The only remaining consumer is
|
||||
/// <c>SiteLocalDbLegacyMigrator</c>, which uses it to locate a pre-Phase-1
|
||||
/// <c>site-tracking.db</c> and copy it in once. Safe to delete from a config once that
|
||||
/// node's migration has run.
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; } = "Data Source=site-tracking.db";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user