2010-03-30 5 views
8

Scenariostoccaggio ottimale della struttura di dati per la ricerca rapida e la persistenza

Ho i seguenti metodi:

public void AddItemSecurity(int itemId, int[] userIds) 
public int[] GetValidItemIds(int userId) 

Inizialmente sto pensando di archiviazione sul modulo:

itemId -> userId, userId, userId 

e

userId -> itemId, itemId, itemId 

AddItemSecurity si basa su come ottengo i dati da un'API di terze parti, GetValidItemIds è come voglio usarlo in fase di esecuzione.

Ci sono potenzialmente 2000 utenti e 10 milioni di articoli. Gli ID articolo sono nel modulo: 2007123456, 20100(10 cifre in cui i primi quattro rappresentano l'anno).

AddItemSecurity non è necessario eseguire super veloce, ma GetValidIds deve essere subsecond. Inoltre, se c'è un aggiornamento su uno esistente itemId ho bisogno di rimuovere quel itemId per gli utenti non più nell'elenco.

Sto cercando di pensare a come archiviare questo in modo ottimale. Preferibilmente su disco (con memorizzazione nella cache), ma voglio che il codice sia mantenibile e pulito.

Se l'ID dell'articolo era iniziato a 0, ho pensato di creare un array di byte della lunghezza di MaxItemId/8 per ciascun utente e impostare un bit vero/falso se l'elemento era presente o meno. Ciò limiterebbe la lunghezza dell'array a poco più di 1mb per utente e offrirà ricerche veloci e un modo semplice per aggiornare l'elenco per utente. Persistendo questo come Memory Mapped Files con il framework .Net 4, penso che anch'io otterrei un caching decente (se la macchina ha abbastanza RAM) senza implementare la logica di caching da solo. Analizzare l'ID, eliminare l'anno e archiviare una serie all'anno potrebbe essere una soluzione.

L'elenco ItemId -> UserId [] può essere serializzato direttamente su disco e leggere/scrivere con un normale FileStream per mantenere l'elenco e diffarlo quando sono presenti modifiche.

Ogni volta che viene aggiunto un nuovo utente, tutte le liste devono essere aggiornate, ma può essere eseguita ogni notte.

Domanda

Devo continuare a provare questo approccio, o ci sono altre strade che dovrebbero essere esplorati come bene? Sto pensando che il server SQL non eseguirà abbastanza veloce, e darebbe un overhead (almeno se è ospitato su un server diverso), ma le mie supposizioni potrebbero essere sbagliate. Ogni pensiero o approfondimento sulla questione è apprezzato. E voglio cercare di risolverlo senza aggiungere troppo hardware :)

[Update 2010-03-31]

Ora ho provato con SQL Server 2008 nelle seguenti condizioni.

  • tabella con due colonne (userid, itemid) entrambi sono Int
  • indice cluster su due colonne
  • Aggiunto ~ 800.000 articoli per 180 utenti - totale di 144 milioni di righe
  • RAM assegnata 4GB per SQL Server
  • dual core a 2,66 GHz portatile
  • disco SSD
  • Utilizzare uno SqlDataReader per leggere tutte le itemid di in un elenco
  • 01.235.164,106174 millions
  • Loop su tutti gli utenti

Se eseguo un thread, la media è di 0,2 secondi. Quando aggiungo un secondo thread sale a 0.4 secondi, che è ancora ok. Da lì i risultati stanno diminuendo. L'aggiunta di un terzo thread porta molte delle query fino a 2 seonds. Un quarto thread, fino a 4 secondi, un quinto spunta alcune delle query fino a 50 secondi.

La CPU è in fase di copertura mentre è in corso, anche su una filettatura. La mia app di test richiede un po 'a causa del ciclo veloce, e per il resto il resto.

Il che mi porta a concludere che non scala molto bene. Almeno non sul mio hardware testato. Ci sono modi per ottimizzare il database, ad esempio memorizzare una serie di int per utente invece di un record per articolo. Ma questo rende più difficile rimuovere gli oggetti.

