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:
@@ -88,6 +88,80 @@ public class SiteStorageServiceTests : IAsyncLifetime, IDisposable
|
||||
Assert.True(configs[0].IsEnabled);
|
||||
}
|
||||
|
||||
// ── Register P1-4: projected single-row reads ──
|
||||
//
|
||||
// GetAllDeployedConfigsAsync + FirstOrDefault materializes every deployed config —
|
||||
// including every ConfigJson blob — to find one row. These lock in that
|
||||
// GetDeployedConfigAsync (single row, still carries ConfigJson) and
|
||||
// GetDeployedConfigMetadataAsync (single row, deliberately WITHOUT ConfigJson) return
|
||||
// exactly the same decision the old full-table-plus-FirstOrDefault path did — the
|
||||
// defect here is pure I/O cost, not an observable behavior difference, so these tests
|
||||
// pin the new projection's contract rather than reproduce a behavior bug.
|
||||
|
||||
[Fact]
|
||||
public async Task GetDeployedConfigAsync_ReturnsSameRowAs_GetAllThenFirstOrDefault()
|
||||
{
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump1", "{\"test\":true}", "dep-001", "sha256:abc", isEnabled: true);
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump2", "{\"other\":1}", "dep-002", "sha256:def", isEnabled: false);
|
||||
|
||||
var viaProjection = await _storage.GetDeployedConfigAsync("Pump1");
|
||||
var viaFullScan = (await _storage.GetAllDeployedConfigsAsync())
|
||||
.FirstOrDefault(c => c.InstanceUniqueName == "Pump1");
|
||||
|
||||
Assert.NotNull(viaProjection);
|
||||
Assert.NotNull(viaFullScan);
|
||||
Assert.Equal(viaFullScan.InstanceUniqueName, viaProjection.InstanceUniqueName);
|
||||
Assert.Equal(viaFullScan.ConfigJson, viaProjection.ConfigJson);
|
||||
Assert.Equal(viaFullScan.DeploymentId, viaProjection.DeploymentId);
|
||||
Assert.Equal(viaFullScan.RevisionHash, viaProjection.RevisionHash);
|
||||
Assert.Equal(viaFullScan.IsEnabled, viaProjection.IsEnabled);
|
||||
Assert.Equal(viaFullScan.DeployedAt, viaProjection.DeployedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDeployedConfigAsync_UnknownInstance_ReturnsNull()
|
||||
{
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump1", "{}", "dep-001", "sha256:abc", isEnabled: true);
|
||||
|
||||
var result = await _storage.GetDeployedConfigAsync("NeverHeardOf");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDeployedConfigMetadataAsync_ReturnsSameIdentityAs_GetAllThenFirstOrDefault()
|
||||
{
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump1", "{\"test\":true}", "dep-001", "sha256:abc", isEnabled: true);
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump2", "{\"other\":1}", "dep-002", "sha256:def", isEnabled: false);
|
||||
|
||||
var metadata = await _storage.GetDeployedConfigMetadataAsync("Pump2");
|
||||
var viaFullScan = (await _storage.GetAllDeployedConfigsAsync())
|
||||
.FirstOrDefault(c => c.InstanceUniqueName == "Pump2");
|
||||
|
||||
Assert.NotNull(metadata);
|
||||
Assert.NotNull(viaFullScan);
|
||||
Assert.Equal(viaFullScan.InstanceUniqueName, metadata.InstanceUniqueName);
|
||||
Assert.Equal(viaFullScan.DeploymentId, metadata.DeploymentId);
|
||||
Assert.Equal(viaFullScan.RevisionHash, metadata.RevisionHash);
|
||||
Assert.Equal(viaFullScan.IsEnabled, metadata.IsEnabled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDeployedConfigMetadataAsync_UnknownInstance_ReturnsNull()
|
||||
{
|
||||
await _storage.StoreDeployedConfigAsync(
|
||||
"Pump1", "{}", "dep-001", "sha256:abc", isEnabled: true);
|
||||
|
||||
var result = await _storage.GetDeployedConfigMetadataAsync("NeverHeardOf");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StoreDeployedConfig_Upserts_OnConflict()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user