Le applicazioni aziendali odierne si basano sui dati e sui software che li gestiscono. Nel processo di digitalizzazione dei flussi di lavoro e nell’ottimizzazione delle query, gli sviluppatori spesso si concentrano esclusivamente sui requisiti di business e sulla logica delle interrogazioni al database, trascurando un aspetto fondamentale: la scelta sui tipi di dato scelti per il database e l’applicazione. Questa sottovalutazione non è un semplice dettaglio implementativo, ma un errore strategico e una lacuna progettuale che può causare prestazioni scadente, insoddisfazione degli utenti e, nei casi più critici, interruzioni di servizio (downtime) imprevisti compromettono la continuità operativa.
Il Contesto
Nella progettazione delle applicazioni, le software house si concentrano spesso sulla digitalizzazione dei flussi aziendali e sulle logiche interne dell'applicativo, trascurando la scelta in merito al tipo di dato da assegnare ai vari componenti. Questa negligenza nella scelta può avere origine sia da un approccio superficiale, che sottovaluta l’importanza di questi aspetti architetturali; sia dal tentativo di offrire una compatibilità universale tra sistemi diversi (e diversi clienti) che spinge verso l’adozione di tipi di dato generici a discapito della specificità. Ciò può portare principalmente a due problematiche significative.
1. Conversioni implicite - I database devono convertire i dati. Ciò è quello che generalmente accade quando c’è una mancata corrispondenza dei tipi di dato tra tabelle diverse o tra i tipi di dati forniti dall'applicazione e quelli con cui i dati sono memorizzati nelle tabelle. Le conversioni sono operazioni con ripercussioni particolarmente onerose che aumentano in modo significativo i tempi di esecuzione delle query.
2. Rischi di overflow a lungo termine - Per le logiche di un'applicazione, frequentemente vengono utilizzate colonne di tabelle con funzione di contatore o identificatore univoco. Quando i tipi di dato vengono assegnati senza valutare il carico applicativo previsto e i volumi di dati che il database dovrà gestire, la scelta di un datatype inadeguato può causare un overflow, esaurendo le combinazioni possibili. Di conseguenza si verifica un'interruzione dei servizi applicativi, costringendo i team IT a interventi d’emergenza, spostandosi rapidamente su un database di supporto e perdendo dati, o utilizzando soluzioni temporanee e artificiose per aggirare il problema.
I Problemi
In questo contesto non è sempre facile capire (e gestire) cosa succede durante la fase di build, in cui le dipendenze compile di Java vengono gestite abbastanza agilmente da gradle, e quello che invece succede durante le fasi di pacchettizzazione o di start dei bundle.
In queste due fasi il compilatore prima e il portale poi devono essere in grado di avere tutte le dipendenze necessarie per potere eseguire il codice del modulo a cui stiamo lavorando.
Il problema di Performance
Per capire nel concreto come si manifesti il problema di performance esaminiamo ora un caso verificatosi su un'istanza di database SQL Server. Quanto è qui illustrato può essere esteso ad altri tipi di database relazionali.
Durante l'analisi di un'istanza SQL Server, ci siamo imbattuti in una query di questo tipo:
SELECT TOP 1 servizio
FROM servizi AS [f]
WHERE [f].[codice_pratica] = @codPratica
Grafico 1 - Dati riferiti ai due mesi analizzati
Analizzando la definizione della tabella, si è notato che la colonna codice_pratica
era di tipo varchar(20)
. Poiché questa colonna veniva confrontata con un parametro di tipo diverso, ovvero nvarchar(4000)
, il database (in questo caso SQL Server) era costretto per ogni valore del campo codice_pratica
ad applicare una funzione di conversione implicita. Questo impediva al database di utilizzare in modo efficiente gli indici per eseguire la query, costringendolo a effettuare operazioni inefficienti come i full scan. In termini di database administrator, si può affermare che il predicato di questa query era non-sargable
, ovvero impediva al database di utilizzare gli indici, nonostante non ci fossero funzioni esplicite.
Nel caso della query sopracitata, il problema appariva nel piano di esecuzione della query come uno step di Index Scan
(scansione completa dell'indice) invece di Index Seek
(ricerca puntuale sull'indice).
Questo tipo di problema per i database OLTP (Online Transaction Processing) è molto impattante. In questi database, infatti, è fondamentale avere query veloci e performanti per sostenere l'elevato numero di richieste giornaliere. Anche un valore di 100 ms, che potrebbe sembrare accettabile, può costituire un serio problema, causando l'accumulo di attese e blocchi. Nel caso in esame, ad esempio, il database generava circa 30 ore di attesa su questa query, rallentando i processi.
Il problema, inoltre, è spesso latente ed eseguire operazioni superficiali di troubleshooting può portare a soluzioni sbagliate e costose, quando non persino a un peggioramento delle performance.
Spesso, infatti, il rallentamento nelle query è causato da una conversione implicita dei dati che non viene riconosciuta come sua vera origine. Di conseguenza, si interviene tentando di trasformare scansioni di tabelle in ricerche puntuali mediante metodi inefficaci, come l’aggiunta di nuovi indici. Questi, oltre a non risolvere il problema, possono appesantire ulteriormente le operazioni di scrittura a causa della necessità di aggiornamenti continui.
In altri casi, si arriva erroneamente a pensare che l’unica soluzione risieda nel potenziamento dell’hardware del server. Tale approccio comporta un aumento dei costi e della complessità gestionale senza però ottimizzare realmente le performance della query originale né sfruttare al meglio l’infrastruttura esistente.
Il problema del raggiungimento del limite massimo di combinazioni per i contatori
Per quanto riguarda invece il secondo problema, proponiamo un caso tipico relativo all’uso di sequenze numeriche per l’aggiornamento delle tabelle. L’incidente specifico ha interessato un software Java che interagiva con un database Oracle, ma il principio sottostante è applicabile a molti altri ambienti.
Il caso è emerso a seguito di una segnalazione critica da parte del team applicativo di un cliente: il software si era improvvisamente bloccato, impedendo qualsiasi nuovo inserimento di documenti in una tabella chiave, interrompendo completamente l'operatività degli utenti. L'origine del problema è stata rapidamente individuata in una sequenza progressiva, creata con il seguente comando SQL
START WITH 0
INCREMENT BY 1
NOCACHE
NOCYCLE;
A seguito della prima analisi, si è riscontrato che né la sequenza del database (che di default supporta valori fino a 1028 ) né il tipo di dato della colonna di destinazione rappresentavano un problema.
L'indagine ha invece rivelato che la root cause risiedeva nell'applicazione Java. Il tipo di dato utilizzato nel software per gestire il valore proveniente dalla sequenza era un INT. Questo tipo di dato a 32 bit ha un limite massimo intrinseco di 2.147.483.647 (ovvero 231-1). Non appena la sequenza del database ha superato questa soglia, l'applicazione non è più stata in grado di processare il valore, generando un errore bloccante che ha fermato ogni nuova transazione.
Le conseguenze di questo disallineamento tecnico sono state immediate e hanno causato un grave disservizio scatenando un effetto a catena:
- Blocco operativo: l'impossibilità di inserire dati ha paralizzato le attività per diverse ore.
- Insoddisfazione dei clienti: il disservizio diretto ha generato frustrazione e numerose lamentele.
- Impatto sul team: i dipendenti sono rimasti bloccati nell’impossibilità di portare a termine le loro attività, alimentando la pressione e la frustrazione interna.
- Perdita di revenue: l'interruzione delle operazioni si è tradotta in una perdita economica diretta per l'azienda.
Questo incidente, quindi, mette in luce come una scelta di progettazione a livello di codice, se non allineata con la potenziale crescita dei dati, può portare a conseguenze operative ed economiche critiche.
Gli obiettivi
A seguito delle analisi delle due problematiche, sono stati definiti specifici obiettivi per ciascuna.
- Nel primo caso, l'obiettivo era trovare soluzioni a basso impatto che risolvessero le cause alla radice. La strategia consisteva nell'ottimizzare le query fondamentali dei processi OLTP per ridurre in modo consistente i tempi di attesa del database e, di conseguenza, ridurre i costi sia on-premise che in cloud.
- Nel secondo caso, l’obiettivo era trovare una soluzione rapida e di semplice implementazione. La priorità era minimizzare l'impatto sugli utenti e far ripartire il business nel minor tempo possibile, contenendo il disagio applicativo.
La Soluzione
Per affrontare il primo problema (performance), esistono principalmente due soluzioni, elencate in ordine di preferenza:
- Correggere il tipo del parametro passato in input alla query.
- Convertire il dato passato in input alla query prima di eseguirla.
In entrambi i casi l’obiettivo è allineare i datatype dei dati da confrontare nel predicato, in particolare nella seguente clausola WHERE
:
Nel caso specifico, si mira a garantire che il parametro @codPratica
abbia il datatype varchar(20)
.
La prima soluzione è indubbiamente la più efficace, perché interviene direttamente sulla struttura dell'applicativo, ottimizzando l'integrazione con il database. In questo modo si evita di effettuare due operazioni, anziché una, sul dato di input. Tuttavia, se risulta più semplice modificare il codice rispetto al tipo di dato in entrata, anche la seconda strada può rappresentare una soluzione valida.
Se il datatype scelto per la colonna è corretto, l'errore è infatti di progettazione dell'applicativo a monte. Pertanto si suggerisce di eseguire la modifica sull’applicativo, coinvolgendo il team appropriato. In questo modo si evita di alterare il database o di ricorrere a workaround che potrebbero risultare particolarmente impattanti.
Modificare la definizione di una tabella, ad esempio, è un'operazione che comporta l'irraggiungibilità della tabella durante le operazioni e, data la frequenza di interrogazione, un periodo di downtime. L'utilizzo di un workaround con una tabella di appoggio, invece, rappresenta una soluzione inefficace, in quanto aggiunge un passaggio ulteriore a una situazione già critica.
Per il secondo problema (software fermo) sono state valutate quattro possibili soluzioni, presentate di seguito in ordine di complessità crescente.
Soluzioni Valutate per Overflow
1. Reset della sequenza a zero
Questa opzione prevede di far ripartire la sequenza da capo. Sebbene fosse la soluzione più semplice dal punto di vista tecnico, è stata scartata immediatamente. Il valore generato dalla sequenza è utilizzato come chiave primaria e il reset avrebbe creato valori duplicati, violando il vincolo di unicità della tabella.
2. Utilizzo di una sequenza negativa
Questa alternativa consiste nel modificare la sequenza a livello database per farla procedere con valori negativi. La configurazione sarebbe stata la seguente:
CREATE SEQUENCE id
START WITH -1
INCREMENT BY -1
NOCACHE
NOCYCLE;
In questo modo, i nuovi valori inseriti sarebbero proseguiti da -1 in poi, garantendo l'unicità e sbloccando l'operatività. Tuttavia, questa soluzione rappresentava un workaround temporaneo, poiché anche l'intervallo di numeri negativi è finito. Va inoltre verificato che la colonna non sia usata per l'ordinamento.
3. Modifica strutturale del software
La soluzione più corretta dal punto di vista architetturale è modificare il codice sorgente, cambiando il tipo di dato utilizzato in Java da INT (32 bit) a LONG (64 bit). Questo avrebbe ampliato enormemente il numero di valori disponibili, risolvendo il problema in modo permanente e garantendo un'operatività pressoché infinita.
4. Ricreazione di una nuova struttura database
Questa è l'opzione più drastica, che prevede di creare un nuovo database e ripartire da zero. È stata subito esclusa. L'impatto sarebbe stato insostenibile, comportando la perdita totale dei dati storici e tempi di implementazione molto lunghi.
Dopo la prima valutazione, la scelta si è quindi ridotta alle opzioni 2 e 3.
Il passaggio a LONG 64 bit (la soluzione 3) è stata scelta come la strategia risolutiva a lungo termine. Questo intervento garantisce, infatti, la coerenza dei dati, mantiene lo storico e risolve il problema alla radice. Tuttavia, la sua implementazione richiedeva un ciclo completo di rilascio software (ricompilazione, test e deployment in produzione), un processo incompatibile con l'urgenza dettata dal fermo dei sistemi.
Di conseguenza, per minimizzare il downtime e ripristinare il servizio nel più breve tempo possibile, è stato scelto di implementare il workaround della soluzione 2, con la forte raccomandazione di pianificare un intervento definitivo simile alla soluzione 3 in un rilascio futuro.
I Risultati
Si presentano di seguito i risultati ottenuti per le due problematiche presentate.
Per il primo problema, durante l’analisi erano stati eseguiti dei test di performance usando la variabile @codPratica
come nvarchar(4000)
e varchar(20)
(il datatype della colonna codice_pratica
), mettendo in evidenza il netto miglioramento della query che utilizzava il datatype varchar(20). Si riporta di seguito il grafico presentato al cliente che mostra in scala logaritmica i tempi di esecuzione su 1000 tentativi della query con il datatype originale (in blu) confrontati con i tempi di esecuzione della query che utilizzava lo stesso valore per la variabile @codPratica
ma con datatype varchar(20)
(in rosso).
Grafico 2 - Tempi di esecuzione
Come si può osservare, i tempi di esecuzione con il parametro che utilizza il datatype corretto erano quasi nulli, mentre i tempi di esecuzione medi per il datatype originale si aggiravano attorno ai 100 ms.
Si riporta di seguito invece il grafico relativo al guadagno percentuale dei tempi di esecuzione della query con il parametro convertito rispetto ai tempi di esecuzione della query con il parametro con datatype originale. Come si osserva il guadagno è quasi costantemente al 100%.
Grafico 3 - Nuovi tempi di esecuzione
A fronte di questi risultati, il team applicativo ha modificato il datatype del parametro in input. Nei giorni immediatamente successivi alla modifica, il tempo di esecuzione della nuova query è stato di circa 0,05 ms. Ad oggi, la query è talmente veloce da non essere più rilevata dal monitoraggio.
A dimostrazione di quanto si è illustrato, si riportano di seguito i dati delle attese totali e delle 15 query più impattanti a cavallo della modifica (effettuata il 24 aprile 2025) che mostrano che le circa 20/30 ore di attesa di tipo Memory/CPU sono scomparse a seguito della modifica.
Grafico 4 - Tempi di attesa totali
Grafico 5 - Tempi delle 15 Query più impattanti
La query analizzata è la query identificata con l’ID 4370546729.
Per quanto riguarda il secondo problema, il risultato ottenuto è stato il ripristino dell'operatività del business nel minor tempo possibile, limitando al minimo gli impatti applicativi. Questa rapidità di intervento ha permesso di contenere il disservizio e le relative perdite economiche, in linea con gli SLA e le esigenze aziendali. È importante sottolineare, tuttavia, che la soluzione adottata è un workaround temporaneo. Se non si vi sarà un intervento per modificare alla radice il datatype del software, il problema si ripresenterà inevitabilmente. A quel punto, non sarebbe però più possibile applicare soluzioni palliative e diventeranno necessarie misure drastiche come le soluzioni 3 e 4 presentate nell’apposita sezione.
Problemi di datatype nei database: le raccomandazioni finali
Nell’operatività quotidiana, i dipartimenti IT sono sempre più impegnati a soddisfare le richieste del business, sviluppare nuove funzionalità e ottimizzare il codice. In questo contesto, la progettazione dei database e la scelta accurata dei tipi di dato, sia per le colonne del database sia per i dati gestiti dal software, rischiano di passare in secondo piano. Tuttavia, questa decisione rappresenta un elemento cruciale che condiziona direttamente le prestazioni, la stabilità e la longevità di qualsiasi sistema software.
Una selezione affrettata o superficiale del datatype può innescare una serie di problemi latenti, che si manifestano nel tempo come rallentamenti e cali di performance del database. Questi problemi spesso costringono a interventi correttivi urgenti e dispendiosi, nonché a una complessiva revisione dell’ottimizzazione delle query e della struttura dati.
Non di rado, nell’incapacità di individuare la vera radice del problema legata ai datatype, si assume erroneamente che la soluzione consista nell’aumento delle risorse hardware. Tale approccio, oltre a incrementare inutilmente i costi, nasconde l’inefficienza sottostante senza risolverla.
Una visione miope che trascura l’infrastruttura, il volume previsto di dati e le reali esigenze di business può avere conseguenze ancora più severe. Tipi di dato incompatibili o con range di valori esauriti possono provocare fermi improvvisi del sistema, interrompendo l’operatività quotidiana e causando rilevanti danni economici e di reputazione.
La scelta del tipo di dato non è pertanto un dettaglio tecnico secondario, ma una decisione strategica che impatta direttamente sull’efficienza operativa e sui costi. Una progettazione attenta e mirata all’ottimizzazione dei tipi di dato in database è fondamentale per garantire l’affidabilità e la performance del software nel lungo termine, ottimizzando al contempo i costi, sia in infrastrutture on-premise sia in ambienti cloud.
Ti è piaciuto quanto hai letto? Iscriviti a MISPECIAL, la nostra newsletter, per ricevere altri interessanti contenuti.
Iscriviti a MISPECIAL