Progettazione di database per la gestione di 1 miliardo di righe e il conteggio


10

Riceviamo dati GPS in tempo reale a una velocità di circa 5000 pr. minuto (da 4 server TCP). Ogni server utilizza una singola connessione per inserire i dati e li inserisce nel buffer tra gli inserti. Ogni 15 minuti circa un servizio recupera questi dati e li trasforma in viaggi. Una volta che i viaggi sono stati generati, i dati GPS effettivi di solito non sono così importanti, solo se l'utente vuole vedere il percorso su una mappa.

Il problema è che sembra che il database stia lottando per tenere il passo con la velocità di inserimento dei dati. A volte quando il carico aumenta, il tempo di inserimento aumenta improvvisamente drasticamente (> 30 secondi), il che a sua volta consente di bufferizzare più dati, il che a sua volta si traduce in inserti più grandi e una durata degli inserti più lunga.

Spero di ottenere alcuni commenti sul design attuale e su alcune delle idee che dobbiamo migliorare per le prestazioni, e le risposte ad alcune delle nostre domande - e qualsiasi altro consiglio che la gente potrebbe avere!

Design attuale

I dati sono attualmente suddivisi in tabelle che rappresentano una settimana e i dati più vecchi di un anno vengono archiviati in un database secondario. Il tutto è unito in una vista modificabile, che viene utilizzata sia per gli inserti che per le letture.

Design del tavolo

  • Id (PK, uniqueidentifier)
  • DeviceId (FK, int)
  • PersonId (FK, int)
  • VehicleId (FK, int)
  • TokenId (FK, int)
  • UtcTime (PK, datetime2 (3))
  • Latitudine (galleggiante)
  • Longitudine (float)
  • Velocità (piccola)
  • Titolo (piccolo)
  • Satelliti (tinyint)
  • IOData (varbinary (100))
  • IgnitionState (tinyint)
  • UserInput (tinyint)
  • CreateTimeUtc (datetime2 (3))

Indici

  • DeviceId_CreateTimeUtc_Desc
  • DeviceId_UtcTime_Desc (Clustered)
  • PersonId_UtcTime_Desc
  • TokenId_UtcTime_Desc
  • VehicleId_UtcTime_Desc

Ogni settimana occupa attualmente circa 10 GB inclusi gli indici e attualmente ci sono circa 300 GB di dati nel database principale.

Le tabelle di dati nel database principale hanno il proprio filegroup con 1 file, ma si trova sullo stesso disco di tutte le altre tabelle nel database principale. Il database secondario si trova su un altro disco, ma sullo stesso computer.

Penso che stiamo anche eseguendo un processo di ricostruzione dell'indice settimanalmente, quando viene utilizzata una nuova partizione di tabella (settimana). Non viene eseguito alcun restringimento.

La macchina è un HP a 8 core con 12 GB di memoria e il disco che contiene il database principale esegue RAID 10.

idee

  • Limitare la quantità di dati memorizzati nel database primario ad es. Al massimo 1 mese. Perlomeno renderebbe il database più gestibile per il backup / ripristino, ma potremmo aspettarci di vedere un miglioramento delle prestazioni in questo modo?
  • Creare 2 file nel filegroup per i dati correnti e distribuirli su 2 diverse partizioni fisiche
  • Crea database master-slave che contengono dati correnti, quindi inserimenti e letture vengono eseguiti su database diversi
  • Inserisci i file per i dati correnti sui dischi SSD (il mirroring farebbe alcuna differenza nelle prestazioni con i dischi SSD?)

Per favore fatemi sapere se sono necessarie ulteriori informazioni. Ci sono orribilmente molti fattori che influenzano le prestazioni e probabilmente anche molti modi per modificarle.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Paul White 9

Risposte:


8

5000 inserti al minuto sono circa 83 inserti al secondo. Con 5 indici che sono 400 righe fisiche inserite al secondo. Se il carico di lavoro fosse in memoria, ciò non costituirebbe un problema anche per il più piccolo dei server. Anche se questo era un inserto riga per riga usando il modo più inefficiente a cui riesco a pensare. 83 banali query al secondo non sono interessanti dal punto di vista della CPU.

Probabilmente, sei legato al disco. Puoi verificarlo guardando le statistiche di attesa o STATISTICS IO.

Le tue query probabilmente toccano molte pagine diverse in modo che il pool di buffer non abbia spazio per tutte. Ciò causa letture frequenti della pagina e probabilmente anche scritture casuali del disco.

