-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



Visualizzazione post con etichetta .NET. Mostra tutti i post
Visualizzazione post con etichetta .NET. Mostra tutti i post

mercoledì 14 ottobre 2026

.NET 11 RC2, C#, sicurezza e architettura

La crittografia post-quantistica non richiede di aspettare l'arrivo di un computer quantistico capace di violare RSA. Il rischio harvest now, decrypt later riguarda i dati cifrati oggi e conservati per essere decifrati in futuro. Con .NET 11 RC2 Microsoft amplia le primitive crittografiche disponibili in C#, introducendo API sperimentali per Composite ML-KEM e HPKE. Per comprendere la portata del cambiamento occorre distinguere algoritmi, protocolli, formati di chiave e disponibilità effettiva delle implementazioni native.

1. Perché RSA ed ECC richiedono una transizione

RSA ed ECC basano la propria sicurezza computazionale su problemi matematici per i quali l'algoritmo quantistico di Shor, eseguito su hardware sufficientemente potente e affidabile, offre una minaccia sostanziale. Gli algoritmi simmetrici come AES non sono colpiti nello stesso modo: Grover comporta un vantaggio quadratico teorico nella ricerca esaustiva, non una rottura equivalente. Questo spiega perché la migrazione interessa soprattutto scambio di chiavi e firme digitali.

2. Il punto di partenza è .NET 10

.NET 10 ha introdotto le classi MLKem, MLDsa, SlhDsa e CompositeMLDsa in System.Security.Cryptography. ML-KEM è un meccanismo di incapsulamento di chiavi, standardizzato da NIST FIPS 203; ML-DSA e SLH-DSA sono schemi di firma, rispettivamente FIPS 204 e FIPS 205. Le classi e i metodi possono avere stati di maturità differenti: alcune API restano marcate [Experimental].

3. KEM non significa cifratura dei documenti

Un KEM produce un segreto condiviso tra mittente e destinatario. Il segreto alimenta normalmente una KDF, dalla quale si ricava una chiave per un algoritmo simmetrico autenticato come AES-GCM. Il KEM non è un sostituto diretto di AesGcm.

Recipient public key
       |
       v
   KEM Encapsulate --------> Encapsulation ciphertext
       |
       v
   Shared secret
       |
       v
  Key derivation
       |
       v
     AES-GCM ------------> Encrypted payload + authentication tag

Recipient private key + encapsulation ciphertext
       |
       v
   KEM Decapsulate --> same shared secret

4. Composite ML-KEM: il nuovo tassello di .NET 11

La RC2 introduce API sperimentali per combinare un meccanismo post-quantum ML-KEM con un meccanismo classico. L'obiettivo dei KEM compositi è ottenere una costruzione che mantenga proprietà di sicurezza anche qualora una delle componenti risulti vulnerabile, secondo le ipotesi e il combiner specificati. Non è corretto concatenare arbitrariamente due segreti e chiamare il risultato 'hybrid secure': il formato, il contesto e la derivazione devono essere definiti da una specifica interoperabile.

Il supporto dipende dalla piattaforma, dai parametri algoritmici e dallo stato delle API sperimentali. Prima di introdurle in un protocollo occorre fissare una versione, verificare gli algoritmi disponibili e testare interoperabilità e downgrade resistance.

5. HPKE: un framework, non un singolo algoritmo post-quantum

Hybrid Public Key Encryption (HPKE) è un framework di cifratura che combina KEM, KDF e AEAD. Il nome 'Hybrid' non significa automaticamente 'classico + post-quantum': HPKE può essere implementato con suite completamente classiche. Nella RC2 il supporto .NET è sperimentale e le suite post-quantum ML-KEM/ibride non risultano ancora implementate. È quindi scorretto presentare la sola presenza di Hpke come prova che un'applicazione utilizzi già cifratura post-quantum end-to-end.

6. Firme post-quantum e certificati

ML-DSA e SLH-DSA rispondono a un problema differente dal KEM: autenticità e integrità. La transizione interessa certificati X.509, CA, trust store, TLS, HSM, firme di software e documenti. Un certificato firmato con un nuovo algoritmo non diventa utilizzabile ovunque soltanto perché il runtime conosce la primitiva: il sistema operativo, la catena di fiducia, il peer e il protocollo devono supportarlo. Microsoft sta sperimentando ecosistemi PKI post-quantum in ambienti controllati, non con certificati pilota pubblicamente trusted.

7. Laboratorio 1: capability detection in C#

Il primo test riproducibile consiste nel verificare il supporto delle primitive già introdotte in .NET 10. È preferibile a un esempio che presupponga l'esistenza di algoritmi nativi su qualsiasi sistema.

using System;
using System.Security.Cryptography;

Console.WriteLine($"ML-KEM supported: {MLKem.IsSupported}");
Console.WriteLine($"ML-DSA supported: {MLDsa.IsSupported}");
Console.WriteLine($"SLH-DSA supported: {SlhDsa.IsSupported}");

if (!MLKem.IsSupported)
{
    Console.Error.WriteLine("ML-KEM unavailable on this platform.");
    return;
}

Console.WriteLine("ML-KEM available: ready for platform-specific tests.");

Creazione del progetto: dotnet new console -f net11.0. Installare SDK .NET 11 RC2 e controllare la versione con dotnet --info. Su Linux il supporto dipende anche da OpenSSL (per le primitive .NET 10, Microsoft indica OpenSSL 3.5 o successivo). Non sopprimere indiscriminatamente i warning SYSLIB: sono indicatori di contratti ancora sperimentali.

8. Laboratorio 2: una firma ML-DSA in ambiente compatibile

La prova seguente mostra la struttura dell'API, da validare sulla piattaforma e sulla specifica build SDK utilizzata.

using System;
using System.Security.Cryptography;
using System.Text;

if (!MLDsa.IsSupported)
{
    Console.WriteLine("ML-DSA not supported on this host.");
    return;
}

byte[] message = Encoding.UTF8.GetBytes("Post-quantum signature test");
using MLDsa signer = MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65);
byte[] signature = signer.SignData(message);
bool valid = signer.VerifyData(message, signature);
Console.WriteLine($"Signature valid: {valid}");

La firma non cifra il messaggio. In un'applicazione reale, verificare sempre la provenienza della chiave pubblica, il formato di esportazione, l'identità del firmatario e le policy di rotazione.

9. Architettura di un servizio di cifratura crypto-agile

Application API
    |
    v
Cryptography abstraction
    |-- Algorithm policy / versioning
    |-- Key management / KMS / HSM
    |-- KEM + KDF
    |-- AEAD encryption
    |-- Signature validation
    |-- Telemetry (never log secrets)
    v
Versioned encrypted envelope
    |-- suite identifier
    |-- key identifier
    |-- encapsulated key
    |-- nonce
    |-- ciphertext
    |-- authentication tag

Un envelope cifrato dovrebbe includere identificatori di versione e suite, e associare metadati critici come additional authenticated data (AAD). Le chiavi non devono essere registrate nei log. Il cambio di algoritmo deve essere governato da policy, non da un fallback silenzioso verso suite più deboli.

10. Minacce e verifiche indispensabili

Una migrazione richiede test di downgrade, manipolazione dei ciphertext, error handling indistinguibile quando necessario, controllo della memoria, zeroization, gestione delle chiavi e compatibilità con certificati e protocolli. Per la crittografia composita verificare esplicitamente il comportamento quando una delle componenti fallisce. Per HPKE verificare che le suite realmente negoziate soddisfino la policy richiesta.

11. Benchmark: misurare l'intero protocollo

Misurare soltanto il tempo di una firma o di un'incapsulazione è insufficiente. Servono throughput, latenza p50/p95/p99, allocazioni, dimensioni di chiavi e ciphertext, handshake TLS, dimensioni delle catene X.509, CPU e interoperabilità tra sistemi operativi. Confrontare i risultati su hardware e implementazioni native dichiarate, evitando di presentare misure locali come caratteristiche universali dell'algoritmo.

12. Strategia di migrazione enterprise

La prima fase è un inventario: TLS endpoint, certificati, CA, HSM, JWT, CMS, firme del codice, VPN, backup e archivi con confidenzialità di lungo periodo. Segue la classificazione dei dati in base alla durata della riservatezza. Poi vengono introdotti test di interoperabilità in ambienti non produttivi, telemetria sugli algoritmi negoziati e un piano di rotazione e rollback esplicito. La scelta di una nuova API .NET non rende automaticamente post-quantum l'intera infrastruttura.

13. Cosa cambia davvero con .NET 11 RC2

La novità è l'arrivo di API sperimentali per Composite ML-KEM e HPKE, che completano il percorso avviato con le primitive PQC di .NET 10. Composite ML-KEM riguarda il key establishment composito; HPKE fornisce un framework di cifratura, ma il supporto iniziale non equivale a suite post-quantum già disponibili. Per progettare un sistema sicuro occorre verificare build, piattaforma, algoritmi supportati, standard e interoperabilità.

14. Conclusioni

La post-quantum cryptography è soprattutto un problema di architettura e migrazione: protocolli, formati, certificati, chiavi e dipendenze infrastrutturali. .NET 11 mette a disposizione ulteriori strumenti, ma la proprietà più preziosa resta la crypto-agility: poter aggiornare gli algoritmi senza riscrivere l'intero sistema e senza compromettere le garanzie di sicurezza.

Fonti tecniche

martedì 13 ottobre 2026

.NET 11 DNS Deep Dive: DnsResolver, record DNS tipizzati, negative caching e HTTP Connection Eviction

Il DNS non è una semplice traduzione da hostname a indirizzo IP

Per anni, molte applicazioni .NET hanno trattato la risoluzione DNS come un'operazione elementare: chiamare Dns.GetHostAddressesAsync(), ottenere uno o più indirizzi e lasciare al sistema operativo il resto. In un'applicazione distribuita moderna questa astrazione è insufficiente per diagnosticare failover, errori intermittenti, divergenze tra resolver e connessioni HTTP persistenti.

.NET 11 introduce due tasselli complementari: API DNS tipizzate in System.Net e una politica programmabile di eliminazione delle connessioni nel pool di SocketsHttpHandler. Non sono la stessa funzionalità: la prima osserva la risoluzione, la seconda influenza il riutilizzo delle connessioni.

Stato al 12 ottobre 2026: .NET 11 è ancora prerelease. Le API e i dettagli di comportamento vanno ricontrollati prima della release stabile. Gli esempi qui riportati si basano sulle API documentate da Microsoft; dove indicato, il codice è uno scheletro architetturale e non una libreria pronta per la produzione.

1. Il problema reale: tre stati che possono divergere

Authoritative DNS Zone
         |
         v
Recursive Resolver / Cache
         |
         v
Application DNS Lookup
         |
         v
HttpClient Connection Pool
         |
         v
Established TCP/TLS Connection
         |
         v
Remote Service

Il record pubblicato dal DNS autorevole non coincide necessariamente con la risposta osservata da un resolver ricorsivo. E la risposta del resolver non coincide necessariamente con l'indirizzo remoto di una connessione TCP/TLS già aperta. Il DNS risolve nomi; non interrompe le connessioni esistenti.

Questo è cruciale per Azure, servizi dietro bilanciatori, ArcGIS Enterprise, API distribuite e ambienti con DNS privati. Un cambiamento di record può essere normale, ma può anche spiegare un degrado se coincide con errori applicativi. La correlazione temporale non è però una prova di causalità.

2. Cosa cambia in System.Net.Dns

Le nuove API comprendono ResolveAddresses, ResolveSrv, ResolveMx, ResolveTxt, ResolveCName, ResolvePtr e ResolveNs, insieme alle varianti asincrone. Il risultato è tipizzato come DnsResult<T>.

APIRecordScenario
ResolveAddressesAsyncA / AAAARisoluzione degli endpoint IPv4/IPv6
ResolveCNameAsyncCNAMEAlias e dipendenze di naming
ResolveSrvAsyncSRVService discovery con porta, priorità e peso
ResolveMxAsyncMXRouting della posta
ResolveTxtAsyncTXTRecord testuali, configurazioni e verifiche
ResolvePtrAsyncPTRReverse lookup
ResolveNsAsyncNSDeleghe e name server

Il vantaggio non è soltanto evitare parsing manuale. È ottenere una rappresentazione del risultato DNS con informazioni sullo stato della risposta, che consente diagnosi più accurate rispetto al semplice successo/fallimento di GetHostAddressesAsync.

3. DnsResult<T>: distinguere risposta, record e cache negativa

Microsoft documenta tre informazioni chiave: Records, ResponseCode e NegativeCacheTtl. Questo permette di distinguere almeno tre condizioni: risposta senza errori con record; risposta senza errori ma senza record del tipo richiesto; risposta DNS negativa o con errore.

using System.Net;

DnsResult<SrvRecord> result =
    await Dns.ResolveSrvAsync("_ldap._tcp.example.com");

if (result.ResponseCode == DnsResponseCode.NoError)
{
    if (result.Records.Count == 0)
    {
        Console.WriteLine("Nessun record SRV restituito.");
    }
    else
    {
        foreach (SrvRecord record in result.Records)
        {
            Console.WriteLine(
                $"{record.Target}:{record.Port} " +
                $"priority={record.Priority} weight={record.Weight}");
        }
    }
}
else
{
    Console.WriteLine($"DNS response: {result.ResponseCode}");
}

Il codice riprende la superficie pubblica mostrata nelle release notes Microsoft. Un monitor non dovrebbe considerare automaticamente un risultato vuoto come errore infrastrutturale: potrebbe non esistere alcun record di quel tipo per il nome interrogato.

4. Negative caching: il dettaglio che cambia la diagnostica

Quando un nome non esiste, o non è disponibile il tipo di record richiesto, un resolver può conservare temporaneamente una risposta negativa. Il caching negativo è descritto dal protocollo DNS e impedisce di ripetere continuamente interrogazioni inutili.

NegativeCacheTtl espone metadati relativi a questa durata. Non va confuso con il TTL dei record positivi né interpretato come una promessa assoluta del tempo di propagazione globale. Cache del sistema operativo, resolver intermedi, policy locali e comportamento dei client possono introdurre differenze.

