# Performance CouchDB (NoSQL) vs. MySQL



## freez (27. Aug 2010)

Hallo,

ich beschäftige mich seit ein paar Tagen mit NoSQL System (speziell CouchDB und Cassandra). Bis jetzt habe ich viel mit iBatis und MySQL gemacht. Da die Java Clients für Cassandra entweder noch nicht ausgereift sind, oder ich einfach nur zu bl...d bin diese zu benutzen habe ich mit CouchDB4J gearbeitet. Ich war ganz angetan, vor allem weil die Werbebotschaften Performance, Einfachheit und ideal für Webanwendungen versprechen. Leider habe ich keinen Performancevergleich zwischen MySQL und CouchDB im Internet gefunden. Also habe ich selbst einen einfachen Performancetest erstellt. Hier mal die Bedingungen:

Hardware für Server und Client: VMWare Server 1 auf Windows XP Rechner, 512 MB RAM
Betriebssystem Datenbankserver: 2x Ubuntu 10 Server Grundinstallation

Anpassungen für CouchDB auf ersten Ubuntuserver:
- apt-get install couchdb
- bind_address in default.ini auf 0.0.0.0 geändert
- hier wird folgendes Dokument immer wieder geschrieben [{"data":"40 Zeichen Text"}]

Anpassungen für MySQL auf zweiten Ubuntuserver:
- apt-get install mysql-server
- bind_address in my.cnf auf 0.0.0.0 geändert
- Tabelle erstellt CREATE TABLE data (data VARCHAR(50),FULLTEXT (data));

Client: Windows XP, 1GB Ram
CouchDB Client:CouchDB4J
MySQL Client: iBatis

Test1: Schreiben von einer Million Daten a 40 Zeichen
Test2: Lesen von 1000, 10.000, 30.000, 100.000 Daten a 40 Zeichen

Im Anhang seht ihr, wie lange es dauert, um 1000 Daten a 40 Zeichen in die Datenbank zu schreiben in Abhängigkeit der Anzahl bereits vorhandener Daten. Ich finde es interessant, dass CouchDB mit der größer werdenden Menge der Daten immer langsamer wird, wogegen MySQL zum Ende etwas langsamer wird, aber eigentlich stabil bleibt.

Interessant ist dabei auch die Datenmenge. Normalerweise erzeuge ich rund 45 MB Daten (1.000.000 * 40 * 1Byte/Char)Byte. MySQL hat dann 48 MB Daten in der Datenbank liegen und 78 MB an Indexes. Die Datenbank in CouchDB ist 2.000MB !!!!! groß.

Das Lesen der Daten ist auch interessant:
CouchDB
1.000 Texte mit 40 Zeichen           250ms
10.000 Texte mit 40 Zeichen       1.735ms
30.000 Texte mit 40 Zeichen       4.704ms
100.000 Texte mit 40 Zeichen     HeapSpace Error

MySQL
1.000 Texte mit 40 Zeichen          406ms
10.000 Texte mit 40 Zeichen        828ms
30.000 Texte mit 40 Zeichen      1.030ms   
100.000 Texte mit 40 Zeichen    1.700ms

Noch ein Hinweis dazu: Bei CouchDB habe ich nur die Indizies geladen. Die eigentlichen Daten müsste ich noch extra nachladen. Ich habe natürlich auch das versucht, aber da braucht CouchDB 3x so lang und der Heap Space Error erscheint schon nach 12.000 Texten. Bei MySQL hatte ich die Daten nach den Zeiten in einer java.util.List vorliegen.

Nun frage ich mich, wo hier die versprochenen Performancevorteile sind? Was meint ihr dazu? Ich habe das Gefühl, dass sich die Vorteile von NoSQL auf "Schemalos" beschränken. Oder habe ich da zufällig den miesesten Vertreter erwischt?

So,hier noch der Quellcode:
MySQL Reader