[Update 2010-03-31 # 2]

Ho fatto un rapido test con gli stessi dati di metterlo come bit in file di memoria mappata. Funziona molto meglio. Sei thread producono tempi di accesso tra 0,02 e 0,06 secondi. Limitato alla memoria. I file mappati sono stati mappati da un processo e accessibili da altri sei contemporaneamente. E poiché la base sql ha preso 4 GB, i file su disco hanno richiesto 23 MB.

risposta

3

Dopo molti test, ho finito per utilizzare i file di mappatura della memoria, contrassegnandoli con il bit sparse (NTFS), utilizzando il codice da NTFS Sparse Files with C#.

Wikipedia ha una spiegazione di ciò che è uno sparse file.

I vantaggi di utilizzare un file sparse è che non ho a cura di ciò che vanno i miei ID sono. Se scrivo solo id tra il 2006 milioni e 2.010.999,999 mila, il file verrà allocare solo 625.000 byte da compensare 250.750.000 nel file. Tutto lo spazio fino a quell'offset non è allocato nel file system. Ogni id è memorizzato come un bit impostato nel file. Una specie di trattato come un array di bit. E se la sequenza id cambia improvvisamente, allora allocherà in un'altra parte del file.

Per recuperare gli ID impostati, è possibile eseguire una chiamata del sistema operativo per ottenere le parti allocate del file sparse e quindi controllare ciascun bit in tali sequenze. Anche controllare se un determinato ID è impostato è molto veloce. Se non rientra nei blocchi assegnati, allora non c'è, se rientra, è solo un byte read e un bit mask check per vedere se il bit corretto è impostato.

Quindi, per lo scenario particolare in cui si hanno molti ID che si desidera controllare con la maggiore velocità possibile, questo è il modo più ottimale che ho trovato finora.

E la parte buona è che i file mappati in memoria possono essere condivisi anche con Java (che si è rivelato essere qualcosa di necessario). Java ha anche il supporto per i file mappati in memoria su Windows e l'implementazione della logica di lettura/scrittura è abbastanza banale.

+0

So che stai usando C# e non ho idea di come siano implementati i file di memoria mappati, ma potresti voler guardare questo per Java: 'http : //download.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html#map (java.nio.channels.FileChannel.MapMode, long, long) ' – user183037

+0

" Modifiche apportate al il buffer risultante verrà infine propagato al file e potrebbe essere reso visibile a altri programmi che hanno mappato lo stesso file. " - Se usi più thread, vorresti fare attenzione a questa parte. – user183037

+1

Non ho avuto problemi con multi thread o multi proc con accesso allo stesso file. Se non sbaglio, due thread/proc accederanno alla stessa pagina di memoria nel sistema operativo se accedono agli stessi dati e il sistema operativo si occuperà della memorizzazione nella cache/paging/accodamento delle richieste. Detto questo, non sono esperto e nel mio scenario ho uno scrittore e più lettori, e ottenere una perdita una volta non è un grosso problema. Se devi essere sicuro al 100% sulla sequenza di eventi, allora potresti non voler usare mmf. Ma mi fiderei di questo dato che i MMF sono uno dei modi consigliati per condividere i dati tra le app. –

1

ho davvero che si dovrebbe provare un bel database prima di prendere la decisione. Qualcosa di simile sarà una sfida da mantenere a lungo termine. La tua base di utenti è in realtà piuttosto piccola. SQL Server dovrebbe essere in grado di gestire ciò di cui hai bisogno senza problemi.

+0

Sto creando un semplice DB ora da riempire con i valori da testare –

+0

Ho eseguito il mio test SQL, eventuali suggerimenti su dove poter migliorare? –

+0

Stai usando SQL Server 2008 Express? Questo spiegherebbe sicuramente la diminuzione delle prestazioni con i thread aggiunti. (Espresso, anche se pienamente capace, è zoppicato per essere molto meno perfomant poiché è la versione gratuita.Ha anche un limite superiore per la dimensione db, credo 4gb.) –

0

Gli utenti 2000 non sono male ma con gli articoli correlati a 10 mil dovresti davvero considerare di inserire questo in un database. I DB fanno tutto lo storage, la persistenza, l'indicizzazione, il caching, ecc. Di cui avete bisogno e funzionano molto bene.

Essi permettono anche per una migliore scalabilità verso il futuro. Se all'improvviso dovessi avere a che fare con due milioni di utenti e miliardi di impostazioni con un buon db sul posto, il ridimensionamento diventerà un problema.