Problemi di controllo delle versioni e strategie di mitigazione in Durable Functions

Il controllo delle versioni in Durable Functions è essenziale perché le funzioni vengono inevitabilmente aggiunte, rimosse e modificate per tutta la durata di un'applicazione. Durable Functions consente di concatenare le funzioni in modi non possibili in precedenza e questo concatenamento influisce sul modo in cui si gestisce il controllo delle versioni.

Questo articolo consente di:

Confronto rapido tra strategie

Se già si sa che la modifica è importante, usare questa tabella per scegliere una strategia di mitigazione:

Strategia Ideale per dettagli
Controllo delle versioni dell'orchestrazione (scelta consigliata) La maggior parte delle applicazioni con modifiche di rilievo. La funzionalità di runtime predefinita funziona con qualsiasi back-end di archiviazione. Passare alla sezione
Distribuzioni parallele (side-by-side) App che non possono usare il controllo delle versioni dell'orchestrazione o che richiedono l'isolamento completo tramite hub di attività o account di archiviazione separati. Passare alla sezione
Arrestare tutte le istanze in corso Creazione di prototipi e sviluppo locale in cui è accettabile la perdita delle orchestrazioni in corso. Passare alla sezione

Suggerimento

Se si cerca la funzionalità di controllo delle versioni di orchestrazione integrata che fornisce l'isolamento automatico della versione a livello di runtime, vedere Controllo delle versioni dell'orchestrazione.

Importante

Prima della distribuzione, verificare se la modifica è un cambiamento critico:

  • È stato modificato il nome, il tipo di input o il tipo di output di un'attività o di una funzione di entità?
  • Sono state aggiunte, rimosse o riordinate le chiamate ad attività, sotto orchestrazioni, timer o eventi esterni nel codice dell'agente di orchestrazione?
  • È stata rinominata o rimossa una funzione che le orchestrazioni in corso potrebbero ancora chiamare?

Se si è risposto a uno di questi, usare una delle strategie di mitigazione seguenti per evitare errori nell'esecuzione di orchestrazioni.

Tipi di modifiche di rilievo

Esistono diversi esempi di modifiche di rilievo. Questo articolo illustra i tipi più comuni. Il tema principale dietro tutti è che le modifiche apportate al codice della funzione influiscono sia sulle orchestrazioni di funzioni nuove che esistenti.

Modifiche alla firma delle funzioni di attività o entità

Una modifica della firma fa riferimento a una modifica del nome, dell'input o dell'output di una funzione. Se si apporta questo tipo di modifica a un'attività o a una funzione di entità, potrebbe interrompere qualsiasi funzione dell'orchestratore che ne dipende. Questo comportamento è particolarmente vero per i linguaggi di programmazione indipendenti dai tipi. Se si aggiorna la funzione dell'agente di orchestrazione per gestire questa modifica, si potrebbero interrompere le istanze esistenti in corso.

Si consideri ad esempio la seguente funzione di orchestrazione.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Questa funzione accetta il risultato di Foo e la passa a Bar. Si supponga di dover modificare il valore restituito di Foo da un valore booleano a un valore String per supportare un'ampia gamma di valori dei risultati. Il risultato è simile al seguente:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Questa modifica funziona correttamente per tutte le nuove istanze della funzione dell'agente di orchestrazione, ma potrebbe interrompe tutte le istanze in corso. Si consideri ad esempio il caso in cui un'istanza di orchestrazione chiama una funzione denominata Foo, restituisce un valore booleano e quindi i checkpoint. Se la modifica della firma viene distribuita a questo punto, l'istanza per cui è stato applicato il checkpoint ha immediatamente esito negativo quando riprende e riesegue la chiamata a Foo. Questo errore si verifica perché il risultato nella tabella di cronologia è un valore booleano, ma il nuovo codice tenta di deserializzarlo in un valore di tipo stringa, causando un comportamento imprevisto o anche un'eccezione di runtime per i linguaggi fortemente tipizzati.

Questo esempio è uno dei molti modi in cui una modifica della firma della funzione può interrompere le istanze esistenti. In generale, se un agente di orchestrazione deve modificare il modo in cui chiama una funzione, è probabile che la modifica sia problematica.

Modifiche alla logica dell'orchestratore

L'altra classe di problemi di controllo delle versioni deriva dalla modifica del codice della funzione dell'agente di orchestrazione in modo da modificare il percorso di esecuzione per le istanze in-flight.

Si consideri la funzione dell'orchestratore seguente:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Si supponga ora di voler aggiungere una nuova chiamata di funzione tra le due chiamate di funzione esistenti.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    if (result)
    {
        await context.CallActivityAsync("SendNotification");
    }

    await context.CallActivityAsync("Bar", result);
}