```
SqlMapClient sm = SQLConnector.getSQLMap();
		long start = System.currentTimeMillis();
		try {
			sm.queryForList("selectMany", 30000);
		} catch (SQLException e) {
			e.printStackTrace();
		}	
		System.out.println("Laufzeit: " + (System.currentTimeMillis()-start) + "ms");
```
SQLCode SELECT:

```
<select id="selectMany" resultClass="String" parameterClass="Integer">
		SELECT * FROM data Limit 0, #value#;
	</select>
```

MySQL Writer

```
BufferedWriter writer = null;
		SqlMapClient sm = SQLConnector.getSQLMap();
		try {
			sm.delete("deleteData");
			System.out.println("Anzahl Datensaetze in 'Data': " + sm.queryForObject("selectAnzahl"));
		} catch (SQLException e) {
			e.printStackTrace();
		}	
		
		long start = System.currentTimeMillis();
		int anzahl = 1000;
		int durchgaenge = 1000;
		String count = null;
				
		try {
			writer = new BufferedWriter(new FileWriter("c:\\data.csv"));
			for(int j = 0; j < durchgaenge; j++){
				long startBrocken = System.currentTimeMillis();

				try {
					sm.startBatch();
					for(int i = 0; i < anzahl; i++)
						sm.insert("insertData", Text.subtext(40));
					
					sm.executeBatch();
					count = (String) sm.queryForObject("selectAnzahl");	
					
				} catch (Exception e) {
					e.printStackTrace();
				}		
				writer.write(count+","+(System.currentTimeMillis() - startBrocken)+"\r\n");
				System.out.println("Durchgang " + j + "/" + durchgaenge + " - Laufzeit: " + (System.currentTimeMillis() - startBrocken) + "ms");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(writer != null)
				try {writer.close();}
				catch (Exception e1) {e1.printStackTrace();}
		}
```
SQLCode INSERT:

```
<insert id="insertData" parameterClass="String">
		INSERT INTO data (data)
		VALUES (#data#);
	</insert>
```



CouchDBReader

```
long start = System.currentTimeMillis();
		
		ViewResults vr = db.getAllDocumentsWithCount(30000);
		
		System.out.println("Laufzeit: " + (System.currentTimeMillis()-start) + "ms");
```

CouchDBWriter

```
BufferedWriter writer = null;
		Session s = new Session("10.164.29.231", 5984);
		s.deleteDatabase("manigdni");
		s.createDatabase("manigdni");
		Database db = s.getDatabase("manigdni");
		long start = System.currentTimeMillis();
		int anzahl = 1000;
		int durchgaenge = 1000;
		Document [] docs = new Document[anzahl];
		
		try {
			writer = new BufferedWriter(new FileWriter("c:\\couchdb.csv"));
			for(int j = 0; j < durchgaenge; j++){
				long startBrocken = System.currentTimeMillis();
				for(int i = 0; i < anzahl; i++){
					docs[i] = new Document();
					docs[i].put("text", Text.subtext(40));
				}
				try {
					db.bulkSaveDocuments(docs);
				} catch (Exception e) {e.printStackTrace();}

				try {
					Document d = db.getDatabaseInfo();
					writer.write(d.get("doc_count").toString()+","+
							d.get("disk_size").toString()+","+
							(System.currentTimeMillis() - startBrocken)+"\r\n");
					System.out.println("Durchgang " + j + "/" + durchgaenge + " - Laufzeit: " + (System.currentTimeMillis() - startBrocken) + "ms");
				} catch (Exception e) {
					e.printStackTrace();
				}		
				

			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(writer != null)
				try {writer.close();}
				catch (Exception e1) {e1.printStackTrace();}
		}

		System.out.println("Laufzeit: " + (System.currentTimeMillis()-start) + "ms");
```

Hilfsklasse Text:

```
public class Text {
	private static String text = "ca 1000 Wörter aus einem Buch sind eigentlich hier drin";
	Random rand = new Random();
	public static String subtext(int anzahlZeichen){
		Random rand = new Random();
		int pos = rand.nextInt(text.length()-anzahlZeichen);
		return text.substring(pos, pos+anzahlZeichen);
	}
}
```


----------



## maki (27. Aug 2010)

