Nio databaser på 2 GB, och den utan server vann.

Ett fungerande system, en datamängd, en kodväg, nio lagringsmotorer och en proxy. Var och en fick en maskin på 2 GB med applikationen boende på den. SQLite tog sex av åtta lässcenarier och delade förstaplatsen med PostgreSQL. Två motorer kom inte i mål.

Written by:Adrian Rosca

De flesta databasjämförelser mäter något ingen kör: likformigt genererade rader, smickrande frågor, ett direkt drivrutinsanrop, en siffra med ett X efter.

En applikation pratar inte med en drivrutin. Den pratar genom ett datalager, en behörighetskontroll, ett filter för mjukraderade poster, en sidbrytningshjälpare och en serialiserare.

Så vi behöll allt det och bytte bara ut lagringsmotorn. Samma data, samma frågor, samma index, samma kodväg: SQLite, PostgreSQL, MongoDB, dpdb, MariaDB, MariaDB bakom MaxScale, ScyllaDB, Cassandra, CockroachDB och CouchDB. Nio scenarier, tio omgångar var.

Det nya är maskinen. En tidigare körning gav varje motor en egen server på 32 GB och PostgreSQL vann sju av nio. Den här ger var och en en låda på 2 GB med jämförelseprocessen boende på den — vilket är hur ett av våra system faktiskt driftsätts, och det ändrar svaret.

Vinnaren

Det är oavgjort, och de två motorer som delar förstaplatsen kunde knappast vara mer olika. SQLite och PostgreSQL har båda snittplaceringen 1,78 över de nio scenarierna.

De tar sig dit på olika vägar. SQLite vinner sex av de åtta lässcenarierna; PostgreSQL vinner de två andra och blir tvåa i sju av nio. Räknar man bara läsningar har SQLite 1,50 mot PostgreSQLs 1,75.

MongoDB är trea på 3,11 och äger skrivkolumnen helt med 23 900 rader per sekund, 91 % före nästa motor. Den vann inget lässcenario — samma resultat som på de stora maskinerna.

Marginalen i toppen är inte subtil. SQLite läser en rad på dess id på 9,67 ms där PostgreSQL tar 80,8: åtta gånger snabbare över 200 sekventiella uppslag, det bredaste glappet den här jämförelsen någonsin producerat mellan två fungerande motorer. Den tar sig dit genom att inte vara en server. Det finns ingen port, ingen socket, ingen tur och retur och ingen andra process som slåss om den enda kärnan.

  • SQLite — snittplacering 1,78, sex scenariovinster, alla läsningar
  • PostgreSQL — 1,78, två vinster (djup sida, exakt räkning), tvåa i sju av nio
  • MongoDB — 3,11, en vinst (massinläggning, och det är inte jämnt)
  • MariaDB — 5,44
  • dpdb — 5,67
  • ScyllaDB — 5,89
  • CockroachDB — 6,11, ur de sju omgångar av tio den orkade slutföra
  • Cassandra — 6,56
  • CouchDB — 8,67, sist på varje läsning den besvarade, vägrade två den inte besvarade, och sexa på skrivningen
Krymp maskinen till 2 GB och motorn som slutar vara en server vinner sex av åtta läsningar.

Uppställningen

250 000 genererade poster per motor, i en egen databas, seedade från tomt. Sex identiska sekundärindex överallt, på plats medan raderna skrevs.

Varje motor fick en egen maskin: en vCPU, 2 GB, 50 GB disk, i samma datacenter. Varje låda skapades för sin motor, mättes, och förstördes så snart dess siffror var i säkerhet.

Jämförelseprocessen kördes PÅ den lådan, bredvid motorn, genom hela varje omgång. Det är premissen snarare än en kompromiss — applikationen och dess databas delar en maskin, så mätningen bör göra det också.

Varje motor hölls till samma 1 GB hur många processer den än fördelar dem över: MariaDBs buffertpool, ScyllaDBs Seastar-allokering, dpdbs heap, MaxScale bredvid den MariaDB den står framför. Det är utjämning, inte trimning.

Tio fulla omgångar var — töm, seeda om, kör om. Publicerade siffror poolar varje råmätning i stället för att medelvärdesbilda omgångarnas medianer: 100 iterationer per lässcell, 500 styckmätningar per skrivcell.

Innan någon tid rapporterades måste varje motor rymma exakt 250 000 rader och bära exakt den deklarerade indexuppsättningen, kontrollerat mot dess egen katalog i början av varje omgång.

Tre motorer tog sig inte igenom det helskinnade, och var och en behandlas där det hände i stället för att döljas: CockroachDB slutförde sju omgångar av tio, CouchDB slutförde tio och vägrade två scenarier i var och en av dem, och FerretDB slutförde aldrig en enda seedning.

Att skriva 250 000 rader

En fyllning från tomt, femtio stycken om femtusen, alla sex index redan på plats.

MongoDB 23 900 rader per sekund — 12,8 sekunder för hela fyllningen, och den enda kolumn den vinner.

ScyllaDB 3 380, vilket är 81 sekunder för samma arbete. Motorerna byggda för att sprida skrivningar över ett kluster betalar mest för att få en nod och en kärna.

CockroachDB är sist på 1 140 rader per sekund — 286 sekunder per fyllning, tjugotvå gånger MongoDBs, och skälet till att den bara slutförde sju omgångar. Raft-konsensus och en serialiserbar transaktion på varje insättning är inte gratis på en kärna.

Massinläggning, 250 000 poster
Higher is better · rader per sekund
MongoDB
23 900 rows/s
Postgres
12 500 rows/s
MariaDB
11 700 rows/s
MaxScale
11 700 rows/s
SQLite
10 400 rows/s
dpdb
6 860 rows/s
CouchDB
5 930 rows/s
Cassandra
4 920 rows/s
ScyllaDB
3 380 rows/s
Cockroach
1 140 rows/s
Staplarna är logaritmiska — spridningen går till 21×. Tio fyllningar per motor, 500 styckmätningar poolade per stapel. Högre är bättre.

Att läsa en rad på dess id

Tvåhundra sekventiella uppslag på primärnyckel. Scenariot som skiljer fältet mest åt, och det skiljer det med en faktor 125.

SQLite 9,67 ms mot PostgreSQLs 80,8. CouchDB är sist på 1 210 och dpdb näst sist på 428.

Båda ändarna har samma orsak. SQLite svarar inuti processen som frågade, så ett uppslag är ett funktionsanrop; dpdb svarar över HTTP till en andra process, så ett uppslag är ett anrop. Tvåhundra sekventiella turer är tvåhundra tillfällen att betala för det, och det är den enda form där skillnaden är hela mätningen. CouchDB, som talar HTTP av konstruktion, sitter i andra änden av samma axel.

Punktläsning på identifierare, 200 anrop
Lower is better · millisekunder
SQLite
9.7 ms
Postgres
81 ms
MariaDB
88 ms
MaxScale
116 ms
Cassandra
125 ms
MongoDB
129 ms
Cockroach
218 ms
ScyllaDB
240 ms
dpdb
428 ms
CouchDB
1.21 s
Staplarna är logaritmiska — spridningen går till 125×. Tvåhundra sekventiella uppslag per iteration, så den här stapeln prissätter en tur och retur lika mycket som en läsning.

Alla mätvärden för en besiktning

Ett indexerat uppslag på en främmande nyckel som returnerar 494 rader — den vanligaste frågan den här applikationen ställer.

SQLite 3,41 ms, PostgreSQL 7,97, MongoDB 11,2. Cassandra är sist av motorerna som svarade, på 22,1.

CouchDB besvarade den inte alls. I alla tio omgångarna kom frågan tillbaka från servern som en timeout — CouchDB som vägrar köra den snarare än kör den långsamt, vilket är beteendet den dokumenterar och det enda på den här sidan som inte är en siffra.

Här ligger sidans jämnaste avgörande: dpdb 18,3 ms mot MariaDB 18,8, ett glapp på 2,7 % mellan två motorer som kördes på två olika maskiner. Behandla det paret som oavgjort.

Alla mätvärden för en besiktning
Lower is better · millisekunder
SQLite
3.4 ms
Postgres
8.0 ms
MongoDB
11 ms
MaxScale
14 ms
ScyllaDB
16 ms
Cockroach
17 ms
dpdb
18 ms
MariaDB
19 ms
Cassandra
22 ms
CouchDB
—
Staplarna är logaritmiska — spridningen går till 6×.

Första sidan

Femtio rader sorterade på datum, fallande. Det en användare ser när hen öppnar en lista.

SQLite 2,88 ms och PostgreSQL 3,85 är snabba nog att nätverket ut till webbläsaren kostar mer än frågan.

ScyllaDB 28,2, Cassandra 39,8 och CockroachDB 46,6: en godtycklig sortering är inte vad CQL är byggt för, oavsett maskinstorlek, och en distribuerad SQL-motor på en nod får ingen kompensation för maskineriet.

Det här är CouchDBs andra vägran. En Mango-sortering behöver ett index som täcker den, och i stället för att degradera avstår motorn.

Första sorterade sidan, 50 rader
Lower is better · millisekunder
SQLite
2.9 ms
Postgres
3.9 ms
MongoDB
7.2 ms
MaxScale
21 ms
MariaDB
22 ms
ScyllaDB
28 ms
dpdb
31 ms
Cassandra
40 ms
Cockroach
47 ms
CouchDB
—
Staplarna är logaritmiska — spridningen går till 16×.

Sida tvåhundra

Samma femtio rader, tiotusen ner. PostgreSQLs första vinst, och det tydligaste den gör bättre än SQLite.

PostgreSQL 4,99 ms, MongoDB 11,6, SQLite 17,4. En djup offset är där en riktig frågeplanerare och en riktig buffertpool tjänar in sitt minne. Det här och den exakta räkningen är de enda två läsningar SQLite inte vinner, och den förlorar båda mot samma två motorer.

ScyllaDB 249 ms och Cassandra 201. CQL har ingen OFFSET, så djup sidbrytning emuleras.

CouchDB tar 20,4 sekunder — fyratusen gånger PostgreSQL, och det bredaste enskilda glappet någonstans i den här jämförelsen.

Djup sida, sida 200
Lower is better · millisekunder
Postgres
5.0 ms
MongoDB
12 ms
SQLite
17 ms
dpdb
59 ms
MaxScale
71 ms
MariaDB
72 ms
Cockroach
85 ms
Cassandra
201 ms
ScyllaDB
249 ms
CouchDB
20.4 s
Staplarna är logaritmiska — spridningen går till 4088×.

Att filtrera på ett fält ingen indexerat

Tiotusen rader som matchar ett booleskt fält utan index på någon motor. Alla läser igenom, så det här mäter genomsökningshastighet och inget annat.

SQLite 75,1 ms, PostgreSQL 141. ScyllaDB 418 och MariaDB 405 — att söka igenom är det CQL är minst villigt att göra, och InnoDB är inte mycket gladare över det här.

CouchDB 4,7 sekunder, och dess p95 är 10,5: ungefär ett anrop av tjugo kostar mer än dubbelt det typiska, vilket är sidans bredaste svans.

