Unsere Erfahrungen mit Ceph - Teil 2

nine Team Dez 14, 2016

Nachdem wir im ersten Teil unserer Blog-Serie “Unsere Erfahrungen mit Ceph” die Probleme, welche es zu lösen galt und unsere Ansätze präsentierten, möchten wir hier nun auf den Aufbau sowie die erwarteten und effektiven Geschwindigkeiten der einzelnen Aspekte unseres neuen Clusters eingehen.

Aufbau und Tests

Nach der Entscheidung für die sehr schnelle NVMe-Version bestellten wir die entsprechende Hardware.

Nach Ankunft und Einbau der Server in die Lab-Racks begannen wir mit der Installation der Systeme. Anschliessend starteten direkt die ersten Benchmarks. Um später die Performance jedes einzelnen Teils des Clusters zu kennen und um so allfällige Engpässe frühzeitig zu bemerken, testeten wir alle Einzelteile - beginnend bei den NVMe-Disks bis hin zur IO-Geschwindigkeit in einer virtuellen Maschine, die dann Ceph RBD als Block Storage nutzte.

Die meisten der folgenden Benchmarks wurden mittels FIO durchgeführt.

Benchmark der Disks (OSDs & Journals)

Wir starteten mit dem Geschwindigkeits-Test der NVMe Disks. Hierzu kreierten wir einen FIO Job, mit dem wir verschiedene Blockgrössen testen konnten und erhielten so die entsprechenden IOPS und MB/s Zahlen:

Test\Blocksize 4k 8k 16k 2048k 4096k
Random Write 314276 / 1227.7 192417 / 1503.3 104569 / 1633.1 892 / 1787.3 445 / 1788.1
Random Read 351525 / 1373.2 246589 / 1926.6 138682 / 2166.1 1248 / 2501.5 648 / 2605.5

Bei diesen ersten Benchmarks stellte sich heraus, dass mit 4k Blöcken etwa 5.6x mehr random Writes geschafft werden konnten als Intel für die entsprechenden NVMes angab (56’000 IOPS) und etwa 100’000 IOPS weniger random reads. Ein möglicher Zusammenhang könnte hier die Durchführung der offiziellen Intel Benchmarks über die gesamte NVME Disk sein, die wir aus Zeitgründen nicht umgesetzt hatten.

Da für jede OSD auch jeweils ein Journal benötigt wurde und wir dieses jeweils auf dieselbe Disk wie die OSD selbst platzieren wollten, mussten wir auch diese Performance testen. Anhand dieses Tests liess sich zudem ablesen, wie die Schreibleistung des Clusters auf jedem Node später sein sollte. Wir erstellten also die entsprechenden Journal-Partitionen und schrieben dann parallel auf alle vier NVMes gleichzeitig. Das Ergebnis war eine Schreibgeschwindigkeit von 6830 MB/s pro Node.

Im nächsten Schritt versuchten wir das Verhalten der OSD-Prozesse (also das Lesen und Schreiben von den OSD-Partitionen) nachzustellen. Hierzu kreierten wir folgenden FIO-Job:

[global]
invalidate=1
ramp_time=5
ioengine=libaio
iodepth=128
exec_prerun="~/clear_caches.sh"
# we need to write more than we have memory as we use buffered IO
size=512G
direct=0
buffered=1
bs=4m

[random-read-write-1]
stonewall
rw=randrw
rwmixread=20
rwmixwrite=80
filename=/dev/nvme0n1p2

[random-read-write-2]
rw=randrw
rwmixread=20
rwmixwrite=80
filename=/dev/nvme1n1p2

[random-read-write-3]
rw=randrw
rwmixread=20
rwmixwrite=80
filename=/dev/nvme2n1p2

[random-read-write-4]
rw=randrw
rwmixread=20
rwmixwrite=80
filename=/dev/nvme3n1p2

