MongoDB ha recentemente introdotto la sua nuova struttura di aggregazione. Questa struttura fornisce una soluzione più semplice per calcolare i valori aggregati piuttosto che fare affidamento su strutture potenti con una mappa ridotta.
Con poche semplici primitive, ti consente di calcolare, raggruppare, modellare e progettare documenti contenuti in una particolare raccolta MongoDB. Il resto di questo articolo descrive il refactoring dell'algoritmo di riduzione della mappa per un uso ottimale della nuova piattaforma di aggregazione MongoDB. Il codice sorgente completo può essere trovato nel repository GitHub di Datablend disponibile pubblicamente.
1. Struttura di aggregazione MongoDB
La piattaforma di aggregazione MongoDB si basa sul noto concetto di Linux Pipeline in cui l'output di un comando viene trasmesso attraverso un nastro trasportatore o reindirizzato per essere utilizzato come input per il comando successivo . Nel caso di MongoDB, più operatori sono riuniti in un unico trasportatore responsabile dell'elaborazione del flusso di documenti.
Alcuni operatori come $ match, $ limit e $ skip accettano il documento come input e producono lo stesso documento se viene soddisfatto un determinato insieme di criteri. Altri operatori, come $ project e $ unwind, accettano un singolo documento come dati di input e ne modificano il formato o formano più documenti in base a una determinata proiezione.
L'operatore di gruppo $ accetta infine più documenti come dati di input e li raggruppa in un unico documento combinando i valori corrispondenti. Le espressioni possono essere utilizzate in alcuni di questi operatori per calcolare nuovi valori o eseguire operazioni sulle stringhe.
Diversi operatori sono combinati in un'unica pipeline, che si applica all'elenco dei documenti. Il nastro trasportatore stesso viene eseguito come comando MongoDB, che si traduce in un unico documento MongoDB, che contiene un array di tutti i documenti che sono usciti alla fine del nastro trasportatore. Il prossimo paragrafo descrive in dettaglio l'algoritmo di refactoring di similarità molecolare come trasportatore di operatori. Assicurati di (ri)leggere i due articoli precedenti per comprendere appieno la logica di implementazione.
2. Piping di similarità molecolare
Quando si applica un trasportatore a una particolare raccolta, tutti i documenti contenuti in tale raccolta vengono trasmessi come input al primo operatore. Si consiglia di filtrare questo elenco il più rapidamente possibile per limitare il numero di documenti trasferiti tramite la pipeline. Nel nostro caso, questo significa filtrare l'intero documento che non raggiungerà mai il fattore Tanimoto target.
Pertanto, come primo passo, confrontiamo tutti i documenti per i quali il numero di impronte digitali rientra in una certa soglia. Se miriamo a un fattore Tanimoto di 0,8 con una connessione target contenente 40 impronte digitali univoche, l'operatore di corrispondenza $ apparirà così:
{"$match" :
{ "fingerprint_count" : {"$gte" : 32, "$lte" : 50}}.
}
Solo le connessioni con un numero di impronte compreso tra 32 e 50 verranno trasferite al prossimo operatore del gasdotto. Per eseguire questo filtraggio, l'operatore di corrispondenza $ può utilizzare l'indice che abbiamo definito per la proprietà fingerprint_count. Per calcolare il coefficiente di Tanimoto dobbiamo calcolare il numero di impronte comuni tra una determinata connessione di input e la connessione di destinazione che stiamo prendendo di mira.
Per lavorare a livello di impronta, utilizziamo l'operatore $ unwind. $ unwind rimuove gli elementi dell'array uno per uno, restituendo il flusso di documenti in cui l'array specificato viene sostituito da uno dei suoi elementi. Nel nostro caso, applichiamo $ unwind alle impronte digitali. Di conseguenza, ogni documento composito risulterà in n documenti compositi, dove n è il numero di impronte digitali univoche contenute in un documento composito.
{"$unwind" :"$fingerprints"}
Per calcolare il numero di impronte digitali comuni, inizieremo filtrando tutti i documenti che non hanno le impronte che si trovano nell'elenco delle impronte digitali della connessione di destinazione. Per fare ciò, utilizziamo di nuovo l'operatore di corrispondenza $, questa volta filtrando la proprietà dell'impronta digitale, in cui sono supportati solo i documenti che contengono un'impronta digitale che si trova nell'elenco delle impronte digitali di destinazione.
{"$match" :
{ "fingerprints" :
{"$in" : [ 1960 , 15111 , 5186 , 5371 , 756 , 1015 , 1018 , 338 , 325 , 776 , 3900 , ..., 2473] }
}
}
Poiché abbiniamo solo le impronte digitali presenti nell'elenco delle impronte digitali di destinazione, l'output può essere utilizzato per calcolare il numero totale di impronte digitali comuni.
Per fare ciò applichiamo l'operatore di gruppo $ alla connessione composita, anche se creiamo un nuovo tipo di documento contenente il numero di impronte corrispondenti (sommando il numero di occorrenze), il numero totale di impronte digitali di connessione in ingresso e le faccine.
{"$group" :
{ "_id" : "$compound_cid". ,
"fingerprintmatches" : {"$sum" : 1} ,
"totalcount" : { "$first" : "$fingerprint_count"} ,
"smiles" : {"$first" : "$smiles"}
}
}
Ora abbiamo tutti i parametri per calcolare il coefficiente di Tanimoto. Per fare ciò, utilizzeremo l'operatore $ project che, oltre a copiare la proprietà composita id e smiles, aggiunge anche una proprietà appena calcolata denominata Tanimoto.
{
"$project"
:
{
"_id"
:
1
,
"tanimoto"
:
{
"$divide"
:
[
"$fingerprintmatches."
,
{
"$subtract"
:
[
{
"$add"
:
[
40
,
"$totalcount"
]
}
,
"$fingerprintmatches."
]
}
]
}
,
"smiles"
:
1
}
}
Poiché siamo interessati solo alle connessioni che hanno un coefficiente target Tanimoto di 0,8, utilizziamo l'operatore di corrispondenza $ opzionale per filtrare tutte quelle che non raggiungono questo coefficiente.
{"$match" :
{ "tanimoto" : { "$gte" : 0.8}
}
Il comando della pipeline completa è disponibile di seguito.
{"aggregate" : "compounds"} ,
"pipeline" : [
{"$match" :
{ "fingerprint_count" : {"$gte" : 32, "$lte" : 50} }
},
{"$unwind" : "$fingerprints"},
{"$match" :
{ "fingerprints" :
{"$in" : [ 1960 , 15111 , 5186 , 5371 , 756 , 1015 , 1018 , 338 , 325 , 776 , 3900,... , 2473] }
}
},
{"$group" :
{ "_id" : "$compound_cid" ,
"fingerprintmatches" : {"$sum" : 1} ,
"totalcount" : { "$first" : "$fingerprint_count"} ,
"smiles" : {"$first" : "$smiles"}
}
},
{"$project" :
{ "_id" : 1 ,
"tanimoto" : {"$divide" : ["$fingerprintmatches"] , { "$subtract" : [ { "$add" : [ 89 , "$totalcount"]} , "$fingerprintmatches"] }. ] } ,
"smiles" : 1
}
},
{"$match" :
{"tanimoto" : {"$gte" : 0.05} }
} ]
}
L'output di questa pipeline contiene un elenco di connessioni con Tanimoto 0.8 o superiore in relazione a una particolare connessione di destinazione. Una rappresentazione visiva di questo trasportatore può essere trovata di seguito:
3. Conclusione
La nuova struttura di aggregazione MongoDB fornisce un insieme di operatori di facile utilizzo che consentono agli utenti di esprimere più brevemente algoritmi di tipo di riduzione delle carte. Il concetto di un trasportatore sottostante offre un modo intuitivo per elaborare i dati.
Non sorprende che questo paradigma della pipeline sia adottato da vari approcci NoSQL, tra cui Gremlin Framework Tinkerpop nell'implementazione e Cypher Neo4j nell'implementazione.
In termini di prestazioni, la soluzione di piping rappresenta un significativo miglioramento nell'implementazione delle mappe di riduzione.
Gli operatori sono inizialmente supportati dalla piattaforma MongoDB, che porta a significativi miglioramenti delle prestazioni rispetto al Javascript interpretato. Poiché Aggregation Framework può funzionare anche in un ambiente isolato, supera facilmente le prestazioni della mia implementazione originale, soprattutto quando il numero di connessioni di input è elevato e l'obiettivo di Tanimoto è basso. Prestazioni eccellenti dal comando MongoDB!