Sono d'accordo con la nozione generale di altre risposte secondo cui questo è un borderline problema relazionale.
La chiave per i modelli di dati MongoDB è la pesantezza della scrittura, ma può essere difficile per questo caso d'uso, principalmente a causa della contabilità che sarebbe necessaria se si volesse collegare direttamente gli utenti agli elementi (una modifica a un gruppo seguita da molti degli utenti incorrerebbe in un numero enorme di scritture e per farlo è necessario un lavoratore).
Indaghiamo se il modello di lettura pesante non è applicabile qui o se stiamo eseguendo un'ottimizzazione prematura.
L'approccio pesante di lettura
La tua preoccupazione principale è il seguente caso d'uso:
un vero problema di prestazioni potrebbe essere quando voglio ottenere tutti i gruppi che un utente sta seguendo per un elemento specifico [...] perché poi devo trovare tutti i gruppi che l'utente sta seguendo, e da quello trovare tutti gli item_group con il gruppo_id $in
e l'ID articolo.
Analizziamolo:
-
Ottieni tutti i gruppi che l'utente sta seguendo
Questa è una semplice query:
db.followers.find({userId : userId})
. Avremo bisogno di un indice suuserId
che renderà il runtime di questa operazione O(log n), o velocissimo anche per n grandi. -
da quello trova tutti gli item_group con il gruppo_id
$in
e l'ID articoloOra questa è la parte più complicata. Assumiamo per un momento che sia improbabile che gli articoli facciano parte di un numero elevato di gruppi. Quindi un indice composto
{ itemId, groupId }
funzionerebbe meglio, perché possiamo ridurre drasticamente il set di candidati attraverso il primo criterio:se un elemento è condiviso in soli 800 gruppi e l'utente sta seguendo 220 gruppi, mongodb deve solo trovare l'intersezione di questi, il che è relativamente facile perché entrambi i set sono piccoli.
Tuttavia, dovremo andare più a fondo di questo:
La struttura dei tuoi dati è probabilmente quello di una rete complessa . Le reti complesse sono disponibili in molte varianti, ma ha senso presumere che il grafico dei follower sia quasi privo di scala, il che è anche praticamente il caso peggiore. In una rete senza scala, un numero molto piccolo di nodi (celebrità, super bowl, Wikipedia) attira molta "attenzione" (cioè ha molte connessioni), mentre un numero molto maggiore di nodi ha difficoltà a ottenere la stessa quantità di attenzione combinato .
I piccoli nodi non sono motivo di preoccupazione , le query precedenti, inclusi i viaggi di andata e ritorno al database, sono comprese nell'intervallo 2 ms sulla mia macchina di sviluppo su un set di dati con decine di milioni di connessioni e> 5 GB di dati. Ora quel set di dati non è enorme, ma indipendentemente dalla tecnologia che scegli, sarà vincolato alla RAM perché gli indici devono essere in ogni caso nella RAM (la località dei dati e la separabilità nelle reti è generalmente scarsa) e la dimensione dell'intersezione impostata è piccolo per definizione. In altre parole:questo regime è dominato da colli di bottiglia hardware.
Che dire dei supernodi però?
Dal momento che sarebbero congetture e sono molto interessato ai modelli di rete, mi sono preso la libertà di implementare uno strumento di rete notevolmente semplificato basato sul tuo modello di dati per effettuare alcune misurazioni. (Mi dispiace che sia in C#, ma generare reti ben strutturate è già abbastanza difficile nella lingua in cui sono più fluente...).
Quando eseguo query sui supernodi, ottengo risultati nell'intervallo di 7ms massimi (questo è su 12 milioni di voci in un db da 1,3 GB, con il gruppo più grande che contiene 133.000 elementi e un utente che segue 143 gruppi.)
Il ipotesi in questo codice è che il numero di gruppi seguiti da un utente non è enorme, ma qui sembra ragionevole. In caso contrario, opterei per l'approccio pesante in scrittura.
Sentiti libero di giocare con il codice. Sfortunatamente, avrà bisogno di un po' di ottimizzazione se vuoi provare questo con più di un paio di GB di dati, perché semplicemente non è ottimizzato e fa alcuni calcoli molto inefficienti qua e là (in particolare lo shuffle casuale ponderato beta potrebbe essere migliorato ).
In altre parole:non mi preoccuperei delle prestazioni dell'approccio di lettura pesante ancora . Il problema spesso non è tanto che il numero di utenti cresce, ma che gli utenti utilizzano il sistema in modi inaspettati.
L'approccio scritto pesante
L'approccio alternativo è probabilmente quello di invertire l'ordine di collegamento:
UserItemLinker
{
userId,
itemId,
groupIds[] // for faster retrieval of the linker. It's unlikely that this grows large
}
Questo è probabilmente il modello di dati più scalabile, ma non lo farei a meno che non si parli di ENORMI quantità di dati in cui lo sharding è un requisito fondamentale. La differenza fondamentale qui è che ora possiamo compartimentalizzare in modo efficiente i dati utilizzando l'ID utente come parte della chiave shard. Ciò consente di parallelizzare le query, eseguire lo shard in modo efficiente e migliorare la località dei dati in scenari multi-datacenter.
Questo potrebbe essere testato con una versione più elaborata del banco di prova, ma non ho ancora trovato il tempo e, francamente, penso che sia eccessivo per la maggior parte delle applicazioni.