fc48db480c
One scoped read that answers "is anything waiting to be deployed?" for the whole fleet: each site's stored artifact revision hash compared against what a deploy would send right now (NeverDeployed / Current / OutOfDate), plus per-site deployed/stale/unknown instance tallies from the batched staleness evaluation. NeverDeployed is deliberately NOT a pending update — the operator has not deployed yet — and exceptions propagate so the caller's single-flight cache can render "unavailable" rather than invent an "up to date". Adds the validated PendingUpdatesCacheTtl (60 s) that TTL will key off. Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
94 lines
4.7 KiB
C#
94 lines
4.7 KiB
C#
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager;
|
|
|
|
/// <summary>
|
|
/// Configuration options for the central-side Deployment Manager.
|
|
/// </summary>
|
|
public class DeploymentManagerOptions
|
|
{
|
|
/// <summary>
|
|
/// Timeout for a lifecycle command round-trip (disable, enable, delete).
|
|
/// Applied as a linked-CTS deadline in <c>DeploymentService</c> so a hung or
|
|
/// unreachable site does not hold the per-instance operation lock indefinitely.
|
|
/// </summary>
|
|
public TimeSpan LifecycleCommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
|
|
|
|
/// <summary>Timeout per site for system-wide artifact deployment.</summary>
|
|
public TimeSpan ArtifactDeploymentTimeoutPerSite { get; set; } = TimeSpan.FromSeconds(120);
|
|
|
|
/// <summary>Timeout for acquiring an operation lock on an instance.</summary>
|
|
public TimeSpan OperationLockTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
|
|
|
/// <summary>
|
|
/// Maximum number of instance deployments whose SITE ROUND-TRIP runs
|
|
/// concurrently during a bulk <c>DeploySiteAsync</c>. The flatten/validate and
|
|
/// persistence phases stay serial regardless (they share one non-thread-safe
|
|
/// <c>DbContext</c>); only the network wait is fanned out.
|
|
///
|
|
/// <para>
|
|
/// Bounded rather than unbounded so a large site cannot open one in-flight
|
|
/// gRPC command per instance against a two-node site pair. Four mirrors the
|
|
/// conservative end of the site's own apply concurrency; raise it only with
|
|
/// evidence from the target-scale load test.
|
|
/// </para>
|
|
/// </summary>
|
|
public int SiteDeploymentMaxParallelism { get; set; } = 4;
|
|
|
|
/// <summary>
|
|
/// Per-instance deadline applied to the site round-trip inside a bulk
|
|
/// <c>DeploySiteAsync</c>. One unreachable or wedged instance is recorded as a
|
|
/// failed row and the rest of the batch continues — the bulk operation never
|
|
/// inherits a single instance's hang.
|
|
/// </summary>
|
|
public TimeSpan SiteDeploymentTimeoutPerInstance { get; set; } = TimeSpan.FromSeconds(120);
|
|
|
|
/// <summary>
|
|
/// Deadline applied to each query-before-redeploy reconciliation round-trip
|
|
/// inside a bulk <c>DeploySiteAsync</c> (the single-instance path keeps the
|
|
/// communication layer's own <c>QueryTimeout</c>).
|
|
///
|
|
/// <para>
|
|
/// Phase 1 is strictly serial, so an unreachable site otherwise costs
|
|
/// <c>QueryTimeout</c> (30 s) PER INSTANCE before a single deploy is sent — for
|
|
/// a 500-instance site roughly four hours, far past the CLI's 5-minute bulk
|
|
/// deploy timeout, which then cancels and re-arms the identical cost on the next
|
|
/// attempt. Deliberately short (seconds): a reconcile that does not answer
|
|
/// promptly falls through to a normal deploy anyway, which is exactly what its
|
|
/// own timeout produces. Works together with the once-per-batch reachability
|
|
/// circuit-breaker, which skips the remaining reconciles after the first
|
|
/// timeout.
|
|
/// </para>
|
|
/// </summary>
|
|
public TimeSpan SiteDeploymentReconcileTimeout { get; set; } = TimeSpan.FromSeconds(5);
|
|
|
|
/// <summary>
|
|
/// Retention window for TERMINAL deployment records (<c>Success</c>,
|
|
/// <c>Failed</c>). Deployments are insert-only — one row per attempt, forever —
|
|
/// so without a window the table grows without bound and every unfiltered
|
|
/// deployment query gets slower with the age of the system.
|
|
///
|
|
/// <para>
|
|
/// Deliberately generous (one year) because deployment history is
|
|
/// operator-facing forensics, and deliberately terminal-only: an
|
|
/// <c>InProgress</c> or <c>Pending</c> row is never purged regardless of age,
|
|
/// because it is exactly the row the query-before-redeploy reconciliation
|
|
/// path needs to find. Set to <see cref="TimeSpan.Zero"/> to disable purging.
|
|
/// </para>
|
|
/// </summary>
|
|
public TimeSpan TerminalDeploymentRecordRetention { get; set; } = TimeSpan.FromDays(365);
|
|
|
|
/// <summary>
|
|
/// How long the Central UI pending-updates snapshot stays fresh; one fleet
|
|
/// flatten per TTL while any Deployer/Administrator circuit is open.
|
|
///
|
|
/// <para>
|
|
/// The snapshot is a whole-fleet read (every site's artifact hash plus every
|
|
/// deployed instance's staleness), so it is held behind a single-flight TTL
|
|
/// cache: however many circuits poll, and however often, the fleet is evaluated
|
|
/// at most once per this window — and not at all when nobody is looking. Must be
|
|
/// positive; zero would make every poll a cache miss and re-flatten the fleet per
|
|
/// circuit, which is precisely the cost the cache exists to avoid.
|
|
/// </para>
|
|
/// </summary>
|
|
public TimeSpan PendingUpdatesCacheTtl { get; set; } = TimeSpan.FromSeconds(60);
|
|
}
|