perf(siteruntime): projected single-row deployed-config reads (P1-4)

HandleEnsureInstanceStarted, HandleDeploymentStateQuery, and HandleEnable each
called GetAllDeployedConfigsAsync — materializing every deployed config,
including every ConfigJson blob — just to FirstOrDefault one row. A
volume-restore reconcile healing hundreds of instances turned that into
hundreds of full-table reads.

Add SiteStorageService.GetDeployedConfigAsync(name) (single-row, full
projection incl. ConfigJson, for callers that (re)start an Instance Actor)
and GetDeployedConfigMetadataAsync(name) (single-row, ConfigJson-free
projection, for callers that only need DeploymentId/RevisionHash/IsEnabled).
Switch the three call sites; leave decisions/semantics identical.

HandleEnsureInstanceStarted gets its own _deployedConfigLoader seam,
deliberately separate from the existing _configLoader (still used by the
boot-time load, which legitimately needs every row) so a test overriding one
cannot accidentally gate the other. Generation fencing (StoreGenerationAtRead,
Medium wave T1) is untouched: the generation is still captured on the actor
thread before the read dispatches.

Tests: new SiteStorageServiceTests lock in that both projections return the
same values GetAllDeployedConfigsAsync().FirstOrDefault(...) did. The three
row-101 ensure-started race tests (ReconcileStartsInstanceTests) are ported
to the new seam and still pass, proving generation fencing survives the
projection. Proof of failure: since this is a pure I/O-cost fix with no
observable behavior change, the new/changed tests were confirmed to fail to
even compile against the unfixed code (stash/unstash) — GetDeployedConfigAsync,
GetDeployedConfigMetadataAsync, and the deployedConfigLoader ctor param don't
exist there.
This commit is contained in:
Joseph Doherty
2026-08-19 16:15:24 -04:00
parent 942acc37b4
commit 65b3c23239
4 changed files with 227 additions and 24 deletions
@@ -108,6 +108,89 @@ public class SiteStorageService
return results;
}
/// <summary>
/// Returns the single deployed instance configuration for <paramref name="instanceUniqueName"/>,
/// or <see langword="null"/> if no row exists for it.
/// </summary>
/// <remarks>
/// Register P1-4: <see cref="GetAllDeployedConfigsAsync"/> plus a <c>FirstOrDefault</c> was
/// materializing every deployed config — including every <c>ConfigJson</c> blob — just to find
/// one row. A volume-restore reconcile healing hundreds of instances turned that into hundreds
/// of full-table reads. Callers that need the full row (including <c>ConfigJson</c>) for exactly
/// one instance — e.g. to (re)start its Instance Actor — should use this instead. Callers that
/// only need identity/staleness metadata should use <see cref="GetDeployedConfigMetadataAsync"/>,
/// which never touches the <c>config_json</c> column.
/// </remarks>
/// <param name="instanceUniqueName">The unique name of the instance.</param>
/// <returns>
/// A task that resolves to the deployed configuration row, or <see langword="null"/> if none
/// exists for the instance.
/// </returns>
public async Task<DeployedInstance?> GetDeployedConfigAsync(string instanceUniqueName)
{
await using var connection = OpenConnection();
await using var command = connection.CreateCommand();
command.CommandText = @"
SELECT instance_unique_name, config_json, deployment_id, revision_hash, is_enabled, deployed_at
FROM deployed_configurations
WHERE instance_unique_name = @name
LIMIT 1";
command.Parameters.AddWithValue("@name", instanceUniqueName);
await using var reader = await command.ExecuteReaderAsync();
if (!await reader.ReadAsync())
return null;
return new DeployedInstance
{
InstanceUniqueName = reader.GetString(0),
ConfigJson = reader.GetString(1),
DeploymentId = reader.GetString(2),
RevisionHash = reader.GetString(3),
IsEnabled = reader.GetInt64(4) != 0,
DeployedAt = reader.GetString(5)
};
}
/// <summary>
/// Returns the deployment-identity metadata for <paramref name="instanceUniqueName"/> —
/// <c>(name, deployment id, revision hash, is_enabled)</c> — without reading the
/// <c>config_json</c> column, or <see langword="null"/> if no row exists for it.
/// </summary>
/// <remarks>
/// Register P1-4: for a caller that only needs staleness/identity metadata (e.g. the
/// central deployment-state query, which reports only <c>DeploymentId</c>/<c>RevisionHash</c>),
/// reading the <c>config_json</c> blob is pure waste. This projection is that lighter read.
/// </remarks>
/// <param name="instanceUniqueName">The unique name of the instance.</param>
/// <returns>
/// A task that resolves to the metadata row, or <see langword="null"/> if none exists for the
/// instance.
/// </returns>
public async Task<DeployedConfigMetadata?> GetDeployedConfigMetadataAsync(string instanceUniqueName)
{
await using var connection = OpenConnection();
await using var command = connection.CreateCommand();
command.CommandText = @"
SELECT instance_unique_name, deployment_id, revision_hash, is_enabled
FROM deployed_configurations
WHERE instance_unique_name = @name
LIMIT 1";
command.Parameters.AddWithValue("@name", instanceUniqueName);
await using var reader = await command.ExecuteReaderAsync();
if (!await reader.ReadAsync())
return null;
return new DeployedConfigMetadata(
InstanceUniqueName: reader.GetString(0),
DeploymentId: reader.GetString(1),
RevisionHash: reader.GetString(2),
IsEnabled: reader.GetInt64(3) != 0);
}
/// <summary>
/// Stores or updates a deployed instance configuration. Uses UPSERT semantics.
/// </summary>
@@ -939,6 +1022,22 @@ public class DeployedInstance
public string DeployedAt { get; init; } = string.Empty;
}
/// <summary>
/// Deployment-identity metadata for one deployed instance — the projection of
/// <see cref="DeployedInstance"/> that omits <c>ConfigJson</c>. Returned by
/// <see cref="SiteStorageService.GetDeployedConfigMetadataAsync"/> for callers that only need to
/// answer "is this instance deployed, and at what revision" (register P1-4).
/// </summary>
/// <param name="InstanceUniqueName">The unique name of the instance.</param>
/// <param name="DeploymentId">The unique deployment identifier.</param>
/// <param name="RevisionHash">The configuration revision hash for staleness detection.</param>
/// <param name="IsEnabled">Whether the instance is enabled.</param>
public record DeployedConfigMetadata(
string InstanceUniqueName,
string DeploymentId,
string RevisionHash,
bool IsEnabled);
/// <summary>
/// Represents a shared script stored locally in SQLite.
/// </summary>