Kuinka Azure Durable Functionit skaalautuvat

Joonas Westlin | 04.06.2020
Lukuaika 5 min

Jos et ole vielä kuullut Azure Durable Functioneista, ne mahdollistavat serverless työnkulkujen rakentamisen C#:a tai Javascriptiä käyttäen. Eli sen sijaan, että käyttäisit graafista/deklaratiivista lähestymistapaa kuten Logic Appseissa, Durable Functioneissa työnkulku rakennetaan imperatiivisesti koodia käyttäen.

Koska Durable Functioneita ajetaan Azure Functionien päällä, niiden ajamisen Consumption Planilla pitäisi mahdollistaa periaatteessa loputon skaalaus. Mutta mahdollistaako se?

Lyhyt vastaus: Kyllä ja ei.

Jos haluat lyhyen version, tsekkaa yhteenveto artikkelin lopusta 🙂 Mutta jos haluat syvän katsauksen niin aloitetaan!

Activity functionien skaalaus

Activityt ovat tavallaan askeleita työnkulussasi. Kaikki pääasiallinen työ tehdään activityissä. Ne voivat tehdä melkein mitä tahansa mitä normaali Azure Function voi tehdä: käyttää output bindingeja, tehdä tietokantakyselyitä, yms. Ne ovat paikka jossa ajat ei-deterministiset osat työnkulustasi; ne osat, joita ei voi ajaa orchestrator functioneissa.

Kaikki activity functionit Functions-sovelluksessasi käynnistetään viesteillä yhdestä viestijonosta (”work item queue”). Instanssit kilpailevat viesteistä tästä jonosta, ja mikä tahansa instanssi joka saa viestin, ajaa activityn.

Tämä tarkoittaa sitä, että instanssien määrä, jotka voivat ajaa activityjä on periaatteessa loputon. Sinulla voi olla 1 instanssi prosessoimassa viestejä jonosta tai 1000 instanssia.

Yksittäinen instanssi voi myös ajaa activityjä rinnakkain useilla säikeillä. Sitä ohjataan asetuksella host.json-tiedostossa:

{
  "extensions": {
    "durableTask": {
      "maxConcurrentActivityFunctions": 10
    }
  }
}

Vakiona tämä asetus on 10 kertaa prosessoriydinten määrä instanssissa. Eli jos instanssilla on 4 prosessoriydintä, enintään 40 activity functionia voidaan ajaa samanaikaisesti. Mutta muista, että jokainen ajettu activity käyttää jonkin verran jaettuja resursseja instanssilta. Henkilökohtaisesti nostaisin tätä asetusta ainoastaan suorituskykytestauksen jälkeen.

Azure Functionien nk. scale controller valvoo viestin haun latenssia work item-jonosta ja nostaa/laskee instanssien määrää tarvittaessa. Koska activityt eivät ylläpidä tilaa, tämänkaltainen skaalaus on mahdollista. Orchestrator ja entity functioneissa näin ei ole.

Orchestrator ja entity functioneiden skaalaus

Orchestrator functionit ovat ne, jotka määrittävät työnkulkusi ja voivat kutsua activityjä ja ali-orchestratoreja. Ne myös vaativat, että niihin kirjoitettu koodi on determinististä, koska tämä koodi ajetaan uudelleen ja uudelleen kun työnkulku etenee.

Entity functionit toisaalta toteuttavat actorien tapaisen mallin ja mahdollistavat tilaa ylläpitävien, osoitteellisten entiteetti-instanssien luonnin ja muokkauksen. Niitä voidaan käyttää erilaisiin tarkoituksiin, esimerkiksi datan aggregointiin erilaisista lähteistä. Niillä on myös varmuus, että kaikki pyynnöt niihin ajetaan peräkkäin, poistaen tarpeen huomioida monisäikeistys entiteettien koodissa.

Orchestratorit ja entityt skaalautuvat samantapaisesti. Ne ovat molemmat nk. stateful singletoneja ja niitä ei voi skaalata loputtomalle määrälle instansseja. Se ei saisi ikinä olla mahdollista, että kaksi Functions instanssia ajaa yhtä orchestrator/entity instanssia ja päivittää sen tilaa. Koska historiatauluun ainoastaan lisätään rivejä, olisi katastrofaalista jos sinne tulisi samoja tapahtumia usealta Functions instanssilta, jotka ajavat samaa orchestratoria.