Un alerting engine dovrebbe perciò distinguere:

  • NXDOMAIN persistente;
  • assenza del record richiesto con nome esistente;
  • errore temporaneo del resolver;
  • timeout di rete;
  • risposta positiva che cambia nel tempo.

Una classificazione grossolana in Up / Down perde informazioni diagnostiche essenziali.

5. DnsResolver: query verso server DNS specifici

Il nuovo DnsResolver, configurato tramite DnsResolverOptions, introduce un modello a istanza e la possibilità di utilizzare una lista di server DNS personalizzati anziché affidarsi esclusivamente alla configurazione del sistema.

Questo cambia il modo in cui si può progettare un monitor. Per lo stesso hostname è possibile confrontare la prospettiva del resolver aziendale, di un resolver pubblico e di un resolver raggiungibile da una rete Azure, purché tali interrogazioni siano consentite dalla rete e dalla policy di sicurezza.

Hostname: gis.example.com
             |
    +--------+---------+
    |        |         |
    v        v         v
Resolver A Resolver B Resolver C
    |        |         |
    v        v         v
  A/AAAA   A/AAAA    A/AAAA
    |        |         |
    +--------+---------+
             |
             v
     Compare Answer Sets

Attenzione: due resolver che restituiscono IP differenti non sono necessariamente incoerenti. GeoDNS, CDN, split-horizon DNS, round-robin e bilanciamento geografico possono produrre risposte diverse per scelta progettuale. Serve una policy per endpoint, non una regola universale di uguaglianza.

Secondo la documentazione Microsoft aggiornata a settembre 2026, le API di risoluzione tipizzata e DnsResolver supportano Windows, Linux e macOS. Le note iniziali della Preview 7 descrivevano invece un'implementazione inizialmente Windows-only: è un esempio concreto del perché occorre leggere lo stato più recente della documentazione prerelease.

6. DNS TTL, cache e misurazione della latenza: tre trappole

Primo: il TTL osservato in una risposta ricorsiva può essere il tempo residuo della cache, non quello originariamente pubblicato dalla zona autorevole.

Secondo: una query completata in un millisecondo potrebbe aver utilizzato una cache locale; non prova che il server autorevole sia raggiungibile in un millisecondo.

Terzo: il tempo della query DNS non misura il tempo di connessione TCP, l'handshake TLS o la latenza HTTP. Questi indicatori vanno raccolti e rappresentati separatamente.

Per un monitor di qualità, i risultati dovrebbero contenere almeno timestamp UTC, identificativo della probe, resolver utilizzato, tipo di record, durata osservata, codice di risposta, record normalizzati e classificazione dell'errore.

7. DNS e HttpClient: perché PooledConnectionLifetime non basta a spiegare tutto

HttpClient utilizza connessioni persistenti per ridurre handshake e overhead. Se il DNS cambia, una connessione già stabilita può continuare a servire richieste verso il precedente indirizzo remoto.

Per questo Microsoft raccomanda da tempo di gestire la durata delle connessioni, per esempio con PooledConnectionLifetime. Ma una durata fissa ricicla connessioni anche quando il DNS non è cambiato.

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(5)
};

using var client = new HttpClient(handler);

Questa è una strategia semplice, consolidata e generalmente prevedibile. Non è però equivalente a una politica DNS-aware.

8. .NET 11: ShouldEvictConnection

SocketsHttpHandler.ShouldEvictConnection introduce un callback asincrono per decidere se una connessione del pool debba essere ritirata. Il contesto include, fra le altre proprietà, età della connessione, endpoint DNS, endpoint remoto, versione HTTP e identificativo della connessione.

Microsoft presenta un esempio che controlla se l'indirizzo IP della connessione sia ancora presente fra quelli restituiti dal DNS:

using System.Net;

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime = Timeout.InfiniteTimeSpan,
    ShouldEvictConnection = async (context, ct) =>
    {
        if (context.RemoteEndPoint is IPEndPoint connectionIp)
        {
            var addresses = await Dns.GetHostAddressesAsync(
                context.DnsEndPoint.Host,
                connectionIp.AddressFamily,
                ct);

            return !addresses.Contains(connectionIp.Address);
        }

        return context.Age > TimeSpan.FromMinutes(10);
    }
};

È un esempio didattico, non una configurazione che adotterei automaticamente in produzione. Eseguire una risoluzione DNS all'interno di una decisione di eviction può aumentare lavoro e latenza; bisogna comprendere frequenza di invocazione, timeout, eccezioni, cache e possibili oscillazioni delle risposte.

Inoltre la proprietà è stata introdotta come sperimentale nelle release notes della Preview 7. Occorre verificare gli attributi e le eventuali diagnostiche del compilatore nella build di SDK effettivamente utilizzata.

9. Progettare una policy DNS-aware senza creare un nuovo problema

Un approccio robusto dovrebbe evitare interrogazioni ripetitive verso il DNS a ogni decisione del pool. Una possibile architettura applicativa prevede un servizio separato che mantiene snapshot DNS periodici, con TTL e limiti di aggiornamento, e una policy di eviction che legge uno snapshot immutabile.

DNS Poller --> Immutable DNS Snapshot
                    |
                    v
             Eviction Policy
                    |
                    v
             Connection Pool

Questa è una proposta di design: non è un comportamento automatico del runtime. La policy deve gestire il caso in cui lo snapshot sia vecchio, non disponibile o temporaneamente vuoto. Evitare che un errore DNS transitorio provochi l'eliminazione massiva di connessioni ancora sane è spesso più importante che reagire immediatamente a ogni variazione.

Una politica ragionevole potrebbe prevedere: tempo minimo di permanenza, snapshot validi, più osservazioni consecutive, limiti alla frequenza di eviction e fallback a una durata massima fissa. I parametri vanno misurati con benchmark e fault injection.

10. A/AAAA e IPv6: il risultato DNS non determina da solo la connessione

Quando un hostname dispone di record IPv4 e IPv6, il sistema può scegliere l'indirizzo remoto secondo policy di rete e strategie di connessione. Un monitor che verifica soltanto A può risultare verde mentre i client IPv6 incontrano problemi, o viceversa.

Conviene conservare distintamente gli insiemi A e AAAA e, quando si monitorano le connessioni HTTP, osservare l'effettivo RemoteEndPoint. L'ordine degli indirizzi nella risposta DNS non va considerato un identificatore stabile della configurazione.

11. CNAME: non confondere alias e indirizzi finali

Una catena di CNAME può essere parte della configurazione normale di servizi cloud. Monitorare l'alias è utile per diagnosticare errori di configurazione, ma non bisogna presumere che ogni nome esponga sempre un record CNAME esplicito. Alcuni provider utilizzano record sintetizzati, alias proprietari o risposte che dipendono dal resolver.

Un buon monitor distingue il nome richiesto, gli eventuali alias osservati e l'insieme finale degli indirizzi. In presenza di variazioni, registra gli eventi senza classificarli automaticamente come incidenti.

12. SRV: service discovery, priority e weight

I record SRV sono interessanti perché includono un endpoint di servizio e non soltanto un nome host. Ogni record può specificare target, porta, priorità e peso.

La priorità più bassa viene preferita; il peso contribuisce alla selezione tra record della stessa priorità secondo le regole del protocollo. Un client non dovrebbe trattare l'elenco come un semplice array ordinato di server equivalenti.

Un monitor può verificare che i target siano risolvibili e che le porte siano raggiungibili, ma deve distinguere la validità del record DNS dalla disponibilità effettiva del servizio pubblicato.

13. MX, TXT, NS e PTR: non soltanto endpoint web

MX: permette di osservare variazioni dei server di posta e delle preferenze; non dimostra che SMTP funzioni.

TXT: utile per verificare la presenza di configurazioni testuali attese. Non ogni stringa TXT è un record SPF o una configurazione di sicurezza.

NS: utile per analizzare deleghe e modifiche dei name server; la diagnosi completa delle deleghe può richiedere query iterative e verifiche della catena DNS.

PTR: associa indirizzi a nomi in reverse DNS; la sua presenza non dimostra automaticamente l'identità o l'affidabilità di un host.

14. Diagnostica: NXDOMAIN, NODATA, SERVFAIL e timeout

Un monitor DNS serio deve distinguere le risposte del protocollo dalle eccezioni di trasporto. Un nome inesistente, un record mancante, un errore del resolver e un timeout non descrivono lo stesso incidente.

CondizioneInterpretazione operativa
Risposta positivaRecord ricevuti, senza garanzia sulla raggiungibilità applicativa
Risposta senza record del tipo richiestoPossibile configurazione normale o record mancante
NXDOMAINNome inesistente nella vista DNS interrogata
SERVFAILIl resolver non è riuscito a completare la risoluzione
TimeoutAssenza di risposta nei limiti configurati
Risposte divergentiPossibile GeoDNS, split-horizon, cache o problema da indagare

Non assocerei mai automaticamente NXDOMAIN a un errore HTTP 404: il primo riguarda il DNS, il secondo è una risposta di un server HTTP raggiunto con successo.

15. Un laboratorio riproducibile: la baseline compatibile con .NET 10

Prima di sperimentare le nuove API, è utile costruire una baseline con API consolidate. Questo esempio misura la durata osservata della risoluzione A/AAAA attraverso il resolver di sistema, senza pretendere di misurare il tempo del server autorevole.

dotnet new console -n DnsBaseline -f net10.0
cd DnsBaseline
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;

string hostname = args.Length > 0
    ? args[0]
    : "example.com";

using var cts = new CancellationTokenSource(
    TimeSpan.FromSeconds(5));

var sw = Stopwatch.StartNew();

try
{
    IPAddress[] addresses =
        await Dns.GetHostAddressesAsync(hostname, cts.Token);

    sw.Stop();

    var normalized = addresses
        .Distinct()
        .OrderBy(ip => ip.AddressFamily)
        .ThenBy(ip => ip.ToString(), StringComparer.Ordinal)
        .ToArray();

    Console.WriteLine($"Host: {hostname}");
    Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:F2} ms");

    foreach (IPAddress address in normalized)
    {
        string family = address.AddressFamily ==
            AddressFamily.InterNetwork ? "A" : "AAAA";

        Console.WriteLine($"{family}: {address}");
    }
}
catch (OperationCanceledException)
{
    Console.Error.WriteLine("DNS lookup timed out/cancelled.");
}
catch (SocketException ex)
{
    Console.Error.WriteLine(
        $"DNS lookup failed: {ex.SocketErrorCode}");
}

La baseline è intenzionalmente semplice. Non espone il DNS response code completo, la cache negativa, il resolver interrogato né i record SRV/MX/TXT. È proprio questa lacuna che le API .NET 11 iniziano a colmare.

16. Laboratorio .NET 11: record SRV e classificazione

Con un SDK .NET 11 prerelease installato, il seguente frammento illustra l'uso delle API tipizzate:

using System.Net;

string service = "_ldap._tcp.example.com";
DnsResult<SrvRecord> response =
    await Dns.ResolveSrvAsync(service);

Console.WriteLine($"Response: {response.ResponseCode}");
Console.WriteLine($"Records: {response.Records.Count}");

foreach (SrvRecord srv in response.Records)
{
    Console.WriteLine(
        $"target={srv.Target} port={srv.Port} " +
        $"priority={srv.Priority} weight={srv.Weight}");
}

Per una prova reale è necessario utilizzare un nome SRV effettivamente pubblicato nell'ambiente di test. La stringa example.com è illustrativa e non implica l'esistenza del servizio LDAP.

Una suite completa dovrebbe includere query separate per record A/AAAA, CNAME, MX, TXT, PTR e NS, raccogliendo gli stessi campi diagnostici in un formato comune.

17. Benchmark: non misurare soltanto la media

Un benchmark DNS affidabile dovrebbe separare query cold e warm, distinguere cache locale e remota, confrontare più resolver e misurare percentili di latenza. La sola media può nascondere code molto lunghe.

Metriche suggerite: dns_lookup_duration_ms (p50/p95/p99), dns_lookup_failures_total per classificazione, dns_answer_set_changes_total, dns_negative_responses_total e dns_resolver_disagreement_total.

Non utilizzare il nome host grezzo come label ad alta cardinalità in un sistema multi-tenant: una crescita incontrollata delle serie temporali può diventare più costosa del monitoraggio stesso. Usare identificativi di monitor stabili e metadata separati.

18. Fault injection: testare la diagnosi prima dell'incidente

Un laboratorio avanzato dovrebbe includere un resolver DNS di test controllato, ad esempio in un ambiente isolato, capace di rispondere con:

  • un set A/AAAA stabile;
  • un cambio di IP dopo un intervallo definito;
  • NXDOMAIN;
  • SERVFAIL;
  • risposte ritardate;
  • record multipli e rotazione dell'ordine;
  • TTL differenti.

Il client HTTP dovrebbe essere testato separatamente con un server raggiungibile ai diversi indirizzi, in modo da distinguere cambio DNS, errore di connessione, handshake TLS e risposta HTTP. Non è sufficiente cambiare un record e osservare che il client prima o poi raggiunge un altro server: occorre misurare tempi di convergenza e richieste fallite.

19. Da availability check a DNS observability: architettura di un servizio enterprise

Un sistema di osservabilità può integrare un DNS Health Check come controllo autonomo, mantenendo separati i monitor HTTP e TLS. Questo permette di diagnosticare le cause dei problemi senza confondere la risoluzione dei nomi con la disponibilità applicativa.

Monitoring Scheduler
       |
       +-- HTTP Monitor
       |
       +-- TLS Certificate Monitor
       |
       +-- DNS Monitor
                 |
                 +-- A / AAAA
                 +-- CNAME / SRV / MX / TXT / NS / PTR
                 +-- Resolver Comparison
                 +-- Answer Set History
                 +-- Response Classification
       |
       v
Correlation / Alert Engine
       |
       v
Dashboard + Notifications

Il monitor dovrebbe registrare la prospettiva di ciascuna probe. Un resolver interno Azure può restituire indirizzi privati dove un resolver pubblico restituisce indirizzi pubblici o nessuna risposta. Questo può essere un comportamento corretto in presenza di Private Endpoint e Private DNS Zone.

La correlazione DNS/HTTP deve restare diagnostica. Non è opportuno forzare HttpClient a utilizzare gli IP raccolti dal monitor DNS: ciò altererebbe il normale percorso di risoluzione, la selezione degli endpoint e potenzialmente le garanzie TLS.

