Cloud native: unire Azure Functions ed Azure SQL Change Tracking per la massima efficienza

Posted by danieleperugini on Friday, May 3, 2024

Nell’ambito dello sviluppo di applicazioni moderne e dinamiche, la capacità di elaborare e reagire ai cambiamenti dei dati in tempo reale è diventata una componente cruciale. La programmazione reattiva emerge come un paradigma fondamentale, promuovendo la creazione di applicazioni asincrone, non bloccanti e orientate agli eventi, per affrontare sfide come la gestione efficace di flussi di dati continui e l’assenza di latenze nelle risposte. In questo contesto, la sinergia tra i trigger di Change Tracking di SQL Azure e Azure Functions si presenta come una soluzione strategica per migliorare notevolmente la reattività e la scalabilità delle applicazioni basate su cloud. Questo articolo esplorerà come tale integrazione possa trasformare l’efficienza operativa delle applicazioni, delineando configurazioni ottimali, best practices, e scenari d’uso complessi.

Panoramica di SQL Azure e il Change Tracking

SQL Azure, un’offerta Database-as-a-Service (DBaaS) all’interno dell’ecosistema Microsoft Azure, elimina la necessità di gestire hardware fisico, concentrandosi piuttosto sulla logica dell’applicazione.

Nell’esempio, ho creato un’istanza serverless di Azure SQL sfruttando il free tier. L’offerta è disponibile per un database per ogni sottoscrizione Azure.

I limiti mensili gratuiti includono 100.000 secondi vCore di elaborazione del database serverless e una dimensione massima di 32 GB di dati.

Ci sono due opzioni che è possibile impostazione impostare nella configurazione Comportamento al raggiungimento del limite gratuito:

  • Una volta raggiunti i limiti mensili di attività vCore o di archiviazione, il database può essere messo in pausa automatica fino all’inizio del mese successivo. Questa è l’opzione “Auto-pause database until next month”.
  • Mantenere il database online, con l’utilizzo di vCore e la quantità di archiviazione oltre i limiti gratuiti addebitati sul metodo di fatturazione della tua sottoscrizione, alle tariffe standard del livello serverless di uso generale. Questa è l’opzione “Continue using database for additional charges”. In ogni caso, l’ammontare gratuito viene rinnovato all’inizio del mese calendario successivo.

Fatto interessante, durante la configurazione della risorsa è possibile usare i dati di esempio. Così il codice di questo articolo si baserà sui dati di test attivati in fase di creazione del database.

Cosa è il Change Tracking

Il Change Tracking in Azure SQL Database è una funzionalità potente che consente agli sviluppatori di identificare e catturare modifiche apportate ai dati nelle tabelle SQL senza dover gestire complesse logiche di tracciamento. Questa funzione è particolarmente utile in scenari di sincronizzazione di dati e in applicazioni che devono rispondere dinamicamente ai cambiamenti dei dati.

Perchè utilizzo spesso questa funzionalità?

  1. Semplicità di Implementazione: A differenza di altre tecnologie di tracciamento delle modifiche, come il Change Data Capture (CDC), il Change Tracking è progettato per essere leggero e facile da configurare. Non richiede la lettura del log delle transazioni SQL, il che lo rende meno oneroso in termini di prestazioni.

  2. Granularità: Il Change Tracking permette di tracciare modifiche a livello di riga, fornendo informazioni dettagliate su quali righe sono state modificate, ma non sui dettagli specifici della modifica (come i valori precedenti e successivi). Gli sviluppatori possono specificare per quali tabelle abilitare il tracciamento e possono configurare la funzionalità per tracciare anche quali colonne sono state modificate.

  3. Controllo delle Versioni: Una volta attivato, il Change Tracking mantiene versioni numeriche per ciascuna modifica. Questo è essenziale per applicazioni che devono sincronizzare i dati tra diverse copie di un database, poiché consente di determinare l’ordine delle modifiche e di applicarle correttamente durante il processo di sincronizzazione.

