Cold Starts in Azure Functions: Wat Eraan Te Doen Valt

Cold starts zijn de achilleshiel van serverless. Praktische technieken om de opstarttijd van Azure Functions drastisch te verlagen.

Jean-Pierre Broeders

Freelance DevOps Engineer

13 maart 20265 min. leestijd
Cold Starts in Azure Functions: Wat Eraan Te Doen Valt

Cold Starts in Azure Functions: Wat Eraan Te Doen Valt

Serverless is fantastisch tot de eerste request van de dag binnenkomt. Dan merk je het: twee, drie, soms vijf seconden wachttijd. De cold start. Voor een achtergrondtaak maakt niemand zich druk. Maar zit er een gebruiker achter een scherm te wachten? Dan wordt het een probleem.

Waarom cold starts bestaan

Azure Functions draait niet permanent. Bij het Consumption plan wordt de onderliggende infrastructuur opgeruimd na een paar minuten inactiviteit. Komt er een nieuw request binnen, dan moet Azure een host opstarten, de runtime laden, dependencies resolven en pas dan de code uitvoeren. Dat kost tijd.

De duur hangt af van meerdere factoren. De runtime maakt verschil — .NET functions zijn over het algemeen sneller dan Node.js of Python, omdat de .NET worker al dicht tegen de Azure-infra aan zit. Maar ook het aantal dependencies speelt mee. Een function met twintig NuGet packages heeft meer tijd nodig dan eentje met twee.

Meten voordat je optimaliseert

Voordat er iets aangepast wordt: meten. Application Insights geeft standaard al cold start metrics, maar de meeste teams kijken er nooit naar. Deze Kusto query haalt de gemiddelde opstarttijd op:

requests
| where timestamp > ago(7d)
| where name == "FunctionName"
| extend coldStart = tobool(customDimensions["ms-azurefunctions-coldstart"])
| where coldStart == true
| summarize avg(duration), percentile(duration, 95), count() by bin(timestamp, 1h)

Die P95 waarde is waar het om draait. Gemiddelden liegen — als 5% van de requests acht seconden duurt, heb je een probleem dat het gemiddelde van 400ms maskeert.

Premium Plan: de meest voor de hand liggende fix

Het Premium plan (Elastic Premium) houdt altijd minimaal één warme instantie beschikbaar. Geen cold starts meer voor de eerste request. Extra instanties worden nog steeds on-demand opgeschaald, maar die basis is er altijd.

De kosten zijn hoger. Reken op €150-200 per maand voor een EP1 instance, tegenover misschien €10-20 bij Consumption voor een gemiddelde workload. Of dat het waard is, hangt af van het gebruik.

PlanCold StartKosten (indicatief)Geschikt voor
Consumption2-10 seconden€5-30/maandAchtergrondtaken, lage volumes
Premium (EP1)Geen (warme instantie)€150-200/maandAPIs, gebruiker-gerichte endpoints
Dedicated (App Service)Geen€50-300/maandVoorspelbare, constante load

Warmhoud-strategie met een timer trigger

Wie niet naar Premium wil overstappen, kan een simpele timer trigger gebruiken die de function elke vier minuten aanroept. Hiermee blijft de host actief:

[Function("KeepWarm")]
public async Task Run(
    [TimerTrigger("0 */4 * * * *")] TimerInfo timer,
    FunctionContext context)
{
    var logger = context.GetLogger("KeepWarm");
    logger.LogInformation("Warm ping at {time}", DateTime.UtcNow);
}

Simpel en effectief. Maar het is een workaround, geen oplossing. Bij meerdere functions in hetzelfde function app hoeft het maar één keer — de hele host blijft warm. Het nadeel: betalen voor die extra executions. Bij Consumption is dat verwaarloosbaar, maar het voelt niet schoon.

Dependencies minimaliseren

Elke dependency die geladen moet worden, kost opstarttijd. Een paar concrete maatregelen:

Lazy loading — initialiseer zware services pas bij eerste gebruik:

private static readonly Lazy<HttpClient> _httpClient = 
    new(() => new HttpClient());

private static readonly Lazy<CosmosClient> _cosmosClient = 
    new(() => new CosmosClient(
        Environment.GetEnvironmentVariable("CosmosConnection")));

Trimmen van ongebruikte packages — na een paar maanden development zitten er altijd NuGet packages in die niemand meer nodig heeft. Een dotnet list package gevolgd door opruimen scheelt soms honderden milliseconden.

Ready-to-run publishing — compileer naar native images met ReadyToRun:

<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

Dit vergroot de deployment package, maar de JIT-compilatie bij opstarten wordt overgeslagen. Voor .NET isolated worker functions scheelt dit merkbaar.

Isolated worker vs In-process

Sinds .NET 8 is het isolated worker model de standaard. Het draait in een apart proces, wat meer flexibiliteit geeft maar ook iets meer overhead bij het opstarten. De communicatie tussen host en worker gaat over gRPC.

In-process functions (het oude model) starten sneller op omdat ze in hetzelfde proces als de host draaien. Maar Microsoft fasert dit uit — .NET 9 ondersteunt alleen nog isolated. Dus optimaliseren binnen isolated is de enige houdbare strategie.

Een ding dat helpt: minimaliseer het aantal middleware components. Elke middleware in de pipeline wordt bij opstarten geïnitialiseerd. Twee of drie is prima. Tien is te veel.

Netwerk en regio

Wordt er vanuit de function een database of externe API aangeroepen? Dan telt de netwerk latency mee bij de eerste request. Een cold start van drie seconden plus een database connection van twee seconden voelt als vijf seconden cold start, ook al is de helft netwerk.

Connection pooling helpt. Statische HttpClient instances (nooit per-request aanmaken). En de function app in dezelfde regio deployen als de database — klinkt logisch, maar het wordt regelmatig vergeten bij multi-regio setups.

De pragmatische aanpak

Niet elke function heeft optimalisatie nodig. Een nachtelijke batch job die om 03:00 draait? Daar merkt niemand een cold start van vijf seconden. Een API endpoint achter een mobiele app? Daar maakt het wel uit.

De strategie die in de praktijk het beste werkt:

  1. Meet cold start tijden via Application Insights
  2. Identificeer welke functions user-facing zijn
  3. Premium plan voor kritieke endpoints, Consumption voor de rest
  4. Optimaliseer dependencies ongeacht het plan
  5. Monitor na wijzigingen — verbeteringen zijn niet altijd zo groot als verwacht

Cold starts zijn niet oplosbaar, maar wel beheersbaar. Het verschil tussen een frustrerende en een acceptabele ervaring zit hem vaak in een paar gerichte aanpassingen.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op