Filtrerad genomsökning på ett oindexerat booleskt fält
Lower is better · millisekunder
SQLite
75 ms
Postgres
141 ms
MongoDB
221 ms
dpdb
244 ms
MaxScale
296 ms
Cockroach
315 ms
Cassandra
347 ms
MariaDB
405 ms
ScyllaDB
418 ms
CouchDB
4.70 s
Staplarna är logaritmiska — spridningen går till 63×.

Ett intervall inom en relation

Ett indexerat uppslag och en numerisk jämförelse tillsammans — frågan en rapport byggs av.

SQLite 2,03 ms, PostgreSQL 3,95, MongoDB 5,83. Sidans snabbaste siffror, på de minsta maskiner den här jämförelsen använt.

CockroachDB 23,5 och CouchDB 315 avslutar den.

Intervallsökning på ett oindexerat tal
Lower is better · millisekunder
SQLite
2.0 ms
Postgres
4.0 ms
MongoDB
5.8 ms
ScyllaDB
12 ms
dpdb
12 ms
MaxScale
13 ms
MariaDB
14 ms
Cassandra
18 ms
Cockroach
24 ms
CouchDB
315 ms
Staplarna är logaritmiska — spridningen går till 155×.

Att räkna samlingen

Hur många rader finns det. Den enklaste frågan här, PostgreSQLs andra vinst, och den enda läsning där SQLite slås med klar marginal — 99 ms mot 437.

MongoDB 156 ms. MariaDB 1,75 sekunder, därför att InnoDB inte håller något exakt radantal och COUNT(*) läser igenom ett index för att få fram det — samma beteende den visade på en maskin med 32 GB, i samma multipel.

ScyllaDB och Cassandra kan inte besvara den som ett enda anrop alls, så deras siffra är en sidbruten genomsökning: 1,62 och 2,79 sekunder.

CouchDB 32,4 sekunder. Den håller en databas per samling, vilket borde göra en exakt räkning till en metadataläsning; på den här maskinen är den inte det.

Exakt räkning av hela samlingen
Lower is better · millisekunder
Postgres
99 ms
MongoDB
156 ms
SQLite
437 ms
Cockroach
1.14 s
dpdb
1.42 s
ScyllaDB
1.62 s
MariaDB
1.75 s
MaxScale
1.75 s
Cassandra
2.79 s
CouchDB
32.4 s
Staplarna är logaritmiska — spridningen går till 327×. ScyllaDB och Cassandra kan inte leverera COUNT(*) över 250 000 rader som ett anrop. Deras siffra är en sidbruten genomsökning.

Att läsa in allt

Alla 250 000 rader, genom applikationen, serialiserade.

SQLite 2,37 sekunder, PostgreSQL 3,02, ScyllaDB 3,72, CockroachDB 4,10. dpdb 7,64, och CouchDB 56,4 — tjugofyra gånger vinnaren.

ScyllaDB är värd en andra blick här: näst sist på den djupa sidan, tredje snabbast på att strömma hela samlingen, och den passerar MongoDB på vägen. Sekventiella massläsningar är åtkomstmönstret den byggdes kring, och det här är sidans enda scenario som ber om dem.

Läs in hela samlingen
Lower is better · millisekunder
SQLite
2.37 s
Postgres
3.02 s
ScyllaDB
3.72 s
Cockroach
4.10 s
Cassandra
4.60 s
MaxScale
4.80 s
MongoDB
6.10 s
MariaDB
6.68 s
dpdb
7.64 s
CouchDB
56.4 s
Staplarna är logaritmiska — spridningen går till 24×.

Vad en proxy kostar

MariaDB kördes två gånger: direkt, och genom MaxScale på samma maskin med samma rader och samma inloggning. Porten är den enda skillnaden, så varje glapp mellan de två kolumnerna är proxyn.

Det är ingen platt skatt, och den går inte ens åt ett håll. Punktläsningen — 200 sekventiella turer — kostar 31 % mer genom MaxScale. Allt som returnerar en bunt kommer tillbaka snabbare: relationsuppslaget med 28 %, det oindexerade filtret med 27 %, hela inläsningen av 250 000 rader med 28 %.

Den exakta räkningen och massinläggningen är identiska med den rapporterade siffran.

Den tidigare körningen mätte samma två riktningar på maskiner med 32 GB och en helt annan processor: 18,7 % långsammare på punktläsningen, 29 % snabbare på hela inläsningen. Att få tillbaka båda halvorna på en sextondel av minnet och en åttondel av kärnorna är ett starkare påstående än någondera körningen gör ensam.

Den troliga orsaken till vinsten på bunten är att proxyn frikopplar läsningen från motorn från skrivningen till klienten. Det är en hypotes; mätningen är det inte.

Motorerna som inte kom i mål

Två motorer besegrades av maskinen snarare än av varandra, och båda är värda mer än placeringen de hamnar på.

CockroachDB slutförde sju omgångar av tio. Två dog med att uppkopplingen bröts mitt i en seedning, och en hängde sig hela takgränsen på tre timmar utan att producera något alls. Dess kolumn är poolad ur de sju som överlevde, vilket smickrar den: omgångarna den förlorade är de där den hade problem.

FerretDB har ingen kolumn alls, och det är dess resultat snarare än en lucka i vårt.

Den slutförde aldrig en enda seedning på den här hårdvaran. Kärnans OOM-dödare tog den vid 783 MB med 50 100 av 250 000 rader skrivna, och den tog den igen vid varje uppdelning av minnesbudgeten vi försökte med.

Det som dödar den är formen på en översättande proxy: den svarar genom att materialisera en hel samling inuti processen som översätter, så dess minnesbehov följer datamängdens storlek snarare än sidans. På 32 GB är det osynligt och den slutförde varje scenario. På 2 GB är det fyndet.

Siffran som hade fått den att rymmas är skälet till att den inte har någon. Varje annan kolumn hålls till samma 1 GB, och en motor som mätts med mer är inte mätt mot dem.

CouchDB hör bara halvvägs hemma här. Den slutförde alla tio omgångarna och vägrade sedan två av de åtta lässcenarierna i var och en av dem — relationsuppslaget och första sidan, båda returnerade av servern som timeouts. De sex den besvarar, besvarar den sist.

Hur mycket av det här är mätfel

Nog för att spela roll i botten av tabellen och inte på långa vägar nog för att spela roll i toppen.

Varje motor kördes på en egen maskin. De är samma produkt i samma datacenter, men den här flottan registrerade ingen kalibrering mellan maskiner — flottan med 32 GB gjorde det, och fann en spridning på 4,2 % mellan nominellt identiska servrar. Anta något i den storleksordningen här och läs allt inom några procent som oavgjort.

Två avgöranden ligger innanför det: dpdbs relationsuppslag på 18,3 ms mot MariaDBs 18,8, och ScyllaDBs intervallsökning på 11,7 ms mot dpdbs 12,2. Ingetdera flyttar en placering ovanför femte.

Inget nära toppen är i fara. SQLites punktläsning är åtta gånger nästa motors och dess smalaste vinst är 34 %; ingen rimlig skillnad mellan två lådor av en produkt når någondera.

CockroachDBs kolumn bär ett andra slags fel de övriga inte har: 70 mätvärden per cell i stället för 100, hämtade ur omgångarna den överlevde. Det är en välvillig uppskattning av en motor som inte kom i mål, och den ska läsas som en.

Proxyjämförelsen är den enda på sidan utan någon maskin i sig alls — MaxScale och MariaDB är en server på en låda, och porten är hela skillnaden.

Varje anrop var sekventiellt och varje motor körde en nod. Inget här beskriver beteende under samtidighet eller över ett kluster, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig — och en enkärnig låda är den minst representativa maskin som finns för någotdera.

Vad det här betyder om ni ska välja en

Rangordningen står ovan och den är verklig. Den är verklig för den här maskinen, vilket är poängen med att köra den på den här maskinen.

  • En applikation, en liten låda, en process som läser — SQLite, och marginalen är inte jämn. Den vann sex av åtta läsningar genom att inte ha något nätverksprotokoll alls
  • Samma egenskap är skälet att titta någon annanstans: SQLite har inget att erbjuda en andra applikationsserver, serialiserar sina skrivare, och dess övertag här ligger lika mycket i driftsättningen som i motorn
  • Ad hoc-frågor, djup sidbrytning, exakta räkningar, åtkomstmönster som kommer att ändras efter lansering — PostgreSQL, som är tvåa nästan överallt och etta där en frågeplanerare spelar roll
  • MongoDB är den snabbaste skrivaren med bred marginal och den bästa allroundmotorn efter de två, precis som på maskiner sexton gånger så stora
  • Skrivtungt med åtkomstmönster kända i förväg — CQL-motorerna tjänar in sina begränsningar, men inte på en kärna: ScyllaDB skriver i en sjundedel av MongoDBs takt här
  • MariaDB är konkurrenskraftig på skrivningar och på punktläsningen, och dess exakta räkning är 18 gånger PostgreSQLs
  • En proxy framför en databas är ingen fast omkostnad och inte alltid en kostnad — mät den på ert eget åtkomstmönster
  • En motor som svarar över HTTP betalar för varje tur och retur, så räkna era turer innan ni läser en punktläsningssiffra som en dom över dess lagring
  • CockroachDBs skrivkostnad köper serialiserbara distribuerade transaktioner över noder; på en liten nod köper den ingenting och kostar tre omgångar av tio. Sätt den inte på en låda av den här storleken
  • CouchDB är byggd kring förberäknade vyer, och ingen av de här frågorna är en. Den är sist på varje läsning den besvarade och vägrar de två mest ordinära en applikation ställer

Siffrorna

Varje siffra ovan är läst ur tabellen nedan i stället för inskriven i den här artikeln. Byt körning — flottan med 32 GB finns kvar — byt basmotor, jämför vilka två som helst.

Referensmotor
Jämförs med
Faktor från
Testslag
Scenariogrupper i fokus

Anger MongoDB mot PostgreSQL, medianvärden. Att smalna av grupperna tonar ned rader som faller utanför — ingenting tas bort från sidan.

Körning H — en maskin på 2 GB per motor, med applikationen på den också

Drop-in-jämförelse
SQLiteInbyggd relationsdatabas
6Vann
3Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

PostgreSQLRelationsdatabas
2Snabbast
7Slagen
0Lika

Referensmotor — varje faktor på sidan anges mot den. Dess egen sammanräkning ovan står mot den snabbaste av de andra motorerna i vart och ett av 9 scenarier, medianvärden.

MongoDBDokumentdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

dpdbDokumentdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

MariaDBRelationsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

MariaDB behind MaxScaleProxy framför MariaDB
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CassandraBredkolumnsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

CockroachDBDistribuerad relationsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CouchDBDokumentdatabas
0Vann
7Förlorade
0Lika