Quali sono le differenze con il Change Data Capture?

La principale differenza tra Change Data Capture (CDC) e Change Tracking risiede nel tipo di informazioni che catturano. Mentre il CDC fornisce dettagli storici sulle modifiche, compresi i dati specifici che sono stati modificati, Change Tracking registra solo il fatto che le righe di una tabella sono state cambiate, senza catturare i dati specifici modificati. Ecco un confronto rapido:

  • Cambiamenti Tracciati: Entrambi tracciano modifiche DML.
  • Informazioni Tracciate: CDC traccia dati storici e il tipo specifico di DML, mentre Change Tracking segnala solo se una riga è stata modificata e il tipo di DML, ma non i dati storici.

Change Tracking è preferibile quando non è necessario conoscere i dati storici ma solo quali dati sono cambiati in un determinato periodo. Questo approccio riduce notevolmente il sovraccarico di archiviazione e di elaborazione. Per esempio, nel caso di un processo ETL che esegue ogni notte per trasferire i dati in un data warehouse o lake house per analisi, l’utilizzo di Change Tracking permette di ottenere solo la versione finale delle righe cambiate nelle ultime 24 ore, riducendo così la quantità di dati trasferiti e il tempo di elaborazione rispetto a un’operazione di troncamento o sostituzione dell’intera tabella.

Come attivare il change tracking

Per attivare il change tracking su SQL Server o Azure SQL, è necessario eseguire alcune configurazioni a livello di database e tabella lanciando i seguenti comandi in una nuova finestra di query.

-- Attivazione del Change Tracking a livello di database 
ALTER DATABASE adventureworks
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);

-- Attivazione sulla tabella desiderata
ALTER TABLE [SalesLT].[SalesOrderHeader]
ENABLE CHANGE_TRACKING;

Questi comandi SQL non solo attivano il Change Tracking a livello di database ma anche consentono una gestione dettagliata delle modifiche su specifiche tabelle, facilitando un controllo accurato e performante.

Una volta attivato il change tracking possiamo provare a lanciare una query di modifica come questa:

update [SalesLT].[SalesOrderHeader] set
    RevisionNumber = RevisionNumber+1,
    OrderDate = DATEADD(D, 1, OrderDate),
    SubTotal = SubTotal + 100
where SalesOrderID in (
	71776, 71780, 71782, 71783,
	71784, 71796, 71797, 71815, 71816
)

Adesso, per verificare se e dove vi siano stati cambiamenti, è possibile accedere all’oggetto “sys.change_tracking_tables” come riportato sotto.

Ecco alcune query di base:

-- verifica la tabella dove ci sono stati cambiamenti
select object_name(object_id) [table],* from sys.change_tracking_tables;

-- verifica la versione attuale
select CHANGE_TRACKING_CURRENT_VERSION() 'Current Version';

-- ottiene le modifiche in dettaglio
select * 
from CHANGETABLE(CHANGES SalesLT.SalesOrderHeader, null) as CT;

Per maggiori informazioni più approfondite Microsoft mette a disposizione un articolo davvero interessante su devblog che è possibile trovare qui

Il ruolo cruciale delle Azure Functions nell’architettura cloud

Azure Functions è un servizio serverless che consente di eseguire codice in risposta a vari trigger ed eventi, esentando dalla gestione dell’infrastruttura sottostante. Queste funzioni si adattano automaticamente al carico di lavoro, garantendo una scalabilità senza interventi manuali e una fatturazione basata sul consumo effettivo delle risorse, rendendole soluzioni estremamente economiche per gestire operazioni che variano nel tempo.

Azure Functions supporta una varietà di linguaggi di programmazione, tra cui C#, Java, JavaScript, TypeScript, e Python. Nel nostro esempio vedremo uno snippet di codice C# che permetterà appunto di invocare la funzione come risposta all’evento di change tracking di Azure SQL.

