MongoDB heeft onlangs zijn nieuwe aggregatiestructuur geïntroduceerd. Deze structuur biedt een eenvoudigere oplossing voor het berekenen van geaggregeerde waarden in plaats van te vertrouwen op krachtige structuren met een verkleinde kaart.
Met slechts een paar eenvoudige primitieven kunt u documenten die in een bepaalde MongoDB-verzameling zijn opgenomen, berekenen, groeperen, vormen en ontwerpen. De rest van dit artikel beschrijft de refactoring van het algoritme voor kaartreductie voor optimaal gebruik van het nieuwe MongoDB-aggregatieplatform. De volledige broncode is te vinden in de openbaar beschikbare Datablend GitHub-repository.
1. MongoDB-aggregatiestructuur
Het MongoDB-aggregatieplatform is gebaseerd op het bekende Linux Pipeline-concept waarbij de uitvoer van een opdracht wordt verzonden via een transportband of wordt omgeleid om te worden gebruikt als invoer voor de volgende opdracht . In het geval van MongoDB worden meerdere operators gecombineerd tot één transportband die verantwoordelijk is voor het verwerken van de documentenstroom.
Sommige operators, zoals $ match, $ limit en $ skip, accepteren het document als invoer en geven hetzelfde document weer als aan een bepaalde reeks criteria wordt voldaan. Andere operators, zoals $ project en $ afwikkeling, accepteren een enkel document als invoergegevens en wijzigen het formaat of vormen meerdere documenten op basis van een bepaalde projectie.
De $ group-operator accepteert uiteindelijk meerdere documenten als invoergegevens en groepeert ze in één document door de bijbehorende waarden te combineren. In sommige van deze operatoren kunnen uitdrukkingen worden gebruikt om nieuwe waarden te berekenen of tekenreeksbewerkingen uit te voeren.
Verschillende operators worden gecombineerd in één pijplijn, wat van toepassing is op de lijst met documenten. De transportband zelf wordt uitgevoerd als het MongoDB-commando, wat resulteert in een enkel MongoDB-document, dat een reeks van alle documenten bevat die aan het einde van de transportband zijn uitgekomen. De volgende paragraaf beschrijft in detail het refactoring-algoritme van moleculaire gelijkenis als een transporteur van operators. Zorg ervoor dat u de vorige twee artikelen (her)lees om de implementatielogica volledig te begrijpen.
2. Piping van moleculaire gelijkenis
Bij het toepassen van een transportband op een bepaalde verzameling, worden alle documenten in die verzameling als invoer doorgegeven aan de eerste operator. Het wordt aanbevolen om deze lijst zo snel mogelijk te filteren om het aantal documenten dat via de pijplijn wordt overgedragen, te beperken. In ons geval betekent dit dat het hele document wordt gefilterd dat nooit aan de beoogde Tanimoto-factor zal voldoen.
Daarom vergelijken we als eerste stap alle documenten waarvan het aantal vingerafdrukken binnen een bepaalde drempel ligt. Als we een Tanimoto-factor van 0,8 nastreven met een doelverbinding met 40 unieke vingerafdrukken, ziet de $ match-operator er als volgt uit:
{"$match" :
{ "fingerprint_count" : {"$gte" : 32, "$lte" : 50}}.
}
Alleen verbindingen met een aantal vingerafdrukken van 32 tot 50 worden overgedragen aan de volgende pijpleidingbeheerder. Om deze filtering uit te voeren, kan de $ match-operator de index gebruiken die we hebben gedefinieerd voor de eigenschap fingerprint_count. Om de Tanimoto-coëfficiënt te berekenen, moeten we het aantal gemeenschappelijke vingerafdrukken berekenen tussen een bepaalde ingangsverbinding en de doelverbinding waarop we ons richten.
Om op vingerafdrukniveau te werken, gebruiken we de $ afwikkel-operator. $ afwikkeling verwijdert de array-elementen één voor één en retourneert de documentstroom waarin de gespecificeerde array is vervangen door een van zijn elementen. In ons geval passen we $ afwikkelen toe op vingerafdrukken. Bijgevolg zal elk samengesteld document resulteren in n samengestelde documenten, waarbij n het aantal unieke vingerafdrukken in een samengesteld document is.
{"$unwind" :"$fingerprints"}
Om het aantal veelvoorkomende vingerafdrukken te berekenen, beginnen we met het filteren van alle documenten die niet de vingerafdrukken hebben die op de vingerafdruklijst van de doelverbinding staan. Om dit te doen, gebruiken we opnieuw de $ match-operator, deze keer filteren we de eigenschap vingerafdruk, waar alleen documenten worden ondersteund die een vingerafdruk bevatten die in de doelvingerafdruklijst staat.
{"$match" :
{ "fingerprints" :
{"$in" : [ 1960 , 15111 , 5186 , 5371 , 756 , 1015 , 1018 , 338 , 325 , 776 , 3900 , ..., 2473] }
}
}
Omdat we alleen de vingerafdrukken matchen die in de doelvingerafdruklijst staan, kan de uitvoer worden gebruikt om het totale aantal veelvoorkomende vingerafdrukken te berekenen.
Om dit te doen passen we de $ group-operator toe op de samengestelde verbinding, hoewel we een nieuw type document maken met het aantal overeenkomende vingerafdrukken (door het aantal voorvallen op te tellen), het totale aantal vingerafdrukken van de invoerverbinding en smileys.
{"$group" :
{ "_id" : "$compound_cid". ,
"fingerprintmatches" : {"$sum" : 1} ,
"totalcount" : { "$first" : "$fingerprint_count"} ,
"smiles" : {"$first" : "$smiles"}
}
}
Nu hebben we alle parameters om de Tanimoto-coëfficiënt te berekenen. Om dit te doen, gebruiken we de $ project-operator die, naast het kopiëren van de samengestelde eigenschap id en smiles, ook een nieuw berekende eigenschap met de naam Tanimoto toevoegt.
{
"$project"
:
{
"_id"
:
1
,
"tanimoto"
:
{
"$divide"
:
[
"$fingerprintmatches."
,
{
"$subtract"
:
[
{
"$add"
:
[
40
,
"$totalcount"
]
}
,
"$fingerprintmatches."
]
}
]
}
,
"smiles"
:
1
}
}
Omdat we alleen geïnteresseerd zijn in verbindingen met een Tanimoto-doelcoëfficiënt van 0,8, gebruiken we de optionele $ match-operator om alle verbindingen uit te filteren die deze coëfficiënt niet halen.
{"$match" :
{ "tanimoto" : { "$gte" : 0.8}
}
De opdracht van de volledige pijplijn is hieronder te vinden.
{"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} }
} ]
}
De uitvoer van deze pijplijn bevat een lijst met verbindingen die Tanimoto 0.8 of hoger hebben in relatie tot een bepaalde doelverbinding. Een visuele weergave van deze transportband vindt u hieronder:
3. Conclusie
De nieuwe MongoDB-aggregatiestructuur biedt een reeks gebruiksvriendelijke operators waarmee gebruikers algoritmen van het type kaartreductie korter kunnen uitdrukken. Het concept van een transportband eronder biedt een intuïtieve manier om gegevens te verwerken.
Het is niet verrassend dat dit pijplijnparadigma wordt overgenomen door verschillende NoSQL-benaderingen, waaronder Gremlin Framework Tinkerpop in de implementatie en Cypher Neo4j in de implementatie.
In termen van prestaties is de piping-oplossing een aanzienlijke verbetering in de implementatie van de reductiekaarten.
Operators worden in eerste instantie ondersteund door het MongoDB-platform, wat leidt tot aanzienlijke prestatieverbeteringen ten opzichte van het geïnterpreteerde Javascript. Omdat het Aggregation Framework ook in een geïsoleerde omgeving kan werken, overtreft het gemakkelijk de prestaties van mijn oorspronkelijke implementatie, vooral wanneer het aantal invoerverbindingen hoog is en het Tanimoto-doel laag is. Uitstekende prestatie van het MongoDB-commando!