Mot PostgreSQL, i 7 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning H — en maskin på 2 GB per motor, med applikationen på den också — 250 000 genererade mätvärdesposter i en egen databas i varje motor, sådda från tomt, ingen levande data inblandad, tio oberoende gånger per motor. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderSQLitePostgresMongoDBdpdbMariaDBMaxScaleScyllaDBCassandraCockroachCouchDBFaktorRelativt
Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00010 400 rows/s7 490 rows/s12 500 rows/s10 900 rows/s23 900 rows/s10 500 rows/s6 860 rows/s2 730 rows/s11 700 rows/s9 890 rows/s11 700 rows/s10 300 rows/s3 380 rows/s2 280 rows/s4 920 rows/s2 450 rows/s1 140 rows/s462 rows/s5 930 rows/s5 200 rows/sMongoDB 1.91×
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
2009.7 ms33 ms81 ms109 ms129 ms205 ms428 ms572 ms88 ms135 ms116 ms166 ms240 ms309 ms125 ms246 ms218 ms434 ms1.21 s1.76 sPostgres 1.6×
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4943.4 ms6.3 ms8.0 ms10 ms11 ms15 ms18 ms25 ms19 ms33 ms14 ms29 ms16 ms20 ms22 ms78 ms17 ms52 ms——Postgres 1.41×
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
502.9 ms3.9 ms3.9 ms5.0 ms7.2 ms10 ms31 ms75 ms22 ms25 ms21 ms26 ms28 ms36 ms40 ms113 ms47 ms143 ms——Postgres 1.86×
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
5017 ms21 ms5.0 ms6.2 ms12 ms24 ms59 ms115 ms72 ms78 ms71 ms84 ms249 ms326 ms201 ms378 ms85 ms164 ms20.4 s21.3 sPostgres 2.32×
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00075 ms87 ms141 ms192 ms221 ms246 ms244 ms325 ms405 ms457 ms296 ms353 ms418 ms495 ms347 ms538 ms315 ms1.08 s4.70 s10.5 sPostgres 1.57×
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.0 ms2.9 ms4.0 ms5.0 ms5.8 ms7.4 ms12 ms21 ms14 ms17 ms13 ms15 ms12 ms15 ms18 ms57 ms24 ms102 ms315 ms367 msPostgres 1.48×
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 000437 ms478 ms99 ms171 ms156 ms171 ms1.42 s1.56 s1.75 s1.86 s1.75 s1.80 s1.62 s1.79 s2.79 s3.17 s1.14 s3.02 s32.4 s33.3 sPostgres 1.58×
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0002.37 s2.49 s3.02 s3.78 s6.10 s6.43 s7.64 s10.9 s6.68 s6.98 s4.80 s4.96 s3.72 s3.94 s4.60 s5.00 s4.10 s8.02 s56.4 s57.5 sPostgres 2.02×

Metod

  • En motor per maskin. En STARTER-1xCPU-2GB i se-sto1 per motor — 1 vCPU, 2 GB, 50 GB disk — skapad för sin motor, mätt, och riven så snart resultaten var av den
  • Mätprocessen kör PÅ maskinen, bredvid motorn, under hela varje runda. Det är premissen snarare än en kompromiss: ett dp-system driftsätter applikationen och dess databas på en maskin, och det är den maskinen som mäts här
  • Varje motor hölls till samma 1 GB, hur många processer den än fördelar det på — MariaDB:s InnoDB-pool, ScyllaDB:s Seastar-tilldelning, dpdb:s --ram-mb, MaxScale bredvid den MariaDB den står framför, FerretDB tillsammans med sin underliggande PostgreSQL. Taket är utjämning snarare än trimning: en motor vars förvalda cache är ett fast litet tal vore annars den enda som inte kunde nå maskinen
  • Tio fullständiga rundor per motor. Varje runda river indexuppsättningen, tömmer, sår om 250 000 rader från tomt och kör om varje scenario, så skrivkolumnen är tio oberoende fyllningar och läskolumnerna tio oberoende processer
  • Publicerade siffror slår samman RÅVÄRDENA från alla tio rundorna — 100 iterationer per läscell, 500 klumptider per skrivcell — och redovisar en median och en p95 över hela mängden. Inte ett medelvärde av tio medianer, vilket är medianen av ingenting och dras just av de avvikare en median står emot
  • 10 iterationer per runda och scenario, 2 uppvärmningar förkastade, 200 punktläsningar i följd per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Skrivningarna mättes som en fyllning från tomt med de sex paritetsindexen redan på plats, i 50 klumpar om 5 000 rader per runda
  • En runda som inte gav några mätvärden är en MISSLYCKAD runda, kontrollerad snarare än förutsatt. Varje steg i en runda kan avsluta med 0 utan att ha mätt något, och en tidigare flotta redovisade åttio sådana rundor som insamlade innan någon öppnade en resultatfil
  • Radantalen kontrollerades EXAKT före varje tidtagning. En motor som håller 250 001 rader svarar på en annan fråga än en som håller 250 000, och ingen tid den producerar skulle visa det
  • Motorversionerna hämtades ur varje servers eget gränssnitt i stället för att skrivas ned: MongoDB 8.0.29, PostgreSQL 17.11, MariaDB 11.4.12, MaxScale 23.08 readwritesplit, ScyllaDB 6.2.3, Cassandra 5.0.9, SQLite 3.53.3, dpdb byggd från 0531553347dc, CockroachDB v24.3.5, CouchDB 3.4.3
  • Ett tak på 120 sekunder per iteration — en motor som överskrider det bokförs som en tidsgräns i stället för att lämnas tom

Indexparitet

  • Varje motor: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • IndexUPPSÄTTNINGEN kommer ur en enda deklaration i kodbasen, läst av varje adapter, så ingen motor kan glida ifrån en annan
  • Närvarande i varje motor medan raderna skrevs, inte tillagda efteråt, i var och en av de tio rundorna
  • Verifierat ur varje motors egen katalog omedelbart före den första mätningen i varje runda, på varje maskin oberoende. Körningen vägrar redovisa en siffra när en motor bär ett index som paritetsuppsättningen inte deklarerar
  • SQLite indexerar ett UTTRYCK direkt — json_extract(doc, '$.field') — utan genererad kolumn och utan schemaändring. Det är den enda motorn här för vilken paritetsuppsättningen inte kostar något strukturellt
  • MariaDB kan inte indexera ett uttryck alls, så dess sex är index på GENERERADE kolumner som håller extraktionen
  • dpdb härleder indexnamn på serversidan ur de fält de täcker, så det är den enda motorn vars paritetsuppsättning inte går att matcha på NAMN. Den verifieras på FÄLT i stället — en skillnad kontrollen fick lära sig, efter att tidigare ha redovisat en motor med exakt de sex rätta indexen som helt utan

Vad körningen inte visar

  • CockroachDB fullbordade SJU av sina tio rundor och dess kolumn är sammanslagen ur de sju — 70 stickprov per läscell där varje annan kolumn har 100. Läs den som en fördelaktig uppskattning snarare än som en jämförbar siffra: de tre förlorade rundorna är de där motorn hade det svårt, så det som överlevde är dess goda beteende. Runda 7 och runda 10 dog båda med klienten rapporterande en avbruten anslutning mitt i insådden, och runda 8 hängde sig hela tretimmarstaket utan att producera något. Tio rundor försöktes på den här motorn precis som på de andra.
  • CouchDB VÄGRADE två av de åtta lässcenarierna i alla tio rundorna. Besiktningsuppslaget och första sidan kom båda tillbaka från POST /_find som en tidsgräns på serversidan — CouchDB som avböjer att köra frågan i stället för att köra den långsamt — och de två cellerna bär vägran i stället för en siffra. Dess övriga sex är tio fullständiga rundor och är direkt jämförbara.
  • FerretDB är en VÄGRAN, inte en tidsgräns och inte en lucka. Den fullbordade aldrig en insådd på den här hårdvaran: kärnans oom-killer tog den vid 783 MB med 50 100 av 250 000 rader skrivna, vid varje minnesfördelning som prövades, och att ge den vad den ville ha hade betytt att överskrida den gigabyte varje annan kolumn hålls till. Siffran som hade fått den att rymmas är skälet till att den inte finns.
  • Den här körningen och körning G svarar på OLIKA FRÅGOR och ingen ersätter den andra. Körning G mäter en databas med en maskin på 32 GB för sig själv; den här mäter en databas som delar 2 GB med applikationen. Lästa mot varandra säger de hur mycket av en motors ställning som är dess egen och hur mycket som var hårdvaran — vilket är det mest användbara någon av dem säger.
  • SQLites vinst är en vinst vid DEN HÄR formen och generaliserar inte förbi den. Varje anrop här är sekventiellt och i en process; SQLite serialiserar skrivare, har inget nätverksprotokoll att multiplexa, och den fördel av att köra i processen som gör dess punktläsning åtta gånger snabbare är samma egenskap som ger den ingenting att erbjuda en andra applikationsserver. Scenariolistan är vad den vann, inte driftsformen.
  • Varje anrop var sekventiellt. Ingenting här säger något om beteende under samtidighet, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig — och det är den axel där en maskin med 1 vCPU är minst representativ för någonting.
  • En nod var. Det här är siffror för en nod från motorer vars hela designpremiss är fler än en nod, på en maskin mindre än vad flera av dem anger som ett minimum.
  • Skrivkolumnen är en fyllning från TOMT. Bara körningar som mäter samma form kan läsas mot varandra.
  • MaxScale är en PROXY, inte en lagringsmotor, och dess kolumn är bara meningsfull läst mot MariaDB:s. De två är samma server på samma maskin och skiljer sig i porten, så varje avstånd mellan de kolumnerna är proxyn. Att läsa MaxScale mot PostgreSQL jämför en proxad motor med en direkt och svarar på ingenting.
  • Proxyn är ingen platt skatt, och det är fyndet snarare än ett förbehåll. Punktläsningen — 200 tur och retur i följd — kostar 31 % mer genom MaxScale. load_all, ett anrop som returnerar 250 000 rader, kommer tillbaka 28 % SNABBARE, och den filtrerade genomsökningen 27 % snabbare. Körning G mätte samma två riktningar på maskiner med 32 GB och en helt annan processor, vilket är ett starkare påstående än någon av körningarna gör ensam.
  • MaxScale konfigurerades med enable_root_user, eftersom den direkta körningen ansluter som root och paret måste skilja sig åt enbart i porten. Det är ett mätbeslut, inte en rekommendation för drift.
  • Djup sida och filtrerad genomsökning är fönster över en OSORTERAD mängd — ingen sortering begärs, så vilka rader som kommer tillbaka är motorns egen naturliga ordning. Varje motor returnerade samma ANTAL i båda; radmängderna skiljer sig legitimt.
  • ScyllaDB och Cassandra kan fortfarande inte betjäna COUNT(*) över 250 000 rader som ett enda anrop, så deras räkning är en sidindelad genomsökning. Det är adaptern som gör vad en produktionsimplementation måste göra.
  • Tre brister i dp:s egna adaptrar hittades av den här körningen snarare än av applikationen, och alla tre är rättelser i raderingsvägen som en mätning belastar hårdare än något annat: MariaDB matchade ingenting på ett booleskt filter, CouchDB avbröt en massradering på en rad som redan var borta, och dpdb återvände från en med en tiondel av raderna kvar. De två första är rättade; den tredje är en känd brist som flottan går runt. Ingen av dem påverkar en siffra nedan — de bröt rundor i stället för att snedvrida dem, vilket är det felläge att föredra.
  • Körning G och körningarna på den bärbara datorn före den står kvar på sidan och deras siffror är inte felaktiga. De är korrekta mätningar av andra maskiner.

