noqqe » blog | sammelsurium | photos | projects | about

MongoDB Cluster wollen nach der Installation wie jede andere DB getestet werden. Performance, Konsistenz bei vielen Writes, usw. Gerade bei Sharding und Indexing über mehrere Knoten verteilt möchte man das schon ausprobieren. Sind die Documents wirklich gleichmässig verteilt?

Alle Zeiten der Auswertung und Interpretation der Ergebnisse spar ich mir jetzt. Der Fokus liegt ersteinmal auf dem “wie messen”.

Write

Der einfachste Weg war das Python Modul pymongo zu benutzen, welches über pip nachinstalliert werden kann.

import pymongo
m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')
i = 0
doc = {'a': 1, 'b': 'foo'}

while (i < 5000000):
        m.Database.testcollection.insert(doc, manipulate=False, w=1)
        i = i + 1

Aufruf im Idealfall mit time python write.py, um auch wirklich die Zeit zu messen. Die 5 Mio erstellten Documents in der Collection testcollection, lassen sich nachher auch für Read-Tests weiterverwenden.

Read

Wie lange es dauert, alle 5 Mio Objekte aus der MongoDB auszulesen ist wahrscheinlich klar. Lange.

import pymongo
m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')
r = m.Database.testcollection.find()
for doc in r:
        print doc["_id"]

Um das komplette Datenset auszugeben: time python readall.py > allids.txt

Read Random Documents

Alle Objekte sequenziell in einem Query ausgeben ist aber ein ziemlich exotischer Use-Case. Näher an der Realität sind kleine Queries die zufällige Dokumente abrufen (gerade wegen des Shardings). Da sowieso schon eine Liste aller ObjectIds existiert allids.txt hab ich dazu einfach ein Python Skript umgebaut dass ich schon hatte.

randompopulation.py wird eine Datei mit Input und die Anzahl der gewünschten Samples übergeben. Mithilfe von linecache ist das auch noch sehr effizient. Die nachfolgende modifizierte Version setzt auch gleich den MongoDB Query ab:

import random
import sys
import linecache
import pymongo
from bson.objectid import ObjectId

# configuration
population=sys.argv[1]
samplesize=int(sys.argv[2])

m = pymongo.MongoClient('mongodb://user:password@localhost:27017/Database')

# count lines of population file
def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

# set length to value
length=file_len(population)

# get random number with max size
x=0
while (x < samplesize):
        y=(int(random.random() * length))
        r=linecache.getline(population, y).rstrip('\n')
        print list(m.Database.testcollection.find( { "_id": ObjectId(r) } ))
        x = x + 1

Und wirft 9000 zufällige Documents aus den angelegten Datensätzen aus.

$ time python choose-random-documents.py allids.txt 9000
[{u'a': 1, u'_id': ObjectId('5399a0620ab2ccca7276853b'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399ab530ab2ccca728a2453'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b0160ab2ccca72aaaf91'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b60f0ab2ccca72cefcde'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399a0780ab2ccca7277341d'), u'b': u'foo'}]
[{u'a': 1, u'_id': ObjectId('5399b56c0ab2ccca72cabd93'), u'b': u'foo'}]

real    0m6.355s
user    0m3.384s
sys     0m0.512s

Distributed Read / Write

Ein Host mit Queries ist natürlich auch witzlos. Schreiben und Lesen von mehreren Hosts! Für derartige Tasks packe ich gerne mal pssh aus.

$ pssh -h hostlist.txt -t 360 -l user -i 'python choose-random-documents.py allids.txt 25'

und selbes natürlich auch für die Write Tests

$ pssh -h hostlist.txt -t 360 -l user -i 'python write.py'

Nachdem ich in write.py noch ein paar Zeitstempel eingebaut habe, kann ich leicht die Schreibzeiten von den Clients visualisieren.

Die regelmäßigen Ausreißer beunruhigen etwas. Im Histogram visualisiert sieht das aber alles viel unproblematischer aus als im Dotchart. Es sind ja immerhin <50 Ausreißer bei 8 Mio Writes. Vertretbar.

Dataset Distribution