Orchestrator ja entity functionit käynnistetään viesteillä nk. ohjausjonosta (”control queue”). Näitä viestijonoja on N kappaletta, joita Durable Functions käyttää. Niiden määrä riippuu partitioiden määrästä (vakiona neljä), jota voidaan muuttaa host.json-tiedostossa:

{
  "extensions": {
    "durableTask": {
      "storageProvider": {
        "partitionCount": 4
      }
    }
  }
}

Tämä asetus tarkoittaa, että käytetään neljää ohjausjonoa. Vain yksi Functions instanssi voi lukea viestejä yhdestä näistä jonoista. Durable Functions käyttää blob leaseja ”task-hubin-nimi-leases” blob containerissa ohjatakseen mikä instanssi voi lukea mitäkin jonoa. Tämä on se asia, joka varmistaa, että vain yksi Functions instanssi voi lukea yhtä jonoa kerrallaan. Sovelluksessa voi olla yksi instanssi, joka lukee kaikkia neljää jonoa, tai enintään neljä instanssia, joista jokainen lukee yhtä jonoa.

Viestit ohjausjonoissa käynnistävät orchestratoreja, välittävät activityjen/ali-orchestratorien tuloksia, käynnistävät ajastettuja ajoja yms. Orchestratorin instanssi id:n perusteella päätetään, mihin jonoon viesti menee. Tästä syystä hyvä jakauma instanssi id:issä auttaa suoritustehoa. Vakiona orchestratorit saavat id:ksi GUID:n ilman väliviivoja, jonka pitäisi antaa hyvä jakauma. Jos määrität id:t itse, tämä on hyvä pitää mielessä.

Azure Functionien scale controller valvoo viestien haun latensseja ohjausjonoista ja päättää tarvitaanko enemmän/vähemmän instansseja, samoin kuin activityissä. Mutta tässä on ylärajana partitioiden määrä; se ei yritä skaalata niiden määrän yli, vaikka ohjausjonojen latenssit pysyisivät korkeina.

Voit totta kai lisätä partitioiden määrää nostaaksesi rinnakkain ajettavien Functionien määrää. Maksimi on 16. Mutta kannattaako tätä määrää nostaa? Dokumentaatiossa sanotaan näin:

Generally speaking, orchestrator functions are intended to be lightweight and should not require large amounts of computing power. It is therefore not necessary to create a large number of control queue partitions to get great throughput for orchestrations. Most of the heavy work should be done in stateless activity functions, which can be scaled out infinitely.

Suurimmassa osassa skenaarioita neljä partitiota todennäköisesti riittää. Kuten he sanovat, orchestratorien ei pitäisi tehdä raskasta työtä. Yksi Functions instanssi voi myös ajaa useita orchestrator/entity functioneja rinnakkain. Tätä myös voidaan ohjata asetuksella host.json-tiedostossa:

{
  "extensions": {
    "durableTask": {
      "maxConcurrentOrchestratorFunctions": 10
    }
  }
}

Vakiona tämä asetus on sama kuin activityissä, 10 kertaa prosessoriydinten määrä instanssissa. Eli jos instanssilla on 4 prosessoriydintä, enintään 40 orchestrator/entity functionia voidaan ajaa samanaikaisesti. Mutta muista taas, että jokainen orchestrator vie osan instanssin jaetuista resursseista.

Yhteenveto

Orchestrator ja entity functioneiden skaalausta rajoittaa host.json-tiedostossa määritetty partitioiden määrä. Enimmillään 16 Functions instanssia voi ajaa niitä. Useimmissa skenaarioissa vakiomäärä 4 partitiota/enintään 4 Functions instanssia on riittävä.

Activity functioneilla ei ole rajoitusta skaalauksessa. Azure Functions voi skaalata niiden ajoa niin monelle instanssille kuin on tarvetta.

Laita kaikki raskas koodi activityihin mahdollistaaksesi nopean ajon orchestratoreille. Pitämällä orchestratorit kevyenä, partitioiden vakiomäärä todennäköisesti riittää.

Voit säätää samanaikaisesti ajettavien functionien määrää per Functions instanssi säätämällä asetuksia host.json-tiedostossa myös. Pidä mielessä kuitenkin, että näiden rajojen nosto kuormittaa jaettuja resursseja enemmän.

Linkkejä