2010-11-16 13 views
5

dichiarazione del problema: come parallelizzare inserti in SQL Server (2008)parallelizzazione massiccia inserti in SQL Server da C# (per migliorare le prestazioni di tempo)

sto eseguendo massiccia calcolo numerico per la ricerca scientifica in C# lavoratori multithread che fondamentalmente fai una cosa: prova migliaia di possibili configurazioni (combinazioni di matrici) per un periodo di tempo (in giorni) e memorizza i risultati in un database di SQL Server.

Se memorizzo i risultati uno a uno in DB (~ 300.000 righe per sessione di calcolo * 100 di sessioni), uno dopo l'altro, finisco per attendere ore per il termine del processo di archiviazione.

La progettazione del database è molto semplice:

  • Combinazione Imposta
    CS_ID1, Valore A1, Valore B1, Valore C1
    CS_ID2, Valore A2, Valore B2, Valore C2
    . ...

  • Risultati per giorno
    CS_ID1, Giorno1 Risultato 1
    CS_ID1, Giorno 2, 2 Risultato
    CS_ID1, 3 ° giorno, Risultato 3
    .........

    .........
    CS_ID2, Giorno1 Risultato N
    CS_ID2, Giorno 2, Risultato N + 1
    CS_ID2, 3 ° giorno, Risultato N + 2

Eac h "Combination Set" viene testato in base ai giorni campione e i suoi risultati giornalieri vengono elaborati in un singolo thread C#, dove viene generata una query LINQ/SQL e inviata a DB appena prima della fine del thread. Tranne le sequenze degli ID dell'insieme di combinazioni, non esiste alcuna relazione logica tra i risultati. Questo è molto importante: Questo è il motivo per cui ho pensato di parallelizzare la roba inserto come fondamentalmente equivale ad un bulk dump di blocchi risultato

Un altro dettaglio che potrebbe essere importante è che è possibile determinare in anticipo quanto molte righe verranno inserite nel Database (per blocco e in totale). Questo probabilmente potrebbe aiutare a organizzare gli spazi dei tavoli, dividerli attraverso le pagine, prefissare intervalli di ID per memorizzare blocchi contemporaneamente, o qualcosa del genere (No, non sono "alto" o qualcosa del genere :-))

Accolgo con favore qualsiasi tipo di suggerimento al fine di rendere questo inserimento il più breve possibile.

Si prega di prendere in considerazione che io sono uno sviluppatore C#, con una conoscenza di base di SQL Server e non molto familiare con i concetti DBA tecnici profondi (ho visto che le modifiche di blocco sono MOLTO numerose, che ci sono anche funzionalità multithreaded e asincrone, ma devo ammettere che mi sono perso solo nella foresta :-))

ho 12 core CPU disponibili, e 24Go RAM


EDIT: Tie-break
Accolgo con favore qualsiasi suggerimento intelligente sul tempo di monitoraggio per l'intero processo: dai thread C# dall'inizio/fine ai report dettagliati sull'inserto del server SQl (cosa succede quando, come e dove).
Ho provato a loggare con NLog ma distorce drasticamente il tempo di elaborazione, quindi sono alla ricerca di soluzioni intelligenti che siano abbastanza semplici e con un impatto minimo. Lo stesso per la parte del server SQL: so che ci sono un paio di registri e SP di monitoraggio disponibili. Non ho ancora capito quali sono adatti alla mia situazione.

+1

Un collega ha suggerito di serializzare i risultati su file binari/di testo e riversarli tutti in DB utilizzando Inserimento di massa da file flat ... Non è sicuro che si tratti di una soluzione valida. –

+0

Non sono affatto un DBA, ma mi chiedo un paio di cose qui: 1) è la tua CPU che lo limita, o il disco? 2) il meccanismo di bloccaggio del DB consente effettivamente inserti paralleli? Se è limitato su disco e il DBMS non blocca più processi, è possibile provare a suddividere i dati da inserire su più dischi e a forzare i processi per inserirli. – syrion

+0

E 'sufficiente dividere le query attraverso connessioni separate? Come reagisce a SQL Server, "fisicamente"? Gli inserti sono realmente scritti contemporaneamente nel DB, in varie posizioni di riga? –

risposta

4

Se si utilizza una transazione separata per ogni inserto, ciò influirebbe sicuramente sulle prestazioni, poiché il server DB dovrebbe eseguire atomicamente ciascun inserto. Non ho mai usato SQL server, ma maggior parte delle varianti di SQL avere un modo per gruppo più di un inserto in un'unica transazione, di solito con qualcosa di simile a