Körning G — en maskin per motor, tio rundor sammanslagna

Drop-in-jämförelse
MongoDBDokumentdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

PostgreSQLRelationsdatabas
7Snabbast
2Slagen
0Lika

Referensmotor — varje faktor på sidan anges mot den. Dess egen sammanräkning ovan står mot den snabbaste av de andra motorerna i vart och ett av 9 scenarier, medianvärden.

CockroachDBDistribuerad relationsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
2Vann
7Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CassandraBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CouchDBDokumentdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

FerretDBDokumentlager ovanpå PostgreSQL
0Vann
8Förlorade
0Lika

Mot PostgreSQL, i 8 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

MariaDBRelationsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

MariaDB behind MaxScaleProxy framför MariaDB
0Vann
8Förlorade
1Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning G — en maskin per motor, tio rundor sammanslagna — 250 000 genererade mätvärdesposter i en egen databas i varje motor, sådda från tomt, ingen levande data inblandad, tio oberoende gånger per motor. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBPostgresCockroachScyllaDBCassandraCouchDBFerretDBMariaDBMaxScaleFaktorRelativt
Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00031 800 rows/s28 000 rows/s14 400 rows/s13 500 rows/s2 610 rows/s1 850 rows/s17 900 rows/s15 900 rows/s28 300 rows/s17 100 rows/s10 600 rows/s10 100 rows/s6 170 rows/s5 850 rows/s12 600 rows/s11 400 rows/s12 400 rows/s11 200 rows/sMongoDB 2.21×
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
200100 ms125 ms69 ms76 ms228 ms285 ms62 ms90 ms80 ms105 ms679 ms705 ms——60 ms78 ms72 ms89 msPostgres 1.44×
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4948.3 ms9.1 ms3.4 ms4.7 ms50 ms67 ms7.1 ms7.8 ms12 ms19 ms130 ms161 ms33.8 s35.8 s8.8 ms14 ms9.2 ms13 msPostgres 2.43×
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
503.3 ms3.6 ms2.1 ms2.4 ms60 ms82 ms13 ms17 ms21 ms27 ms243 ms1.57 s40.2 s42.2 s13 ms18 ms13 ms14 msPostgres 1.52×
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
5011 ms11 ms4.2 ms4.3 ms29 ms37 ms269 ms287 ms138 ms148 ms3.46 s3.64 s4.23 s4.35 s74 ms75 ms73 ms74 msPostgres 2.6×
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 000151 ms163 ms51 ms61 ms101 ms123 ms393 ms404 ms262 ms271 ms1.34 s7.47 s3.60 s3.69 s156 ms165 ms146 ms154 msPostgres 2.94×
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1654.4 ms4.9 ms2.1 ms3.4 ms49 ms65 ms6.3 ms6.7 ms11 ms11 ms115 ms124 ms33.7 s34.6 s9.9 ms10 ms10 ms11 msPostgres 2.1×
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 000157 ms161 ms38 ms39 ms524 ms576 ms2.76 s2.84 s2.26 s2.36 s14.7 s15.2 s30.3 s31.8 s1.81 s1.82 s1.81 s1.82 sPostgres 4.12×
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0003.46 s3.52 s1.11 s1.18 s1.49 s1.55 s5.26 s5.38 s3.32 s3.39 s19.3 s19.9 s55.7 s57.3 s3.81 s3.87 s2.71 s2.81 sPostgres 3.12×

Metod

  • En motor per maskin. Sju m5d.2xlarge i eu-north-1a, 8 vCPU, 32 GB och en lokal NVMe var, en motor installerad per maskin och ingenting annat på den
  • Ingen minnesgräns, ingen processorgräns, ingen granne. Varje motor hade hela maskinen, vilket är den miljö en databas faktiskt driftsätts i och motsatsen till körning F
  • Tio fullständiga rundor per motor. Varje runda river motorn, sår om 250 000 rader och kör om varje scenario, så skrivkolumnen är tio oberoende fyllningar och läskolumnerna tio oberoende processer
  • Ingen rotation av motorerna, eftersom det inte finns någon ordning att rotera. Körning F roterade för att upphäva effekten av att sju motorer delade en disk; en maskin per motor tar bort effekten i stället för att korrigera för den
  • Publicerade siffror slår samman råvärdena från alla tio rundorna, 100 iterationer per läscell och 500 klumptider per skrivcell, och redovisar en median och en p95 över hela mängden
  • Ett scenario som gick över tidsgränsen bidrar med inga stickprov och står obesvarat. FerretDB:s punktläsning gick över tidsgränsen i alla tio rundorna och publiceras som en tidsgräns, inte som en lucka
  • 10 iterationer per runda och scenario, 2 uppvärmningar förkastade, 200 punktläsningar i följd per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Radernas IDENTITET jämfördes MELLAN MASKINER. Varje maskin sammanfattade _id-mängden för varje scenario den körde och skrev den till disk; de sju sammanfattningsfilerna jämfördes efteråt och krävdes stämma överens. Motorer som aldrig samexisterar måste ändå returnera samma rader
  • Indexpariteten kontrollerades vid mättillfället på varje maskin och i varje runda, och körningen vägrar redovisa en siffra när en motor bär ett index som paritetsuppsättningen inte deklarerar
  • Skrivningarna mättes som en fyllning från tomt med de sex paritetsindexen redan på plats, i 50 klumpar om 5 000 rader per runda
  • Varje maskin kalibrerades före och efter sina rundor — ett sha256-pass på en kärna och en disksond med direkt-IO — så att en långsam maskin syns som en långsam maskin i stället för som en långsam motor
  • Motorversionerna hämtades ur varje servers eget gränssnitt: MongoDB 8.0.28, PostgreSQL 17.10, CockroachDB v24.3.5, ScyllaDB 6.2.3, Cassandra 5.0.8, CouchDB 3.4.3, FerretDB v1.24.0, MariaDB 11.4.12, MaxScale 23.08.13
  • MariaDB och MaxScale kördes på en NIONDE maskin av samma typ, efter varandra i stället för samtidigt, så att var och en ändå hade maskinen för sig själv medan den mättes. Samma verktyg, samma tio rundor, samma 250 000 rader, samma paritetsuppsättning
  • Proxyparet skiljer sig åt i PORTEN och ingenting annat — samma server, samma adapter, samma inloggning, samma datamängd. 3306 direkt, 4006 genom MaxScales readwritesplit-lyssnare
  • Ett tak på 120 sekunder per iteration — en motor som överskrider det bokförs som en tidsgräns i stället för att lämnas tom

Indexparitet

  • Alla nio motorerna: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • IndexUPPSÄTTNINGEN kommer ur en enda deklaration i kodbasen, läst av varje adapter, så ingen motor kan glida ifrån en annan
  • Närvarande i varje motor medan raderna skrevs, inte tillagda efteråt, i var och en av de tio rundorna
  • Verifierat ur varje motors egen katalog omedelbart före den första mätningen i varje runda, på var och en av de sju maskinerna oberoende
  • FerretDB:s sex paritetsindex skapades, bekräftades och konstaterades finnas som b-träd i dess egen underliggande PostgreSQL — och användes aldrig en enda gång. Dess punktläsning är en fullständig genomsökning av samlingen per uppslag, och det är därför den går över tidsgränsen
  • MariaDB kan inte indexera ett uttryck alls, så dess sex är index på GENERERADE kolumner som håller extraktionen. Det gör frågans formulering avgörande snarare än kosmetisk: EXPLAIN på den sådda tabellen ger type=ref, key=dpx__besiktning_id, 4 rader när kolumnen refereras, och type=ALL utan nyckel när samma filter skrivs som den inbäddade extraktionen. MariaDB skriver inte om det ena till det andra så som MySQL 8 gör

Vad körningen inte visar

  • Maskinerna var inte identiska. Turboklockan låg mellan 3105 och 3215 MHz och sha256-kalibreringen på en kärna mellan 1310 och 1365 ms, en spridning på 4,2 %; diskarna gick inte att skilja åt vid 138–140 MB/s skrivning och 131 MB/s läsning. Per maskin: FerretDB 1310, CouchDB 1314, Cassandra 1315, ScyllaDB 1333, MongoDB 1353, PostgreSQL 1353, MariaDB 1353, CockroachDB 1365.
  • Den knappaste placeringen på sidan är MariaDB:s punktläsning på 60,4 ms mot ScyllaDB:s 62,2, en marginal på 3,0 %. Den överlever korrigering och mer därtill: MariaDB:s maskin kalibrerar 1,5 % LÅNGSAMMARE, så en normalisering flyttar den till 59,5 ms och vidgar avståndet till 4,5 %. Det är ändå den enda siffran här där spridningen mellan rundorna är värd att läsa bredvid medianen — MariaDB låg mellan 55,1 och 72,7 ms över sina tio rundor mot ScyllaDB:s 59,3 till 64,6, och vann åtta av tio och förlorade två rejält.
  • Varje motor kördes i Docker med en publicerad port, så varje tur och retur korsade ett veth-par och en DNAT i kärnan — ungefär 20–50 mikrosekunder. Värdnätverk hade tagit bort det gratis och användes inte. Det är en konstant per scenario, identisk för varje motor, så det pressar ihop avstånden något och kan inte kasta om någon ordning; punktläsningen med sina 200 anrop bär mest av det.
  • Containrar snarare än installationer direkt på värden, med avsikt. Sju installationer på värden betyder sju olika konfigurationsytor — bara ScyllaDB:s uppsättningsskript ställer om IO-schemaläggaren och hugepages — och leverantörernas avbildningar är det närmaste lika behandling som finns.
  • Varje anrop var sekventiellt. Ingenting här säger något om beteende under samtidighet, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig.
  • En nod var. Det här är siffror för en nod från motorer vars hela designpremiss är fler än en nod.
  • Skrivkolumnen är en fyllning från TOMT. Bara körningar som mäter samma form kan läsas mot varandra.
  • Djup sida och filtrerad genomsökning är fönster över en OSORTERAD mängd — ingen sortering begärs, så vilka rader som kommer tillbaka är motorns egen naturliga ordning. Alla sju returnerade samma ANTAL i båda; radmängderna skiljer sig legitimt, och verifieringen mellan maskinerna namnger dem i stället för att dölja dem.
  • ScyllaDB och Cassandra kunde fortfarande inte betjäna COUNT(*) över 250 000 rader som ett enda anrop, så deras räkning är en sidindelad genomsökning. Det är adaptern som gör vad en produktionsimplementation måste göra.
  • CouchDB blev snabbare nästan överallt när taket togs bort — dess punktläsning gick från 2 970 ms till 679 ms — och tre gånger LÅNGSAMMARE på den djupa sidan, 1 090 ms till 3 460 ms. Att få mer minne är inte enbart goda nyheter för den.
  • MaxScale är en PROXY, inte en lagringsmotor, och dess kolumn är bara meningsfull läst mot MariaDB:s. De två är samma server på samma maskin och skiljer sig i porten — så varje avstånd mellan de kolumnerna är proxyn. Att läsa MaxScale mot PostgreSQL eller MongoDB jämför en proxad motor med en direkt och svarar på ingenting.
  • Proxyn är ingen platt skatt, och det är fyndet snarare än ett förbehåll. Punktläsningen — 200 tur och retur i följd — kostar 18,7 % mer genom MaxScale. load_all — ett anrop som returnerar 250 000 rader — kommer tillbaka 29 % SNABBARE, 3 811 ms direkt mot 2 706 ms proxat, och de tio rundorna på var sida överlappar inte alls. Den troligaste förklaringen är att proxyn kopplar isär serverns läsning från klientens tolkning så att de två överlappar, men det är en hypotes om en mekanism vi inte instrumenterade, och mätningen står utan den.
  • MaxScale konfigurerades med enable_root_user, eftersom den direkta körningen ansluter som root och paret måste skilja sig åt enbart i porten. Det är ett mätbeslut, inte en rekommendation för drift.
  • MariaDB:s buffertpool sattes till 24G i stället för att lämnas på sin förvalda 128 MB. InnoDB öppnar sina datafiler med O_DIRECT — bekräftat på maskinen — så operativsystemets sidcache backar den inte, och förvalet hade i praktiken varit hela dess cache medan varje annan motor här når större delen av maskinen. Det är utjämning för att matcha de andra, inte trimning förbi dem: ingen dimensionering av redo-loggen, ingen trimning av utskrivningen, ingen trådpool.
  • Körning F står kvar på sidan och dess siffror är inte felaktiga. De är korrekta mätningar av sju motorer som delade en bärbar dator, en av dem med sina data inuti en annan, tagna fem gånger.

