MongoDB introducerede for nylig sin nye aggregeringsstruktur. Denne struktur giver en enklere løsning til beregning af aggregerede værdier i stedet for at stole på kraftfulde strukturer med et reduceret kort.
Med blot nogle få enkle primitiver giver det dig mulighed for at beregne, gruppere, forme og designe dokumenter, der er indeholdt i en bestemt MongoDB-samling. Resten af denne artikel beskriver refaktoriseringen af kortreduktionsalgoritmen for optimal brug af den nye MongoDB aggregeringsplatform. Den komplette kildekode kan findes i det offentligt tilgængelige Datablend GitHub-lager.
1. MongoDB aggregeringsstruktur
MongoDB-aggregationsplatformen er baseret på det velkendte Linux Pipeline-koncept, hvor outputtet af en kommando transmitteres gennem en transportør eller omdirigeres til at blive brugt som input til den næste kommando . I tilfælde af MongoDB er flere operatører kombineret til en enkelt transportør, der er ansvarlig for at behandle dokumentstrømmen.
Nogle operatører såsom $ match, $ limit og $ springer over at acceptere dokumentet som input og output det samme dokument, hvis et bestemt sæt kriterier er opfyldt. Andre operatører, såsom $ project og $ unwind, accepterer et enkelt dokument som inputdata og ændrer dets format eller danner flere dokumenter baseret på en bestemt projektion.
$-gruppeoperatøren accepterer endelig flere dokumenter som inputdata og grupperer dem i ét dokument ved at kombinere de tilsvarende værdier. Udtryk kan bruges i nogle af disse operatorer til at beregne nye værdier eller udføre strengoperationer.
Flere operatører er samlet i en enkelt pipeline, som gælder for listen over dokumenter. Selve transportøren udføres som MongoDB-kommandoen, hvilket resulterer i et enkelt MongoDB-dokument, som indeholder en række af alle dokumenter, der kom ud for enden af transportøren. Det næste afsnit beskriver i detaljer refactoring-algoritmen for molekylær lighed som en transportør af operatører. Sørg for at (gen)læse de to foregående artikler for fuldt ud at forstå implementeringslogikken.
2. Piping af molekylær lighed
Når du anvender en transportør til en bestemt samling, videregives alle dokumenter indeholdt i den pågældende samling som input til den første operatør. Det anbefales, at du filtrerer denne liste så hurtigt som muligt for at begrænse antallet af dokumenter, der overføres via pipelinen. I vores tilfælde betyder det at filtrere hele dokumentet, som aldrig vil opfylde målet Tanimoto-faktoren.
Derfor sammenligner vi som et første skridt alle dokumenter, hvor antallet af fingeraftryk er inden for en vis grænse. Hvis vi målretter en Tanimoto-faktor på 0,8 med en målforbindelse, der indeholder 40 unikke fingeraftryk, vil $ match-operatoren se sådan ud:
{"$match" :
{ "fingerprint_count" : {"$gte" : 32, "$lte" : 50}}.
}
Kun forbindelser med et antal fingeraftryk fra 32 til 50 vil blive overført til den næste rørledningsoperatør. For at udføre denne filtrering kan $ match-operatoren bruge det indeks, som vi definerede for egenskaben fingerprint_count. For at beregne Tanimoto-koefficienten skal vi beregne antallet af almindelige fingeraftryk mellem en bestemt inputforbindelse og den målforbindelse, vi målretter mod.
For at arbejde på fingeraftryksniveau bruger vi $ unwind operatoren. $ unwind fjerner array-elementerne ét efter ét, og returnerer dokumentstrømmen, hvori det angivne array er erstattet af et af dets elementer. I vores tilfælde anvender vi $ unwind til fingeraftryk. Som følge heraf vil hvert sammensat dokument resultere i n sammensatte dokumenter, hvor n er antallet af unikke fingeraftryk indeholdt i et sammensat dokument.
{"$unwind" :"$fingerprints"}
For at beregne antallet af almindelige fingeraftryk starter vi med at filtrere alle dokumenter, der ikke har de fingeraftryk, der er på fingeraftrykslisten for målforbindelsen. For at gøre dette bruger vi $ match-operatoren igen, denne gang filtrerer fingeraftryksegenskaben, hvor kun dokumenter, der indeholder et fingeraftryk, der er på målfingeraftrykslisten, understøttes.
{"$match" :
{ "fingerprints" :
{"$in" : [ 1960 , 15111 , 5186 , 5371 , 756 , 1015 , 1018 , 338 , 325 , 776 , 3900 , ..., 2473] }
}
}
Da vi kun matcher de fingeraftryk, der er på målfingeraftrykslisten, kan outputtet bruges til at beregne det samlede antal almindelige fingeraftryk.
For at gøre dette anvender vi $-gruppeoperatoren på den sammensatte forbindelse, selvom vi opretter en ny type dokument, der indeholder antallet af matchende fingeraftryk (ved at lægge antallet af forekomster sammen), det samlede antal input-forbindelsesfingeraftryk og smileys.
{"$group" :
{ "_id" : "$compound_cid". ,
"fingerprintmatches" : {"$sum" : 1} ,
"totalcount" : { "$first" : "$fingerprint_count"} ,
"smiles" : {"$first" : "$smiles"}
}
}
Nu har vi alle parametre til at beregne Tanimoto-koefficienten. For at gøre dette vil vi bruge $-projektoperatøren, som udover at kopiere id og smiles composite-egenskaben også tilføjer en nyberegnet egenskab ved navn Tanimoto.
{
"$project"
:
{
"_id"
:
1
,
"tanimoto"
:
{
"$divide"
:
[
"$fingerprintmatches."
,
{
"$subtract"
:
[
{
"$add"
:
[
40
,
"$totalcount"
]
}
,
"$fingerprintmatches."
]
}
]
}
,
"smiles"
:
1
}
}
Da vi kun er interesserede i forbindelser, der har en Tanimoto-målkoefficient på 0,8, bruger vi den valgfrie $-matchoperator til at bortfiltrere alle dem, der ikke når denne koefficient.
{"$match" :
{ "tanimoto" : { "$gte" : 0.8}
}
Kommandoen for den komplette pipeline kan findes nedenfor.
{"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} }
} ]
}
Outputtet fra denne pipeline indeholder en liste over forbindelser, der har Tanimoto 0.8 eller højere i forhold til en bestemt målforbindelse. En visuel gengivelse af denne transportør kan findes nedenfor:
3. Konklusion
Den nye MongoDB-aggregationsstruktur giver et sæt brugervenlige operatører, der giver brugerne mulighed for at udtrykke algoritmer af kortreduktionstype mere kort. Konceptet med en transportør under den tilbyder en intuitiv måde at behandle data på.
Ikke overraskende er dette pipeline-paradigme overtaget af forskellige NoSQL-tilgange, inklusive Gremlin Framework Tinkerpop i implementeringen og Cypher Neo4j i implementeringen.
Med hensyn til ydeevne er rørløsningen en væsentlig forbedring af implementeringen af reduktionskortene.
Operatører understøttes oprindeligt af MongoDB-platformen, hvilket fører til betydelige præstationsforbedringer i forhold til det fortolkede Javascript. Da Aggregation Framework også kan fungere i et isoleret miljø, overgår det let ydeevnen af min oprindelige implementering, især når antallet af inputforbindelser er højt og Tanimoto-målet er lavt. Fremragende ydeevne fra MongoDB-kommandoen!