$$%% examples \newcommand{\exGraph}{\graph_{\mathrm{ex}}} \newcommand{\exOnto}{\onto_{\mathrm{ex}}} \newcommand{\exMappings}{\mappings_{\mathrm{ex}}} \newcommand{\exExtensions}{\extensions_{\mathrm{ex}}} \newcommand{\exRule}{r_{\mathrm{ex}}} \newcommand{\RDFSrules}{\rules_{\mathrm{RDFS}}} %% RDF \newcommand{\triple}[3]{(#1, #2, #3)} \newcommand{\tuple}[1]{\langle #1 \rangle} \newcommand{\subject}{\mathtt{s}} \newcommand{\prop}{\mathtt{p}} \newcommand{\object}{\mathtt{o}} \newcommand{\blank}{\_{:}b} \newcommand{\blankn}[1]{\_{:}#1} \newcommand{\irin}[1]{{:}\mathrm{#1}} \newcommand{\class}{\mathtt{c}} \newcommand{\nsrdf}{\mathrm{rdf{:}}} \newcommand{\nsrdfs}{\mathrm{rdfs{:}}} \newcommand{\rdftype}{\mathrm{rdf{:}type}} \newcommand{\rdfLiteral}{\mathrm{rdf{:}Literal}} \newcommand{\rdfssubClassOf}{\mathrm{rdfs{:}subClassOf}} \newcommand{\rdfssubPropertyOf}{\mathrm{rdfs{:}subPropertyOf}} \newcommand{\rdfsdomain}{\mathrm{rdfs{:}domain}} \newcommand{\rdfsrange}{\mathrm{rdfs{:}range}} \newcommand{\rdfsClass}{\mathrm{rdfs{:}Class}} \newcommand{\rdfProperty}{\mathrm{rdf{:}Property}} \newcommand{\xsdint}{\mathrm{xsd{:}int}} %% \newcommand{\type}{\tau} \newcommand{\subclass}{\prec_{sc}} \newcommand{\subproperty}{\prec_{sp}} \newcommand{\domain}{\hookleftarrow_{d}} \newcommand{\range}{\hookrightarrow_{r}} \newcommand{\rdfentailment}{\vdash_{^\mathrm{RDF}}} \newcommand{\RDFS}[1]{\mathrm{RDFS}(#1)} \newcommand{\aka}{a.k.a.~} \newcommand{\etc}{etc} \newcommand{\wrt}{w.r.t.~} \newcommand{\st}{s.t.~} \newcommand{\ie}{i.e.,~} \newcommand{\eg}{e.g.,~} \newcommand{\graph}{G} \newcommand{\rules}{\mathcal{R}} \newcommand{\sources}{\mathcal{S}} \newcommand{\views}{\mathcal{V}} \newcommand{\extensions}{\mathcal{E}} \newcommand{\onto}{\mathcal{O}} \newcommand{\mappings}{\mathcal{M}} \newcommand{\modelsrdf}{\models_\rules} \newcommand{\bgp}{P} \newcommand{\Bl}[1]{\mathrm{Bl}(#1)} \newcommand{\Val}[1]{\mathrm{Val}(#1)} \newcommand{\Var}[1]{\mathrm{Var(#1)}} \newcommand{\ext}[1]{\mathrm{ext}(#1)} \newcommand{\cert}{\mathrm{cert}} \newcommand{\ans}{\mathrm{ans}} \newcommand{\query}{\leftarrow} \newcommand{\body}[1]{\textrm{body}(#1)} \newcommand{\head}[1]{\textrm{head}(#1)} \newcommand{\cs}{\mathrm{cs}} \newcommand{\lcs}{\mathrm{lcs}} \newcommand{\cl}{\mathrm{cl}} \newcommand{\lua}{\mathrm{lua}} \newcommand{\lur}{\mathrm{lur}} \newtheorem{lemma}{Lemma} \newtheorem{definition}{Definition} \newtheorem{problem}{Problem} \newtheorem{property}{Property} \newtheorem{corollary}{Corollary} \newtheorem{example}{Example} \newtheorem{theorem}{Theorem} \newcommand{\URIs}{\mathscr U} \newcommand{\IRIs}{\mathscr I} \newcommand{\BNodes}{\mathscr B} \newcommand{\Literals}{\mathscr L} \newcommand{\Variables}{\mathscr V} % DB \newcommand{\CQ}{\ensuremath{\mathtt{CQ}}\xspace} \newcommand{\UCQ}{\ensuremath{\mathtt{UCQ}}\xspace} \newcommand{\SQL}{\ensuremath{\mathtt{SQL}}\xspace} \newcommand{\rel}[1]{\mathsf{#1}} % Cost model \newcommand{\cans}[1]{|#1|_t} \newcommand{\cref}[1]{|#1|_r} \newcommand{\db}{\mathtt{db}} % DL \newcommand{\cn}{\ensuremath{N_{C}}\xspace} \newcommand{\rn}{\ensuremath{N_{R}}\xspace} \newcommand{\inds}{\ensuremath{N_{I}}\xspace} \newcommand{\ainds}{\ensuremath{\mathrm{Ind}}\xspace} \newcommand{\funct}{\mathit{funct} \ } \newcommand{\KB}{\mathcal{K}\xspace} \newcommand{\dlr}{DL-Lite$_{\mathcal{R}}$\xspace} % Logics \newcommand{\FOL}{\ensuremath{\mathtt{FOL}}\xspace} \newcommand{\datalog}{\ensuremath{\mathtt{Datalog}}\xspace} \newcommand{\dllite}{DL-Lite\xspace} \newcommand{\true}{\mathrm{true}} \newcommand{\false}{\mathrm{false}} \newcommand{\dis}{\mathtt{dis}} \newcommand{\vars}[1]{\ensuremath{\mathrm{vars}(#1)}} %\newcommand{\terms}[1]{\ensuremath{\mathrm{terms}(#1)}} %math \renewcommand{\phi}{\varphi} \newcommand\eqdef{\stackrel{\mathclap{\normalfont\mbox{def}}}{=}} \newcommand\restr[2]{#1_{|#2}} \newcommand{\ontoBody}[1]{\mathrm{body}_\onto(#1)} %proof of the rewriting theorem \newcommand{\rdfGraph}{\graph^{\mappings}_{\extensions}} \newcommand\systemGraph{\graph^{\mappings \cup \mappings^{\text{STD}}_\onto}_{\extensions \cup \extensions_\onto}} \newcommand\viewsGraph{\graph^{\mappings^{\rules,\onto} \cup \mappings^{\text{STD}}_\onto}_{\extensions \cup \extensions_\onto}} \newcommand{\standMappings}{\mappings^{\text{STD}}_\onto} \newcommand{\reminder}[1]{[\vadjust{\vbox to0pt{\vss\hbox to0pt{\hss{\Large $\Longrightarrow$}}}}{{\textsf{\small #1}}}]} %\newcommand{\FG}[1]{\textcolor{blue}{\reminder{FG:~#1}}} \newcommand{\extVersion}{false} \newcommand{\printIfExtVersion}[2] { \ifthenelse{\equal{\extVersion}{true}}{#1}{} \ifthenelse{\equal{\extVersion}{false}}{#2}{} } \newcommand{\bda}{\true} \newcommand{\ifBDA}[2]% {% \ifthenelse{\equal{\bda}{true}}{#1}{}% \ifthenelse{\equal{\bda}{false}}{#2}{}% } %%% Local Variables: %%% TeX-master: "paper" %%% End: $$

MongoDB Join Benchmark

We produce a benchmark for join in MongoDB and we want to compare the performance of join evaluate inside MongoDB with join done outside using Tatooine plan.

Description of the benchmark

For this benchmark, we will generate collections of JSON documents of the form:

{ 
  _id: "ID",
    k: "value"
}

where the id is unique and the key k can have two kind of values: no joinable and joinable values. The generator have the following parameters:

  • card is the number of documents in the collection
  • rangeNJ is the number of different no joinable values
  • rangeJ is the number of different joinable values
  • ratioNJJ is the ratio of no joinable values over joinable values

The javascript generator function:

function generateCollection(file, card, rangeNJ, rangeJ, symbolNJ, symbolJ, ratioNJJ) {
  // empty file
  if (fs.existsSync(file))
    fs.truncateSync(file, 0)

  for (let c = 0; c < card; c++) {
    let obj = {}
    // we choose between NJ and J
    if (ratioNJJ * Math.random() > 1) {
      // we choose one NJ in its range 
      obj.k = symbolNJ + Math.floor(rangeNJ * Math.random())
    } else {
      // we choose one J in its range 
      obj.k = symbolJ + Math.floor(rangeJ * Math.random())
    }

    fs.appendFileSync(file, JSON.stringify(obj) + '\n')
  }
}

Small Intersection of values

We generate two collections of 100M documents with few intersection in joinable values with the following parameters:

  • card = 100M
  • rangeNJ = 1M
  • rangeJ = 1000
  • ratioNJJ = 100 000

The number of pair of joinable document should be around 1000.

We load the two collection into mongoDB

mongoimport --drop -d join -c S --file collectionS.json
mongoimport --drop -d join -c R --file collectionR.json

We create an index for the two collections:

db.S.createIndex({k: 1})
db.R.createIndex({k: 1})

We query the join of the two collections which takes 1h1min30s:

db.S.explain("executionStats").aggregate([{$lookup: {from: "R", localField: "k", foreignField: "k", as: "kr"}}, {$unwind: "$kr"}])

{
	"stages" : [
		{
			"$cursor" : {
				"query" : {
					
				},
				"queryPlanner" : {
					"plannerVersion" : 1,
					"namespace" : "join.S",
					"indexFilterSet" : false,
					"parsedQuery" : {
						
					},
					"winningPlan" : {
						"stage" : "COLLSCAN",
						"direction" : "forward"
					},
					"rejectedPlans" : [ ]
				},
				"executionStats" : {
					"executionSuccess" : true,
					"nReturned" : 100000000,
					"executionTimeMillis" : 3691177,
					"totalKeysExamined" : 0,
					"totalDocsExamined" : 100000000,
					"executionStages" : {
						"stage" : "COLLSCAN",
						"nReturned" : 100000000,
						"executionTimeMillisEstimate" : 16530,
						"works" : 100000002,
						"advanced" : 100000000,
						"needTime" : 1,
						"needYield" : 0,
						"saveState" : 787888,
						"restoreState" : 787888,
						"isEOF" : 1,
						"invalidates" : 0,
						"direction" : "forward",
						"docsExamined" : 100000000
					}
				}
			}
		},
		{
			"$lookup" : {
				"from" : "R",
				"as" : "kr",
				"localField" : "k",
				"foreignField" : "k",
				"unwinding" : {
					"preserveNullAndEmptyArrays" : false
				}
			}
		}
	],
	"ok" : 1
}

Large Intersection of Values

We generate two collections of 100M documents with a lot intersection in joinable values with the following parameters:

  • card = 100M
  • rangeNJ = 1M
  • rangeJ = 1000
  • ratioNJJ = 10 000

The number of pair of joinable document should be around 100 000.

> db.S.explain("executionStats").aggregate([{$lookup: {from: "R", localField: "k", foreignField: "k", as: "kr"}}, {$unwind: "$kr"}])
{
	"stages" : [
		{
			"$cursor" : {
				"query" : {
					
				},
				"queryPlanner" : {
					"plannerVersion" : 1,
					"namespace" : "join.S",
					"indexFilterSet" : false,
					"parsedQuery" : {
						
					},
					"winningPlan" : {
						"stage" : "COLLSCAN",
						"direction" : "forward"
					},
					"rejectedPlans" : [ ]
				},
				"executionStats" : {
					"executionSuccess" : true,
					"nReturned" : 100000000,
					"executionTimeMillis" : 3070369,
					"totalKeysExamined" : 0,
					"totalDocsExamined" : 100000000,
					"executionStages" : {
						"stage" : "COLLSCAN",
						"nReturned" : 100000000,
						"executionTimeMillisEstimate" : 19450,
						"works" : 100000002,
						"advanced" : 100000000,
						"needTime" : 1,
						"needYield" : 0,
						"saveState" : 787888,
						"restoreState" : 787888,
						"isEOF" : 1,
						"invalidates" : 0,
						"direction" : "forward",
						"docsExamined" : 100000000
					}
				}
			}
		},
		{
			"$lookup" : {
				"from" : "R",
				"as" : "kr",
				"localField" : "k",
				"foreignField" : "k",
				"unwinding" : {
					"preserveNullAndEmptyArrays" : false
				}
			}
		}
	],
	"ok" : 1
}

Postgres Comparison

We build a similar CSV generator

function generator_csv () {
    FILE=$1
    CARD=$2
    RANGENJ=$3
    RANGEJ=$4
    SYMBOLNJ=$5
    SYMBOLJ=$6
    RATIONJJ=$7

    # empty the file
    > $FILE

    id=0
    while [ $id -lt $CARD ]
    do
        if [ $((RANDOM % (RATIONJJ + 1))) -gt 0 ]
        then
            echo -e "$id\t$SYMBOLNJ$(( ( RANDOM % RANGENJ )  + 1 ))" >> $FILE
        else
            echo -e "$id\t$SYMBOLJ$(( ( RANDOM % RANGEJ )  + 1 ))" >> $FILE
        fi
        ((id++))
    done

}

generator_csv "tableR.csv" 100000000 1000000 1000 "A" "J" 100000
generator_csv "tableS.csv" 100000000 1000000 1000 "B" "J" 100000
CREATE TABLE R (
id integer primary key,
k varchar);

CREATE INDEX on R (k);

CREATE TABLE S (
id integer primary key,
k varchar);

CREATE INDEX on S (k);

For small intersection tables, Postgres computes the result in 241809,116 ms, with the following execution plan:

mongo_join=# explain select * from s AS S, r AS R where S.k = R.k;

                                     QUERY PLAN                                      
-------------------------------------------------------------------------------------
 Merge Join  (cost=755377247.21..3937606397.52 rows=302921560607 width=20)
   Merge Cond: ((s.k)::text = (r.k)::text)
   ->  Index Scan using s_k_idx on s  (cost=0.57..5066424.21 rows=99997608 width=10)
   ->  Index Scan using r_k_idx on r  (cost=0.57..5065805.49 rows=99997960 width=10)
(4 lignes)


mongo_join=# select count(*) from s AS S, r AS R where S.k = R.k;
 count 
-------
  9017
(1 ligne)