Integrazione di Change Tracking con Azure Functions

L’integrazione dei trigger di Azure SQL con Azure Functions permette di creare architetture reattive che rispondono dinamicamente ai cambiamenti nei dati. Questo riduce significativamente la latenza e incrementa l’efficienza operativa.

Il trigger utilizza un ciclo di polling per verificare la presenza di modifiche, attivando la funzione utente quando queste vengono rilevate. Ad alto livello, il ciclo è strutturato nel modo seguente:

  1. Recupera l’elenco delle modifiche sulla tabella - fino a un massimo controllato dall’impostazione Sql_Trigger_MaxBatchSize
  2. Attiva la funzione con l’elenco delle modifiche
  3. Attendere un intervallo definito dall’impostazione Sql_Trigger_PollingIntervalMs

Le modifiche vengono elaborate nell’ordine in cui sono state apportate, con le modifiche più vecchie che vengono elaborate per prime. Alcune note sul processo di elaborazione delle modifiche:

  • Se le modifiche a più righe vengono effettuate contemporaneamente, l’ordine esatto in cui queste vengono inviate alla funzione dipende dall’ordine restituito dalla funzione CHANGETABLE.
  • Le modifiche sono “raggruppate” per riga. Se vengono apportate più modifiche a una riga tra un’iterazione e l’altra del ciclo, esisterà solo una singola voce di modifica per quella riga che mostrerà la differenza tra lo stato elaborato più recentemente e lo stato attuale.
  • Se vengono apportate modifiche a un insieme di righe e poi un ulteriore insieme di modifiche alla metà di quelle stesse righe, la metà delle righe che non sono state cambiate una seconda volta verrà elaborata per prima. Questa logica di elaborazione è dovuta al fatto che le modifiche vengono raggruppate - il trigger visualizzerà solo l’ultima modifica effettuata e userà quella per determinare l’ordine di elaborazione.

Ecco un esempio di codice C#:

// SalesOrderHeader.cs
namespace AzureSQLTrigger.Domain;

public record SalesOrderHeader
{
    public int SalesOrderID { get; set; }
    public byte RevisionNumber { get; set; }
    public DateTime OrderDate { get; set; }
    public DateTime DueDate { get; set; }
    public DateTime? ShipDate { get; set; }
    public byte Status { get; set; }
    public bool OnlineOrderFlag { get; set; }
    public string? SalesOrderNumber { get; set; }
    public string? PurchaseOrderNumber { get; set; }
    public string? AccountNumber { get; set; }
    public int CustomerID { get; set; }
    public int? ShipToAddressID { get; set; }
    public int? BillToAddressID { get; set; }
    public string? ShipMethod { get; set; }
    public string? CreditCardApprovalCode { get; set; }
    public decimal SubTotal { get; set; }
    public decimal TaxAmt { get; set; }
    public decimal Freight { get; set; }
    public decimal TotalDue { get; set; }
    public string? Comment { get; set; }
    public Guid Rowguid { get; set; }
    public DateTime ModifiedDate { get; set; }
}

// AdventureWorksChangesInterceptor.cs
using AzureSQLTrigger.Domain;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace AzureSQLTrigger
{
    public class AdventureWorksChangesInterceptor
    {
        private readonly ILogger _logger;

        public AdventureWorksChangesInterceptor(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory
	            .CreateLogger<AdventureWorksChangesInterceptor>();
        }
        
        [Function("Function1")]
        public void Run(
            [SqlTrigger(
	            "[SalesLT].[SalesOrderHeader]", 
				"adventureworks_db_conn_string")]
			IReadOnlyList<SqlChange<SalesOrderHeader>> changes,
			FunctionContext context)
        {
			_logger.LogInformation("SQL Changes: " +
				JsonConvert.SerializeObject(changes));
        }
    }
}