20. Schema dati proposto per il monitor

{
  "monitorId": "api-production-dns",
  "hostname": "api.example.com",
  "recordType": "A",
  "probeId": "azure-italynorth-01",
  "resolverProfile": "system",
  "timestampUtc": "2026-10-12T08:00:00Z",
  "durationMs": 13.4,
  "responseClass": "Positive",
  "answers": ["192.0.2.10", "192.0.2.11"],
  "answerSetChanged": false,
  "status": "Healthy"
}

Gli indirizzi 192.0.2.0/24 appartengono allo spazio riservato agli esempi documentali. Il JSON è una proposta di contratto applicativo, non un formato nativo di .NET 11.

Le modifiche del set di indirizzi devono essere confrontate come insiemi normalizzati, non come stringhe concatenate nell'ordine ricevuto. Per servizi con GeoDNS o CDN conviene introdurre una modalità di baseline dinamica, per evitare falsi positivi.

21. Alerting: distinguere change event e incident

Un cambio IP è un evento; un servizio non raggiungibile è un possibile incidente. Non devono essere sinonimi.

Una classificazione operativa può prevedere tre livelli:

  • Informational: variazione dell'answer set, nuovo CNAME, modifica del TTL osservato.
  • Warning: divergenza persistente rispetto a una policy esplicita, aumento del p95, errori intermittenti.
  • Critical: impossibilità persistente di risolvere un nome essenziale, confermata da più campioni e correlata a fallimenti del servizio.

Le soglie vanno parametrizzate per monitor e ambiente. Un nome interno risolvibile soltanto nella VNet non dovrebbe essere considerato guasto perché non si risolve da Internet.

22. Sicurezza: un DNS monitor può diventare uno strumento di scansione

Se un servizio di monitoraggio accetta hostname e resolver arbitrari, occorre evitare che utenti non autorizzati possano utilizzarlo per enumerare reti interne, interrogare server riservati o generare carico verso infrastrutture DNS.

Le contromisure comprendono validazione degli hostname, policy di autorizzazione per i resolver, limiti di frequenza, timeout, limiti di concorrenza, audit log e separazione delle probe per rete/tenant. Non assumere che un hostname innocuo rimanga sempre associato agli stessi IP.

23. Strategia di adozione: non migrare a .NET 11 soltanto per il DNS

Questa è la distinzione più importante dal punto di vista architetturale. Una nuova API di runtime non impone automaticamente l'aggiornamento dell'intera applicazione.

Per un sistema in esecuzione su una versione LTS stabile, è consigliabile definire un'astrazione IDnsProbe con implementazione compatibile con il runtime corrente e un adapter opzionale specifico per .NET 11. L'adozione della versione prerelease dovrebbe rimanere confinata al laboratorio.

La funzionalità può essere progettata oggi con contratti stabili e test indipendenti dal provider DNS. Quando .NET 11 sarà stabile, sarà possibile valutare se le nuove API riducono davvero dipendenze, codice e complessità operativa.

24. Checklist per un DNS Health Monitor enterprise

  • Distinguere resolver di sistema e resolver esplicito.
  • Registrare record A e AAAA separatamente.
  • Gestire NXDOMAIN, NODATA, SERVFAIL, timeout ed errori locali.
  • Separare TTL positivo e negative caching.
  • Conservare lo storico degli answer set normalizzati.
  • Identificare probe e rete di provenienza.
  • Misurare p50, p95 e p99 senza creare cardinalità incontrollata.
  • Non generare alert critici per ogni cambio IP.
  • Non sostituire il controllo HTTP con il controllo DNS.
  • Testare failover, split-horizon e Azure Private DNS.
  • Proteggere i resolver configurabili da abusi.
  • Verificare API e attributi sperimentali prima della release stabile.

Conclusioni: DNS-aware non significa automaticamente resilient

.NET 11 migliora l'osservabilità del DNS e rende più espressiva la gestione delle connessioni HTTP. Ma un sistema resiliente non nasce da una singola API. Nasce da politiche di cache, controllo delle dipendenze, gestione dei fallimenti, osservabilità e test riproducibili.

L'obiettivo di un sistema DNS-aware non è mostrare semplicemente l'indirizzo IP risolto, ma ricostruire la storia di un incidente: quale resolver ha risposto, quali record erano visibili, quali connessioni HTTP erano in uso e quando sono comparsi gli errori.

Il DNS dice dove un servizio dovrebbe essere raggiungibile. Il connection pool rivela dove il client è ancora connesso. L'observability deve raccontare la differenza.

Riferimenti ufficiali

venerdì 9 ottobre 2026

Agentic Planning vs Deterministic Execution: architetture AI affidabili con Azure Durable Functions, DAG e GeoAgent

Quando l'intelligenza pianifica, ma non controlla l'esecuzione

Per molto tempo abbiamo progettato gli agenti AI secondo un modello relativamente semplice: un Large Language Model interpreta una richiesta, seleziona uno strumento, ne osserva il risultato e decide cosa fare successivamente.

Il modello funziona bene per task brevi. Diventa però progressivamente meno efficiente quando deve coordinare decine di strumenti, elaborare grandi volumi di dati, gestire retry, attendere eventi esterni e garantire che operazioni già completate non vengano ripetute accidentalmente.

Con Dynamic Workflows for Azure Functions Hosted Skills, annunciato da Microsoft l'8 ottobre 2026, emerge un'architettura differente: l'LLM genera un piano strutturato, mentre un runtime durevole e deterministico si occupa della sua esecuzione.

La distinzione è fondamentale. Il modello non diventa improvvisamente affidabile perché produce JSON anziché testo. È il sistema che interpreta quel JSON a dover imporre vincoli, autorizzazioni, dipendenze e garanzie operative.

Principio architetturale: un LLM può proporre una sequenza di azioni, ma non dovrebbe essere l'autorità finale che decide se quelle azioni sono consentite, sicure o già state eseguite.

1. Il problema del reasoning loop tradizionale

Consideriamo un agente che deve analizzare un evento meteorologico, interrogare servizi GIS, produrre una mappa di rischio e inviare una notifica.

User / Event
    |
    v
   LLM
    |
    +-- Tool: read rainfall
    |       |
    |       v
    |      LLM
    |
    +-- Tool: read terrain
    |       |
    |       v
    |      LLM
    |
    +-- Tool: run spatial analysis
    |       |
    |       v
    |      LLM
    |
    +-- Tool: generate map
    |       |
    |       v
    |      LLM
    |
    +-- Tool: publish result
            |
            v
           LLM

Ogni operazione comporta un nuovo passaggio attraverso il modello. Se gli strumenti restituiscono dati voluminosi, il contesto si riempie di risultati intermedi che il modello potrebbe non aver bisogno di interpretare direttamente.

Il problema non è soltanto economico. È anche architetturale:

  • Latenza: le chiamate al modello possono serializzare operazioni indipendenti.
  • Context pollution: risultati intermedi e log possono occupare una parte significativa della context window.
  • Recovery: un crash durante un'operazione lunga richiede meccanismi di ripresa espliciti.
  • Retry: ripetere un tool può duplicare effetti esterni.
  • Audit: ricostruire perché un'azione sia stata eseguita può diventare difficile.
  • Security: il contenuto restituito da un tool può influenzare impropriamente le decisioni successive dell'agente.

Un reasoning loop è appropriato quando ogni risultato richiede una nuova decisione semantica. Non è necessariamente la scelta migliore quando le operazioni sono note, verificabili e largamente indipendenti.

2. L'architettura emergente: Plan, Validate, Execute, Verify

La nuova separazione delle responsabilità può essere rappresentata così:

                 USER / EVENT
                      |
                      v
              +---------------+
              |  LLM PLANNER  |
              +---------------+
                      |
                 Proposed DAG
                      |
                      v
              +---------------+
              | DAG VALIDATOR |
              | Policy Engine |
              +---------------+
                      |
                Approved Plan
                      |
                      v
              +---------------+
              | DURABLE ENGINE|
              +---------------+
                /     |     \
               v      v      v
           Tool A   Tool B   Tool C
               \      |      /
                \     |     /
                 v    v    v
                AGGREGATION
                      |
                      v
                VERIFICATION
                      |
                      v
                FINAL RESULT

Il passaggio più importante è quello fra planner e runtime.

Il piano generato dal modello deve essere considerato un input non attendibile, anche quando il modello appartiene a un provider affidabile. La validazione non può essere una semplice verifica della sintassi JSON: deve includere controlli strutturali, semantici, autorizzativi e di consumo delle risorse.

Microsoft applica questo paradigma nella preview Dynamic Workflows di Azure Functions Hosted Skills. Il runtime utilizza Durable Functions per eseguire il piano senza riportare continuamente i risultati intermedi nel reasoning loop dell'LLM.

È importante distinguere la funzionalità specifica Microsoft, attualmente in preview e basata su tool Python, da un'implementazione equivalente realizzata autonomamente in C# e .NET con Durable Functions.

3. Il DAG come contratto eseguibile

Un Directed Acyclic Graph è un grafo diretto senza cicli. Ogni nodo rappresenta un'operazione; ogni arco rappresenta una dipendenza.

Consideriamo un piano per valutare il rischio associato a precipitazioni intense:

         ReadRainfall
              |
              +----------------+
              |                |
              v                v
        LoadTerrain      LoadAssets
              |                |
              v                |
        ComputeRunoff          |
              |                |
              +--------+-------+
                       |
                       v
                IntersectRisk
                       |
                       v
                 GenerateMap
                       |
                       v
                PublishResult

Il DAG esplicita immediatamente il parallelismo possibile: LoadTerrain e LoadAssets possono partire quando ReadRainfall è completato, se le rispettive operazioni non dipendono l'una dall'altra.

In un sistema reale conviene spingersi oltre, rendendo indipendenti tutte le acquisizioni che non richiedono il risultato delle precipitazioni.

Un contratto JSON minimale

{
  "schemaVersion": "1.0",
  "workflowId": "rainfall-risk-20261009",
  "nodes": [
    {
      "id": "rainfall",
      "tool": "gis.readRainfall",
      "dependsOn": []
    },
    {
      "id": "terrain",
      "tool": "gis.loadTerrain",
      "dependsOn": []
    },
    {
      "id": "assets",
      "tool": "gis.loadAssets",
      "dependsOn": []
    },
    {
      "id": "risk",
      "tool": "gis.computeRisk",
      "dependsOn": [
        "rainfall",
        "terrain",
        "assets"
      ]
    },
    {
      "id": "map",
      "tool": "gis.generateMap",
      "dependsOn": ["risk"]
    }
  ]
}

Questo JSON è una rappresentazione concettuale del nostro contratto applicativo, non lo schema ufficiale della preview Microsoft.

Un contratto production-grade dovrebbe inoltre includere versione del piano, input tipizzati, riferimenti agli output, classificazione dei tool, limiti di esecuzione e policy di autorizzazione. Gli argomenti non devono poter contenere endpoint arbitrari o istruzioni eseguibili non previste.

4. DAG validation: il vero confine di sicurezza

La validazione di un piano prodotto da un LLM deve essere eseguita prima che il workflow venga avviato.

I controlli minimi comprendono:

  • Identificatori univoci dei nodi.
  • Assenza di dipendenze inesistenti.
  • Assenza di cicli.
  • Limiti su numero di nodi, profondità e fan-out.
  • Tool presenti in una allowlist server-side.
  • Compatibilità tra input e output dei tool.
  • Autorizzazioni coerenti con utente, tenant e risorsa.
  • Limiti su costo, durata e concorrenza.
  • Approvazioni esplicite per operazioni con effetti esterni sensibili.

Validazione strutturale in C#

public sealed record WorkflowNode(
    string Id,
    string Tool,
    string[] DependsOn);

public sealed record WorkflowPlan(
    string SchemaVersion,
    WorkflowNode[] Nodes);

public static class DagValidator
{
    private static readonly HashSet<string> AllowedTools =
        new(StringComparer.Ordinal)
        {
            "gis.readRainfall",
            "gis.loadTerrain",
            "gis.loadAssets",
            "gis.computeRisk",
            "gis.generateMap"
        };

    public static void Validate(WorkflowPlan plan)
    {
        ArgumentNullException.ThrowIfNull(plan);

        if (plan.SchemaVersion != "1.0")
            throw new InvalidOperationException(
                "Unsupported schema version.");

        if (plan.Nodes is null ||
            plan.Nodes.Length is < 1 or > 50)
            throw new InvalidOperationException(
                "Invalid workflow size.");

        var nodes = new Dictionary<string, WorkflowNode>(
            StringComparer.Ordinal);

        foreach (var node in plan.Nodes)
        {
            if (string.IsNullOrWhiteSpace(node.Id) ||
                !nodes.TryAdd(node.Id, node))
                throw new InvalidOperationException(
                    "Invalid or duplicate node ID.");

            if (!AllowedTools.Contains(node.Tool))
                throw new InvalidOperationException(
                    $"Tool not allowed: {node.Tool}");
        }

        var indegree = nodes.Keys.ToDictionary(
            id => id,
            _ => 0,
            StringComparer.Ordinal);

        var children = nodes.Keys.ToDictionary(
            id => id,
            _ => new List<string>(),
            StringComparer.Ordinal);

        foreach (var node in nodes.Values)
        {
            var dependencies = node.DependsOn ??
                throw new InvalidOperationException(
                    "Dependencies cannot be null.");

            foreach (var dependency in dependencies.Distinct())
            {
                if (!nodes.ContainsKey(dependency))
                    throw new InvalidOperationException(
                        $"Unknown dependency: {dependency}");

                indegree[node.Id]++;
                children[dependency].Add(node.Id);
            }
        }

        var ready = new Queue<string>(
            indegree
                .Where(x => x.Value == 0)
                .Select(x => x.Key));

        var visited = 0;

        while (ready.Count > 0)
        {
            var id = ready.Dequeue();
            visited++;

            foreach (var child in children[id])
            {
                if (--indegree[child] == 0)
                    ready.Enqueue(child);
            }
        }

        if (visited != nodes.Count)
            throw new InvalidOperationException(
                "Workflow contains a cycle.");
    }
}