BEGIN TRANSACTION; 

...<various SQL statements>... 

COMMIT TRANSACTION; 

Per la sintassi SQL Server vedere:

http://msdn.microsoft.com/en-us/library/ms188929.aspx

http://msdn.microsoft.com/en-us/library/ms190295.aspx

Nella mia esperienza l'inserimento in bundle di inserimenti come questo aiuta sicuramente le prestazioni del server e, in qualche misura, l'utilizzo di risorse e di rete.

EDIT:

La maggior parte (?) Tutti i server DB decenti utilizzano una sorta di blocco per-fila, piuttosto che le serrature per-tavolo. Dovresti essere in grado di avere più transazioni simultanee, ognuna con più inserti, senza problemi - ecco perché sono progettati i server DB. È possibile che ogni thread worker esegua le proprie transazioni, parallelizzando in tal modo gli inserimenti da thread diversi.

Dato che apparentemente si utilizza un singolo computer per i calcoli e il DB, il parallelismo estensivo delle transazioni DB non influirebbe troppo sul rendimento e potrebbe addirittura peggiorare, poiché non si hanno realmente latenze di rete per ridurre il tempo impatto. Finché tutti i core della CPU sono occupati, il che probabilmente implicherebbe un numero di lavoratori> = 12, dovresti considerare altre ottimizzazioni.

Se le discussioni generano la loro produzione in un unico andare dopo trasformazione (ad esempio, se si calcola una grande matrice e poi discarica al database) dubito che ci si guadagna nulla, memorizzando il risultato in un file e quindi avendo il DB rileggerlo in una tabella.

Se, al contrario, i thread eseguono l'output pezzo per pezzo, è possibile trarre vantaggio memorizzando parti delle loro uscite in memoria, quindi inserendo tali parti nel DB, eseguendo più di una transazione per round. Aumentare il numero di thread di lavoro in quel caso potrebbe consentire un migliore utilizzo della CPU mentre il DB sta memorizzando i dati, se la CPU è sottoutilizzata.

Memorizzare l'output worker in un file dovrebbe essere evitato poiché esso triplica efficacemente il carico sul sottosistema del disco. L'unica ragione per cui potresti volerlo fare è se davvero non hai memoria per la memorizzazione intermedia dei risultati.

+0

No, sicuramente non eseguo una transazione per inserto (che finirebbe con 300.000+ transazioni :-)). La mia domanda è più sulla parallelizzazione di blocchi di istruzioni di inserimento, piuttosto che inviarli blocco per blocco al Database. –

+0

L'invio di questi ultimi in blocchi può migliorare in modo definitivo le prestazioni poiché non si effettua un round trip su SQL Server per ciascun inserto. –

+0

Grazie per l'utile Modifica. I miei fili fanno davvero la loro produzione pezzo per pezzo. Lo guarderò da vicino. –

2

Ecco un articolo sul fare inserimento di massa utilizzando C#: http://blogs.msdn.com/b/nikhilsi/archive/2008/06/11/bulk-insert-into-sql-from-c-app.aspx

pensieri aggiuntivi su inserimento di massa con C# sono in una domanda Stack Overflow: What’s the best way to bulk database inserts from c#?

Spero che questo aiuti.

+0

Grazie, sembra davvero interessante. Sono ancora curioso di sapere come questo sia tecnicamente diverso dalle normali transazioni e perché sia ​​più veloce, quindi vado a scavare un po '. La parte delicata è che sto usando un ORM basato su LINQ (AgileFX) e non so se sia fattibile "così com'è". –

+0

Ok. Non ho ancora giocato a LINQ (ancora) quindi non sono sicuro che sia compatibile. Spero che lo sia. –

+0

La differenza tra inserimenti normali e in blocco è il metodo utilizzato per aggiornare B-Tree. Gli inserti normali seguono l'approccio classico "top down/split", gli inserti in serie costruiscono l'albero dalle foglie. –

8

Gli inserti 300k sono una questione di secondi, nel peggiore dei minuti, non di ore. Devi essere sbagliato. Il ETL SSIS world record nel 2008 era a 2,36 TB/ora, i record 300k sono nulla.

Le regole di base del pollice sono:

  • lotto commettere. questa è la cosa più importante NON INSERIRE una riga, quindi INSERIRE una riga, quindi INSERIRE una riga alla nauseam, ogni inserzione int la propria transazione. Il tuo programma deve attendere che il log (LDF) si scarichi dopo ogni istruzione nel suo caso, e sarà lento. Molto lento. Invece avviare una transazione, quindi inserire un batch di righe, quindi il commit della transazione:

Pseudocodice:

do 
    { 
    using (TransactionScope scope = new TransactionScope(
    Required, new TransactionOptions() {IsolationLevel = ReadCommitted)) 
    { 
    for (batchsize) 
    { 
     ExecuteNonQuery ("Insert ...") 
    } 
    scope.Complete(); 
    } 
} while (!finished); 

La prima opzione da solo ottieni oltre 3000 inserti al secondo (~ 2 minuti per 300k). La seconda opzione dovrebbe portarti in decine di migliaia al secondo raggio. Se avete bisogno di più, ci sono trucchi più avanzati:

  • uso cumuli invece di B-tree (senza indice cluster)
  • disabilitare gli indici secondari
  • affinità tra client ai nodi NUMA morbide e spingere in tabelle bloccate per Conenction client, quindi passare a tutti in utilizzando la commutazione di partizione alla fine. Questo è per Davvero di fascia alta, milioni di righe al secondo.

Ti suggerisco di iniziare con le nozioni di base: commit batch.

+0

Grazie per la tua comprensione Remus, è stato MOLTO utile. Non mi sono reso chiaro riguardo alla volumetria: sono 300K + record per computazione, ma ho centinaia o migliaia di calcoli ogni giorno. Inoltre, il nostro DB sta per diventare davvero enorme (non è possibile indicare esattamente la dimensione per ora, ma probabilmente alcuni TB). Un dettaglio importante è che sto usando un LINQ ORM Framework (AgileFX), ma suppongo di dover tornare a una soluzione fatta a mano se voglio avere le mani sulle procedure di transazione ... –

+0

Ho aggiunto un "Tiebreaker" alla fine del mio post. Potresti essere di aiuto anche in questo caso, per quanto riguarda il monitoraggio del DB –

+0

Per monitorare il codice C#, aggiungi contatori delle prestazioni alla tua app: http://rusanu.com/2009/04/11/using-xslt-to-generate -Performance-contatori-code. Per monitorare il DB, provare a seguire una procedura come Waits and Queues: http://msdn.microsoft.com/en-us/library/cc966413.aspx –

1

Forse questo potrebbe aiutare a

io abbiamo una guida passo per passo su come eseguire le stored procedure parallele in SQL here.

Potrebbe essere possibile combinare un inserto di grandi dimensioni con questo.

1

Si può provare a utilizzare un Parallel For per fare gli inserti ...

... ma vorrei provare BULK INSERT o Batch commettono prima ...

1

Questo è un problema interessante. Innanzitutto, come stai usando i valori nel database? Partecipano ai calcoli successivi o il database è solo "dump" per archiviare i risultati per l'elaborazione successiva? Inoltre, l'applicazione/processo è in esecuzione 24 ore al giorno?
Perché sto chiedendo - se potessi dividere le operazioni "risultati negozio" e "risultati processo", potresti ottenere un throughput più alto "strappando" i dati da una sessione e archiviandoli come un blob. In seguito, nel tempo di off-peek, è possibile eseguire a piedi ed elaborare ed "espandere" questi BLOB in tabelle, ad esempio utilizzando un processo o un altro processo. In teoria, se ciò fosse OK, è possibile archiviare questi blob di "staging" in file binari, non direttamente nel database, per ottenere probabilmente la massima velocità di scrittura possibile (limitata solo dal file system, dal sistema operativo e dall'hardware del disco sottostante).

+0

Beh, la cosa semplice di questo è che non ci sono accessi in scrittura in lettura simultanea (beh non ancora, almeno). Ho appena scaricato tutti i dati dei risultati direttamente nel DB per l'elaborazione successiva/Data Mining. Nessun processo in esecuzione 24 ore su 24: i calcoli vanno come i ricercatori decidono durante il giorno (e talvolta lasciano che i server facciano lavori programmati di notte). –

+0

Se ho capito bene la tua idea, si tratta di posticipare il processo di archiviazione per alleggerire temporaneamente il carico dal processore/database. Non ci ho pensato, potrebbe essere un'alternativa interessante, per un caso particolare d'uso in cui l'Analista della Ricerca dovrebbe essere in grado di aspettare fino al giorno dopo per ottenere i risultati ed eseguire lo "sbandamento" di notte. –

Problemi correlati