In questo snippet, l’attributo SqlTrigger specifica la tabella e le configurazioni di connessione, attivando la funzione quando si verificano modifiche. Ogni modifica viene quindi processata, facilitando una risposta immediata e appropriata.

Vantaggi, best practices e troubleshooting

L’integrazione di Change Tracking con Azure Functions non solo ottimizza le prestazioni ma migliora anche la gestione delle risorse. Adottando best practices di sicurezza, come l’utilizzo di managed identities per le connessioni, e seguendo strategie consigliate per il debugging e il troubleshooting, gli sviluppatori possono evitare problemi comuni e migliorare la resilienza delle applicazioni.

Da notare inoltre come sia possibile configurare alcuni parametri per migliorare l’efficienza in base ai casi d’uso. Infatti i cambiamenti vengono sempre intercettati come batch, e di conseguenza la funzione viene invocata passando un array di elementi. Questo permette di ottimizzare la funzione. E’ possibile però modificare, come detto prima (nel file “host.json”) il numero massimo di elementi nel batch delle modifiche.

Inoltre le funzioni possono essere scalate automaticamente in base al numero di modifiche in attesa di essere elaborate nella tabella utente. Per farlo è necessario avere però a disposizione un piano Premium ed attivare la funzionalità di runtime scale monitoring.

Qui sotto il comando CLI:

az resource update -g <RESOURCE_GROUP> \
  -n <FUNCTION_APP_NAME>/config/web \
  --set properties.functionsRuntimeScaleMonitoringEnabled=1 \
  --resource-type Microsoft.Web/sites

Conclusione

Questa funzionalità che era in preview fino a non molto tempo fa, è davvero un’ottima notizia per chi lavora molto con i database relazionali, in particolare SQL Server.

In particolare, chi utilizza un approccio “cloud-native” si sarà sentito disorientato passando da SQL Server, un database server con tantissime funzionalità ad Azure SQL, che può sembrare più “essenziale”.

Adesso, con strumenti come questo è possibile riempire quei gap che ad esempio non permettevano ad alcuni di poter passare ad Azure SQL per sfruttare le potenzialità del modello DbaaS (Db as a service).

Per maggiori informazioni, non esitare a contattarmi!


IL MIO LIBRO: WHY YOUR DATA MATTER

Il libro dedicato ai manager e CIO che hanno a cuore i dati della propria azienda e vogliono avere sonni tranquilli (anticipando problematiche poco piacevoli legate al recupero, alla gestione o alla sicurezza dei dati)

Leggi il libro

REGISTRATI ALLA NEWSLETTER

Una piccola newsletter su data, azure e AI.

Dicono di me

Ricordo ancora bene cosa mi spinse a coinvolgerlo per la prima volta. Oltre che a trasmettermi competenza ed affidabilità, Daniele mi è sembrato fin da subito propenso a mettersi in gioco e a fare squadra con Fapim. Ho percepito in maniera marcata che questa persona avrebbe fatto suo il problema e avrebbe cercato di risolverlo concretamente.

(Leggi la testimonianza completa)

Fapim S.p.a.Ombretta Pacini, responsabile comunicazione e immagine aziendale
È orbitando nell’area Microsoft che abbiamo conosciuto Daniele. Abbiamo iniziato a collaborarci nel 2016 in un momento nel quale, dopo aver introdotto in azienda la metodologia di Agile grazie ad un lungo periodo di formazione interna su questo argomento, iniziavamo a metterla in pratica su nuovi progetti e necessitavamo di Project Manager e Team Leader con esperienza.

(Leggi la testimonianza completa)

Vivido S.r.l.Claudio Menzani, Paolo Ciccioni
Ho conosciuto Daniele grazie ad una sua ex collega che mi ha parlato molto bene di lui e dei suoi servizi di consulenza e collaborazione nel campo della consulenza. Mi ha spinta a rivolgermi a Daniele la sua preparazione, professionalità e disponibilità.

(Leggi la testimonianza completa)