Körning F — en indexuppsättning, inga extraindex från adaptern, fem rundor sammanslagna

Drop-in-jämförelse
MongoDBDokumentdatabas
3Vann
6Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

PostgreSQLRelationsdatabas
6Snabbast
3Slagen
0Lika

Referensmotor — varje faktor på sidan anges mot den. Dess egen sammanräkning ovan står mot den snabbaste av de andra motorerna i vart och ett av 9 scenarier, medianvärden.

CockroachDBDistribuerad relationsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CassandraBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CouchDBDokumentdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

FerretDBDokumentlager ovanpå PostgreSQL
0Vann
8Förlorade
0Lika

Mot PostgreSQL, i 8 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning F — en indexuppsättning, inga extraindex från adaptern, fem rundor sammanslagna — 250 000 genererade mätvärdesposter i en egen databas i varje motor, sådda från tomt, ingen levande data inblandad, fem oberoende gånger. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBPostgresCockroachScyllaDBCassandraCouchDBFerretDBFaktorRelativt
Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00046 900 rows/s27 700 rows/s19 300 rows/s12 000 rows/s3 720 rows/s2 430 rows/s12 000 rows/s3 900 rows/s42 200 rows/s13 300 rows/s9 430 rows/s1 820 rows/s9 480 rows/s6 620 rows/sMongoDB 2.43×
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20071 ms104 ms105 ms150 ms346 ms528 ms118 ms147 ms162 ms292 ms2.97 s3.04 s——MongoDB 1.49×
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4945.0 ms6.2 ms2.1 ms5.8 ms40 ms82 ms5.7 ms11 ms21 ms146 ms261 ms308 ms18.9 s24.8 sPostgres 2.38×
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
501.6 ms2.2 ms1.3 ms2.1 ms37 ms78 ms9.2 ms18 ms30 ms112 ms523 ms647 ms22.7 s28.6 sPostgres 1.28×
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
505.2 ms6.6 ms46 ms76 ms43 ms176 ms233 ms350 ms91 ms402 ms1.09 s1.21 s2.67 s4.12 sMongoDB 8.83×
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00088 ms127 ms48 ms119 ms102 ms283 ms448 ms837 ms144 ms318 ms2.20 s3.35 s2.39 s3.30 sPostgres 1.85×
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.9 ms4.3 ms1.5 ms8.8 ms35 ms76 ms4.7 ms21 ms9.3 ms45 ms252 ms297 ms18.5 s24.9 sPostgres 1.91×
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00074 ms100 ms28 ms95 ms331 ms1.40 s3.16 s4.60 s1.14 s2.73 s28.8 s31.8 s12.8 s15.7 sPostgres 2.64×
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0001.77 s2.24 s554 ms1.47 s748 ms1.06 s5.52 s10.0 s1.70 s3.60 s31.0 s36.3 s28.8 s33.8 sPostgres 3.19×

Metod

  • Fem fullständiga rundor. Varje runda river alla sju motorerna, sår om 250 000 rader i var och en och kör om varje scenario, så skrivkolumnen är fem oberoende fyllningar och läskolumnerna fem oberoende processer snarare än fem stickprov ur en
  • Motorernas ORDNING roterar ett steg per runda. En fast ordning parar varje motor med samma maskintillstånd varje gång — först på en vilande maskin, sist efter att sex motorer bearbetat en och samma disk — och den glidningen går annars inte att skilja från motorn
  • Publicerade siffror slår samman råvärdena från alla fem rundorna, 50 iterationer per läscell och 250 klumptider per skrivcell, och redovisar en median och en p95 över hela mängden
  • Ett scenario som gick över tidsgränsen bidrar med inga stickprov och står obesvarat. En motor som misslyckas med fyra rundor och klarar den femte har inte förtjänat en median
  • 10 iterationer per runda och scenario, 2 uppvärmningar förkastade, 200 punktläsningar i följd per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Mätningen skriver in i sin egen databas, fb_bench, så att alla sju motorerna håller exakt 250 000 rader och ingen bär produktionsdata
  • Radernas IDENTITET jämfördes innan någon tid togs: varje scenario spelades om i varje motor, _id-mängden sammanfattades, och sammanfattningarna krävdes stämma överens — inte bara antalen
  • Indexpariteten kontrollerades vid mättillfället i varje motor och varje runda, och körningen vägrar redovisa en siffra när en motor bär ett index som paritetsuppsättningen inte deklarerar
  • Den mätande processen utfärdar ingen DDL alls, så ingenting den gör kan ändra den indexuppsättning den mäter mot
  • Skrivningarna mättes som en fyllning från tomt med de sex paritetsindexen redan på plats, i 50 klumpar om 5 000 rader per runda
  • Motorversionerna hämtades ur varje servers eget gränssnitt i stället för att skrivas ned för hand
  • Ett tak på 120 sekunder per iteration — en motor som överskrider det bokförs som en tidsgräns i stället för att lämnas tom

Indexparitet

  • Alla sju motorerna: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • IndexUPPSÄTTNINGEN kommer ur en enda deklaration i kodbasen, läst av varje adapter, så ingen motor kan glida ifrån en annan
  • Närvarande i varje motor medan raderna skrevs, inte tillagda efteråt, i var och en av de fem rundorna
  • Ingen motor bär ett index som en annan saknar. Varje motor gick in i varje scenario med sin primärnyckel och de sex, verifierat ur dess egen katalog omedelbart före den första mätningen i varje runda
  • Det här är rättelsen av körning E. PostgreSQL och CockroachDB bar fem adapterindex där, ett inverterat GIN över hela dokumentet bland dem, och skrivkolumnen mätte det
  • FerretDB:s sex paritetsindex skapades, bekräftades och konstaterades finnas som b-träd i dess underliggande PostgreSQL — och användes aldrig en enda gång

Vad körningen inte visar

  • Skrivkolumnen är en fyllning från TOMT, som körning C och körning E snarare än körning D:s tillägg till en befolkad samling. Bara körningar som mäter samma form kan läsas mot varandra.
  • Varje anrop var sekventiellt. Ingenting här säger något om beteende under samtidighet, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig.
  • En nod var, utvecklarinställningar, alla sex containrarna på en och samma maskin vid sidan av en MongoDB installerad direkt på värden. Ingen motor fick en egen värd.
  • MongoDB kör direkt på värden över loopback medan de andra sex korsar en containergräns under WSL2. Det gynnar MongoDB och korrigeras inte för.
  • CockroachDB är med bred marginal den minst upprepbara motorn här. Dess exakta räkning låg mellan 175 ms och 710 ms över de fem rundorna och dess punktläsning mellan 248 ms och 505 ms, utan samband med dess plats i ordningen. Dess median är en median över en genuint bred fördelning, och det är p95 som är siffran att läsa.
  • Cassandras svans är den andra värd att läsa. Dess besiktningsuppslag medianar 21,4 ms och p95:ar 146 ms, och dess första sida medianar 30 ms och p95:ar 112 ms — ungefär en femtedel av anropen kostar flera gånger det typiska.
  • Djup sida och filtrerad genomsökning är fönster över en OSORTERAD mängd — ingen sortering begärs, så vilka 50 rader som är sida 200 och vilka 10 000 rader taket returnerar är motorns egen naturliga ordning. Varje motor returnerade samma ANTAL i båda; radmängderna skiljer sig legitimt, och verifieringssteget namnger dem i stället för att dölja dem.
  • ScyllaDB och Cassandra kunde inte betjäna COUNT(*) över 250 000 rader som ett enda anrop — Cassandra vägrade med READ_FAILURE och ScyllaDB med READ_TIMEOUT — så deras räkning är en sidindelad genomsökning. Det är adaptern som gör vad en produktionsimplementation måste göra, och det är därför deras räknekolumn står i sekunder i stället för millisekunder.
  • Körning E:s siffror står kvar på sidan. De är inte felaktiga mätningar; de är korrekta mätningar av en uppsättning där två motorer bar fem index som de andra fem saknade, tagna en gång.

Körning E — sju motorer, lika villkor, egen databas

Drop-in-jämförelse
MongoDBDokumentdatabas
3Vann
6Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

PostgreSQLRelationsdatabas
6Snabbast
3Slagen
0Lika

Referensmotor — varje faktor på sidan anges mot den. Dess egen sammanräkning ovan står mot den snabbaste av de andra motorerna i vart och ett av 9 scenarier, medianvärden.

CockroachDBDistribuerad relationsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CassandraBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CouchDBDokumentdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

FerretDBDokumentlager ovanpå PostgreSQL
0Vann
8Förlorade
0Lika

Mot PostgreSQL, i 8 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning E — sju motorer, lika villkor, egen databas — 250 000 genererade mätvärdesposter i en egen databas i varje motor, sådda från tomt, ingen levande data inblandad. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBPostgresCockroachScyllaDBCassandraCouchDBFerretDBFaktorRelativt
Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00062 500 rows/s43 100 rows/s20 200 rows/s15 200 rows/s285 rows/s226 rows/s25 300 rows/s16 600 rows/s48 500 rows/s21 200 rows/s10 900 rows/s8 980 rows/s11 400 rows/s8 870 rows/sMongoDB 3.09×
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20050 ms68 ms105 ms109 ms242 ms283 ms137 ms147 ms165 ms179 ms3.00 s3.05 s——row counts differ
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4943.5 ms4.0 ms2.0 ms2.1 ms11 ms12 ms6.9 ms7.6 ms21 ms34 ms249 ms254 ms16.0 s16.4 sPostgres 1.76×
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
501.5 ms2.0 ms1.1 ms1.4 ms10 ms12 ms13 ms15 ms48 ms63 ms250 ms254 ms19.4 s20.1 sPostgres 1.31×
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
505.4 ms6.1 ms42 ms44 ms20 ms26 ms7.58 s10.6 s1.51 s1.63 s29.4 s29.6 s2.49 s2.59 sMongoDB 7.72×
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00067 ms111 ms36 ms45 ms42 ms140 ms5.09 s5.52 s1.44 s1.51 s26.6 s26.8 s2.25 s2.34 sPostgres 1.87×
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.3 ms2.9 ms1.3 ms1.5 ms9.8 ms20 ms3.8 ms4.7 ms7.7 ms8.0 ms240 ms252 ms15.4 s16.1 sPostgres 1.7×
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00071 ms74 ms24 ms26 ms195 ms1.28 s5.07 s5.16 s1.44 s1.49 s29.3 s29.4 s11.4 s11.5 sPostgres 2.95×
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0001.89 s2.01 s488 ms508 ms533 ms567 ms5.10 s5.18 s1.45 s1.48 s29.4 s29.7 s24.9 s25.7 sPostgres 3.87×