Nachdem alles geschrieben und gelesen ist, kann man sich auch mal anschauen wies in MongoDB aussieht. Status der Chunks anzeigen:

mongos> sh.status()
{  "_id" : "Database",  "partitioned" : true,  "primary" : "rs0" }
 Database.testcollection
  shard key: { "_id" : "hashed" }
  chunks:
          rs0     8
          rs1     9
  { "_id" : { "$minKey" : 1 } } -->> { "_id" : NumberLong("-8278359716552185568") } on : rs0 Timestamp(2, 26)
  { "_id" : NumberLong("-8278359716552185568") } -->> { "_id" : NumberLong("-7260263158060599530") } on : rs0 Timestamp(2, 27)
  { "_id" : NumberLong("-7260263158060599530") } -->> { "_id" : NumberLong("-6016783570264293634") } on : rs0 Timestamp(2, 16)
  { "_id" : NumberLong("-6016783570264293634") } -->> { "_id" : NumberLong("-4611686018427387902") } on : rs0 Timestamp(2, 17)
  { "_id" : NumberLong("-4611686018427387902") } -->> { "_id" : NumberLong("-3654885303726982419") } on : rs0 Timestamp(2, 24)
  { "_id" : NumberLong("-3654885303726982419") } -->> { "_id" : NumberLong("-2474593789826765065") } on : rs0 Timestamp(2, 25)
  { "_id" : NumberLong("-2474593789826765065") } -->> { "_id" : NumberLong("-1237168844051948825") } on : rs0 Timestamp(2, 18)
  { "_id" : NumberLong("-1237168844051948825") } -->> { "_id" : NumberLong(0) } on : rs0 Timestamp(2, 19)
  { "_id" : NumberLong(0) } -->> { "_id" : NumberLong("960408942766083593") } on : rs1 Timestamp(2, 22)
  { "_id" : NumberLong("960408942766083593") } -->> { "_id" : NumberLong("2141950729934882470") } on : rs1 Timestamp(2, 23)
  { "_id" : NumberLong("2141950729934882470") } -->> { "_id" : NumberLong("3159510070215249954") } on : rs1 Timestamp(2, 20)
  { "_id" : NumberLong("3159510070215249954") } -->> { "_id" : NumberLong("3849612569857039248") } on : rs1 Timestamp(2, 30)
  { "_id" : NumberLong("3849612569857039248") } -->> { "_id" : NumberLong("4611686018427387902") } on : rs1 Timestamp(2, 31)
  { "_id" : NumberLong("4611686018427387902") } -->> { "_id" : NumberLong("5474895056408077106") } on : rs1 Timestamp(2, 28)
  { "_id" : NumberLong("5474895056408077106") } -->> { "_id" : NumberLong("6550645461419446020") } on : rs1 Timestamp(2, 29)
  { "_id" : NumberLong("6550645461419446020") } -->> { "_id" : NumberLong("7856429257149966918") } on : rs1 Timestamp(2, 14)
  { "_id" : NumberLong("7856429257149966918") } -->> { "_id" : { "$maxKey" : 1 } } on : rs1 Timestamp(2, 15)

Und auch wie es um die Verteilung der einzelnen Objekte steht (etwas gekürzt):

mongos> db.stats()
{
   "raw" : {
           "rs0/mongo01:27018,mongo02:27018" : {
                   "db" : "Database",
                   "collections" : 3,
                   "objects" : 3458713,
                   "avgObjSize" : 48.00009251996335,
                   "dataSize" : 166018544,
                   "storageSize" : 243331072,
           },
           "rs1/mongo03:27018,mongo04:27018" : {
                   "db" : "Database",
                   "collections" : 3,
                   "objects" : 3458108,
                   "avgObjSize" : 48.00009253614982,
                   "dataSize" : 165989504,
                   "storageSize" : 243331072,
   },
   "objects" : 6916821,
   "avgObjSize" : 48,
   "dataSize" : 332008048,
   "storageSize" : 486662144,
}

Sharded Cluster Visualisierung CC-NC-BY-SA MongoDB

Comments (1)

Martin on 2014-06-19T09:40:54.409166
Interessant auch die Jepsen Ergebnisse zu mongo im Fall von Node failures.