perf(site-runtime): warm the next startup batch during the stagger delay and compile a batch's distinct units in parallel (review 2026-10-21 01/SR-P3)
DeployCompileValidator.WarmCompileCache dedupes script bodies/trigger expressions across a batch and compiles them with Parallel.ForEach (half the cores). CreateInstanceActorBatch starts the next batch's warm as it arms the stagger timer; creation order and delay are unchanged. The warm's parsed configs are reused at creation.
This commit is contained in:
@@ -154,6 +154,69 @@ public sealed class DeployCompileValidator
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the shared compile cache for a whole startup batch (review 2026-10-21 01/SR-P3):
|
||||
/// every DISTINCT script body and trigger expression across <paramref name="configs"/> is
|
||||
/// compiled once, up to <paramref name="maxDegreeOfParallelism"/> at a time. Same compile units
|
||||
/// as <see cref="Validate(FlattenedConfiguration, string?)"/> — scripts and alarm on-trigger
|
||||
/// scripts against <c>ScriptGlobals</c>, Expression triggers against
|
||||
/// <c>TriggerExpressionGlobals</c> — but deduplicated first, because the cache is keyed on
|
||||
/// code: N instances of one template would otherwise race N identical cold compiles in
|
||||
/// parallel. Best-effort: verdicts are discarded and a throwing unit is skipped, since every
|
||||
/// Instance Actor's own compile remains the correctness backstop.
|
||||
/// </summary>
|
||||
/// <param name="configs">The batch's parsed configurations.</param>
|
||||
/// <param name="maxDegreeOfParallelism">Upper bound on concurrent Roslyn compiles.</param>
|
||||
public void WarmCompileCache(IReadOnlyList<FlattenedConfiguration> configs, int maxDegreeOfParallelism)
|
||||
{
|
||||
var units = new Dictionary<(bool IsTrigger, string Code), string>();
|
||||
foreach (var config in configs)
|
||||
{
|
||||
foreach (var script in config.Scripts)
|
||||
{
|
||||
units.TryAdd((false, script.Code), script.CanonicalName);
|
||||
AddTriggerUnit(units, script.TriggerType, script.TriggerConfiguration,
|
||||
$"script-trigger-{script.CanonicalName}");
|
||||
}
|
||||
|
||||
// Alarm on-trigger scripts compile the referenced SCRIPT's body against ScriptGlobals,
|
||||
// i.e. the same cache key as that script — already a unit above.
|
||||
foreach (var alarm in config.Alarms)
|
||||
AddTriggerUnit(units, alarm.TriggerType, alarm.TriggerConfiguration,
|
||||
$"alarm-trigger-expr-{alarm.CanonicalName}");
|
||||
}
|
||||
|
||||
Parallel.ForEach(
|
||||
units,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = Math.Max(1, maxDegreeOfParallelism) },
|
||||
unit =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (unit.Key.IsTrigger)
|
||||
_compilationService.CompileTriggerExpression(unit.Value, unit.Key.Code);
|
||||
else
|
||||
_compilationService.Compile(unit.Value, unit.Key.Code);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort warm — the Instance Actor's PreStart compiles it the hard way.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddTriggerUnit(
|
||||
Dictionary<(bool IsTrigger, string Code), string> units,
|
||||
string? triggerType, string? triggerConfigJson, string compileName)
|
||||
{
|
||||
if (!string.Equals(triggerType, "Expression", StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
var expression = TriggerExpressionGlobals.ExtractExpression(triggerConfigJson);
|
||||
if (expression != null)
|
||||
units.TryAdd((true, expression), compileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review 2026-10-21 01/SR-C1: every name that becomes an Akka actor name on the site — the
|
||||
/// Instance Actor itself (<c>DeploymentManagerActor.CreateInstanceActor</c>) and its
|
||||
|
||||
Reference in New Issue
Block a user