L'algoritmo utilizza una variante del topological sorting di Kahn. La complessità è O(V + E), dove V rappresenta i nodi ed E le dipendenze.

Questo codice valida soltanto la struttura e una allowlist di base. Non rappresenta un security validator completo. In produzione servono anche controllo degli argomenti, autorizzazione contestuale, compatibilità dei tipi, quote e validazione della provenienza del piano.

Un ulteriore accorgimento consiste nel calcolare un hash canonico del piano validato, registrando insieme ad esso identità del richiedente, versione delle policy e identificativo dell'esecuzione. Il runtime dovrebbe eseguire esattamente quel piano immutabile, evitando modifiche successive alla validazione.

5. Durable Functions: perché l'orchestrazione deve essere deterministica

Azure Durable Functions utilizza una forma di event sourcing per ricostruire lo stato delle orchestrazioni.

Quando un'orchestrazione riprende dopo un evento, il runtime può rieseguire il codice dell'orchestratore dall'inizio, recuperando dalla history i risultati delle attività già completate.

Questo meccanismo prende il nome di replay.

Execution history
       |
       +-- ActivityScheduled(A)
       +-- ActivityCompleted(A)
       +-- ActivityScheduled(B)
       +-- ActivityCompleted(B)
       |
       v
  Orchestrator Replay
       |
       +-- A: result from history
       +-- B: result from history
       +-- C: schedule new activity

Il codice dell'orchestratore deve quindi essere deterministico. Non dovrebbe effettuare direttamente chiamate HTTP, interrogazioni SQL, operazioni GIS o letture dell'orologio di sistema.

Le operazioni con effetti esterni appartengono alle Activity Functions.

Esempio di orchestrazione C# con .NET isolated worker

using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

public static class GeoWorkflow
{
    [Function(nameof(RunGeoWorkflow))]
    public static async Task<string> RunGeoWorkflow(
        [OrchestrationTrigger]
        TaskOrchestrationContext context)
    {
        var rainfallTask =
            context.CallActivityAsync<string>(
                "ReadRainfall");

        var terrainTask =
            context.CallActivityAsync<string>(
                "LoadTerrain");

        var assetsTask =
            context.CallActivityAsync<string>(
                "LoadAssets");

        await Task.WhenAll(
            rainfallTask,
            terrainTask,
            assetsTask);

        var input = new RiskInput(
            rainfallTask.Result,
            terrainTask.Result,
            assetsTask.Result);

        var risk =
            await context.CallActivityAsync<string>(
                "ComputeRisk",
                input);

        return await context.CallActivityAsync<string>(
            "GenerateMap",
            risk);
    }
}

public sealed record RiskInput(
    string RainfallUri,
    string TerrainUri,
    string AssetsUri);

Questo è un esempio di orchestrazione statica. Non è ancora un interprete dinamico di DAG: serve a mostrare il modello Durable Functions e il parallelismo fan-out/fan-in.

Le tre Activity iniziali restituiscono riferimenti a dati persistenti, non interi raster o dataset serializzati nella history dell'orchestrazione.

Per interpretare DAG generati dinamicamente è preferibile utilizzare un executor generico che riceva un piano validato, identifichi i nodi pronti e li programmi attraverso un catalogo di Activity consentite.

6. Un DAG executor dinamico: la differenza fra scheduling e reasoning

Il runtime non deve chiedere all'LLM quale nodo eseguire dopo ogni completamento. Deve applicare una regola deterministica:

Ready(node) =
    AllDependenciesCompleted(node)
    AND PolicyAllows(node)
    AND ResourcesAvailable(node)

Un possibile algoritmo è:

completed = {}
pending = allNodes

while pending is not empty:

    ready = nodes where:
        every dependency is completed

    if ready is empty:
        fail invalid workflow

    execute ready nodes concurrently
    await completion

    persist outputs
    mark nodes completed
    remove nodes from pending

Questo pseudocodice illustra l'idea, ma in produzione richiede alcuni raffinamenti importanti.

Prima di tutto, i nodi devono essere ordinati stabilmente per evitare divergenze durante il replay. Inoltre la concorrenza deve essere limitata, gli errori devono essere gestiti per nodo e il risultato di ogni Activity deve essere registrato con un identificatore stabile.

Un semplice Task.WhenAll() su tutti i nodi pronti può produrre fan-out eccessivo. In un workflow geografico, cinquanta richieste simultanee a un servizio ArcGIS possono saturare il servizio, esaurire risorse o produrre throttling.

È quindi utile distinguere tre livelli di concorrenza:

  • Workflow concurrency: quante orchestrazioni possono essere attive.
  • Node concurrency: quanti nodi dello stesso DAG possono essere eseguiti contemporaneamente.
  • Resource concurrency: quante operazioni possono utilizzare uno specifico servizio, database o pool di worker.

Il terzo livello è particolarmente importante: limitare il fan-out di una singola orchestrazione non impedisce a cento orchestrazioni concorrenti di sovraccaricare lo stesso servizio.

7. Idempotenza: at-least-once non significa exactly-once

Uno dei problemi più pericolosi dei workflow distribuiti è la ripetizione delle operazioni.

Durable Functions può rieseguire un'Activity quando si verifica un errore o quando il completamento dell'attività non è stato registrato correttamente.

Supponiamo che un'Activity esegua:

POST /arcgis/rest/services/Analysis/GPServer/SubmitJob

Il servizio remoto riceve la richiesta e avvia il job. Subito dopo, il worker Azure Functions si interrompe prima di registrare il risultato.

Al riavvio, il runtime può ripetere l'Activity.

Il risultato potrebbe essere:

First attempt  --> ArcGIS Job 1001
Worker crash
Second attempt --> ArcGIS Job 1002

Il sistema ha prodotto due elaborazioni per una sola richiesta logica.

Il retry è quindi corretto dal punto di vista dell'infrastruttura, ma non necessariamente dal punto di vista del business.

La soluzione: Operation Ledger e Idempotency Key

Ogni operazione con effetti esterni dovrebbe avere una chiave stabile:

IdempotencyKey =
    WorkflowId + ":" + NodeId

Una tabella SQL può mantenere lo stato dell'operazione:

CREATE TABLE WorkflowOperations
(
    OperationKey NVARCHAR(200) NOT NULL
        PRIMARY KEY,

    State NVARCHAR(30) NOT NULL,

    ExternalJobId NVARCHAR(200) NULL,

    ResultUri NVARCHAR(2000) NULL,

    UpdatedAt DATETIMEOFFSET NOT NULL
);

Il pattern operativo diventa:

1. Reserve OperationKey atomically
2. Check previous execution state
3. Submit or reconcile external job
4. Persist ExternalJobId
5. Poll external job
6. Persist ResultUri
7. Mark operation Completed

È fondamentale riconoscere che il ledger, da solo, non garantisce exactly-once.

Esiste infatti una finestra critica fra l'invio della richiesta al sistema esterno e il salvataggio dell'ExternalJobId. Se il processo si interrompe in quel punto, il database locale non sa se l'operazione remota sia stata accettata.

Per eliminare questa ambiguità serve un meccanismo di riconciliazione: una chiave di idempotenza supportata dal servizio remoto, un identificatore applicativo ricercabile oppure un adapter che possa individuare un job già creato.

Se l'API esterna non supporta alcuna forma di deduplicazione o riconciliazione, la garanzia di exactly-once non può essere ottenuta semplicemente aggiungendo retry e una tabella SQL.

8. Retry, timeout, circuit breaker e compensazione

Un workflow distribuito affidabile deve distinguere gli errori transitori da quelli permanenti.

Un HTTP 429 o 503 può giustificare un retry con backoff. Un errore di autorizzazione, un input GIS non valido o un piano che viola una policy non dovrebbe invece essere ripetuto automaticamente.

Per gli errori transitori è possibile utilizzare le retry policy delle Activity Durable Functions.

Per operazioni che modificano sistemi esterni è inoltre utile introdurre una logica di compensazione.

Consideriamo:

CreateAnalysisJob
        |
        v
PublishFeatureLayer
        |
        v
UpdateWebMap
        |
        v
NotifyUsers

Se UpdateWebMap fallisce dopo la pubblicazione del layer, il sistema potrebbe dover eliminare il layer temporaneo o ripristinare la configurazione precedente.

Questo modello è vicino al pattern Saga: ogni azione può avere un'azione compensativa, ma la compensazione non equivale necessariamente a un rollback transazionale perfetto.

Per un workflow geografico occorre inoltre distinguere:

  • Risultati temporanei, eliminabili automaticamente.
  • Risultati pubblicati, che richiedono autorizzazioni specifiche per la rimozione.
  • Modifiche a dati autorevoli, che possono richiedere approvazione umana.
  • Notifiche già inviate, che non possono essere semplicemente annullate.

La progettazione delle compensazioni deve essere parte del catalogo dei tool, non una decisione improvvisata dall'LLM dopo un errore.

9. Isolamento dei sub-agent: un agente specializzato non è un processo fidato

Un workflow può delegare attività a sub-agent specializzati.

Per esempio:

GeoCoordinator
      |
      +-- RainfallAgent
      |
      +-- TerrainAgent
      |
      +-- InfrastructureAgent
      |
      +-- RiskVerificationAgent

Il vantaggio è la specializzazione. Il rischio è l'ampliamento della superficie di attacco.

Un sub-agent non dovrebbe ereditare automaticamente l'intera cronologia del coordinatore, tutte le sue credenziali o la capacità di invocare qualsiasi tool.

Nella preview Microsoft Dynamic Workflows, le invocazioni dei sub-agent utilizzano sessioni isolate e stateless, con istruzioni, modelli, strumenti e timeout propri. Non ricevono automaticamente la conversation history o la sandbox del coordinatore.

Questo è un buon punto di partenza, ma non sostituisce l'isolamento infrastrutturale.

Capability-based execution

Ogni sub-agent dovrebbe ricevere soltanto le capability necessarie.

RainfallAgent
    READ rainfall observations
    READ station metadata
    NO write access
    NO publishing permissions

TerrainAgent
    READ elevation datasets
    EXECUTE terrain analysis
    NO access to notifications

PublisherAgent
    READ approved outputs
    PUBLISH to approved folders
    NO arbitrary service access

Il controllo deve essere imposto dal gateway dei tool e dalle identità utilizzate verso i servizi, non soltanto da istruzioni testuali come "non pubblicare dati".

In Azure, Managed Identity, Azure RBAC, Key Vault e isolamento di rete possono contribuire a questo modello, purché le identità siano effettivamente separate secondo i confini di autorizzazione richiesti.

10. Context engineering: la memoria dell'agente non è lo stato del workflow

Un altro errore frequente consiste nell'utilizzare la conversation history come archivio dello stato operativo.

La memoria del modello è utile per mantenere obiettivi, vincoli e informazioni semantiche. Non dovrebbe essere la fonte autorevole per determinare se un job è completato, quale output è stato pubblicato o quali autorizzazioni sono state concesse.

Propongo di separare quattro livelli:

+----------------------------------+
| Semantic Context                 |
| Goal, assumptions, constraints   |
+----------------------------------+
| Execution State                  |
| Node status, retries, timestamps |
+----------------------------------+
| Artifact Store                   |
| Raster, JSON, logs, results      |
+----------------------------------+
| Audit & Authorization            |
| Identity, approvals, provenance  |
+----------------------------------+

Il modello dovrebbe ricevere soltanto il contesto necessario per decidere il piano e, alla fine, interpretare i risultati.

Gli output voluminosi dovrebbero essere conservati in storage e rappresentati attraverso riferimenti immutabili o versionati, accompagnati da metadata come checksum, schema, sistema di riferimento e provenienza.

Questo evita che raster, grandi FeatureSet, log o migliaia di record GIS vengano inseriti nella context window senza alcun beneficio.

11. GeoAgent: un laboratorio architetturale con ArcGIS e Azure

Immaginiamo di voler realizzare un agente che risponda alla richiesta:

Analizza le precipitazioni delle ultime 24 ore, individua le aree potenzialmente critiche e produci una mappa delle infrastrutture esposte.

Il modello non dovrebbe generare direttamente una mappa di rischio basandosi soltanto sul testo. Deve selezionare strumenti GIS deterministici, ciascuno con input, output e limiti ben definiti.

Pipeline proposta

Weather / Rainfall API
          |
          v
   Observation Validation
          |
          v
   Spatial Interpolation
      IDW / Spline
          |
          v
   Rainfall Raster
          |
          +------------------+
          |                  |
          v                  v
    Terrain Data       Asset Inventory
          |                  |
          +---------+--------+
                    |
                    v
             Spatial Analysis
                    |
                    v
             Exposure Overlay
                    |
                    v
             Result Validation
                    |
                    v
             ArcGIS Publishing
                    |
                    v
               WebMap / App

Una possibile implementazione utilizza:

  • C#/.NET: API di ingresso, validazione, orchestrazione e integrazione enterprise.
  • Azure Durable Functions: scheduling durevole, retry e recovery.
  • Azure SQL: operation ledger, metadata, policy e audit applicativo.
  • Azure Blob Storage: artifact store per input e output voluminosi.
  • ArcGIS Enterprise: geoprocessing, analisi raster, feature service e pubblicazione.
  • LLM: interpretazione della richiesta, costruzione del piano e sintesi finale.

È importante non confondere l'interpolazione delle precipitazioni con una simulazione idrologica o idraulica completa.

IDW e Spline producono superfici interpolate a partire dalle osservazioni. Per modellare runoff, propagazione delle piene o dinamiche fisiche occorrono modelli e dati aggiuntivi, opportunamente calibrati e validati.

Inoltre l'eventuale esecuzione di Spatial Analyst richiede di verificare disponibilità delle capability e licenze nella versione e nell'ambiente ArcGIS effettivamente distribuiti.

12. Un adapter ArcGIS robusto deve conoscere il ciclo di vita dei job

Un adapter per un servizio ArcGIS Geoprocessing asincrono non dovrebbe limitarsi a invocare SubmitJob e attendere indefinitamente.

Deve modellare esplicitamente gli stati del job remoto:

NotSubmitted
      |
      v
  Submitted
      |
      v
   Executing
      |
      +----> Succeeded
      |
      +----> Failed
      |
      +----> Cancelled
      |
      +----> TimedOut