Metod

  • Median av 10 iterationer, 2 uppvärmningar förkastade, p95 redovisad bredvid
  • 200 punktläsningar i följd per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Mätningen skriver in i sin egen databas, fb_bench, så att alla sju motorerna håller exakt 250 000 rader och ingen bär produktionsdata
  • Radantalen jämfördes mellan alla sju motorerna i varje scenario innan någon tid redovisades
  • Skrivningarna mättes som en fyllning från tomt med de sex paritetsindexen redan på plats, i 50 klumpar om 5 000 rader — så skrivkolumnen har en median och en p95 som varje annan rad, inte ett enda genomsnitt av väggklocka
  • Motorversionerna hämtades ur varje servers eget gränssnitt i stället för att skrivas ned för hand
  • Ett tak på 120 sekunder per iteration — en motor som överskrider det bokförs som en tidsgräns i stället för att lämnas tom

Indexparitet

  • Alla sju motorerna: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • IndexUPPSÄTTNINGEN kommer ur en enda deklaration i kodbasen, läst av varje adapter, så ingen motor kan glida ifrån en annan
  • Närvarande i varje motor medan raderna skrevs, inte tillagda efteråt
  • PostgreSQL och CockroachDB bär dessutom fem index skapade av adaptern, GIN inräknat — verifierat med EXPLAIN som oanvändbara för varje scenario här, så de är en skrivkostnad och ingen läsfördel
  • FerretDB:s sex paritetsindex skapades, bekräftades och konstaterades finnas som b-träd i dess underliggande PostgreSQL — och användes aldrig en enda gång

Vad körningen inte visar

  • Skrivkolumnen är en fyllning från TOMT. Körning D mätte ett tillägg till en befolkad samling och körning C mätte en fyllning. Bara körningar som mäter samma form kan läsas mot varandra, och den här matchar körning C:s form snarare än körning D:s.
  • Varje anrop var sekventiellt. Ingenting här säger något om beteende under samtidighet, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig.
  • En nod var, utvecklarinställningar, alla sex containrarna på en och samma maskin vid sidan av en MongoDB installerad direkt på värden. Ingen motor fick en egen värd.
  • MongoDB kör direkt på värden över loopback medan de andra sex korsar en containergräns under WSL2. Det gynnar MongoDB och korrigeras inte för.
  • ScyllaDB:s COUNT(*) överskred serverns egen lästidsgräns (kod 4608) på den här datamängden, så dess räkning är en sidindelad genomsökning i stället för en aggregering på serversidan. Cassandras gjorde samma sak i körning D och gör det här.
  • CockroachDB:s count() returnerade en sträng i stället för ett tal, eftersom dess ::int är INT8 och node-postgres lämnar tillbaka int8 som text. Radantalen stämde; typerna gjorde det inte. Rättat i adaptern efter körningen — siffrorna nedan mättes med bristen på plats, vilket inte kostade något eftersom bara paritetskontrollen läste värdet.

Körning D — sju motorer, strikt indexparitet

Drop-in-jämförelse
MongoDBDokumentdatabas
2Vann
7Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

PostgreSQLRelationsdatabas
6Snabbast
2Slagen
1Lika

Referensmotor — varje faktor på sidan anges mot den. Dess egen sammanräkning ovan står mot den snabbaste av de andra motorerna i vart och ett av 9 scenarier, medianvärden.

CockroachDBDistribuerad relationsdatabas
0Vann
8Förlorade
1Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CassandraBredkolumnsdatabas
1Vann
8Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Apache CouchDBDokumentdatabas
0Vann
9Förlorade
0Lika

Mot PostgreSQL, i 9 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

FerretDBDokumentlager ovanpå PostgreSQL
0Vann
8Förlorade
0Lika

Mot PostgreSQL, i 8 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning D — sju motorer, strikt indexparitet — 250 000 mätvärdesposter från en sådd generator, så att varje motor håller byteidentiska rader. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBPostgresCockroachScyllaDBCassandraCouchDBFerretDBFaktorRelativt
Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00044 238 rows/s—11 155 rows/s—536 rows/s—13 305 rows/s—31 328 rows/s—10 001 rows/s—10 399 rows/s—MongoDB 3.97×
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20062 ms75 ms98 ms106 ms222 ms270 ms103 ms112 ms150 ms171 ms3.06 s3.08 s——MongoDB 1.59×
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4943.8 ms4.2 ms1.9 ms2.5 ms11 ms12 ms4.6 ms5.3 ms8.9 ms24 ms261 ms266 ms16.7 s18.8 sPostgres 2.01×
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
501.6 ms1.7 ms1.1 ms1.3 ms9.5 ms11 ms9.7 ms11 ms16 ms17 ms251 ms263 ms20.5 s20.7 sPostgres 1.45×
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
505.5 ms6.1 ms4.6 ms5.1 ms11 ms15 ms5.15 s5.20 s1.57 s1.66 s30.0 s30.7 s2.50 s2.56 sPostgres 1.18×
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00072 ms104 ms35 ms41 ms39 ms55 ms5.09 s5.13 s1.49 s1.54 s27.7 s28.5 s2.29 s2.32 sPostgres 2.05×
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.5 ms2.9 ms1.4 ms1.4 ms10 ms13 ms3.9 ms4.4 ms7.5 ms8.2 ms252 ms261 ms16.8 s17.3 sPostgres 1.76×
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00072 ms73 ms26 ms28 ms169 ms214 ms5.11 s5.15 s1.49 s1.53 s29.8 s34.0 s11.6 s11.9 srow counts differ
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0001.55 s1.64 s522 ms653 ms531 ms688 ms5.12 s5.21 s1.50 s1.54 s29.7 s29.9 s25.8 s26.2 srow counts differ

Metod

  • Median av 10 iterationer, 2 uppvärmningar förkastade, p95 redovisad bredvid
  • 200 punktläsningar i följd per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Radantalen jämfördes mellan alla sju motorerna innan någon tid redovisades
  • Läsningarna mättes vid 250 000 rader; skrivningarna mättes efteråt genom att lägga ytterligare 250 000 ovanpå, så varje motor skriver in i en befolkad och indexerad samling
  • Motorversionerna hämtades ur varje servers eget gränssnitt i stället för att skrivas ned för hand
  • Ett tak på 120 sekunder per iteration — en motor som överskrider det bokförs som en tidsgräns i stället för att lämnas tom

Indexparitet

  • Alla sju motorerna: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • IndexUPPSÄTTNINGEN kommer ur en enda deklaration i kodbasen, läst av varje adapter, så ingen motor kan glida ifrån en annan
  • Närvarande i varje motor medan raderna skrevs, inte tillagda efteråt
  • godkand och varde lämnades oindexerade med avsikt — men se förbehållet: PostgreSQL och CockroachDB bär ytterligare index skapade av adaptern, däribland ett GIN-index över hela dokumentet

Vad körningen inte visar

  • PostgreSQL och CockroachDB kör INTE oindexerat i genomsökningsscenarierna. dp-adaptern skapar idx_fb_matning_doc_gin — ett GIN-index över hela JSONB-dokumentet — vid sidan av de sex paritetsindexen, så deras filtrerade genomsökning och intervallsökning är indexstödda där de andra fem motorerna genomsöker.
  • Skrivkolumnen är ett TILLÄGG till en samling som redan håller 250 000 rader. Körning C mätte en fyllning av en tom. De två skrivjämförelserna mäter olika arbete och får inte läsas mot varandra.
  • MongoDB:s radantal är 250 007, inte 250 000 — den håller också sju verkliga mätvärdesrader från Franska Bukten. Skillnaden på sju rader lämnas kvar i stället för att raderas ur en levande samling.
  • Varje anrop var sekventiellt. Ingenting här säger något om beteende under samtidighet, vilket är där ScyllaDB, Cassandra och CockroachDB är byggda för att löna sig.
  • En nod var, utvecklarinställningar, alla sju containrarna på en och samma maskin. Tomgångsbelastningen låg under 1,5 % per container under körningen, men ingen motor fick en egen värd.
  • FerretDB:s indexerade uppslag blev långsammare än dess fullständiga genomsökningar, vilket tyder på att paritetsindexen togs emot och sedan inte användes. Det är oförklarat och redovisas som mätt snarare än tolkat.

Körning C — strikt indexparitet, indexen närvarande under insättningen

Drop-in-jämförelse
MongoDBDokumentdatabas
0Vann
0Förlorade
0Lika

Mot PostgreSQL, i 0 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

ScyllaDBBredkolumnsdatabas
0Vann
0Förlorade
0Lika

Mot PostgreSQL, i 0 scenarier, medianvärden. Ingen samlad faktor anges: spridningen är resultatet.

Körning C — strikt indexparitet, indexen närvarande under insättningen — 250 006 mätvärdesposter, genererade från en sådd generator så att båda motorerna håller identiska rader. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBScyllaDBFaktorRelativt
Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00011 028 rows/s—17 147 rows/s——
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20068 ms83 ms115 ms122 ms—
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4944.5 ms6.3 ms6.1 ms6.9 ms—
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
501.9 ms2.5 ms12 ms13 ms—
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
506.0 ms6.5 ms6.74 s9.63 s—
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00080 ms147 ms6.23 s6.56 s—
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.5 ms3.7 ms4.9 ms5.7 ms—
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00076 ms78 ms6.34 s7.93 s—
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0001.95 s2.38 s6.43 s7.15 s—

Metod

  • Median av 10 iterationer, 2 uppvärmningar förkastade, p95 redovisad bredvid
  • 200 punktläsningar per iteration
  • Varje LÄSNING går genom applikationens datatjänst, frågehanterare, adapterladdare och datalager — aldrig genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna
  • Radantalen jämfördes mellan motorerna innan någon tid redovisades, och de stämde i varje scenario
  • Hela produktionsdatabasen migrerades först: 41 samlingar, noll avvikelser i radantal

Indexparitet

  • Båda motorerna: sekundärindex på _type, _besiktning_id, _system_id, _ventil_id, _room_id och _anmarkning_id
  • Verifierat genom att läsa varje motors egen indexkatalog, inte genom att lita på uppsättningsskriptet
  • Närvarande i båda motorerna medan de 250 000 raderna skrevs
  • godkand och varde lämnades oindexerade på båda sidor med avsikt, så att genomsökningsscenarierna är genuint oindexerat arbete för alla

