Skip to content

Commit 0f44811

Browse files
committed
Preserve sim prewarm across transport rotation
1 parent bc21172 commit 0f44811

6 files changed

Lines changed: 123 additions & 24 deletions

File tree

connect/SIM-LATENCY.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ evidence (round-trip link latency and bytes/second), materializes
101101
final `client_connection_reliability_score` rows
102102
for every score lookback — rather than backfilling raw reliability blocks,
103103
which fought the running-window/shift/degraded maintenance. The performance
104-
evidence is attached to the current connection so later pipeline refreshes keep
105-
seeing it; connection tests belong to a particular platform transport, and a
106-
new active transport can otherwise make the entire fixture look untested. Each
104+
evidence is attached to the current connection, and prewarmed pipeline refreshes
105+
restore it on the provider-scoped derived snapshot; connection tests belong to a
106+
particular platform transport, and a new active transport can otherwise make
107+
the entire fixture look untested. Each
107108
reliability row uses the fixture provider's seeded
108109
`uptime / (uptime + downtime)` duty cycle, so a mature mobile churn profile does
109110
not rank as perfectly reliable. The pipeline then runs in prewarmed mode

connect/sim-latency/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ The warm-up phases and the knobs that make it **as fast as possible**:
360360
`--prewarm` writes the fixture's round-trip latency and bandwidth evidence onto
361361
each active connection, then writes the final reliability scores for every
362362
connected provider using its seeded uptime duty cycle, rather than replaying
363-
~8.4h of history. Attaching the evidence to the active connection makes later
364-
pipeline snapshots preserve it; tests belong to one transport, so a replacement
363+
~8.4h of history. Prewarmed pipeline refreshes restore that evidence on the
364+
provider-scoped snapshot; tests belong to one transport, so a replacement
365365
transport can otherwise make an established provider look untested.
366366
`--prewarm 0` restores
367367
the true cold start and live connection-test behavior. A shorter