La gestione deve distinguere lo stato del job ArcGIS dallo stato dell'Activity Azure Functions.

Un job ArcGIS può continuare a essere eseguito anche quando il worker che lo ha avviato non è più disponibile. Per questo è preferibile salvare l'identificatore del job e utilizzare un meccanismo di polling o riconciliazione durevole.

Per processi molto lunghi, il pattern può diventare:

Submit GP Job
     |
     v
Persist Job ID
     |
     v
Durable Timer
     |
     v
Check GP Status
     |
     +-- Running --> Durable Timer
     |
     +-- Success --> Fetch Results
     |
     +-- Failure --> Error Policy

Il polling non dovrebbe occupare un thread o una Function instance per tutta la durata del job.

Per i servizi GIS occorre inoltre definire timeout, frequenza di polling, politiche di cancellazione e comportamento in caso di perdita temporanea della connettività.

13. Observability: misurare il workflow, non soltanto i token

Un sistema agentico enterprise dovrebbe essere osservabile su più livelli.

Il livello LLM deve registrare modello, versione, token, latenza, versione del prompt e identificativo del piano generato.

Il livello workflow deve registrare durata dei nodi, retry, timeout, dipendenze, errori e stato complessivo.

Il livello GIS deve registrare servizio utilizzato, identificativo del job, tempi di elaborazione, parametri analitici, dataset e riferimenti agli output.

Una struttura di correlazione può essere:

TraceId
   |
   +-- PlanningSpan
   |
   +-- ValidationSpan
   |
   +-- WorkflowInstanceId
          |
          +-- Node: rainfall
          +-- Node: terrain
          +-- Node: risk
          +-- Node: publishing
                    |
                    +-- ArcGIS JobId

OpenTelemetry e Application Insights possono contribuire a questa osservabilità, ma occorre propagare esplicitamente gli identificatori di correlazione fra sistemi.

Il replay delle orchestrazioni richiede inoltre attenzione: log prodotti ingenuamente durante il replay possono essere duplicati. Dove disponibile, conviene utilizzare il replay-safe logger fornito dal runtime Durable Functions.

Metriche che misurerei

  • Planning latency e planning token cost.
  • Percentuale di piani rifiutati dal validator.
  • Numero di nodi e profondità media del DAG.
  • Parallelismo effettivo rispetto a quello teorico.
  • Retry per tool e per servizio esterno.
  • Numero di job remoti riconciliati dopo failure.
  • Workflow completion rate.
  • Tempo medio e percentile P95/P99 per nodo.
  • Costo per workflow completato.
  • Accuratezza e validità dei risultati GIS.

Una riduzione dei token non rappresenta automaticamente un miglioramento del sistema se aumentano i workflow falliti o diminuisce la qualità del risultato.

14. Sicurezza: il piano generato dall'LLM è una superficie di attacco

Un piano apparentemente valido può essere pericoloso anche senza contenere codice eseguibile.

Consideriamo alcuni scenari:

  • Tool escalation: il modello seleziona un tool di pubblicazione quando l'utente ha richiesto soltanto un'analisi.
  • Data exfiltration: un parametro contiene un endpoint esterno non autorizzato.
  • Resource exhaustion: il piano richiede centinaia di analisi raster molto costose.
  • Cross-tenant access: un nodo tenta di utilizzare dati appartenenti a un altro tenant.
  • Prompt injection: un dataset o documento esterno influenza il planner inducendolo a inserire operazioni non richieste.
  • Approval spoofing: un output intermedio dichiara falsamente che l'utente ha autorizzato un'azione.

Le contromisure devono essere applicate fuori dal modello:

Untrusted User / Tool Data
             |
             v
        LLM Planner
             |
             v
        Schema Validator
             |
             v
        Policy Decision
             |
             v
       Approval Gateway
             |
             v
        Tool Dispatcher
             |
             v
     Authorized Execution

Un'approvazione dovrebbe essere un evento autenticato e correlato a una specifica operazione, non una proprietà booleana modificabile dal modello.

Il tool dispatcher dovrebbe risolvere le credenziali dal contesto server-side, evitando che il piano possa scegliere liberamente identità, segreti o endpoint.

15. Benchmark Microsoft: cosa dimostrano realmente i numeri

Nel proprio annuncio, Microsoft ha confrontato un normale reasoning loop con Dynamic Workflows su un benchmark sintetico di analisi di dieci servizi.

I risultati riportati sono:

MetricaReasoning loopDynamic Workflow
Token utilizzati99.0976.781
Latenza247,1 s12,7 s
Riduzione token—93,1%
Riduzione latenza—95,1%

I numeri sono notevoli, ma vanno interpretati correttamente. Il benchmark riguarda un task specifico, strutturato e adatto alla parallelizzazione. Non significa che qualsiasi workflow agentico possa ottenere automaticamente riduzioni equivalenti.

Il vantaggio dipende soprattutto da tre fattori:

  • Le attività indipendenti possono essere eseguite in parallelo.
  • Gli output intermedi non devono attraversare continuamente il modello.
  • Il numero di round-trip LLM viene drasticamente ridotto.

Un workflow che richiede decisioni semantiche dopo ogni passaggio potrebbe invece beneficiare meno di questo modello.

16. Il trade-off: meno autonomia durante l'esecuzione, più affidabilità

Separare pianificazione ed esecuzione introduce anche un limite.

Un DAG statico non può adattarsi arbitrariamente a qualsiasi informazione inattesa. Se un risultato intermedio richiede una nuova strategia, il workflow deve prevedere una decisione condizionale oppure una fase esplicita di replanning.

Per questo considero particolarmente interessante un modello ibrido:

LLM Planning
      |
      v
Validated DAG
      |
      v
Deterministic Execution
      |
      +-- Expected Result
      |       |
      |       v
      |   Continue DAG
      |
      +-- Unexpected Result
              |
              v
        Controlled Replan
              |
              v
        New Validation
              |
              v
        Resume Execution

Il replanning deve essere limitato e tracciato. Ogni nuovo piano deve essere nuovamente validato, e il sistema deve conoscere quali operazioni precedenti siano già state completate.

Non dovrebbe essere possibile aggirare un rifiuto del validator semplicemente generando ripetutamente nuovi piani.

17. Verso una nuova architettura: il GeoAgent come compilatore di workflow geografici

La prospettiva più interessante è considerare l'LLM non come il motore di esecuzione, ma come un compilatore semantico.

L'utente esprime un obiettivo in linguaggio naturale. Il modello lo traduce in una rappresentazione intermedia tipizzata. Il sistema valida questa rappresentazione, la ottimizza e la esegue utilizzando strumenti GIS affidabili.

Natural Language Goal
          |
          v
      LLM Planner
          |
          v
    GeoWorkflow IR
          |
          v
   Semantic Validation
          |
          v
   Optimization Passes
          |
          v
    Execution Plan
          |
          v
 Durable GIS Runtime
          |
          v
 Verified GeoArtifacts

Questa analogia con un compilatore è particolarmente potente.

La rappresentazione intermedia potrebbe contenere informazioni tipizzate come:

  • Sistema di riferimento spaziale.
  • Tipo geometrico.
  • Risoluzione raster.
  • Unità di misura.
  • Estensione geografica.
  • Dipendenze tra dataset.
  • Requisiti di licenza e capability GIS.
  • Classificazione dei dati.
  • Stima del costo computazionale.

Il validator potrebbe rifiutare un'operazione che combina raster con sistemi di riferimento incompatibili, utilizza unità di misura non coerenti oppure richiede capability non disponibili.

Un optimizer potrebbe invece individuare acquisizioni parallelizzabili, eliminare elaborazioni duplicate, riutilizzare risultati già presenti nello storage o selezionare differenti backend di elaborazione.

È importante distinguere ciò che è già disponibile da ciò che rappresenta una possibile evoluzione progettuale: questa GeoWorkflow IR è una proposta architetturale, non un prodotto Esri o Microsoft attualmente disponibile con tale nome.

18. Oltre il DAG: workflow gerarchici, feedback loop e sistemi autonomi

Il DAG è ideale per rappresentare dipendenze acicliche. Non descrive però naturalmente sistemi che devono reagire indefinitamente a nuovi eventi.

Un sistema di monitoraggio territoriale continuo può richiedere cicli di osservazione, analisi, verifica e aggiornamento.

La soluzione non è introdurre cicli arbitrari dentro un DAG non controllato, ma comporre workflow durevoli di durata limitata attraverso eventi, sub-orchestrazioni e nuove istanze.

Observation Event
       |
       v
Analysis Workflow
       |
       v
Verification
       |
       +-- No Change --> Archive
       |
       +-- Relevant Change
                  |
                  v
             New Event
                  |
                  v
          Follow-up Workflow

In questo modo ogni esecuzione rimane identificabile, verificabile e soggetta a quote, mentre il sistema complessivo può funzionare continuamente.

È una distinzione importante per il futuro degli agenti persistenti: persistenza della responsabilità non significa esecuzione illimitata di un singolo processo o di una singola conversazione LLM.

19. Checklist di produzione

Prima di portare in produzione un'architettura agentic workflow, verificherei almeno questi aspetti:

  • Il piano viene validato prima dell'esecuzione.
  • Ogni tool è registrato in un catalogo server-side.
  • Gli input sono tipizzati e soggetti a limiti.
  • Le autorizzazioni vengono verificate sul backend.
  • Le operazioni con effetti esterni hanno idempotency key e riconciliazione.
  • Gli orchestratori rispettano i vincoli di determinismo del replay.
  • I job remoti possono essere recuperati dopo un crash.
  • Gli output voluminosi sono esternalizzati dalla history.
  • I sub-agent hanno capability limitate.
  • Le approvazioni sono eventi autenticati.
  • Il parallelismo è limitato anche a livello di risorsa condivisa.
  • Le modifiche ai workflow già attivi sono gestite con versioning.
  • Gli audit log sono protetti da modifiche non autorizzate.
  • Le metriche misurano correttezza, costo e affidabilità, non soltanto latenza.
  • Esistono test di fault injection per crash, timeout, duplicazioni e retry.

20. Conclusione: l'LLM non deve diventare il transaction manager

Dynamic Workflows rappresenta un passaggio significativo nell'evoluzione delle architetture agentiche.

Non perché renda i modelli più intelligenti, ma perché riconosce esplicitamente che intelligenza, autorizzazione ed esecuzione affidabile sono problemi differenti.

Il modello interpreta obiettivi ambigui e propone piani. Il validator controlla che quei piani siano ammissibili. Il workflow engine esegue le attività rispettando dipendenze, retry e checkpoint. I tool applicano le operazioni reali attraverso API e identità autorizzate.

Nel mondo GIS questa separazione è ancora più importante: un LLM può scegliere quali analisi geospaziali effettuare, ma la correttezza geometrica, statistica e fisica deve rimanere responsabilità di algoritmi, dati e procedure verificabili.

La prossima generazione di GeoAgent potrebbe quindi non essere un chatbot con accesso a qualche geoprocessing tool, ma un vero sistema capace di compilare richieste territoriali in workflow geografici tipizzati, verificabili, riproducibili e osservabili.

La direzione è chiara: l'LLM definisce l'intenzione, il piano diventa un contratto e il runtime garantisce l'esecuzione. L'autonomia utile nasce quando il ragionamento probabilistico incontra un'infrastruttura deterministica.

Riferimenti tecnici

martedì 6 ottobre 2026

ArcGIS Crime Analysis 6.0: dalla mappa dei reati alla Spatio-Temporal Intelligence

Una mappa dei reati non è Crime Analysis.

Visualizzare migliaia di incidenti come punti su una mappa può essere utile, ma il vero problema analitico è molto più profondo: capire se ciò che osserviamo rappresenta un pattern statisticamente significativo, come evolve nel tempo, quanto rapidamente decade il rischio attorno a un evento, se un intervento ha realmente modificato il fenomeno e, soprattutto, quanta incertezza esiste nei dati che stiamo analizzando.

Con Crime Analysis 6.0, rilasciato a settembre 2026, Esri ha aggiornato in modo significativo la propria soluzione per ArcGIS Pro. La nuova release supporta ArcGIS Pro 3.7, introduce un nuovo add-in e due strumenti particolarmente interessanti: Aoristic Analysis e Weighted Displacement Difference.

Ma questi strumenti diventano ancora più interessanti se li inseriamo in un quadro più ampio:

Crime Mapping
      ↓
Spatial Statistics
      ↓
Spatio-Temporal Analysis
      ↓
Crime Pattern Detection
      ↓
Uncertainty Modelling
      ↓
Vector + Graph Representation
      ↓
GeoAI / Agentic Spatial Intelligence

In questo deep dive vediamo quindi Crime Analysis non semplicemente come una toolbar di ArcGIS Pro, ma come il punto di ingresso verso una moderna architettura di spatial intelligence.

1. Il dato criminale è un evento spazio-temporale

Il modello più semplice rappresenta un incidente come:

CrimeEvent = (x, y, t)

dove x,y identificano la posizione e t il tempo.

Nella realtà un evento è molto più ricco:

CrimeEvent =
{
    Geometry,
    OccurFrom,
    OccurTo,
    CrimeType,
    ModusOperandi,
    Target,
    Context,
    Relationships
}

Già qui emerge un problema fondamentale: spesso t non è conosciuto.

Un furto in abitazione può essere scoperto alle 18:00, mentre il proprietario ha lasciato casa alle 08:00. L'unica informazione corretta è quindi:

08:00 ≤ t ≤ 18:00

Attribuire arbitrariamente il crimine alle 08:00, alle 13:00 o alle 18:00 introduce informazione che il dato originale non contiene.

Ed è esattamente qui che entra in gioco una delle novità più interessanti di Crime Analysis 6.0.

2. Aoristic Analysis: modellare il tempo che non conosciamo

L'Aoristic Analysis affronta gli eventi per i quali conosciamo un intervallo temporale ma non l'istante esatto.

Supponiamo che un evento possa essere avvenuto in quattro intervalli temporali:

10:00
11:00
12:00
13:00

In assenza di altra informazione, ciascun intervallo riceve peso:

P(t) = 1 / 4 = 0.25

Più generalmente, per un evento i compatibile con n intervalli:

w(i,t) = 1 / n