> ..Heap Space Error..


MIt welchen Heap Parametern hast du die JVM denn gestartet?


----------



## Sonecc (27. Aug 2010)

Es ist hinreichend bekannt, dass CouchDB beim Indexen extrem langsam ist

Nachtrag: Die Datenbankgröße ist deshalb so groß, weil die Indizes bei jedem Update des Dokuments kopiert werden müssen. (Hab vor einiger Zeit mal nen Bericht darüber gelesen, weiß aber nimmer wo)


----------



## freez (27. Aug 2010)

maki hat gesagt.:


> MIt welchen Heap Parametern hast du die JVM denn gestartet?



Standardparameter. Ist aber für den test nicht ganz so wichtig, weil beide mit den gleichen Parametern laufen und das auch ne interessante Aussage ist.


----------



## freez (27. Aug 2010)

Sonecc hat gesagt.:


> Es ist hinreichend bekannt, dass CouchDB beim Indexen extrem langsam ist
> 
> Nachtrag: Die Datenbankgröße ist deshalb so groß, weil die Indizes bei jedem Update des Dokuments kopiert werden müssen. (Hab vor einiger Zeit mal nen Bericht darüber gelesen, weiß aber nimmer wo)



Ich date doch aber keine Dokumente ab, sondern erzeuge neue Dokumente. Und auch wenn es der Grund wäre, ist über das 10fache einfach zu viel.


----------



## Sonecc (27. Aug 2010)

Hab mich falsch ausgedrückt...

Bei jeder Veränderung eines Dokuments müssen die Indizes kopiert werden. Also auch bei neu Erstellung von Dokumenten. Das wirkte sich, soweit ich mich erinnere am meisten bei der Erstellung vieler Dokumente aus. CouchDB ist (wie gesagt, alles Erinnerungswerte) auch nicht darauf ausgelegt, viele Dokumente zu erzeugen, sondern sollte eher wenige, dafür aber größere Dokumente nutzen


----------



## freez (27. Aug 2010)

Ehrlich? Dann muss ich doch mal Cassandra testen. Wenn Facebook so eine Datenbank einsetzt dann sollte nicht so eine Datenflut entstehen ... dann wären die Server schnell voll. Aber ich glaube trotzdem, dass irgendwas nicht stimmt. Entweder ist das Design falsch, es gibt ein Bug oder ich muss noch an einer Schraube drehen.

Ansonsten ist mein Fazit: Lieber MySQL!


----------



## maki (27. Aug 2010)

freez hat gesagt.:


> Standardparameter. Ist aber für den test nicht ganz so wichtig, weil beide mit den gleichen Parametern laufen und das auch ne interessante Aussage ist.


Sorry, aber das ist Blödsinn.

CouchDB4J ist eine Javaanwendung, die Standardeinstellungen für eine 32 Bit JVM sind 64 MiB(inkl. deinem Programm), da hat MySQL sicherlich mehr 
Ceteris parabus ist nicht wirklich erreichbar bei so einem Vergleich, aber zumindest sollte man Fair sein und vernünftige Startwerte setzen, so wie sich dein test liest (Heap Space Error) läuft doch bestimmt die ganze Zeit der GC und verfälscht deine Ergebnisse.

Würde Xms und Xmx auf 512m setzen, soviel hat MySQL doch auch mindestens.


----------



## freez (27. Aug 2010)

maki hat gesagt.:


> Sorry, aber das ist Blödsinn.
> 
> CouchDB4J ist eine Javaanwendung, die Standardeinstellungen für eine 32 Bit JVM sind 64 MiB(inkl. deinem Programm), da hat MySQL sicherlich mehr
> Ceteris parabus ist nicht wirklich erreichbar bei so einem Vergleich, aber zumindest sollte man Fair sein und vernünftige Startwerte setzen, so wie sich dein test liest (Heap Space Error) läuft doch bestimmt die ganze Zeit der GC und verfälscht deine Ergebnisse.
> ...