Questa modifica aggiunge una nuova chiamata di funzione a SendNotification tra Foo e Bar. Nessuna modifica della firma. Il problema si verifica quando un'istanza esistente riprende dalla chiamata a Bar. Durante la riproduzione, se la chiamata originale a Foo ha restituito true, l'agente di orchestrazione richiama la funzione SendNotification, che non si trova nella cronologia di esecuzione. Il runtime rileva questa incoerenza e genera un errore di orchestrazione non deterministico perché ha rilevato una chiamata a SendNotification quando si prevede di visualizzare una chiamata a Bar. Lo stesso tipo di problema può verificarsi quando si aggiungono chiamate API ad altre operazioni durevoli, ad esempio la creazione di timer durevoli, l'attesa di eventi esterni o la chiamata di sotto orchestrazioni.

Strategie di mitigazione

Avviso

La distribuzione di modifiche importanti senza una strategia di mitigazione (approccio "non fare nulla") può causare errori di orchestrazione non deterministica, blocco indefinito dell'orchestrazione nello stato Running o attivare errori di runtime di basso livello che riducono le prestazioni. Durante la distribuzione di modifiche importanti, usare sempre una delle strategie seguenti.

A differenza delle altre strategie di questa sezione, il controllo delle versioni dell'orchestrazione è una funzionalità di runtime predefinita che fornisce l'isolamento automatico della versione. Non è necessario gestire distribuzioni separate, hub di attività o account di archiviazione. Il runtime tiene invece traccia delle informazioni sulla versione e garantisce che le istanze di orchestrazione vengano elaborate da ruoli di lavoro compatibili.

Con il controllo delle versioni dell'orchestrazione:

  • Ogni istanza di orchestrazione ottiene una versione associata in modo permanente alla versione al momento della creazione.
  • Le funzioni dell'agente di orchestrazione possono esaminare la versione e l'esecuzione del ramo di conseguenza, mantenendo i percorsi di codice precedenti e nuovi nella stessa codebase.
  • I worker che eseguono versioni più recenti delle funzioni dell'orchestratore possono continuare ad eseguire istanze di orchestrazione create da versioni precedenti.
  • Il runtime impedisce agli operai che eseguono versioni precedenti delle funzioni dell'orchestratore di avviare orchestrazioni di versioni più recenti.

Questo approccio richiede una configurazione minima (una stringa di versione e una strategia di corrispondenza facoltativa) ed è compatibile con qualsiasi provider di archiviazione. Si tratta della strategia consigliata per le applicazioni che devono supportare modifiche di rilievo mantenendo implementazioni senza interruzioni.

Per indicazioni dettagliate sulla configurazione e sull'implementazione, vedere Versionamento dell'orchestrazione.

Arrestare tutte le istanze in corso

Un'altra opzione è quella di arrestare tutte le istanze in corso. Se si usa il provider di Archiviazione di Azure per Durable Functions predefinito, arrestare tutte le istanze cancellando il contenuto della coda di controllo e della coda di elementi di lavoro interne. In alternativa, arrestare l'app per le funzioni, eliminare queste code e riavviare l'app. Le code vengono ricreate automaticamente al riavvio dell'app. Le istanze di orchestrazione precedenti potrebbero rimanere nello stato "In esecuzione" all'infinito, ma non riempiono i registri con messaggi di errore e non causano alcun danno all'app. Questo approccio è ideale per lo sviluppo rapido di prototipi, incluso lo sviluppo locale.

Avviso

Questo approccio richiede l'accesso diretto alle risorse di archiviazione sottostanti e non è appropriato per tutti i provider di archiviazione supportati da Durable Functions.

Distribuzioni affiancate

Il modo più a prova di errore per garantire che le modifiche importanti vengano implementate in sicurezza consiste nel implementarle insieme alle versioni precedenti. È possibile usare una delle tecniche seguenti:

  • Account di archiviazione diverso: distribuire tutti gli aggiornamenti come nuova app per le funzioni con un account di archiviazione diverso. In questo modo lo stato della nuova versione viene completamente isolato dalla versione precedente.
  • Hub attività diverso: distribuire una nuova copia dell'app per le funzioni con lo stesso account di archiviazione ma con un nome aggiornato dell'hub attività. Questo approccio crea nuovi artefatti di archiviazione per la nuova versione mentre la versione precedente continua a usare gli artefatti esistenti.

Quando si eseguono distribuzioni side-by-side in Azure, è possibile usare deployment slot per eseguire entrambe le versioni contemporaneamente con una sola versione come slot attivo production. Quando si è pronti per esporre la nuova logica di orchestrazione, scambiare la nuova versione nello slot di produzione.

Annotazioni

Queste linee guida usano termini specifici di Archiviazione di Azure, ma si applicano in genere a tutti i provider di archiviazione Durable Functions supportati.

Annotazioni

Gli scambi di slot di distribuzione funzionano meglio con i trigger HTTP e webhook. Per i trigger non-HTTP, come ad esempio code o hub eventi, la definizione del trigger deve derivare da un'impostazione dell'app modificata nell'ambito dell'operazione di scambio.

Passaggi successivi