Vad körningen inte visar

  • Att MongoDB gör nyckeluppslag snabbare i allmänhet — punktläsningen var sekventiell, och ScyllaDB korsade en nätverksgräns som MongoDB inte gjorde.
  • Någonting om samtidighet. Varje anrop i den här körningen var sekventiellt; ScyllaDB:s arkitektur är byggd för att löna sig under last, och last lades inte på.
  • Någonting om beteende över flera noder, replikering eller redundans. En nod, utvecklarläge.
  • Hur bra någon av motorerna skulle kunna prestera efter en omarbetning kring den — det är ett annat test, och det är inte det här.

Körning B — paritetsindexen skapade efter insådden

Drop-in-jämförelse
Ogiltig körning, publicerad ändå.De sex MongoDB-indexen skapades efter att raderna skrivits, så det finns ingen skrivjämförelse för den här körningen, och läsningarna betjänades av index byggda på färdig data i stället för underhållna genom insättningen. Läsningarna ligger nära den giltiga körningen; metoden håller ändå inte.
Körning B — paritetsindexen skapade efter insådden — 250 006 mätvärdesposter. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 8 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBScyllaDBFaktorRelativt
Medianp95Medianp95
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20056 ms66 ms116 ms119 ms—
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
4944.5 ms5.2 ms4.9 ms6.8 ms—
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
501.6 ms2.2 ms9.7 ms21 ms—
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
505.1 ms6.0 ms5.51 s5.62 s—
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00081 ms89 ms5.44 s5.54 s—
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
1652.8 ms8.4 ms4.6 ms6.3 ms—
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00066 ms68 ms5.45 s5.47 s—
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0001.99 s2.50 s5.48 s5.56 s—

Metod

  • Median av 10 iterationer, 2 uppvärmningar förkastade
  • 200 punktläsningar per iteration
  • MongoDB såddes först och indexerades sedan — det är bristen
  • Ingen skrivmätning togs, eftersom en giltig sådan inte var möjlig

Indexparitet

  • MongoDB: sex sekundärindex, skapade efter insådden
  • ScyllaDB: samma sex, skapade med tabellen och närvarande under insättningen
  • Paritet i fält, men inte i livscykel

Vad körningen inte visar

  • Varje påstående om skrivgenomströmning. Det finns inget i den här körningen.
  • Att ett index skapat efter en massinläggning beter sig likadant som ett som underhållits genom den.

Körning A — ingen indexparitet

Drop-in-jämförelse
Ogiltig körning, publicerad ändå.MongoDB bar bara sitt standardindex på identifieraren medan ScyllaDB bar sex sekundärindex. De tre ScyllaDB-vinsterna nedan är artefakter av den skillnaden, inte egenskaper hos någon av motorerna. Tiderna är verkliga; de svarar på fel fråga.
Körning A — ingen indexparitet — 250 006 mätvärdesposter. Faktorerna räknas ur de medianvärden som står i tabellen, och anger MongoDB mot Postgres. Alla 9 scenarier är listade; gruppfiltret tonar ned rader, det tar inte bort dem.
ScenarioRaderMongoDBScyllaDBFaktorRelativt
Medianp95Medianp95
Massinläggning, 250 000 posterSkrivgenomströmning med sex sekundärindex redan på plats.
Skrivningar
250 00027 105 rows/s—21 077 rows/s——
Punktläsning på identifierare, 200 anrop200 uppslag på primärnyckel i följd, ett tur och retur vardera.
Nyckelåtkomst
20051 ms61 ms114 ms124 ms—
Alla mätvärden för en besiktningVarje underordnad post till en förälder, på en indexerad främmande nyckel.
Nyckelåtkomst
49488 ms93 ms5.2 ms6.5 ms—
Första sorterade sidan, 50 raderSida ett av en besiktnings mätvärden, sorterade på mätdatum med det senaste först.
Sidindelning
5086 ms88 ms9.4 ms18 ms—
Djup sida, sida 200Hoppa över 9 950 rader och returnera de 50 följande. Det en numrerad bläddrare ber om.
Sidindelning
505.9 ms6.6 ms6.12 s20.8 s—
Filtrerad genomsökning på ett oindexerat booleskt fältVarje post där en godkännandeflagga är falsk. Indexerat på ingen av dem.
Genomsökningar och filter
10 00085 ms93 ms5.76 s7.36 s—
Intervallsökning på ett oindexerat talEn indexerad likhet plus ett större-än på ett oindexerat mätvärde.
Genomsökningar och filter
16594 ms103 ms4.8 ms8.1 ms—
Exakt räkning av hela samlingenHur många poster finns det. Inte en uppskattning.
Arbete över hela samlingen
250 00075 ms78 ms5.61 s6.01 s—
Läs in hela samlingenVarje post, deserialiserad in i applikationen.
Arbete över hela samlingen
250 0002.30 s2.85 s5.63 s5.94 s—

Metod

  • Median av 10 iterationer, 2 uppvärmningar förkastade
  • 200 punktläsningar per iteration
  • Varje LÄSNING går genom applikationens datatjänst, inte genom drivrutinen
  • Skrivkolumnen går in ett steg lägre. Såmaskinen anropar motoradapterns create_many direkt, så massinläggningen är adapter plus motor utan datatjänsten ovanför — dp-kod i båda fallen och samma djup i varje motor, men inte samma djup som läskolumnerna

Indexparitet

  • MongoDB: enbart standardindexet på identifieraren
  • ScyllaDB: sex sekundärindex, skapade automatiskt av adaptern
  • Ingen paritet — det är just den bristen körningen finns för att visa

Vad körningen inte visar

  • Ingenting. Ingen jämförelse i den här tabellen är giltig.
  • Att lägga till de sex motsvarande MongoDB-indexen flyttade besiktningsuppslaget från 88 ms till 4,5 ms och den sorterade första sidan från 86 ms till 1,6 ms.
  • Alla tre ScyllaDB-vinster försvann så snart indexuppsättningarna stämde överens.

Motorerna

MongoDB

Dokumentdatabas
Version
8.2.3
Drivrutin
mongodb 7.2.0

Byggd för

  • Flexibla dokumentstrukturer
  • Allmänna sekundärindex
  • Fri filtrering och sortering
  • Intervallfrågor och aggregering
  • Frågemönster som ändras efter lansering

Så driftsattes den

  • Docker-container från avbildningen mongo:8.0, ensam på en m5d.2xlarge — 8 vCPU, 32 GB, lokal NVMe
  • Ingen minnesgräns, ingen processorgräns, ingen granne, ingenting annat installerat på maskinen
  • Genom körning F var den den ENDA motorn som körde direkt på värden över loopback utan tak medan sex andra delade samma bärbara dator i containrar. Det var den vänligaste driftsformen i jämförelsen och den är borta: den vann två lässcenarier där och inga här

ScyllaDB

Bredkolumnsdatabas
Version
6.2.3-0.20250119.bff9ddde1283, CQL 3.3.1 (Cassandra-compat 3.0.8)
Drivrutin
cassandra-driver 4.9.0, consistency LOCAL_ONE, fetch size 5000

Byggd för

  • Hög skrivgenomströmning
  • Förutsägbart låg fördröjning under samtidighet
  • Horisontell skalning över noder
  • Läsningar på partitionsnyckel
  • Datamodeller designade kring kända frågor

Så driftsattes den

  • Docker-container från avbildningen scylladb/scylla:6.2, ensam på en m5d.2xlarge
  • --smp 8 --memory 24G --overprovisioned 0 --developer-mode 1 — varje kärna på maskinen, mot två skärvor och 4 GB i körning F
  • Seastar reserverar utrymme utöver vad --memory ber om, och vägrar starta när summan överstiger vad maskinen faktiskt har: 24G av 32 är taket som startar
  • Tog punktläsningen rakt av här på 62,2 ms, ett scenario den kom trea i när den hade två skärvor
  • Dess COUNT(*) överskrider serverns egen lästidsgräns på 250 000 rader (kod 4608), så adaptern räknar med en sidindelad genomsökning — samma reservväg som Cassandra behöver

PostgreSQL

Relationsdatabas
Version
17.10 (Debian 17.10-1.pgdg13+1)
Drivrutin
pg 8.22.0

Byggd för

  • Relationell integritet och transaktioner
  • Sammansatta join och aggregeringar
  • Blandad läs- och skrivbelastning på en nod

Så driftsattes den

  • Docker-container från avbildningen postgres:17, ensam på en m5d.2xlarge, ingen minnesgräns
  • Genom körning F kördes den med tak på 2 GB och DELADE den buffertpoolen med FerretDB, som lagrade sin egen kopia av alla 250 000 dokument i samma server. Att ge den maskinen tog dess djupa sida från 46,1 ms till 4,16 ms — elva gånger snabbare på en långsammare processor
  • Dokumenten lagras som JSONB, indexerade fält som index på uttrycket doc ->> 'field'
  • Genom körning E bar den fem extra index skapade av adaptern ovanpå de sex paritetsindexen, ett GIN-index över hela dokumentet bland dem. Från körning F bär den de sex och sin primärnyckel, som varje annan motor
  • De fem gav den ingenting på läsningarna i något av fallen. EXPLAIN placerar den filtrerade genomsökningen på en Parallel Seq Scan: dp skickar doc ->> 'field', och ett GIN-index med jsonb_ops svarar på innehåll, inte på textextraktion. De var enbart en skrivkostnad

Apache Cassandra

Bredkolumnsdatabas
Version
5.0.8, CQL 3.4.7
Drivrutin
cassandra-driver 4.9.0, consistency LOCAL_ONE, fetch size 5000

Byggd för

  • Hög skrivgenomströmning
  • Replikering över flera datacenter
  • Tabelldesign styrd av frågorna

Så driftsattes den

  • Docker-container från avbildningen cassandra:5.0, ensam på en m5d.2xlarge, ingen minnesgräns och JVM dimensionerad mot hela de 32 GB
  • Kördes vid 3,686 GB mot ett tak på 4 GB i körning F, under verkligt tryck från skräpsamlingen hela tiden. Att ta bort taket gjorde den inte snabbare i absoluta tal — dess skrivtakt föll från 42 200 rader per sekund till 28 300 — men varje motor blev långsammare på den här hårdvaran, och den höll andraplatsen på skrivningar i båda körningarna
  • Delar hela CQL-adaptern med ScyllaDB, så allt som skiljer de två åt är körtiden och ingenting annat
  • Dess COUNT(*) överskred serverns egen lästidsgräns på 5 sekunder, så adaptern räknar med en sidindelad genomsökning i stället

Couchbase

Dokumentdatabas
Ännu inte mätt

Byggd för

  • Nyckel–värde-läsningar ur cache med låg fördröjning
  • SQL-liknande frågor över dokument
  • Att skala läs- och indextjänsterna oberoende av varandra

Så driftsattes den

  • Ännu inte driftsatt.

CockroachDB

Distribuerad relationsdatabas
Version
CCL v24.3.5 (built 2025-02-03, go1.22.8)
Drivrutin
pg 8.22.0 — the PostgreSQL driver, unchanged