quando il time bucket t appartiene alla finestra temporale dell'evento, e zero altrimenti.

Aggregando molti eventi otteniamo:

A(t) = Σ w(i,t)

che rappresenta il peso aoristico complessivo associato a quel periodo.

La differenza concettuale è importante: non stiamo inventando l'orario più probabile del singolo evento. Stiamo propagando esplicitamente la sua incertezza temporale.

3. Implementarlo in C#

Il principio può essere riprodotto facilmente in .NET. Immaginiamo un modello minimale:

public sealed record CrimeIncident(
    long Id,
    DateTime OccurFrom,
    DateTime OccurTo,
    string CrimeType);

Possiamo calcolare quanto un evento contribuisce a un determinato bucket temporale:

static double AoristicWeight(
    DateTime occurFrom,
    DateTime occurTo,
    DateTime bucketFrom,
    DateTime bucketTo)
{
    if (occurTo <= occurFrom)
        return 0;

    var from = occurFrom > bucketFrom
        ? occurFrom
        : bucketFrom;

    var to = occurTo < bucketTo
        ? occurTo
        : bucketTo;

    if (to <= from)
        return 0;

    return (to - from).TotalSeconds /
           (occurTo - occurFrom).TotalSeconds;
}

Questa variante utilizza la frazione effettiva di sovrapposizione dell'intervallo, rendendo esplicito ciò che normalmente viene nascosto dietro una semplice classificazione temporale.

In un add-in ArcGIS Pro potremmo quindi leggere le feature con ArcGIS Pro SDK, estrarre OccurFrom e OccurTo, costruire i bucket e produrre una tabella o una feature class derivata.

Il punto interessante non è riscrivere uno strumento che ArcGIS possiede già. È capire il modello matematico per poterlo integrare in pipeline analitiche più complesse.

4. Kernel Density non significa Hot Spot Analysis

Una delle confusioni più comuni nella spatial analysis è interpretare qualsiasi superficie con zone rosse come un hot spot statisticamente significativo.

Kernel Density Estimation (KDE) costruisce una superficie continua distribuendo attorno agli eventi una funzione kernel. In forma semplificata:

f̂(x) = 1/(nh²) Σ K((x-Xᵢ)/h)

dove h è il bandwidth.

Aumentando il search radius si ottiene una superficie più liscia e generalizzata; riducendolo emergono strutture locali più dettagliate.

Il risultato rimane però una stima di densità, non automaticamente una prova di significatività statistica.

high density ≠ statistically significant hot spot

5. Getis-Ord Gi*: quando il clustering diventa statistico

Per individuare cluster statisticamente significativi ArcGIS Pro mette a disposizione, tra gli altri strumenti, Optimized Hot Spot Analysis, basato sulla statistica Getis-Ord Gi*.

Concettualmente non ci interessa soltanto sapere se una feature ha un valore elevato. Vogliamo sapere se essa è circondata da valori elevati in misura difficilmente compatibile con una distribuzione casuale.

Il risultato comprende:

Gi* z-score
p-value
confidence bin

Questa distinzione dovrebbe essere alla base di qualsiasi sistema serio di crime intelligence:

Visualization
    ≠
Density estimation
    ≠
Spatial clustering
    ≠
Statistical evidence

6. DBSCAN, HDBSCAN e clustering spazio-temporale

Non tutti i pattern hanno forma regolare.

Density-based Clustering in ArcGIS Pro consente di identificare cluster immersi nel rumore utilizzando algoritmi come DBSCAN, HDBSCAN e OPTICS.

Questa proprietà è particolarmente interessante nel crime analysis perché un pattern può essere molto evidente per tre settimane e scomparire completamente nel mese successivo.

Una distanza puramente spaziale:

d(i,j) = √((xᵢ-xⱼ)² + (yᵢ-yⱼ)²)

non cattura questa dinamica.

Possiamo concettualmente introdurre una distanza spazio-temporale:

D(i,j) = αDspace(i,j) + βDtime(i,j)

con scale opportunamente normalizzate.

7. Repeat e Near-Repeat: il rischio non è statico

Uno dei concetti più potenti della crime geography è la repeat/near-repeat victimization.

Dopo un evento, il luogo colpito e le aree vicine possono presentare per un periodo limitato un rischio maggiore di eventi analoghi. Tale influenza tende a diminuire sia con la distanza sia con il tempo.

Crime Analysis contiene strumenti dedicati per classificare eventi come originator, repeat e near-repeat utilizzando bande spaziali e temporali.

Possiamo rappresentare concettualmente il decadimento come:

R(d,t) = R₀ · e-λd · e-μt

dove:

d = distanza dall'incidente
t = tempo trascorso
λ = decadimento spaziale
μ = decadimento temporale

Non è quindi una superficie di rischio permanente.

È una superficie che decade continuamente nello spazio e nel tempo.

8. Prediction Zones: attenzione alla parola "prediction"

Crime Analysis può generare Prediction Zones a partire dai pattern repeat/near-repeat.

È però importante capire cosa significa "prediction".

Non significa:

qui avverrà il prossimo crimine

Significa piuttosto:

date le regolarità spazio-temporali
osservate nel dataset,
quest'area presenta un rischio relativo
più elevato nel breve periodo.

È una differenza enorme, sia statisticamente sia eticamente.

9. Space-Time Cube: quando la mappa 2D non basta più

Un altro salto concettuale consiste nel trattare lo spazio e il tempo come un'unica struttura analitica.

Lo Space-Time Cube suddivide i dati in bin spaziali e temporali:

             TIME
              ↑
              │  ▢
              │  ▢ ▢
              │  ▢ ▢
              │  ▢ ▢ ▢
              │
              └────────────→ SPACE

Emerging Hot Spot Analysis permette di osservare non solo dove sono presenti cluster, ma come essi evolvono nel tempo.

La domanda non è più:

Dove sono gli hot spot?

ma:

Dove stanno nascendo?
Quali persistono?
Quali si stanno intensificando?
Quali stanno scomparendo?

Questa è già spatio-temporal intelligence.

10. Crime series e comportamento spaziale

Crime Analysis include workflow per ordinare cronologicamente gli eventi appartenenti a una serie e rappresentarne le connessioni.

Questo consente di studiare concetti classici del geographic profiling, come la distinzione tra comportamento marauder e commuter.

La sequenza:

P1 → P2 → P3 → P4 → P5

diventa quindi molto più di una polilinea.

Possiamo analizzare:

distance(Pn, Pn+1)
bearing(Pn, Pn+1)
Δt(Pn, Pn+1)
velocity
dispersion
directional persistence

e cercare cambiamenti nella struttura della serie.

11. Weighted Displacement Difference: l'intervento ha funzionato?

Crime Analysis 6.0 introduce anche Weighted Displacement Difference.

Qui la domanda cambia completamente.

Non vogliamo più soltanto descrivere il fenomeno. Vogliamo valutare un intervento.

Response Area
      +
Response Displacement Zone

Control Area
      +
Control Displacement Zone

        ↓

Before vs After
        ↓
Weighted Displacement Difference

La statistica confronta il cambiamento degli incidenti nell'area interessata dall'intervento con quello osservato in un'area di controllo e considera anche le aree circostanti per verificare se il fenomeno sia stato semplicemente spostato.

Questo è un passaggio metodologico importantissimo:

Crime ↓ nell'area A

non implica necessariamente:

Intervento efficace

perché potrebbe essersi verificato:

A ↓
B ↑

ovvero displacement.

12. Da Crime Analysis a un Crime Graph

Fin qui abbiamo considerato principalmente geometrie, attributi e tempo.

Ma un'indagine contiene naturalmente anche relazioni.

Person ── called ── Person
   │                   │
 visited             owns
   │                   │
   ▼                   ▼
Place ── near ── CrimeEvent
                       │
                    similar
                       │
                       ▼
                  CrimeEvent

Crime Analysis contiene già strumenti investigativi per lavorare con call links, settori delle celle telefoniche e corrispondenze spazio-temporali.

La naturale evoluzione architetturale è quindi rappresentare parte del dominio come un spatio-temporal knowledge graph.

13. Il passo successivo: Crime Event Embeddings

Ed è qui che possiamo andare oltre gli strumenti standard.

Un incidente non deve necessariamente essere confrontato con un altro soltanto attraverso la distanza geografica.

Possiamo costruire una rappresentazione multidimensionale:

E = [
    spatial features,
    temporal features,
    modus operandi,
    target characteristics,
    environmental context,
    semantic description,
    graph relationships
]

e trasformarla in un vettore:

z = f(E) ∈ Rⁿ

A quel punto due incidenti possono essere confrontati attraverso una funzione di similarità che combina più domini:

D(i,j) = αDspace + βDtime + γDsemantic + δDMO + εDgraph

La query investigativa cambia radicalmente.

Da:

Find incidents within 500 meters.

a:

Find incidents that are:

geographically compatible
AND temporally compatible
AND behaviorally similar
AND semantically similar
AND connected through relevant entities.

Questo è il punto d'incontro tra GIS, vector search e graph analytics.

14. Un'architettura GeoAI

Una possibile architettura enterprise potrebbe diventare:

                ArcGIS Pro
                    │
             Crime Analysis
                    │
          Spatial Statistics
                    │
             ArcGIS Enterprise
                    │
        ┌───────────┴───────────┐
        │                       │
 Spatial Features         Operational Data
        │                       │
        └───────────┬───────────┘
                    │
               Azure SQL
                    │
       ┌────────────┼────────────┐
       │            │            │
   relational     vector       graph
     data         index      relationships
       │            │            │
       └────────────┼────────────┘
                    │
             Retrieval Layer
                    │
                 GeoAgent
                    │
             ArcGIS Pro / Web

Il GeoAgent potrebbe ricevere una richiesta come:

Analyze residential burglaries from the last six weeks.

Identify statistically significant spatial clusters,
test for repeat/near-repeat behavior,
compare modus-operandi similarity,
and explain which evidence supports each candidate series.

15. L'LLM non deve fare statistica al posto del GIS

Questo è forse il punto architetturale più importante.

Non costruirei un sistema nel quale chiediamo direttamente a un Large Language Model:

Where will the next crime occur?

Un LLM non dovrebbe sostituire Getis-Ord Gi*, KDE, DBSCAN, Space-Time Cube o un modello statistico validato.

Dovrebbe invece funzionare come orchestratore analitico:

User
  │
  ▼
GeoAgent
  │
  ├──► ArcGIS Spatial Statistics
  │
  ├──► Space-Time Analysis
  │
  ├──► Vector Search
  │
  ├──► Graph Query
  │
  └──► Operational Database
          │
          ▼
      Evidence
          │
          ▼
   LLM explanation

In altre parole:

LLM ≠ analytical truth

LLM = reasoning + orchestration + explanation

mentre il risultato quantitativo deve continuare a provenire da strumenti deterministici, modelli statistici o algoritmi esplicitamente validati.

16. Explainability prima della prediction

Un sistema di crime intelligence dovrebbe poter rispondere non soltanto:

Risk = HIGH

ma anche:

WHY?

• 7 recent related incidents
• statistically significant Gi* cluster
• 5 incidents inside the near-repeat temporal window
• high spatial proximity
• strong modus-operandi similarity
• pattern intensifying over the last 3 weeks

La spiegabilità non è un accessorio dell'AI.

In domini ad alto impatto è parte dell'architettura.

17. Il problema più difficile: i dati non sono la realtà

Esiste infine un limite che nessun algoritmo può eliminare automaticamente.

Un database criminale non rappresenta necessariamente:

all crimes that happened

ma più precisamente qualcosa di simile a:

events reported
+
events detected
+
events recorded
+
events classified
+
effects of enforcement activity

Questa distinzione è fondamentale.

Una maggiore concentrazione di osservazioni in una zona può derivare da un maggior numero di eventi, ma può anche essere influenzata da maggiore presenza operativa, differenti livelli di denuncia, procedure amministrative o qualità diversa del dato.

Se utilizziamo questi dati senza comprenderne il processo generativo, rischiamo di creare un feedback loop:

more observations
      ↓
higher estimated risk
      ↓
more attention
      ↓
more detected events
      ↓
even higher estimated risk

La precisione matematica del modello non elimina il bias del processo che ha prodotto i dati.

18. Dal GIS descrittivo alla Spatial Intelligence

Crime Analysis 6.0 mostra bene quanto il GIS moderno si stia allontanando dalla semplice rappresentazione cartografica.

Il percorso ormai è:

Where did it happen?
        ↓
Where is it concentrated?
        ↓
Is the concentration statistically significant?
        ↓
How is the pattern evolving?
        ↓
How does risk decay in space and time?
        ↓
Are apparently separate events related?
        ↓
Did an intervention change the pattern?
        ↓
What evidence supports the conclusion?

E il prossimo passo potrebbe essere:

Can an intelligent spatial agent
autonomously assemble the right analyses,
test competing hypotheses,
retrieve supporting evidence
and explain its conclusions?

Conclusione

Crime Analysis 6.0 è interessante non tanto perché aggiunge nuovi pulsanti ad ArcGIS Pro, ma perché rende evidente una trasformazione più ampia.

La geografia del crimine sta diventando sempre più una disciplina spazio-temporale, probabilistica e relazionale.

Aoristic Analysis ci ricorda che anche il tempo può essere incerto. Repeat/Near-Repeat Analysis introduce il decadimento spazio-temporale del rischio. Space-Time Cube permette di osservare l'evoluzione dei cluster. Weighted Displacement Difference porta l'analisi verso la valutazione degli interventi.

Embeddings, vector search, graph analytics e GeoAI possono costituire il livello successivo, purché non sostituiscano la statistica con un modello linguistico.

Il GIS del futuro non dovrà semplicemente mostrare dove si trova qualcosa.

Dovrà essere in grado di spiegare:

what happened
where
when
how it is connected
how certain we are
how the pattern is changing
and which evidence supports the hypothesis.

È in quel momento che una mappa smette di essere soltanto una rappresentazione dello spazio e diventa un vero sistema di Spatial Intelligence.

mercoledì 30 settembre 2026

DiskANN in Azure SQL: Vector Search, RAG e GeoAI direttamente nel database relazionale