connect/sim-latency/provision.go

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,15 @@ func generatedClientPoolEntries(config *Config) []ProviderEntry {
301301
//
302302
// The pipeline must run in prewarmed mode afterwards (Services.SetPrewarmed),
303303
// so the periodic reliability-score recompute does not overwrite these rows;
304-
// it keeps refreshing the location reliabilities (so churn still gates
305-
// selection) and re-exporting the redis samples.
306-
func provisionPrewarm(ctx context.Context, lookback time.Duration, entries []ProviderEntry) error {
304+
// it refreshes connected/location state, restores provider-scoped fixture
305+
// performance after mechanical transport replacement, and re-exports the
306+
// redis samples (so churn still gates selection).
307+
func provisionPrewarm(
308+
ctx context.Context,
309+
lookback time.Duration,
310+
entries []ProviderEntry,
311+
services *Services,
312+
) error {
307313
reliabilities, err := matureProviderReliabilities(entries)
308314
if err != nil {
309315
return err
@@ -312,6 +318,7 @@ func provisionPrewarm(ctx context.Context, lookback time.Duration, entries []Pro
312318
if err != nil {
313319
return err
314320
}
321+
services.SetPrewarmed(performances)
315322

316323
// The prewarm is DB-heavy (a fleet-wide reliability rebuild plus a score
317324
// upsert). With a very large fleet connected, postgres can be transiently
@@ -425,8 +432,9 @@ func prewarmOnce(
425432

426433
// build network_client_location_reliability from the currently connected,
427434
// latency/speed-tested providers
428-
writeMatureProviderPerformances(ctx, performances)
435+
writeMatureProviderConnectionPerformances(ctx, performances)
429436
model.UpdateClientLocationReliabilities(ctx, now.Add(-lookback), now)
437+
writeMatureProviderPerformanceSnapshot(ctx, performances)
430438
writeMatureReliabilityScores(ctx, now, lookback, reliabilities)
431439
return nil
432440
}
@@ -435,9 +443,9 @@ func prewarmOnce(
435443
// building the location-reliability snapshot. Completed tests belong to a
436444
// specific platform transport, so relying on an earlier transport's tests made
437445
// a newly active connection look untested and excluded it from matchmaking.
438-
// Writing the connection tables, instead of only the derived snapshot, also
439-
// makes later pipeline refreshes preserve the prewarmed evidence.
440-
func writeMatureProviderPerformances(
446+
// The provider-scoped snapshot restoration below handles later transport
447+
// replacement and pipeline refreshes.
448+
func writeMatureProviderConnectionPerformances(
441449
ctx context.Context,
442450
performances []matureProviderPerformance,
443451
) {
@@ -504,6 +512,50 @@ func writeMatureProviderPerformances(
504512
})
505513
}
506514

515+
// Restores provider-scoped mature-market performance after a location refresh.
516+
// A fixture provider can replace its platform transport before the new
517+
// connection finishes tests; the derived snapshot would otherwise discard the
518+
// provider's established evidence and collapse the matchmaking pool.
519+
func writeMatureProviderPerformanceSnapshot(
520+
ctx context.Context,
521+
performances []matureProviderPerformance,
522+
) {
523+
clientIds := make([]server.Id, 0, len(performances))
524+
minRelativeLatencyMillisValues := make([]int64, 0, len(performances))
525+
maxBytesPerSecondValues := make([]int64, 0, len(performances))
526+
for _, performance := range performances {
527+
clientIds = append(clientIds, performance.clientId)
528+
minRelativeLatencyMillisValues = append(minRelativeLatencyMillisValues, performance.minRelativeLatencyMillis)
529+
maxBytesPerSecondValues = append(maxBytesPerSecondValues, performance.maxBytesPerSecond)
530+
}
531+
532+
server.Db(ctx, func(conn server.PgConn) {
533+
server.RaisePgResult(conn.Exec(
534+
ctx,
535+
`
536+
WITH mature_performance AS (
537+
SELECT client_id, min_relative_latency_ms, max_bytes_per_second
538+
FROM unnest($1::uuid[], $2::bigint[], $3::bigint[])
539+
AS mature(client_id, min_relative_latency_ms, max_bytes_per_second)
540+
)
541+
UPDATE network_client_location_reliability
542+
SET
543+
min_relative_latency_ms = mature.min_relative_latency_ms::integer,
544+
max_bytes_per_second = mature.max_bytes_per_second,
545+
has_latency_test = true,
546+
has_speed_test = true
547+
FROM mature_performance mature
548+
WHERE
549+
network_client_location_reliability.client_id = mature.client_id AND
550+
network_client_location_reliability.connected = true
551+
`,
552+
clientIds,
553+
minRelativeLatencyMillisValues,
554+
maxBytesPerSecondValues,
555+
))
556+
})
557+
}
558+
507559
// Writes the seeded mature reliability beside the real connected/location and
508560
// latency/speed evidence. An inner join keeps disconnected providers out.
509561
func writeMatureReliabilityScores(

connect/sim-latency/provision_test.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func TestMatureProviderPerformancesRejectInvalidGroundTruth(t *testing.T) {
245245
}
246246
}
247247

248-
func TestWriteMatureProviderPerformancesRestoresMatchmakingPool(t *testing.T) {
248+
func TestPrewarmedPipelineRestoresPerformanceAfterTransportReplacement(t *testing.T) {
249249
server.DefaultTestEnv().Run(t, func(t testing.TB) {
250250
ctx := context.Background()
251251
providerNetworkId := server.NewId()
@@ -354,13 +354,39 @@ func TestWriteMatureProviderPerformancesRestoresMatchmakingPool(t *testing.T) {
354354
if err != nil {
355355
t.Fatalf("matureProviderPerformances: %v", err)
356356
}
357-
writeMatureProviderPerformances(ctx, performances)
357+
writeMatureProviderConnectionPerformances(ctx, performances)
358358
model.UpdateClientLocationReliabilities(ctx, server.NowUtc().Add(-13*time.Hour), server.NowUtc())
359+
writeMatureProviderPerformanceSnapshot(ctx, performances)
359360
after := findProviders()
360361
if len(after.Providers) != 1 || after.Providers[0].ClientId != providerClientId {
361362
t.Fatalf("matchmaking providers = %+v, want %s", after.Providers, providerClientId)
362363
}
364+
model.DisconnectNetworkClient(ctx, connectionId)
365+
replacementConnectionId, _, _, _, err := model.ConnectNetworkClient(
366+
ctx,
367+
providerClientId,
368+
"127.0.0.2:20001",
369+
model.CreateNetworkClientHandler(ctx),
370+
)
371+
if err != nil {
372+
t.Fatalf("replacement ConnectNetworkClient: %v", err)
373+
}
374+
if err := model.SetConnectionLocation(
375+
ctx,
376+
replacementConnectionId,
377+
locationId,
378+
&model.ConnectionLocationScores{},
379+
); err != nil {
380+
t.Fatalf("replacement SetConnectionLocation: %v", err)
381+
}
363382
model.UpdateClientLocationReliabilities(ctx, server.NowUtc().Add(-13*time.Hour), server.NowUtc())
383+
withoutRestore := findProviders()
384+
if len(withoutRestore.Providers) != 0 {
385+
t.Fatalf("untested replacement entered matchmaking: %+v", withoutRestore.Providers)
386+
}
387+
services := &Services{}
388+
services.SetPrewarmed(performances)
389+
services.RunPipelineOnce(ctx)
364390
afterRefresh := findProviders()
365391
if len(afterRefresh.Providers) != 1 || afterRefresh.Providers[0].ClientId != providerClientId {
366392
t.Fatalf("providers after pipeline refresh = %+v, want %s", afterRefresh.Providers, providerClientId)

connect/sim-latency/run.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,7 @@ func Run(options *RunOptions) (retErr error) {
439439
// propagate the redis sample export.
440440
if 0 < options.Prewarm {
441441
logf("prewarming: establishing the connected fleet (~%s reliability window)", options.Prewarm)
442-
services.SetPrewarmed(true)
443-
if err := provisionPrewarm(ctx, options.Prewarm, config.Fleet); err != nil {
442+
if err := provisionPrewarm(ctx, options.Prewarm, config.Fleet, services); err != nil {
444443
return phaseError(ctx, "prewarm_failed", "prewarm", err)
445444
}
446445
logf("prewarm complete; running pipeline")

connect/sim-latency/services.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,32 @@ type Services struct {
4141
runErr error
4242

4343
// when prewarmed, the pipeline does not recompute reliability scores (which
44-
// would overwrite the prewarmed scores); it only refreshes the location
45-
// reliabilities (so churn gates selection) and re-exports the redis samples.
44+
// would overwrite the prewarmed scores); it refreshes connected/location
45+
// state, restores provider-scoped fixture performance, and re-exports the
46+
// redis samples (so churn still gates selection).
4647
prewarmed atomic.Bool
48+
49+
prewarmedPerformancesLock sync.RWMutex
50+
prewarmedPerformances []matureProviderPerformance
4751
}
4852

4953
const servicesDrainTimeout = 15 * time.Second
5054

51-
// SetPrewarmed switches the pipeline to prewarmed mode (see the field doc).
52-
func (self *Services) SetPrewarmed(prewarmed bool) {
53-
self.prewarmed.Store(prewarmed)
55+
// SetPrewarmed switches the pipeline to prewarmed mode and freezes the fixture
56+
// performance evidence that each location refresh must restore. The pipeline
57+
// can already be running when warm-up reaches this point, so publish the copied
58+
// evidence before exposing the mode change.
59+
func (self *Services) SetPrewarmed(performances []matureProviderPerformance) {
60+
self.prewarmedPerformancesLock.Lock()
61+
self.prewarmedPerformances = append([]matureProviderPerformance(nil), performances...)
62+
self.prewarmedPerformancesLock.Unlock()
63+
self.prewarmed.Store(true)
64+
}
65+
66+
func (self *Services) prewarmedPerformanceSnapshot() []matureProviderPerformance {
67+
self.prewarmedPerformancesLock.RLock()
68+
defer self.prewarmedPerformancesLock.RUnlock()
69+
return append([]matureProviderPerformance(nil), self.prewarmedPerformances...)
5470
}
5571

5672
// ServicesConfig configures the in-process environment.
@@ -281,9 +297,14 @@ func (self *Services) RunPipelineOnce(ctx context.Context) {
281297
now := server.NowUtc()
282298
if self.prewarmed.Load() {
283299
// keep the location reliabilities fresh (churn -> connected state), then
284-
// re-export the redis samples from the prewarmed scores. Do not recompute
285-
// reliability scores (that would wipe the prewarm).
286-
server.HandleError(func() { model.UpdateClientLocationReliabilities(ctx, now.Add(-12*time.Hour), now) })
300+
// restore the fixture's initial performance evidence. Tests are attached
301+
// to one short-lived platform transport, while the mature-market score is
302+
// provider-scoped and must survive mechanical transport replacement. Do
303+
// not recompute reliability scores (that would wipe the prewarm).
304+
server.HandleError(func() {
305+
model.UpdateClientLocationReliabilities(ctx, now.Add(-12*time.Hour), now)
306+
writeMatureProviderPerformanceSnapshot(ctx, self.prewarmedPerformanceSnapshot())
307+
})
287308
} else {
288309
server.HandleError(func() { model.RollupClientReliabilityStats(ctx, now) })
289310
server.HandleError(func() { model.UpdateClientReliabilityScores(ctx, now, true) })

0 commit comments

Comments
 (0)