Immagina una tabella in cui inserire solo fisicamente alla fine a causa di una chiave in costante aumento. Il set di lavoro sarebbe una pagina: l'ultima. Ciò genererebbe IO sequenziali e quando il processo pigro di scrittura o checkpoint scrive la "fine" della tabella su disco.

Immagina una tabella con inserti posizionati casualmente (esempio classico: una chiave guid). Qui, tutte le pagine sono il set di lavoro perché verrà toccata una pagina casuale per ogni inserto. Gli IO sono casuali. Questo è il caso peggiore quando si tratta di set di lavoro.

Sei nel mezzo. I tuoi indici sono della struttura (SomeValue, SequentialDateTime). Il primo componente randomizza parzialmente la sequenzialità fornita dal secondo. Suppongo che ci siano alcuni possibili valori per " SomeValue" in modo da avere molti punti di inserimento posizionati casualmente nei tuoi indici.

Dici che i dati vengono suddivisi in tabelle da 10 GB a settimana. Questo è un buon punto di partenza perché il set di lavoro è ora limitato da 10 GB (ignorando qualsiasi lettura che potresti fare). Con 12 GB di memoria del server, è improbabile, tuttavia, che tutte le pagine pertinenti possano rimanere in memoria.

Se potessi ridurre le dimensioni delle "partizioni" settimanali o aumentare di un po 'la memoria del server, probabilmente stai bene.

Mi aspetto che gli inserti all'inizio della settimana siano più veloci rispetto alla fine. È possibile testare questa teoria su un server di sviluppo eseguendo un benchmark con una determinata dimensione dei dati e riducendo gradualmente la memoria del server fino a quando non viene visualizzato il serbatoio delle prestazioni.

Ora, anche se tutte le letture e le scritture si adattano alla memoria, è possibile che si verifichi comunque un IO di flushing delle pagine sporche. L'unico modo per sbarazzarsene è scrivere in posizioni collocate negli indici. Se è possibile convertire gli indici in modo da utilizzare (più) chiavi sequenziali che potrebbero essere di grande aiuto.

Come soluzione rapida aggiungerei un livello di buffering tra i client e la tabella principale. Forse accumula 15 minuti di scritture in una tabella di gestione temporanea e lo svuota periodicamente. Ciò elimina i picchi di carico e utilizza un piano più efficiente per scrivere sul grande tavolo.


1
@usr Grazie per la risposta molto completa e ben spiegata! In realtà abbiamo discusso sull'aumento della memoria del server, senza sapere quanto di un effetto avrebbe - ma ora abbiamo davvero un motivo molto convincente per farlo :) Hai ragione che "SomeValue" randomizza parzialmente i punti di inserimento - probabilmente ci sono circa 10000 ID dispositivo. Per quanto riguarda la tabella di gestione temporanea, il tuo suggerimento è una tabella senza indici e quindi un lavoro da inserire nella tabella principale ogni X minuti?
Sondergard,

@usr Reg. il tuo suggerimento per convertire l'indice cluster in sequenziale, potremmo aggiungere un auto-inc. colonna identità (numero intero) e modificare l'indice cluster in questa colonna al solo scopo di mantenerlo sequenziale? Non sarebbe univoco tra le tabelle, ma finché è la chiave primaria, dovremmo andare bene.
Sondergard,

1
Se la tabella di gestione temporanea è piccola e le tue query possono convivere con essa, non è necessario indicizzarle affatto. Ma potresti .; Una strategia sarebbe quella di rendere l'IC su una colonna di identità (come dici tu). Questo può fare miracoli se l'IC è grande e gli altri indici sono piccoli. Poiché gli elementi della configurazione sono scritti ora sono sequenziali e contribuiscono molto meno al problema. Questa strategia ha più successo se c'è una differenza di dimensione significativa .; Un'altra idea sarebbe quella di avere un tavolo al giorno. Forse unisci mensilmente.
usr

Ok, quindi abbiamo cercato di creare una colonna di identità per CI, ma sfortunatamente non è possibile in una vista parzionata (nessuna colonna di identità consentita, nessun valore predefinito e tutte le colonne devono essere incluse in insert). Forse la vista divisa era un progetto mal scelto, sebbene fosse raccomandata da un consulente
sondergard

2
Scherzi a parte, per chiunque abbia lo stesso problema, se hai molte scritture e solo poche letture, vuoi davvero aggiungere alla fine e ritardare qualsiasi indicizzazione. D'altra parte, se desideri letture veloci e non ti interessa quanto tempo ci vuole per inserire, hai bisogno di un indice cluster.
tiktak,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.