La ricerca vettoriale sta progressivamente entrando nel cuore dei database relazionali. Non si tratta semplicemente di aggiungere una nuova funzione SQL: significa poter interrogare gli stessi dati contemporaneamente nello spazio relazionale, nello spazio geografico e nello spazio semantico degli embedding.

Con DiskANN, Azure SQL può memorizzare embedding e utilizzare un indice Approximate Nearest Neighbor direttamente accanto a tabelle, chiavi, transazioni, metadati, filtri di sicurezza e, potenzialmente, dati spaziali.

Per un architect questo apre una domanda interessante: abbiamo davvero bisogno di introdurre un vector database separato per ogni applicazione RAG?

In molti scenari la risposta potrebbe essere no.

Cos'è DiskANN

DiskANN significa Disk-based Approximate Nearest Neighbor. È una famiglia di tecniche sviluppate per eseguire rapidamente ricerche di similarità su collezioni vettoriali di grandi dimensioni.

Non genera embedding, non interpreta documenti e non è un Large Language Model. DiskANN interviene dopo che i dati sono già stati trasformati in vettori.

Document
    ↓
Text extraction
    ↓
Chunking
    ↓
Embedding model
    ↓
Vector
    ↓
Azure SQL
    ↓
DiskANN
    ↓
Nearest neighbors

Il problema che risolve è apparentemente semplice: dato un vettore q, trovare i vettori del database che gli sono più vicini.

Dallo spazio semantico allo spazio vettoriale

Un embedding model trasforma un oggetto, ad esempio una frase:

ArcGIS Enterprise supports federated GIS servers.

in una sequenza numerica:

[0.0182, -0.0317, 0.0041, ..., 0.0227]

Formalmente possiamo pensare all'embedding come a una funzione:

f : X → ℝᵈ

dove X è lo spazio degli oggetti originali e d è la dimensionalità del vettore.

La singola coordinata non ha generalmente un significato interpretabile isolatamente. È la posizione complessiva del vettore nello spazio multidimensionale a rappresentare le caratteristiche apprese dal modello.

Se due contenuti hanno significato simile, il modello tende a collocarli in regioni vicine dello spazio vettoriale.

Exact Nearest Neighbor: perché non basta

Il modo più semplice di trovare i vettori più vicini consiste nel confrontare la query con ogni vettore presente nella tabella.

query
  │
  ├── distance(vector 1)
  ├── distance(vector 2)
  ├── distance(vector 3)
  ├── ...
  └── distance(vector N)

Con N vettori e dimensionalità D, il lavoro cresce approssimativamente come:

O(N × D)

Con poche migliaia di record può essere perfettamente accettabile.

Con:

10.000.000 vectors
×
1.536 dimensions

la scansione esaustiva diventa invece molto più costosa.

È qui che entra in gioco l'Approximate Nearest Neighbor Search.

ANN: sacrificare un po' di perfezione per molta velocità

Un algoritmo ANN non garantisce necessariamente di restituire sempre gli stessi vicini che otterremmo esaminando esaustivamente l'intero dataset.

Accetta un compromesso:

Exactness
    ↓ leggermente

Search space
    ↓ drasticamente

Latency
    ↓ drasticamente

Supponiamo che i veri dieci nearest neighbors siano:

A B C D E F G H I J

e che ANN restituisca:

A B C D E F G H I K

Il recall@10 sarebbe:

9 / 10 = 0.90

Per molte pipeline RAG questo trade-off è estremamente conveniente: non abbiamo bisogno di dimostrare matematicamente quale chunk sia il più vicino, ma di recuperare molto rapidamente un insieme di candidati altamente rilevanti.

DiskANN e grafi di prossimità

DiskANN utilizza un approccio graph-based. Possiamo immaginare ogni embedding come un nodo collegato ad altri nodi utili per navigare nello spazio vettoriale.

              ●──────●
             /        \
        ●───●          ●
        │    \        /
        ●─────●──────●
               \
                ●────●

La ricerca non deve quindi fare:

query → confronta tutti i vettori

ma può procedere concettualmente attraverso il grafo:

query
  ↓
entry point
  ↓
candidate neighbors
  ↓
better neighbors
  ↓
better neighbors
  ↓
Top-K

Il grafo consente di raggiungere rapidamente regioni promettenti dello spazio senza valutare ogni riga.

Perché si chiama DiskANN

Uno degli elementi caratteristici del progetto DiskANN è l'attenzione alla possibilità di lavorare con collezioni vettoriali molto grandi senza dipendere esclusivamente dalla RAM.

              RAM
       ┌────────────────┐
       │ search state   │
       │ cache          │
       │ graph metadata │
       └───────┬────────┘
               │
               │ selective access
               ▼
       ┌────────────────┐
       │ SSD / storage  │
       │ vector data    │
       └────────────────┘

Naturalmente, quando utilizziamo DiskANN tramite Azure SQL non dobbiamo confondere il principio algoritmico con l'implementazione interna del servizio managed: cache, storage engine, I/O scheduling, quantizzazione e query optimization sono responsabilità del database engine.

DiskANN è ora production-ready su Azure SQL

Il 29 settembre 2026 Microsoft ha annunciato la General Availability di DiskANN Vector Index & Search su Azure SQL Database, su Azure SQL Managed Instance configurato con la policy di aggiornamento always-up-to-date e su SQL database in Microsoft Fabric.

La versione GA include elementi particolarmente importanti per workload reali:

  • Approximate Nearest Neighbor Search basata su DiskANN;
  • supporto a INSERT, UPDATE e DELETE;
  • manutenzione asincrona dell'indice vettoriale;
  • iterative filtering;
  • integrazione con il query optimizer;
  • possibilità di combinare vector search e predicati relazionali.

Il tipo VECTOR di SQL

Il database può memorizzare direttamente un embedding in una colonna vettoriale.

Un modello dati orientato a RAG potrebbe essere:

CREATE TABLE dbo.Documents
(
    Id          BIGINT IDENTITY PRIMARY KEY,
    TenantId    INT NOT NULL,
    FileName    NVARCHAR(500) NOT NULL,
    Title       NVARCHAR(500) NULL,
    MimeType    NVARCHAR(100) NULL,
    CreatedAt   DATETIME2 NOT NULL
        DEFAULT SYSUTCDATETIME()
);

I chunk possono invece essere mantenuti separatamente:

CREATE TABLE dbo.DocumentChunks
(
    Id               BIGINT IDENTITY PRIMARY KEY,
    DocumentId       BIGINT NOT NULL,
    TenantId         INT NOT NULL,

    PageNumber       INT NULL,
    ChunkNumber      INT NOT NULL,

    Content          NVARCHAR(MAX) NOT NULL,

    EmbeddingModel   NVARCHAR(100) NOT NULL,
    ChunkingVersion  INT NOT NULL,

    Embedding        VECTOR(1536) NOT NULL,

    CONSTRAINT FK_DocumentChunks_Documents
        FOREIGN KEY (DocumentId)
        REFERENCES dbo.Documents(Id)
);

1536 è solamente un esempio. La dimensionalità deve essere coerente con il modello che genera gli embedding.

Versionare embedding e pipeline

Un embedding dovrebbe essere trattato come dato derivato, non come sorgente primaria.

Conviene quindi conservare almeno:

Original document
Extracted content
Chunk
ChunkingVersion
Embedding
EmbeddingModel
EmbeddingVersion
CreatedAt

Questa informazione diventa fondamentale quando cambiamo modello.

Due modelli differenti possono produrre spazi vettoriali differenti: non possiamo semplicemente mischiare embedding generati da modelli diversi e aspettarci che le distanze rimangano semanticamente confrontabili.

Creazione dell'indice DiskANN

Sulla colonna vettoriale possiamo creare un indice:

CREATE VECTOR INDEX IX_DocumentChunks_Embedding
ON dbo.DocumentChunks(Embedding)
WITH
(
    METRIC = 'cosine',
    TYPE = 'diskann'
);

Le metriche supportate dalla vector search SQL comprendono:

  • cosine;
  • dot;
  • euclidean.

La metrica non va scelta arbitrariamente: deve essere coerente con il modello di embedding e con il modo in cui è stato addestrato.

Exact search con VECTOR_DISTANCE

Possiamo eseguire una ricerca esatta calcolando la distanza:

DECLARE @query VECTOR(1536) = @QueryEmbedding;

SELECT TOP (10)
    Id,
    DocumentId,
    Content,
    VECTOR_DISTANCE(
        'cosine',
        @query,
        Embedding
    ) AS Distance
FROM dbo.DocumentChunks
ORDER BY Distance;

Questa strategia è utile anche come baseline per misurare la qualità dell'indice ANN.

Possiamo infatti confrontare:

Exact Top-K
     vs
DiskANN Top-K

e misurare il recall effettivo sul nostro dataset.

Approximate search con VECTOR_SEARCH

Con gli indici vettoriali di nuova generazione la sintassi corrente utilizza VECTOR_SEARCH insieme a:

TOP (N) WITH APPROXIMATE

Ad esempio:

DECLARE @query VECTOR(1536) = @QueryEmbedding;

SELECT TOP (10) WITH APPROXIMATE
    c.Id,
    c.DocumentId,
    c.Content,
    r.distance
FROM VECTOR_SEARCH(
    TABLE = dbo.DocumentChunks AS c,
    COLUMN = Embedding,
    SIMILAR_TO = @query,
    METRIC = 'cosine'
) AS r
ORDER BY r.distance;

La precedente forma basata sul parametro:

TOP_N = 10

appartiene alla generazione precedente dell'indice ed è deprecata per i nuovi indici.

WITH APPROXIMATE non è solo sintassi

Con:

SELECT TOP (10) WITH APPROXIMATE

stiamo dicendo esplicitamente al motore che accettiamo risultati ANN.

Senza WITH APPROXIMATE, VECTOR_SEARCH può eseguire una ricerca kNN esatta.

VECTOR_SEARCH
      │
      ├── exact kNN
      │
      └── approximate ANN / DiskANN

La distinzione è molto importante quando facciamo benchmarking.

Il query optimizer entra nella vector search

Uno degli aspetti architetturalmente più interessanti è che la ricerca vettoriale non viene trattata come una scatola completamente separata dal motore SQL.

Azure SQL può valutare se utilizzare la strategia ANN oppure altre strategie in funzione della query e della selettività.

Supponiamo di avere:

100.000.000 chunks

ma di applicare:

WHERE TenantId = 42

e che il tenant 42 contenga soltanto 5.000 chunk. In determinate condizioni un piano differente potrebbe risultare più conveniente rispetto all'attraversamento del grafo ANN globale.

FORCE_ANN_ONLY

Quando vogliamo imporre l'utilizzo dell'indice Approximate Nearest Neighbor, Azure SQL mette a disposizione anche l'hint:

FORCE_ANN_ONLY

Concettualmente:

SELECT TOP (10) WITH APPROXIMATE
    ...
FROM VECTOR_SEARCH(
    TABLE = dbo.DocumentChunks AS c,
    COLUMN = Embedding,
    SIMILAR_TO = @query,
    METRIC = 'cosine'
) AS r WITH (FORCE_ANN_ONLY)
ORDER BY r.distance;

È utile soprattutto per test, benchmark o casi in cui sappiamo esattamente quale strategia vogliamo utilizzare.

Come ogni hint SQL, non dovrebbe diventare automaticamente la scelta predefinita.

Iterative filtering: fondamentale per sistemi reali

Immaginiamo una piattaforma SaaS multi-tenant.

Il retrieval deve rispettare:

TenantId = 42

Una implementazione ANN ingenua potrebbe fare:

DiskANN
   ↓
10 nearest vectors
   ↓
WHERE TenantId = 42
   ↓
2 risultati validi

Gli altri otto vettori potrebbero appartenere ad altri tenant.

Con l'iterative filtering la ricerca può continuare nel grafo finché non vengono trovati abbastanza risultati che soddisfano il predicato:

DiskANN
   ↓
candidate set
   ↓
relational filter
   ↓
enough results?
   │
   ├── yes → return Top-K
   │
   └── no
        ↓
   continue graph traversal
        ↓
   more candidates
        ↓
   relational filter

Questa caratteristica è importantissima per:

  • multi-tenancy;
  • ACL;
  • categorie;
  • stati applicativi;
  • vincoli temporali;
  • filtri geografici o amministrativi;
  • security policies.

Vector search e security boundary

In un'applicazione RAG enterprise la sicurezza non deve essere applicata dopo il retrieval.

Il flusso corretto deve essere progettato come:

User
 ↓
Identity
 ↓
Tenant / ACL / Security Policy
 ↓
Vector retrieval
 ↓
Permitted chunks
 ↓
LLM

In altre parole: il retrieval fa parte del security boundary dell'applicazione.

Il vero RAG inizia prima di DiskANN

Una delle semplificazioni più pericolose è pensare che una pipeline RAG sia:

PDF → embedding → vector database

Una pipeline seria assomiglia molto di più a:

                   PDF
                    │
          ┌─────────┴──────────┐
          │                    │
     Native text          Scanned PDF
          │                    │
   Text extraction            OCR
          │                    │
          └─────────┬──────────┘
                    ↓
             normalization
                    ↓
          structure analysis
                    ↓
               chunking
                    ↓
               embedding
                    ↓
               Azure SQL
                    ↓
                DiskANN

PDF nativi e PDF scansionati

Un PDF può contenere un vero text layer. In questo caso è possibile estrarre direttamente:

Page
 ├── Heading
 ├── Paragraph
 ├── Paragraph
 └── Table

Un PDF scansionato può invece contenere essenzialmente immagini:

PDF
 ├── page-001 image
 ├── page-002 image
 └── page-003 image

In questo caso serve OCR.

In ambiente Azure una pipeline possibile è:

Azure Blob Storage
        ↓
Document ingestion service
        ↓
Azure AI Document Intelligence
        ↓
OCR + Layout + Tables + Structure
        ↓
Semantic chunking
        ↓
Embedding model
        ↓
Azure SQL
        ↓
DiskANN

Il punto importante è che non basta recuperare caratteri: serve preservare il più possibile la struttura semantica del documento.

Chunking: spesso più importante del vector database

Consideriamo un manuale tecnico di 500 pagine.

Creare:

manual.pdf → one embedding

è generalmente poco utile.

È molto più efficace ottenere:

manual.pdf
   │
   ├── chunk 001 → embedding
   ├── chunk 002 → embedding
   ├── chunk 003 → embedding
   ├── ...
   └── chunk 850 → embedding

Fixed-size chunking

L'approccio più semplice consiste nel suddividere il testo per numero di token:

chunk 1 → tokens   0 - 499
chunk 2 → tokens 450 - 949
chunk 3 → tokens 900 - 1399

L'overlap aiuta a evitare che un concetto venga spezzato esattamente sul confine tra due chunk.

Semantic chunking

Per documentazione tecnica possiamo però fare di meglio.

Section
 ├── paragraph
 ├── paragraph
 ├── code sample
 ├── table
 └── note

L'obiettivo è costruire unità informative semanticamente coerenti, non semplicemente blocchi di lunghezza costante.

Hierarchical chunking

Possiamo mantenere una gerarchia:

Document
   ↓
Chapter
   ↓
Section
   ↓
Chunk

Se DiskANN recupera il chunk 37 possiamo, ad esempio, recuperare anche:

chunk 36
chunk 37
chunk 38

oppure la sezione padre.

Per questo conviene conservare:

DocumentId
SectionId
ParentChunkId
PageNumber
ChunkNumber

Embedding generation lato applicazione

Una pipeline .NET può essere concettualmente molto semplice:

var chunks = chunker.Split(document);

foreach (var chunk in chunks)
{
    var embedding =
        await embeddingService.GenerateAsync(chunk.Text);

    db.DocumentChunks.Add(
        new DocumentChunk
        {
            DocumentId = documentId,
            TenantId = tenantId,
            PageNumber = chunk.Page,
            ChunkNumber = chunk.Index,
            Content = chunk.Text,
            EmbeddingModel = embedding.Model,
            ChunkingVersion = 1,
            Embedding = embedding.Vector
        });
}

await db.SaveChangesAsync();

Dal punto di vista architetturale terrei ben separati:

Extraction
Chunking
Embedding
Persistence
Indexing
Retrieval
Generation

Questo rende la pipeline versionabile, osservabile e riprocessabile.

Azure SQL può generare embedding direttamente

Lo stack SQL moderno consente anche di registrare modelli esterni e invocarli dal database.

Un external model può essere definito concettualmente così:

CREATE EXTERNAL MODEL MyEmbeddingModel
WITH
(
    LOCATION = '...',
    API_FORMAT = 'Azure OpenAI',
    MODEL_TYPE = EMBEDDINGS,
    MODEL = '...',
    CREDENTIAL = ...
);

e successivamente:

AI_GENERATE_EMBEDDINGS(
    N'ArcGIS Server federation'
    USE MODEL MyEmbeddingModel
)

Microsoft supporta inoltre scenari in cui AI_GENERATE_CHUNKS e AI_GENERATE_EMBEDDINGS vengono combinati direttamente da T-SQL.

Questo rende possibile una pipeline:

SQL row
   ↓
AI_GENERATE_CHUNKS
   ↓
AI_GENERATE_EMBEDDINGS
   ↓
VECTOR
   ↓
DiskANN

Embedding nel database o nell'applicazione?

Il fatto che qualcosa sia possibile in SQL non significa che debba necessariamente essere eseguito lì.

Generare embedding nell'application layer offre generalmente maggiore controllo su:

  • retry;
  • rate limiting;
  • batching;
  • fallback;
  • observability;
  • model routing;
  • versioning della pipeline.

Generarli direttamente nel database può invece semplificare alcune pipeline data-centric.

È quindi una decisione architetturale, non semplicemente sintattica.

RAG: Retrieve Wide, Rank Narrow

DiskANN non deve necessariamente restituire direttamente i chunk che finiranno nel prompt.

Una pipeline più sofisticata può essere:

10.000.000 chunks
       │
       │ DiskANN
       ↓
      50
       │
       │ reranker
       ↓
       8
       │
       │ context builder
       ↓
      LLM

Questo pattern viene spesso sintetizzato come:

Retrieve wide
     ↓
Rank narrow

Reranking

La prima fase privilegia throughput e recall.

Una seconda fase può utilizzare un modello più costoso per stimare meglio la rilevanza rispetto alla domanda originale:

Question
   +
50 candidate chunks
        ↓
     Reranker
        ↓
   Best 5 - 10

DiskANN e reranker risolvono quindi problemi differenti:

DiskANN:
find plausible candidates quickly

Reranker:
order those candidates accurately

Hybrid Search

Gli embedding non sostituiscono necessariamente la ricerca lessicale.

Una query come:

KB5034123 ArcGIS Server TLS

contiene token precisi, codici e identificativi che possono essere estremamente significativi.

Una strategia moderna può combinare:

Vector similarity
       +
Keyword search
       +
Relational filters
       +
Reranking

La vector search è quindi un ulteriore retrieval signal, non necessariamente l'unico.

.NET ed Entity Framework Core

Per chi sviluppa applicazioni .NET è importante distinguere le versioni.

In EF Core 10 Microsoft ha introdotto il supporto alla traduzione di VectorDistance(), quindi alla distanza vettoriale esatta.

In EF Core 11 Microsoft documenta inoltre le API dedicate a vector index e VECTOR_SEARCH.

Ad esempio:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<DocumentChunk>()
        .HasVectorIndex(
            x => x.Embedding,
            "cosine");
}

La ricerca ANN può essere espressa concettualmente come:

var results = await db.DocumentChunks
    .VectorSearch(
        x => x.Embedding,
        queryEmbedding,
        "cosine")
    .OrderBy(x => x.Distance)
    .Take(10)
    .WithApproximate()
    .ToListAsync();

WithApproximate() corrisponde semanticamente alla richiesta:

TOP (...) WITH APPROXIMATE

È però importante notare che Microsoft documenta attualmente queste API di EF Core 11 come funzionalità ancora soggette a evoluzione.

Per chi oggi usa .NET 10

In un'applicazione .NET 10 / EF Core 10 possiamo comunque utilizzare pienamente Azure SQL Vector Search attraverso T-SQL, stored procedure, raw SQL o VectorDistance() dove appropriato.

Non è quindi necessario aspettare EF Core 11 per costruire una soluzione DiskANN.

.NET 10
   ↓
EF Core / ADO.NET
   ↓
VECTOR_SEARCH T-SQL
   ↓
Azure SQL
   ↓
DiskANN

Architettura RAG completa su Azure

                 ┌────────────────────┐
                 │ Azure Blob Storage │
                 └─────────┬──────────┘
                           │
                           ▼
                 ┌────────────────────┐
                 │ .NET Ingestion     │
                 │ Worker / Service   │
                 └─────────┬──────────┘
                           │
            ┌──────────────┴──────────────┐
            │                             │
            ▼                             ▼
     Text extraction          Document Intelligence
                                     OCR/Layout
            │                             │
            └──────────────┬──────────────┘
                           ▼
                    Semantic Chunking
                           │
                           ▼
                     Embedding Model
                           │
                           ▼
                   ┌────────────────┐
                   │   Azure SQL    │
                   │                │
                   │ relational     │
                   │ metadata       │
                   │ security       │
                   │ chunks         │
                   │ vectors        │
                   │ DiskANN        │
                   └───────┬────────┘
                           │
                           ▼
                       Retrieval
                           │
                           ▼
                        Reranker
                           │
                           ▼
                    Context Builder
                           │
                           ▼
                          LLM
                           │
                           ▼
                       Response

Vector database dedicato o Azure SQL?

Non esiste una risposta universale.

Un vector database specializzato può essere preferibile quando:

  • la vector search domina completamente il workload;
  • la scala è estremamente elevata;
  • servono feature vector-native molto specializzate;
  • l'architettura richiede distribuzione specificamente progettata per ANN.

Azure SQL è invece particolarmente interessante quando:

  • i dati applicativi sono già relazionali;
  • servono transazioni;
  • la sicurezza dipende dal modello relazionale;
  • i filtri sui metadati sono importanti;
  • vogliamo evitare sincronizzazione tra database;
  • vector search integra, ma non sostituisce, il workload applicativo.

La domanda corretta diventa quindi:

Which vector database should I add?

oppure:

Do I actually need another database?

Sono due domande architetturalmente molto diverse.

DiskANN + Spatial SQL: il collegamento con il GIS

Per chi lavora con GIS la parte più interessante inizia probabilmente qui.

Una feature geografica potrebbe avere contemporaneamente:

Feature
 ├── Id
 ├── Geometry
 ├── Attributes
 ├── Description
 ├── Category
 ├── Temporal information
 └── Embedding

La stessa entità appartiene quindi a tre spazi differenti.

Relational space
Geographic space
Semantic vector space

Tre tipi diversi di distanza

Consideriamo due aree territoriali.

La distanza geografica può essere:

180 km

ma la loro distanza nello spazio degli embedding può indicare una fortissima similarità:

Semantic similarity = very high

Due territori possono essere lontani fisicamente ma molto simili rispetto a:

  • demografia;
  • uso del suolo;
  • mobilità;
  • densità commerciale;
  • accessibilità;
  • servizi;
  • indicatori economici;
  • caratteristiche ambientali.

GeoAI: da distanza geometrica a similarità funzionale

Una possibile query concettuale potrebbe essere:

Find areas

within 50 km

AND

land_use = 'industrial'

AND

area > 50000 m²

AND

semantically similar to:

"areas suitable for logistics,
close to major transport infrastructure,
with low residential pressure"

Qui stiamo combinando:

Spatial predicate
       +
Relational predicate
       +
Vector similarity

Spatial Index e Vector Index sono concetti differenti

Uno spatial index indicizza oggetti come:

POINT
LINESTRING
POLYGON

Un vector index indicizza invece:

[0.018, -0.032, 0.004, ..., 0.071]

Il fatto che entrambi utilizzino la parola "vector" in alcuni contesti non deve creare confusione.

Sono strutture pensate per risolvere problemi completamente differenti.

Ma possono lavorare insieme.

Un possibile modello GeoAI

CREATE TABLE GeoAssets
(
    Id           BIGINT PRIMARY KEY,
    TenantId     INT NOT NULL,

    Name         NVARCHAR(250),
    Category     NVARCHAR(100),

    Geometry     GEOGRAPHY,

    Description  NVARCHAR(MAX),

    Embedding    VECTOR(1536)
);

Concettualmente potremmo eseguire:

Relational filtering
        ↓
Spatial filtering
        ↓
Vector retrieval
        ↓
Reranking
        ↓
GeoAI result

Embedding territoriali

Possiamo andare ancora oltre e generare un embedding non da un semplice testo, ma da una rappresentazione complessa del territorio.

Territory
   │
   ├── Population
   ├── Income
   ├── Land use
   ├── Mobility
   ├── POIs
   ├── Accessibility
   ├── Satellite features
   └── Demographics
             │
             ▼
          Encoder
             │
             ▼
       Territory embedding

A quel punto possiamo chiedere:

Find areas similar to this area

senza limitarci a una distanza euclidea o geografica.

Passiamo dalla prossimità geometrica alla similarità funzionale appresa.

Osservabilità

Una pipeline RAG production-grade va misurata end-to-end.

Almeno:

Document extraction latency
OCR latency
Chunking throughput
Embedding latency
Embedding failures
Indexing throughput
ANN latency
Exact KNN latency
Recall@K
Reranking latency
Context size
LLM latency
Token usage
End-to-end latency

Possiamo modellare la latenza totale come:

Ttotal =
    Textract
  + Tembed
  + Tretrieve
  + Trerank
  + Tcontext
  + Tllm

Ottimizzare DiskANN di 10 ms ha poco senso se la generazione dell'LLM richiede diversi secondi.

Valutare scientificamente il retrieval

Un buon sistema RAG non dovrebbe essere valutato soltanto chiedendo:

La risposta sembra buona?

Dobbiamo costruire un dataset di valutazione:

Question
Expected relevant chunks
Expected source documents

e misurare:

  • Recall@K;
  • Precision@K;
  • MRR;
  • NDCG;
  • latency;
  • cost per query.

Possiamo così confrontare in modo ripetibile:

Embedding Model A vs B

Chunk size 300 vs 800

Overlap 50 vs 150

Exact KNN vs DiskANN

Top-10 vs Top-50

With reranker vs without reranker

Vector-only vs Hybrid Search

DiskANN non sostituisce il database design

La presenza di embedding non rende improvvisamente irrilevanti:

  • primary key;
  • foreign key;
  • indici B-tree;
  • statistics;
  • query plans;
  • partitioning;
  • security;
  • transactions;
  • data lifecycle;
  • backup e disaster recovery.

La vector search è un'altra modalità di accesso al dato, non un sostituto del modello relazionale.

La vera convergenza

L'aspetto più interessante di Azure SQL + DiskANN non è poter eseguire una query con cosine distance.

È poter mantenere nello stesso dominio dati:

Transactions
Business entities
Users
Security
Documents
Metadata
Spatial objects
Embeddings

e interrogarli utilizzando:

Relational Search
Full-Text Search
Spatial Search
Vector Search

L'AI non deve necessariamente possedere una copia parallela del database aziendale.

Può diventare un'altra modalità con cui l'applicazione interroga gli stessi dati operativi.

Conclusione

DiskANN è interessante dal punto di vista algoritmico perché rende possibile una Approximate Nearest Neighbor Search efficiente su collezioni vettoriali di grandi dimensioni.

Ma il cambiamento più importante è probabilmente architetturale.

Azure SQL
   │
   ├── Relational data
   ├── Transactions
   ├── Security
   ├── Documents
   ├── Spatial data
   ├── Vectors
   └── DiskANN

In una moderna applicazione AI questo può ridurre la necessità di introdurre un'infrastruttura dati parallela esclusivamente per il retrieval vettoriale.

E nel GIS il concetto diventa ancora più interessante.

Relational space
      +
Geographic space
      +
Semantic vector space
      ↓
     GeoAI

La domanda GIS tradizionale:

What is near this location?

può evolvere verso:

What is geographically, semantically and functionally similar to this location?

È qui che DiskANN smette di essere semplicemente una tecnologia per "fare RAG sui PDF" e diventa qualcosa di più generale: un nuovo strumento per interrogare dati, conoscenza e territorio.

Riferimenti tecnici