Quando è stato introdotto MongoDB, la caratteristica principale evidenziata era la sua capacità di essere "senza schemi". Cosa significa? Significa che è possibile archiviare documenti JSON, ciascuno con una struttura diversa, nella stessa raccolta. Questo è abbastanza bello. Ma il problema inizia quando devi recuperare i documenti. Come si fa a sapere se un documento recuperato ha una certa struttura o se contiene un campo particolare o meno? Devi scorrere tutti i documenti e cercare quel campo particolare. Questo è il motivo per cui è utile pianificare attentamente lo schema MongoDB, soprattutto per le applicazioni di grandi dimensioni.
Quando si tratta di MongoDB, non esiste un modo specifico per progettare lo schema. Tutto dipende dalla tua applicazione e da come la tua applicazione utilizzerà i dati. Tuttavia, ci sono alcune pratiche comuni che puoi seguire durante la progettazione dello schema del database. Qui parlerò di queste pratiche e dei loro pro e contro.
Modellazione da uno a pochi (incorporamento)
Questo design è un ottimo esempio di incorporamento di documenti. Considera questo esempio di una raccolta Person per illustrare questa modellazione.
{
name: "Amy Cooper",
hometown: "Seoul",
addresses: [
{ city: 'New York', state: 'NY', cc: 'USA' },
{ city: 'Jersey City', state: 'NJ', cc: 'USA' }
]
}
Pro:
- Puoi ottenere tutte le informazioni in un'unica query.
Contro:
- I dati incorporati dipendono completamente dal documento principale. Non puoi cercare i dati incorporati in modo indipendente.
- Considera l'esempio in cui stai creando un sistema di tracciamento delle attività utilizzando questo approccio. Quindi incorporerai tutte le attività specifiche di una persona nella raccolta Persona. Se vuoi attivare una query del tipo:Mostrami tutte le attività che hanno domani come scadenza. Questo può essere molto difficile, anche se si tratta di una semplice query. In questo caso, dovresti considerare altri approcci.
Modellazione uno-a-molti (riferimento)
In questo tipo di modellazione, il documento padre conterrà l'ID di riferimento (ObjectID) dei documenti figlio. È necessario utilizzare i join a livello di applicazione (combinando due documenti dopo averli recuperati dal DB a livello di applicazione) per recuperare i documenti, quindi nessun join a livello di database. Pertanto, il carico su un database verrà ridotto. Considera questo esempio:
// Parts collection
{
_id: ObjectID(1234),
partno: '1',
name: ‘Intel 100 Ghz CPU',
qty: 100,
cost: 1000,
price: 1050
}
// Products collection
{
name: 'Computer WQ-1020',
manufacturer: 'ABC Company',
catalog_number: 1234,
parts: [
ObjectID(‘1234’), <- Ref. for Part No: 1
ObjectID('2345'),
ObjectID('3456')
]
}
Supponiamo che ogni prodotto possa avere diverse migliaia di parti ad esso associate. Per questo tipo di database, il riferimento è il tipo ideale di modellazione. Inserisci gli ID di riferimento di tutte le parti associate nel documento del prodotto. Quindi puoi utilizzare i join a livello di applicazione per ottenere le parti per un particolare prodotto.
Pro:
- In questo tipo di modellazione, ogni parte è un documento separato, quindi puoi applicare tutte le query relative alle parti a questi documenti. Non c'è bisogno di dipendere dal documento del genitore.
- Operazioni CRUD (Crea, Leggi, Aggiorna, Scrivi) molto facili da eseguire su ciascun documento in modo indipendente.
Contro:
- Uno dei principali svantaggi di questo metodo è che devi eseguire una query aggiuntiva per ottenere i dettagli della parte. In modo da poter eseguire join a livello di applicazione con il documento del prodotto per ottenere il set di risultati necessario. Quindi potrebbe portare a un calo delle prestazioni del DB.
Modellazione da uno a milioni (riferimento dei genitori)
Quando è necessario archiviare tonnellate di dati in ogni documento, non è possibile utilizzare nessuno degli approcci precedenti perché MongoDB ha un limite di dimensioni di 16 MB per documento. Un perfetto esempio di questo tipo di scenario può essere un sistema di registrazione degli eventi che raccoglie i registri da diversi tipi di macchine e li archivia nelle raccolte Registri e Macchine.
Qui, non puoi nemmeno pensare di utilizzare l'approccio di incorporamento che memorizza tutte le informazioni dei registri per una particolare macchina in un unico documento. Questo perché in poche ore la dimensione del documento sarà superiore a 16 MB. Anche se memorizzi solo gli ID di riferimento di tutti i registri, esaurirai comunque il limite di 16 MB perché alcune macchine possono generare milioni di messaggi di registro in un solo giorno.
Quindi, in questo caso, possiamo utilizzare l'approccio di riferimento del genitore. In questo approccio, invece di memorizzare gli ID di riferimento dei documenti figlio nel documento padre, memorizzeremo l'ID di riferimento del documento padre in tutti i documenti figlio. Quindi, per il nostro esempio, memorizzeremo ObjectID della macchina nei documenti Logs. Considera questo esempio:
// Machines collection
{
_id : ObjectID('AAA'),
name : 'mydb.example.com',
ipaddr : '127.66.0.4'
}
// Logs collection
{
time : ISODate("2015-09-02T09:10:09.032Z"),
message : 'WARNING: CPU usage is critical!',
host: ObjectID('AAA') -> references Machine document
}
Supponiamo di voler trovare i 3000 log più recenti di Machine 127.66.0.4:
machine = db.machines.findOne({ipaddr : '127.66.0.4'});
msgs = db.logmsg.find({machine: machine._id}).sort({time : -1}).limit(3000).toArray()
Riferimento a due vie
In questo approccio, memorizziamo i riferimenti su entrambi i lati, il che significa che il riferimento del genitore verrà archiviato nel documento figlio e il riferimento del figlio verrà archiviato nel documento padre. Questo rende la ricerca relativamente facile in uno a molti modelli. Ad esempio, possiamo cercare sia nei documenti principali che in quelli delle attività. D'altra parte, questo approccio richiede due query separate per aggiornare un documento.
// person
{
_id: ObjectID("AAAA"),
name: "Bear",
tasks [
ObjectID("AAAD"),
ObjectID("ABCD"), -> Reference of child document
ObjectID("AAAB")
]
}
// tasks
{
_id: ObjectID("ABCD"),
description: "Read a Novel",
due_date: ISODate("2015-11-01"),
owner: ObjectID("AAAA") -> Reference of parent document
}
Conclusione
Alla fine, tutto dipende dai requisiti della tua applicazione. Puoi progettare lo schema MongoDB in un modo che sia il più vantaggioso per la tua applicazione e ti offra prestazioni elevate. Ecco alcune considerazioni riepilogative che puoi considerare durante la progettazione del tuo schema.
- Progetta lo schema in base ai modelli di accesso ai dati della tua applicazione.
- Non è necessario incorporare documenti ogni volta. Combina i documenti solo se intendi utilizzarli insieme.
- Prendete in considerazione la duplicazione dei dati perché oggi lo storage è più economico della potenza di calcolo.
- Ottimizza lo schema per casi d'uso più frequenti.
- Gli array non dovrebbero crescere fuori limite. Se sono presenti più di un paio di centinaia di documenti figlio, non incorporarli.
- Preferisci i join a livello di applicazione ai join a livello di database. Con un'indicizzazione corretta e un uso corretto dei campi di proiezione, può farti risparmiare un sacco di tempo.