Mi scuso in anticipo per la durata di questo post, ma voglio dipingere un'immagine precisa dei problemi che la mia app sta affrontando e quindi porrò alcune domande di seguito;
Sto cercando di risolvere alcuni problemi di progettazione auto-inflitti che stanno causando il blocco dell'applicazione a causa di errori di memoria insufficienti.
Una descrizione in forma abbreviata del dominio problematico è la seguente;
- L'applicazione contiene un "set di dati" costituito da numerosi file di testo contenenti dati correlati
- Un singolo file di testo all'interno del set di dati di solito contiene circa 20 "intestazioni" che contengono metadati relativi ai dati che contiene. Contiene inoltre una sezione delimitata da una grande scheda contenente dati correlati ai dati in uno degli altri file di testo contenuti nel set di dati. Il numero di colonne per file è molto variabile da 2 a 256+ colonne.
L'applicazione originale è stata scritta per consentire agli utenti di caricare un set di dati, mappare determinate colonne di ciascuno dei file che in pratica indicano le informazioni chiave sui file per mostrare come sono correlati e identificare alcuni nomi di colonna previsti. Una volta eseguita questa operazione, viene eseguito un processo di convalida per applicare le varie regole e garantire che tutte le relazioni tra i file siano valide. Fatto ciò, i dati vengono importati in un database SQL Server. La progettazione del database è un modello EAV (Entity-Attribute-Value) utilizzato per soddisfare le colonne variabili per file. So che l'EAV ha i suoi detrattori, ma in questo caso, ritengo che sia stata una scelta ragionevole dati i dati disparati e il numero variabile di colonne presentate in ciascun set di dati.
Il problema di memoria
Dato che la dimensione combinata di tutti i file di testo era al massimo di circa 5 mega, e nel tentativo di ridurre il tempo di transazione del database, si è deciso di leggere TUTTI i dati dai file in memoria e quindi eseguire quanto segue;
- esegue tutta la convalida mentre i dati erano in memoria
- si riferiscono a un modello di oggetto
- Avvia la transazione DB e scrivi le colonne chiave riga per riga, prendendo nota dell'ID della riga scritta (tutte le tabelle nel database utilizzano le colonne Identity), quindi l'ID della riga appena scritta viene applicato a tutti i dati correlati
- Una volta aggiornati tutti i dati correlati con le informazioni chiave a cui si riferisce, questi record vengono scritti utilizzando SqlBulkCopy. A causa del nostro modello EAV, abbiamo essenzialmente; x colonne di y righe da scrivere, dove x può essere maggiore di 256+ e le righe spesso sono decine di migliaia.
- Una volta che tutti i dati sono stati scritti senza errori (possono richiedere alcuni minuti per set di dati di grandi dimensioni), eseguire il commit della transazione.
Il problema ora deriva dal fatto che ora stiamo ricevendo singoli file contenenti oltre 30 mega di dati. In un set di dati, possiamo ricevere qualsiasi numero di file. Abbiamo iniziato a vedere set di dati di circa 100 mega in arrivo e mi aspetto che diventerà più grande da qui in poi. Con file di queste dimensioni, i dati non possono nemmeno essere letti in memoria senza che l'app cada, figuriamoci essere convalidato e importato. Prevedo di dover modificare grossi pezzi del codice per consentire la convalida mediante l'analisi dei file riga per riga e non sono esattamente deciso su come gestire l'importazione e le transazioni.
Potenziali miglioramenti
- Mi sono chiesto se utilizzare i GUID per mettere in relazione i dati anziché fare affidamento sui campi di identità. Ciò consentirebbe ai dati di essere correlati prima di scrivere nel database. Ciò certamente aumenterebbe lo spazio di archiviazione richiesto. Soprattutto in un design EAV. Pensereste che sia una cosa ragionevole da provare, o semplicemente persisto con i campi di identità (le chiavi naturali non possono essere considerate attendibili per essere uniche in tutti i submitter).
- Uso di tabelle di staging per ottenere dati nel database e solo l'esecuzione della transazione per copiare i dati dall'area di staging alle tabelle di destinazione effettive.
Domande
- Per sistemi come questo che importano grandi quantità di dati, come si fa a mantenere piccole le transazioni. Li ho mantenuti il più piccoli possibile nel progetto corrente, ma sono ancora attivi per diversi minuti e scrivono centinaia di migliaia di record in un'unica transazione. C'è una soluzione migliore?
- La sezione dati delimitata da tabulazioni viene letta in un DataTable per essere visualizzato in una griglia. Non ho bisogno della piena funzionalità di un DataTable, quindi sospetto che sia eccessivo. Esiste comunque la possibilità di disattivare varie funzionalità di DataTable per renderle più leggere?
Ci sono altre cose ovvie che dovresti fare in questa situazione per ridurre al minimo l'impronta di memoria dell'applicazione descritta sopra?
Grazie per la cortese attenzione.