Ich habe das Gefühl, wir reden von verschiedenen Sachen. CouchDB und MySQL laufen jeweils auf einem Ubuntu Server. Die HeapSpace Meldung stammt von dem JavaClient. Bei CouchDB ist das CouchDB4J und bei MySQL ist es iBatis. Also laufen beide JavaClients mit den gleichen Werten.


----------



## Cage Hunter (28. Aug 2010)

Also die beiden DB's miteinander zu vergleichen geht so gar nicht^^
Wie bereits gesagt wurde : 
CouchDB ist für wenige, aber große Daten/Texte ausgelegt
MySQL ist halt eher die klassische DB für alles irgendwie
Selbst die Architekturen sind unterschiedlich und beide haben ihre Vorzüge und zwar auf verschiedenen Gebieten


----------



## maki (28. Aug 2010)

> Ich habe das Gefühl, wir reden von verschiedenen Sachen. CouchDB und MySQL laufen jeweils auf einem Ubuntu Server. Die HeapSpace Meldung stammt von dem JavaClient. Bei CouchDB ist das CouchDB4J und bei MySQL ist es iBatis. Also laufen beide JavaClients mit den gleichen Werten.


Das ist schon klar.
Was du aber misst ist so durch den GC verfälscht, dass dabei keine verwertbare Aussage rauskommt, dass deckt sich doch mit deinen Beobachtungen sobald die Datenmenge ansteigt.
Wenn du die Bedingungen wenigstens etwas realistischer machen würdest, könnte man sich zumindest an deinen Messwerten orientieren.
Kenne keine WebApp die mit 64 MiB läuft 
So ist das leider nix ganzes und nix halbes.

Nebenbei, Virtualle Maschinen sind nicht gut als Plattformen für "präzise" Performancetests geeignet, da diese sowieso eher ungenau laufen.


----------



## freez (28. Aug 2010)

maki hat gesagt.:


> Das ist schon klar.
> Was du aber misst ist so durch den GC verfälscht, dass dabei keine verwertbare Aussage rauskommt, dass deckt sich doch mit deinen Beobachtungen sobald die Datenmenge ansteigt.
> Wenn du die Bedingungen wenigstens etwas realistischer machen würdest, könnte man sich zumindest an deinen Messwerten orientieren.
> Kenne keine WebApp die mit 64 MiB läuft
> ...



Naja, ich hatte nicht den Anspruch einen perfekten Performance Test zu kreiren. Ich wollte nur einen Eindruck gewinnen, wie sich die beiden bei diesem einen Anwendungsfall "Einfach mal viele kleine Texte speichern" verhalten. Und, VM als Plattform: Ich gebe dir recht ... allerdings lief bei jedem Test immer nur diese eine VM auf dem Server ... also denke ich, dass ich das schon vergleichen kann.


----------



## freez (28. Aug 2010)

Cage Hunter hat gesagt.:


> Also die beiden DB's miteinander zu vergleichen geht so gar nicht^^
> Wie bereits gesagt wurde :
> CouchDB ist für wenige, aber große Daten/Texte ausgelegt
> MySQL ist halt eher die klassische DB für alles irgendwie
> Selbst die Architekturen sind unterschiedlich und beide haben ihre Vorzüge und zwar auf verschiedenen Gebieten



Ja, richtig! Für den Anwendungsfall "Mal eben Millionen Textbrocken speichern" habe ich sie verglichen. Das war ja der Sinn dieser Sache. Ich denke das Performanceverhalten eines DBMS ist zwar nicht das einzigste Kriterium, aber auch für eine Entscheidung wichtig.


----------



## freez (28. Aug 2010)

So, habe gerade festgestellt, dass Ubuntu nur die Version 0.10.0 von CouchDB mitliefert (ich hatte fälschlicherweise 1.0.0 gelesen). Aktuell ist 1.0.1 und dazwischen ist viel passiert. Ich habe einen ersten kleinen, nicht vergleichbaren Test auf einem anderen System gemacht. Sieht schon viel besser aus. Werde berichten, sobald ich diesen Test in der richtigen Umgebung durchgeführt habe.


----------

