polish(localdb): final integration-review fixes — drift XML doc, trim core deps, metrics doc/encapsulation
Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -24,6 +24,7 @@ public sealed class LocalDbMetrics : IDisposable
|
|||||||
private readonly Func<DateTimeOffset> _utcNow;
|
private readonly Func<DateTimeOffset> _utcNow;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
|
/// <summary>Creates the meter and its instruments. <paramref name="utcNow"/> is a test seam for the lag gauge's clock; null uses <see cref="DateTimeOffset.UtcNow"/>.</summary>
|
||||||
public LocalDbMetrics(Func<DateTimeOffset>? utcNow = null)
|
public LocalDbMetrics(Func<DateTimeOffset>? utcNow = null)
|
||||||
{
|
{
|
||||||
_utcNow = utcNow ?? (() => DateTimeOffset.UtcNow);
|
_utcNow = utcNow ?? (() => DateTimeOffset.UtcNow);
|
||||||
@@ -41,34 +42,40 @@ public sealed class LocalDbMetrics : IDisposable
|
|||||||
/// Supplies the current unacked oplog backlog for the <c>localdb.oplog.depth</c> gauge. Invoked
|
/// Supplies the current unacked oplog backlog for the <c>localdb.oplog.depth</c> gauge. Invoked
|
||||||
/// only at collection time; a cheap synchronous SELECT is acceptable at that frequency.
|
/// only at collection time; a cheap synchronous SELECT is acceptable at that frequency.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<long>? OplogDepthProvider { get; set; }
|
public Func<long>? OplogDepthProvider { get; internal set; }
|
||||||
|
|
||||||
/// <summary>Supplies the last successful sync time for the <c>localdb.sync.lag.seconds</c> gauge; null means no sync yet, and the gauge then reports no measurement.</summary>
|
/// <summary>Supplies the last successful sync time for the <c>localdb.sync.lag.seconds</c> gauge; null means no sync yet, and the gauge then reports no measurement.</summary>
|
||||||
public Func<DateTimeOffset?>? LastSyncUtcProvider { get; set; }
|
public Func<DateTimeOffset?>? LastSyncUtcProvider { get; internal set; }
|
||||||
|
|
||||||
/// <summary>The backing meter — exposed so a test <see cref="MeterListener"/> can filter by instance reference rather than the process-shared meter name.</summary>
|
/// <summary>The backing meter — exposed so a test <see cref="MeterListener"/> can filter by instance reference rather than the process-shared meter name.</summary>
|
||||||
internal Meter Meter => _meter;
|
internal Meter Meter => _meter;
|
||||||
|
|
||||||
|
/// <summary>Adds <paramref name="count"/> rows applied from inbound delta batches to <c>localdb.sync.applied</c> (no-op when not positive).</summary>
|
||||||
public void RecordApplied(long count)
|
public void RecordApplied(long count)
|
||||||
{
|
{
|
||||||
if (count > 0) _applied.Add(count);
|
if (count > 0) _applied.Add(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Adds <paramref name="count"/> inbound rows that lost LWW to <c>localdb.sync.conflicts_discarded</c> (no-op when not positive).</summary>
|
||||||
public void RecordConflictsDiscarded(long count)
|
public void RecordConflictsDiscarded(long count)
|
||||||
{
|
{
|
||||||
if (count > 0) _conflictsDiscarded.Add(count);
|
if (count > 0) _conflictsDiscarded.Add(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Adds <paramref name="count"/> poison / over-drift rows routed to the dead-letter table to <c>localdb.sync.dead_lettered</c> (no-op when not positive).</summary>
|
||||||
public void RecordDeadLettered(long count)
|
public void RecordDeadLettered(long count)
|
||||||
{
|
{
|
||||||
if (count > 0) _deadLettered.Add(count);
|
if (count > 0) _deadLettered.Add(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Increments <c>localdb.sync.reconnects</c> for one initiator reconnect attempt.</summary>
|
||||||
public void RecordReconnect() => _reconnects.Add(1);
|
public void RecordReconnect() => _reconnects.Add(1);
|
||||||
|
|
||||||
|
/// <summary>Increments <c>localdb.sync.snapshots</c> with <c>direction=sent</c> for one completed outbound snapshot.</summary>
|
||||||
public void RecordSnapshotSent() =>
|
public void RecordSnapshotSent() =>
|
||||||
_snapshots.Add(1, new KeyValuePair<string, object?>("direction", "sent"));
|
_snapshots.Add(1, new KeyValuePair<string, object?>("direction", "sent"));
|
||||||
|
|
||||||
|
/// <summary>Increments <c>localdb.sync.snapshots</c> with <c>direction=received</c> for one completed inbound snapshot.</summary>
|
||||||
public void RecordSnapshotReceived() =>
|
public void RecordSnapshotReceived() =>
|
||||||
_snapshots.Add(1, new KeyValuePair<string, object?>("direction", "received"));
|
_snapshots.Add(1, new KeyValuePair<string, object?>("direction", "received"));
|
||||||
|
|
||||||
@@ -99,6 +106,7 @@ public sealed class LocalDbMetrics : IDisposable
|
|||||||
return [new Measurement<double>(lag)];
|
return [new Measurement<double>(lag)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Disposes the backing meter; idempotent.</summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ namespace ZB.MOM.WW.LocalDb.Replication;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class LocalDbSchemaMismatchException : Exception
|
public sealed class LocalDbSchemaMismatchException : Exception
|
||||||
{
|
{
|
||||||
|
/// <summary>Creates the exception with a message describing the schema disagreement.</summary>
|
||||||
public LocalDbSchemaMismatchException(string message) : base(message) { }
|
public LocalDbSchemaMismatchException(string message) : base(message) { }
|
||||||
|
|
||||||
|
/// <summary>Creates the exception with a message and the underlying cause.</summary>
|
||||||
public LocalDbSchemaMismatchException(string message, Exception innerException)
|
public LocalDbSchemaMismatchException(string message, Exception innerException)
|
||||||
: base(message, innerException) { }
|
: base(message, innerException) { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ public sealed class LocalDbSyncService : LocalDbSync.LocalDbSyncBase
|
|||||||
// 0 = idle, 1 = a stream is active. Interlocked-guarded across concurrent calls (singleton service).
|
// 0 = idle, 1 = a stream is active. Interlocked-guarded across concurrent calls (singleton service).
|
||||||
private int _active;
|
private int _active;
|
||||||
|
|
||||||
// status is taken as the public ISyncStatus (SyncStatus is internal and cannot appear in a
|
/// <summary>
|
||||||
// public signature); DI always supplies the concrete SyncStatus this cast recovers.
|
/// Creates the passive sync service. <paramref name="status"/> is taken as the public
|
||||||
|
/// <see cref="ISyncStatus"/> (SyncStatus is internal and cannot appear in a public signature);
|
||||||
|
/// DI always supplies the concrete SyncStatus the ctor recovers by cast.
|
||||||
|
/// </summary>
|
||||||
public LocalDbSyncService(
|
public LocalDbSyncService(
|
||||||
ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory,
|
ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory,
|
||||||
LocalDbMetrics? metrics = null, ISyncStatus? status = null)
|
LocalDbMetrics? metrics = null, ISyncStatus? status = null)
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ public sealed class ReplicationOptions
|
|||||||
/// <summary>Maximum tolerated HLC drift where a peer's clock runs ahead of ours.</summary>
|
/// <summary>Maximum tolerated HLC drift where a peer's clock runs ahead of ours.</summary>
|
||||||
public TimeSpan MaxHlcDriftAhead { get; set; } = TimeSpan.FromMinutes(5);
|
public TimeSpan MaxHlcDriftAhead { get; set; } = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
/// <summary>When true, drift beyond <see cref="MaxHlcDriftAhead"/> rejects the batch instead of clamping.</summary>
|
/// <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; }
|
public bool FailClosedOnDrift { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public sealed class ReplicationOptionsValidator : IValidateOptions<ReplicationOp
|
|||||||
failures.Add(
|
failures.Add(
|
||||||
$"LocalDb:Replication:PeerAddress must be an absolute http/https URI; got '{options.PeerAddress}'.");
|
$"LocalDb:Replication:PeerAddress must be an absolute http/https URI; got '{options.PeerAddress}'.");
|
||||||
|
|
||||||
|
// ApiKey needs no validation: null/empty legitimately means "no key auth".
|
||||||
|
|
||||||
if (options.FlushInterval <= TimeSpan.Zero)
|
if (options.FlushInterval <= TimeSpan.Zero)
|
||||||
failures.Add($"LocalDb:Replication:FlushInterval must be greater than zero; got {options.FlushInterval}.");
|
failures.Add($"LocalDb:Replication:FlushInterval must be greater than zero; got {options.FlushInterval}.");
|
||||||
if (options.MaxBatchSize <= 0)
|
if (options.MaxBatchSize <= 0)
|
||||||
|
|||||||
@@ -35,8 +35,11 @@ public sealed class SyncBackgroundService : BackgroundService
|
|||||||
/// <summary>Number of connection attempts made this lifetime (one per reconnect loop iteration).</summary>
|
/// <summary>Number of connection attempts made this lifetime (one per reconnect loop iteration).</summary>
|
||||||
internal int ConnectionAttempts;
|
internal int ConnectionAttempts;
|
||||||
|
|
||||||
// status is taken as the public ISyncStatus (SyncStatus is internal and cannot appear in a
|
/// <summary>
|
||||||
// public signature); DI always supplies the concrete SyncStatus this cast recovers.
|
/// Creates the initiating sync host. <paramref name="status"/> is taken as the public
|
||||||
|
/// <see cref="ISyncStatus"/> (SyncStatus is internal and cannot appear in a public signature);
|
||||||
|
/// DI always supplies the concrete SyncStatus the ctor recovers by cast.
|
||||||
|
/// </summary>
|
||||||
public SyncBackgroundService(
|
public SyncBackgroundService(
|
||||||
ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory,
|
ILocalDb db, IOptions<ReplicationOptions> options, ILoggerFactory loggerFactory,
|
||||||
LocalDbMetrics? metrics = null, ISyncStatus? status = null)
|
LocalDbMetrics? metrics = null, ISyncStatus? status = null)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ namespace ZB.MOM.WW.LocalDb.Registration;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class LocalDbRegistrationException : Exception
|
public sealed class LocalDbRegistrationException : Exception
|
||||||
{
|
{
|
||||||
|
/// <summary>Creates the exception with a message describing why registration was rejected.</summary>
|
||||||
public LocalDbRegistrationException(string message) : base(message) { }
|
public LocalDbRegistrationException(string message) : base(message) { }
|
||||||
|
|
||||||
|
/// <summary>Creates the exception with a message and the underlying cause.</summary>
|
||||||
public LocalDbRegistrationException(string message, Exception innerException)
|
public LocalDbRegistrationException(string message, Exception innerException)
|
||||||
: base(message, innerException) { }
|
: base(message, innerException) { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user