Byggd för

  • Horisontell skalning utan att ge upp SQL
  • Serialiserbara distribuerade transaktioner
  • Att överleva förlusten av en nod eller en region

Så driftsattes den

  • Docker-container från avbildningen cockroachdb/cockroach:v24.3.5, en nod, --insecure, ensam på en m5d.2xlarge, ingen minnesgräns
  • Delar hela PostgreSQL-adaptern och samma index på uttryck
  • Bar samma fem extra index skapade av adaptern som PostgreSQL genom körning E, GIN inräknat. Att ta bort dem inför körning F flyttade dess skrivgenomströmning med en faktor tretton — dess schemaändringar är asynkrona, så den hade byggt ett inverterat index över 250 000 dokument under mätningen
  • Returnerar sina radantal som strängar där varje annan motor returnerar tal
  • Den var den minst upprepbara motorn i uppsättningen på den delade bärbara datorn — dess exakta räkning låg mellan 175 ms och 710 ms över körning F:s fem rundor. Ensam på en maskin drog den ihop sig till en p95 inom 1,1× av sin median i samma scenario, vilket tyder på att spridningen var trängsel snarare än motorn
  • Skriver fortfarande långsammare än allt annat här med bred marginal: 2 610 rader per sekund, och 100,8 sekunder för en fyllning MongoDB gör på 8,4
  • På en maskin med 2 GB och en kärna kunde den inte fullborda körningen: sju rundor av tio blev klara, två dog med klienten rapporterande en avbruten anslutning mitt i insådden, och en hängde sig hela tretimmarstaket. Dess fyllning där går i 1 140 rader per sekund — 286 sekunder, mot MongoDB:s 12,8 — och dess publicerade kolumn är sammanslagen ur de sju som överlevde

Apache CouchDB

Dokumentdatabas
Version
3.4.3 (e12b967d7)
Drivrutin
none — CouchDB's whole interface is HTTP and JSON, so fetch is the client

Byggd för

  • Replikering mellan likvärdiga noder, även till klienter utan uppkoppling
  • Hållbarhet byggd för att krascha
  • Läsning genom förberäknade vyer

Så driftsattes den

  • Docker-container från avbildningen couchdb:3.4, ensam på en m5d.2xlarge, ingen minnesgräns
  • Motorn som vann mest på att få taket borttaget och ändå förlorade på ett scenario: punktläsning 2 970 ms till 679 ms, räkning 28,8 s till 14,7 s, första sidan 523 ms till 243 ms — och djup sida 1 090 ms till 3 460 ms, tre gånger långsammare
  • Har den bredaste svansen i körning G. Dess första sida medianar 243 ms och p95:ar 1 570 ms, och dess filtrerade genomsökning medianar 1 340 ms och p95:ar 7 470 ms — ungefär ett anrop av tjugo kostar sex gånger det typiska
  • En CouchDB-databas per dp-samling, vilket gör en exakt räkning till en metadataläsning i stället för en genomsökning
  • dp:s fält med inledande understreck är OTILLÅTNA här — CouchDB reserverar det prefixet och svarar doc_validation: Bad special document member: _type. Varje fält byter namn vid skrivning och tillbaka vid läsning.
  • Ett Mango-index krävs för att sortera: det här är den enda motorn som vägrar en fråga i stället för att bli långsammare, så adaptern sorterar i applikationen när inget index täcker fältet
  • På en maskin med 2 GB och en kärna blev den klar med alla tio rundorna och vägrade sedan två scenarier i var och en av dem: besiktningsuppslaget och första sidan kom tillbaka från POST /_find som en tidsgräns på serversidan, motorn som avböjer frågan i stället för att köra den långsamt

FerretDB

Dokumentlager ovanpå PostgreSQL
Version
v1.24.0, presenting MongoDB wire 7.0.42
Drivrutin
mongodb 7.2.0 — the MongoDB driver, unchanged

Byggd för

  • Att behålla MongoDB-drivrutiner och -frågor på en öppen stack
  • Att återanvända PostgreSQL:s drift, säkerhetskopior och verktyg
  • Att flytta från en dokumentdatabas utan att skriva om frågorna

Så driftsattes den

  • Docker-container från avbildningen ghcr.io/ferretdb/ferretdb:1.24.0, ensam på en m5d.2xlarge med en egen PostgreSQL 17.10-container bredvid sig, ingen minnesgräns på någon av dem
  • GENOM KÖRNING F VAR DESS UNDERLIGGANDE LAGER DEN POSTGRESQL SOM MÄTTES. Dess data — en andra fullständig kopia av alla 250 000 dokument — låg i buffertpoolen hos motorn i nästa kolumn, med tak på 2 GB. Ingen indexparitetskontroll fångar det, och ingenting i körning F:s utdata visar det
  • Att skilja dem åt kostade FerretDB ungefär halva farten rakt igenom och gjorde PostgreSQL elva gånger snabbare på den djupa sidan. Delandet hjälpte aldrig FerretDB; det förstörde PostgreSQL
  • Behövde ingen adapterkod alls — dp:s MongoDB-frågor kommer fram oförändrade
  • Inga ändringsströmmar, så dp:s prenumerationer kan inte betjänas: den enda funktionsluckan här som inte är en prestandafråga
  • Dess indexerade uppslag mäter LÅNGSAMMARE än dess fullständiga genomsökningar, och dess underliggande PostgreSQL säger varför: 302 sekventiella genomsökningar, 67 750 000 lästa tupler, idx_scan = 0 på varje index inklusive det på _id. De sex paritetsindexen finns där som riktiga b-träd och användes aldrig en enda gång.

MariaDB

Relationsdatabas
Version
11.4.12-MariaDB-ubu2404
Drivrutin
mysql2 3.23.1

Byggd för

  • Relationell integritet och transaktioner
  • Sammansatta join och aggregeringar
  • Direkt ersättare för MySQL-installationer
  • Blandad läs- och skrivbelastning på en nod

Så driftsattes den

  • Docker-container från avbildningen mariadb:11.4, ensam på en m5d.2xlarge
  • innodb-buffer-pool-size=24G. Det är UTJÄMNING, inte trimning: InnoDB öppnar sina datafiler med O_DIRECT — bekräftat på maskinen, @@innodb_flush_method = O_DIRECT — så operativsystemets sidcache backar den inte och förvalet på 128 MB vore i praktiken hela cachen. Varje annan motor här når större delen av maskinen som förval, och att lämna MariaDB på 128 MB hade gjort den till den enda som inte kunde
  • max_allowed_packet=256M, eftersom såmaskinen skriver i klumpar om femtusen dokument och varje klump anländer som en INSERT med många rader. Ett protokollkrav från mätverktyget, inte ett prestandareglage
  • Ingenting annat är satt. Ingen dimensionering av redo-loggen, ingen trimning av utskrivningen, ingen trådpool
  • utf8mb4_bin rakt igenom. Förvalet utf8mb4_general_ci är OKÄNSLIGT för skiftläge, vilket hade fått `equals` att matcha rader som PostgreSQL, MongoDB och CQL alla förkastar — ett annat radantal ur samma filter, och en avvikelse i sammanfattningen som hade sett ut som ett fel i adaptern
  • MariaDB KAN INTE indexera ett uttryck. PostgreSQL indexerar doc ->> 'field' direkt och MySQL 8 har funktionella index; MariaDB har ingetdera, så vart och ett av de sex paritetsfälten bär en genererad kolumn och indexet sitter på den kolumnen
  • Det gör frågans formulering avgörande snarare än kosmetisk. EXPLAIN på den här tabellen: att referera den genererade kolumnen ger type=ref, key=dpx__besiktning_id, 4 rader. Samma fråga skriven som den inbäddade extraktionen ger type=ALL, key=NULL, fullständig genomsökning. MariaDB skriver inte om det ena till det andra

dpdb

Dokumentdatabas
Version
built from 0531553347dc, --ram-mb 700
Drivrutin
none — fetch over HTTP for queries, a websocket for subscriptions

Byggd för

  • Att köra bredvid applikationen på en liten maskin
  • Ett minnestak som inte följer datamängden
  • Prenumerationer som pushas, som en förstklassig funktion
  • dp:s egna frågeformer utan översättningslager

Så driftsattes den

  • En enda kompilerad binär under systemd på en STARTER-1xCPU-2GB i se-sto1 — inte en container, eftersom en container inte är hur dpdb driftsätts någonstans
  • Den enda motorn på sidan utan avbildning att hämta och utan tjänst att konfigurera bortom en unit-fil och en datakatalog
  • Dess minnestak är ett argument (--ram-mb 700) snarare än en cgroup-gräns, vilket är skillnaden raden ovan påstår: motorn begränsar sig själv i stället för att bli begränsad

SQLite

Inbyggd relationsdatabas
Version
SQLite 3.53.3
Drivrutin
node:sqlite, built into Node v24.19.0 — there is no package to install

Byggd för

  • Att driftsätta en databas genom att inte skicka med någon
  • Lästung belastning på en maskin
  • Inbyggda installationer och installationer i kanten utan operatör
  • Ingen driftyta alls — ingen port, ingen användare, ingen tjänst

Så driftsattes den

  • En STARTER-1xCPU-2GB i se-sto1, som varje annan motor här, och den enda där ingenting installerades på den
  • Hela installationssteget är `mkdir /var/lib/dp-sqlite`. Flottans uppsättning lägger Node på maskinen, och Node innehåller redan databasen — den tomheten är fyndet, inte ett förbiseende
  • Ingen port, ingen användare, ingen tjänst, ingen container och ingen minnesgräns, eftersom det inte finns någon andra process att begränsa. Motorn kör inuti mätprocessen på mätningens egen tråd

MariaDB behind MaxScale

Proxy framför MariaDB
Version
MaxScale 23.08.13 (readwritesplit) in front of MariaDB 11.4.12
Drivrutin
mysql2 3.23.1 — the same driver, pointed at port 4006

Byggd för

  • Anslutningspooler och lastbalansering framför ett kluster
  • Uppdelning av läsning och skrivning utan ändringar i applikationen
  • Redundansväxling som applikationen aldrig märker
  • Filtrering, omskrivning och loggning av frågor på ett ställe

Så driftsattes den

  • Docker-container från avbildningen mariadb/maxscale:23.08, på SAMMA m5d.2xlarge som den MariaDB den står framför
  • En maskin och inte två med avsikt. Proxyn är den enda variabeln, så processorn, disken, buffertpoolen, datamängden och adaptern måste vara samma fysiska sak snarare än samma specifikation
  • readwritesplit, inte readconnroute. readconnroute vidarebefordrar byte utan att förstå dem och hade mätt ett TCP-hopp; readwritesplit TOLKAR varje sats för att avgöra vart den går, vilket är vad en verklig MaxScale-installation gör och där dess kostnad faktiskt sitter
  • En server bakom, markerad Master av mariadbmon. Att lägga till repliker hade ändrat motorn såväl som vägen till den, och då hade ingenting särskiljbart återstått att mäta
  • Noll rader adapterkod. dp:s MariaDB-frågor kommer fram oförändrade — anslutningssträngen är hela skillnaden i kodbasen, vilket är just det som gör paret till en mätning av proxyn