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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user