Metodi efficienti per la memorizzazione di decine di milioni di oggetti per l'interrogazione, con un numero elevato di inserti al secondo?

14

Questa è fondamentalmente un'applicazione di conteggio / conteggio che sta contando il numero di pacchetti e contando il tipo di pacchetto, ecc. su una rete di chat p2p. Ciò equivale a circa 4-6 milioni di pacchetti in un periodo di 5 minuti. E poiché prendo solo una "istantanea" di queste informazioni, rimuovo solo pacchetti più vecchi di 5 minuti ogni cinque minuti. Pertanto, il massimo degli elementi che saranno presenti in questa raccolta è compreso tra 10 e 12 milioni.

Dato che ho bisogno di creare 300 connessioni con superpoteri diversi, è una possibilità che ogni pacchetto stia tentando di essere inserito almeno 300 volte (il che probabilmente è il motivo per cui tenere questi dati in memoria è l'unica opzione ragionevole).

Attualmente, sto usando un dizionario per memorizzare queste informazioni. Ma a causa della grande quantità di elementi che sto cercando di archiviare, mi imbatto in problemi con l'heap degli oggetti di grandi dimensioni e la quantità di utilizzo della memoria cresce continuamente nel tempo.

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

Ho provato a utilizzare mysql, ma non è stato in grado di tenere il passo con la quantità di dati che ho bisogno di inserire (durante il controllo per assicurarmi che non fosse un duplicato), e ciò mentre usavo le transazioni.

Ho provato mongodb, ma l'uso della CPU per quello era pazzo e non lo tenevo neanche.

Il mio problema principale si verifica ogni 5 minuti, perché rimuovo tutti i pacchetti che sono più vecchi di 5 minuti e scatta una "istantanea" di questi dati. Poiché sto utilizzando query LINQ per contare il numero di pacchetti contenenti un determinato tipo di pacchetto. Sto anche chiamando una query distinta () sui dati, dove spoglio 4 byte (indirizzo ip) dalla chiave keyvaluepair, e combinalo con il valore requestingport nel valore del keyvalupair e lo uso per ottenere un numero distinto di colleghi da tutti i pacchetti.

L'applicazione al momento si aggira intorno a 1,1 GB di utilizzo della memoria e, quando viene chiamata un'istantanea, può arrivare a raddoppiare l'utilizzo.

Ora questo non sarebbe un problema se avessi una quantità folle di ram, ma il vm su cui sto girando è limitato a 2GB di ram al momento.

C'è una soluzione facile?

    
posta Josh 14.03.2012 - 08:01
fonte

5 risposte

11

Invece di avere un dizionario e cercare quel dizionario per voci troppo vecchie; avere 10 dizionari. Ogni 30 secondi circa crea un nuovo dizionario "attuale" e scarta il dizionario più vecchio senza alcuna ricerca.

Successivamente, quando scarti il dizionario più vecchio, metti tutti i vecchi oggetti su una coda FILO per dopo, e invece di usare "nuovo" per creare nuovi oggetti estrai un vecchio oggetto dalla coda FILO e usa un metodo per ricostruire il vecchio oggetto (a meno che la coda dei vecchi oggetti non sia vuota). Questo può evitare un sacco di allocazioni e un sacco di spese generali per la raccolta dei dati inutili.

    
risposta data 15.03.2012 - 22:23
fonte
3

Il primo pensiero che ti viene in mente è perché aspetti 5 minuti. Potresti fare gli snapshot più spesso e quindi ridurre il grande sovraccarico che vedi al limite dei 5 minuti?

In secondo luogo, LINQ è ottimo per il codice conciso, ma in realtà LINQ è zucchero sintattico su C "normale" e non vi è alcuna garanzia che genererà il codice più ottimale. Come esercizio puoi provare a riscrivere i punti caldi senza LINQ, potresti non migliorare le prestazioni ma avrai un'idea più chiara di ciò che stai facendo e renderà più semplice il lavoro di profilazione.

Un'altra cosa da considerare sono le strutture dati. Non so cosa fai con i tuoi dati, ma potresti semplificare i dati che archivi in qualche modo? Potresti usare una stringa o una matrice di byte e quindi estrarre le parti pertinenti da quegli elementi quando ne hai bisogno? Potresti usare una struct al posto di una classe e persino fare qualcosa di male con stackalloc per mettere da parte la memoria ed evitare le esecuzioni di GC?

    
risposta data 15.03.2012 - 15:04
fonte
3

Approccio semplice: prova memcached .

  • È ottimizzato per eseguire attività come questa.
  • Può riutilizzare la memoria di riserva su caselle meno trafficate, non solo sulla casella dedicata.
  • Ha un meccanismo di scadenza della cache integrato, che è pigro, quindi non c'è intoppi.

Lo svantaggio è che è basato sulla memoria e non ha alcuna persistenza. Se un'istanza è inattiva, i dati sono spariti. Se hai bisogno di persistenza, serializza i dati da solo.

Approccio più complesso: prova Redis .

Il lato negativo è che è leggermente più complesso.

    
risposta data 15.03.2012 - 22:49
fonte
1

Non è necessario memorizzare tutti i pacchetti per le query che hai menzionato. Ad esempio - contatore del tipo di pacchetto:

Hai bisogno di due array:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

Il primo array tiene traccia del numero di pacchetti in diversi tipi. Il secondo array tiene traccia di quanti più pacchetti sono stati aggiunti ogni minuto in modo tale da sapere quanti pacchetti devono essere rimossi ad ogni intervallo di minuti. Spero che tu possa dire che il secondo array è usato come coda FIFO rotonda.

Quindi per ogni pacchetto, vengono eseguite le seguenti operazioni:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

In qualsiasi momento, i contatori dei pacchetti possono essere recuperati dall'indice all'istante e non memorizzare tutti i pacchetti.

    
risposta data 15.03.2012 - 23:15
fonte
1

(So che questa è una vecchia domanda, ma l'ho incontrata mentre cercavo una soluzione a un problema simile in cui il pass di raccolta della seconda generazione stava sospendendo l'app per diversi secondi, quindi la registrazione per altre persone in situazioni simili) .

Usa una struct piuttosto che una classe per i tuoi dati (ma ricorda che viene trattata come un valore con semantica pass-by-copy). Questo elimina un livello di ricerca nel gc che deve fare ogni passaggio del segno.

Usa gli array (se conosci la dimensione dei dati che stai memorizzando) o List - che usa gli array internamente. Se hai veramente bisogno dell'accesso casuale veloce, usa un dizionario di indici di array. Questo elimina un altro paio di livelli (o una dozzina o più se usi un SortedDictionary) affinché il gc debba cercare.

A seconda di ciò che si sta facendo, la ricerca in un elenco di strutture potrebbe essere più veloce della ricerca del dizionario (a causa della localizzazione della memoria) - profilo per la propria applicazione specifica.

La combinazione di struct & list riduce sia l'utilizzo della memoria sia le dimensioni dello sweep del garbage collector in modo significativo.

    
risposta data 03.02.2014 - 17:26
fonte

Leggi altre domande sui tag