Das Lesen und Schreiben auf den Datenspeichern der OSDs (nicht auf dem Journal) geschieht “buffered”, wird also im RAM gecached. Es ist dabei wichtig, mehr zu schreiben als sich Memory im Node befindet. So generierten wir eine Workload von 80% Schreibzugriffen und 20% Lesezugriffen. Unsere Resultate hier waren eine Schreibgeschwindigkeit von ca. 3773 MB/s und eine Lesegeschwindigkeit von ca. 947 MB/s pro Node. Im realen Fall sollten die Geschwindigkeiten allerdings geringer sein, da beim Schreiben auf eine OSD auch gleichzeitig auf das Journal geschrieben (“double write penalty”) wird. Dieser Fall wurde allerdings in unserem Test nicht mit abgedeckt.

Um nun herauszufinden, wie sich die Schreibgeschwindigkeit in der Wirklichkeit aus Sicht der OSD-Prozesse verhält, installierten und konfigurierten wir Ceph auf den Servern. Hierzu bringt Ceph eigene Werkzeuge mit, um die Performance einzelner Teile zu testen. Dazu zählt unter anderem ceph tell osd.<ID> bench <size> <blocksize>. Die sequentiellen Schreibgeschwindigkeiten mit unterschiedlichen Blockgrössen sehen folgendermassen aus:

OSD Nr. 4k 8k 16k 2048k 4096k
osd.9 24.16 MB/s 61.24 MB/s 108.47 MB/s 1140.04 MB/s 1119.95 MB/s
osd.15 22.53 MB/s 68.60 MB/s 102.60 MB/s 1103.43 MB/s 1087.54 MB/s

Besonders bei geringen Blockgrössen bricht die Schreibgeschwindigkeit stark ein.

Netzwerk

Zu Beginn planten wir das Netzwerk mit zwei der vier 10Gbps-Anschlüsse und eines Bondings für die interne Kommunikation des Storage-Clusters. Die anderen beiden Ports, ebenfalls mit Bonding zusammengefasst, wollten wir für das Management der Server und die Kommunikation nach aussen (zu den Compute-Nodes) nutzen. Hierbei planten wir, die Kommunikation mit den Compute-Nodes über ein VLAN zu realisieren.

Um auch Kennzahlen von der möglichen Geschwindigkeit des Netzwerks und um eventuelle Hinweise auf Probleme erhalten zu können, waren verschiedene Tests der unterschiedlichen involvierten Netzwerke nötig. Diese führten wir mit dem Tool iperf durch.

Anfangs liefen die Tests wie geplant und wir erhielten ungefähr die Werte, die wir erwarteten. Die Ergebnisse, die dann jedoch beim Testen des Durchsatzes zwischen Ceph- und Compute-Nodes herauskamen, waren nicht erwartungsgemäss, da über die 10Gbps-Interfaces nur 2.86 Gbits pro Sekunde durch die Leitung gingen. Um das Problem definieren zu können, belasteten wir die Leitung mit vielen verschiedenen Kombinationen. Dabei stellten wir fest, dass die schlechte Performance nur dann auftrat, wenn der Traffic mit einem VLAN-Tag versehen durch eine gebondete Leitung verschickt wurde. Sobald entweder das Bonding oder die VLAN-Tags entfernt wurden, erhielten wir die erwarteten Geschwindigkeiten. Aufgrund dieser Basis machten wir viele Versuche mit unterschiedlichen Variationen. Dazu gehörte auch die direkte Verbindung zweier Server ohne Switch und das Testen mit Tagging und Bonding, was ohne Einbussen funktionierte.

So stellte sich heraus, dass die Probleme mit unseren Switches zusammenhängen mussten und wir informierten den Hersteller entsprechend.

Da wir also das Problem nicht “einfach” lösen konnten und auf ein allfälliges Update der Switches angewiesen sein würden, entschieden wir uns für eine entsprechende Verbindung ohne Tagging. So können wir nun die komplette Netzwerkbandbreite nutzen.

Der Storage Cluster

Nach der Beendigung der Tests aller einzelnen Cluster-Teile, konnten wir diesen dank Puppet, unserem Konfigurationsmanagement, sehr schnell und einfach fertig aufsetzen.

Anschliessend testeten wir nun die Zusammenarbeit der einzelnen Server und begannen mit dem Benchmark der Schreibgeschwindigkeit auf einen sogenannten Pool. Hierfür war rados bench ein sehr nützliches Werkzeug:

# rados bench -p tmppool 30 write

Total time run:         30.183657
Total writes made:      9648
Write size:             4194304
Object size:            4194304
Bandwidth (MB/sec):     1278.57
Stddev Bandwidth:       87.7862
Max bandwidth (MB/sec): 1436
Min bandwidth (MB/sec): 1056
Average IOPS:           319
Stddev IOPS:            21
Max IOPS:               359
Min IOPS:               264
Average Latency(s):     0.0500232
Stddev Latency(s):      0.0477811
Max latency(s):         0.652174
Min latency(s):         0.0133229

Anhand der Werte liess sich gut erkennen, dass beim Schreiben auf einen Pool etwa dieselbe Geschwindigkeit erreicht wird, wie bei einem früheren Test auf eine einzelne OSD.

So erhielten wir also das Resultat zur Performance eines einzelnen Pools. In den Pools selbst werden die einzelnen RBD-Volumes erstellt, die den virtuellen Servern im Endeffekt als Disks dienen. Um auch die Geschwindigkeit eines solchen RBD-Volume zu messen, hat Ceph bereits verschiedene Tools integriert. Wir nutzten rbd bench-write und erhielten folgenden IOPS-Werte:

Test\Blocksize 4k 8k 16k 2048k 4096k
Serial Write 45385.67 34774.90 21122.50 309.98 160.60
Random Write 12690.85 12189.42 1329.33 284.44 152.23

Um diese Ergebnisse noch auf weitere Arten zu untermauern, führten wir mit FIO weitere Tests durch, bei denen wir direkt auf das RBD-Image schrieben bzw. davon lasen. Die Messungen führten wir mit fio --rw=write --size=10G --ioengine=rbd --direct=1 --iodepth=1 --pool=volumes --rbdname=testing --name=testing --clientname=libvirt --blocksize=4k durch, wobei für die einzelnen Messungen --blocksize und --rw jeweils angepasst wurden. Die Ergebnisse sahen hier zunächst folgendermassen aus:

Test\Blocksize 4k 16k 4096k
Serial Write 25941 22599 117 IOPS / 471.78MB/s
Serial Read 1382 1306 135 IOPS / 543.33MB/s
Random Write 10907 11748 153 IOPS / 613.95MB/s
Random Read 1196 1085 91 IOPS / 365.50MB/s

Nachdem wir nun die ersten Performance-Daten eines RBD-Images hatten, versuchten wir zwei Tunings umzusetzen, die wir auf der Ceph-Mailingliste fanden. Dies waren die folgenden zwei Einstellungen:

  • Die CPUs sollten immer auf der höchstmöglichen Frequenz laufen. Das ‘Aufwachen’ aus niedrigeren Frequenzen kostet zu viel Zeit.
  • die rq_affinity (siehe https://www.kernel.org/doc/Documentation/block/queue-sysfs.txt) sollte für alle NVMe-Disks auf 2 gesetzt werden. Damit werden abgearbeitete IO-Requests jeweils der CPU zugestellt, die sie auch ausgelöst hatte. Da wir unsere OSD-Prozesse auf einzelnen CPUs pinnen, sollte sich daraus ein Performancezuwachs ergeben.

Anschliessend testeten wir erneut und erhielten mit dem gleichen Test die folgenden Werte:

Test\Blocksize 4k 16k 4096k
Serial Write 26791 28086 142 IOPS / 571.36MB/s
Serial Read 2147 1960 176 IOPS / 705.56MB/s
Random Write 12408 13370 255 IOPS / 1020.1MB/s
Random Read 1512 1451 175 IOPS / 703.50MB/s

Im nächsten Blogpost - Teil 3 unserer Ceph-Erfahrungen, messen wir die IO-Geschwindigkeiten in einer virtuellen Maschine, finden heraus, ob unterschiedliche CPUs einen grossen Performanceeinfluss haben und versuchen, das optimale Verhältnis von NVMe-Diskd und der Anzahl der darauf laufenden OSD Prozesse zu definieren.

Hier kommen Sie zum ersten Teil der Serie.