- Da sich bei Lichess wegen der hohen Request-Last die regelmäßige RAID-Prüfung des 7-piece Syzygy tablebase server verzögerte, wurde statt eines vollständigen Scans aller Blöcke auf Integritätsprüfung beim Lesen umgestellt
- Um die 17-TiB-Tablebase ohne lange Downtime umzuziehen, wurde ein neuer Server bereitgestellt und mit realen Request-Logs unter einer Konfiguration aus 32GiB RAM, 2×201GiB NVMe und 6×5.46TiB HDD validiert
- Beim Replay von 1 Million Requests, die in der Produktion aufgezeichnet wurden, mit 12 parallelen Clients zeigte sich, dass nicht die durchschnittliche Antwortzeit, sondern die vom Nutzer wahrgenommene Tail-Latenz der zentrale Engpass ist
- Bei der Implementierung erwies sich
pread(2) gegenüber mmap bei Fehlerbehandlung und Tail-Latenz als vorteilhaft, und Random-Access-Hinweise wie POSIX_FADV_RANDOM und MADV_RANDOM wirkten meist kontraproduktiv
- Auf die begrenzte SSD wurden Table-Prefixe gelegt und die internen Probes eines Requests parallelisiert, um langsame Plattenzugriffe zu verringern; zudem wurde geprüft, ob sich Benchmark-Verbesserungen auch in den Antwortzeiten der Produktion niederschlagen
Umstellung von vollständiger RAID-Prüfung auf Verifikation beim Lesen
- Der 7-piece Syzygy tablebase server von Lichess konnte während hoher Tablebase-Request-Last die periodischen RAID-Integritätsprüfungen nur schwer abschließen
- Die neue Konfiguration verwendet dm-integrity on LVM, um nicht mehr periodisch alle Datenblöcke zu prüfen, sondern jeden Block bei jedem Lesen zu verifizieren
- Um die 17TiB große Tablebase ohne Downtime von mehreren Stunden zu migrieren, wurde ein separater neuer Server aufgebaut
- Vor der eigentlichen Umstellung konnten so kontrollierte Benchmarks mit der kompletten Tablebase durchgeführt werden
- Danach wurde auf den neuen Server umgeschaltet und der alte Server außer Betrieb genommen
Aufbau des neuen Servers
- Der RAM blieb wie zuvor bei 32GiB
- Beim Storage kamen zusätzlich 2×201GiB NVMe hinzu, die der bisherige Server nicht hatte; der restliche Platz der 476GiB großen Laufwerke wurde für OS und Arbeitsbereich reserviert
- Die HDDs wurden von zuvor 5 auf 6×5.46TiB HDD erweitert
- Als Betriebssystem läuft Debian bookworm mit einem Kernel aus der Reihe
Linux 6.1.0-21-amd64
- Standardmäßig war als I/O-Scheduler auf NVMe
none und auf HDD mq-deadline ausgewählt
RAID-5-Konfiguration und Monitoring
- RAID 5 eignet sich gut für den Tablebase-Server, da es einen einzelnen Plattenausfall verkraftet und Random Reads über mehrere Laufwerke verteilen kann
- Die initiale Konfiguration sah so aus:
lvcreate --type raid5 --raidintegrity y --raidintegrityblocksize 512 --name tables --size 21T vg-hdd
- Die Performance in ersten Tests war in Ordnung, aber ohne Monitoring hätte man womöglich übersehen, dass sich manche Laufwerke nicht in gleichem Maß beteiligten
- Wenn
--stripes weggelassen wird, werden standardmäßig nicht alle Physical Volumes verwendet
- Monitoring der Leseaktivität pro Laufwerk war nötig, um die fehlerhafte RAID-Konfiguration zu erkennen
Engpässe anhand realer Request-Logs
- Unter normalen Bedingungen erhält der Server 10 bis 35 Requests pro Sekunde
- In der Produktion wurden 1 Million Requests aufgezeichnet; in ausgewählten Szenarien reichten 12 parallele Clients diese nacheinander ein
- Die Tables werden verzögert geöffnet, und die Caches von Anwendung und OS füllen sich schrittweise
- Die ersten 800.000 Antwortzeiten wurden als Warm-up ausgeschlossen
- Danach wurden die Antwortzeiten von 200.000 Requests analysiert
- Die durchschnittliche Antwortzeit ist schnell genug, aber die Tail-Latenz ist hoch und wurde daher zum Fokus der Optimierung
- Die ECDF-Grafik zeigt für jede Antwortzeit den Anteil der Requests, die schneller waren; die x-Achse ist logarithmisch skaliert
- In der Grafik wurden zu jeder Antwortzeit 30ms addiert, um die 30ms Ping-Zeit des Clients zu berücksichtigen
- So wird verhindert, dass bei einer logarithmischen x-Achse Unterschiede von wenigen Millisekunden im unteren Bereich überbetont werden
Warum pread(2) gegenüber mmap im Vorteil war
- Die Syzygy-Tablebase-Implementierung shakmaty-syzygy bietet ein Interface, mit dem sich sowohl die Art des Öffnens als auch des Lesens von Table-Dateien austauschen lässt
- Es gab zwei Hauptkandidaten
mmap: mappt die Table-Datei in den Speicher; beim Zugriff auf diesen Speicherbereich werden Plattenzugriffe transparent ausgelöst
pread(2): führt bei jedem Lesevorgang einen System Call aus und meldet Lesefehler über den Rückgabewert
mmap benötigt nach dem Mapping keine weiteren System Calls, aber weil das Lesen wie normaler Speicherzugriff aussieht, müssen Fehler out-of-band etwa über Signale behandelt werden
- In der Server-Implementierung rechtfertigte bereits die robustere Fehlerbehandlung den Einsatz von
pread, und auch in den Benchmarks war pread im relevanten Szenario schneller
- Ein möglicher Grund ist, dass ein Zugriff auf einen einzelnen datenhaltigen Block in einem Memory-Mapping beim Überschreiten einer Seitengrenze zwei Plattenzugriffe auslösen kann
- Für Schach-Engines muss
pread nicht zwingend direkt übernommen werden
- In Engine-Matches wird die Tablebase meist dann eingesetzt, wenn alle WDL-Tables auf ausreichend schnellem Storage liegen können
- In diesem Fall liegen die typischen Antwortzeiten außerhalb des in der Grafik sichtbaren Bereichs, und Memory Mapping ist wegen des geringeren System-Call-Overheads vorteilhafter
Kontraproduktive Random-Access-Hinweise
posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM) und entsprechende Hinweise für Memory Maps erwiesen sich letztlich meist als kontraproduktiv
POSIX_FADV_RANDOM teilt dem OS mit, dass Dateizugriffe zufällig sind und automatisches Read-ahead vermutlich wenig bringt; das ist ein Hinweis zur Reduktion des Drucks auf den Page Cache
- Das Zugriffsmuster auf Tablebases beim Analysieren von Endspielen durch Menschen ist möglicherweise weniger zufällig als erwartet
- Bei Schach-Engines können die Probes stärker auf verschiedene mögliche Endspiele verteilt sein, sodass das Ergebnis anders ausfallen kann
Table-Prefixe auf begrenztem SSD-Speicher
- Ein Table-Probe kodiert zunächst die Position anhand der Informationen im Header der Table als Integer-Index
- Danach muss der komprimierte Datenblock gefunden werden, der das Ergebnis für diesen Index enthält
- Syzygy stellt dafür eine sparse block length list bereit, die in die Nähe des richtigen Eintrags zeigt; anschließend wird über die block length list der relevante Datenblock gefunden
- Die Größen der Table-Abschnitte sind wie folgt:
| Table section |
WDL |
DTZ |
Total |
| Headers and sparse block length lists |
38GiB |
9GiB |
47GiB |
| Block length lists |
274GiB |
64GiB |
339GiB |
| Compressed data blocks |
8433GiB |
8458GiB |
16891GiB |
- SSD-Kapazität könnte auch als adaptive Cache-Schicht genutzt werden, um hot list entries und data blocks zu cachen
- Mit dem Ziel, die Tail-Latenz zu senken, war es sinnvoller, für den Worst Case sparse block length lists und block length lists auf die SSD zu legen
- Dieses Layout begrenzt langsame Plattenzugriffe pro Table-Probe unabhängig von Hot/Cold-Zuständen auf höchstens einen
- Auf diesem Server reichte der SSD-Platz nicht für RAID-1-Mirroring; da es sich um eine selektive Optimierung handelte, wurde auf Redundanz verzichtet und RAID 0 verwendet
Parallelisierung der internen Probes eines Requests
- Ein typischer Tablebase-Request einer Schach-Engine fragt einen einzelnen WDL-Wert ab
- In der Benutzeroberfläche sollen dagegen DTZ-Werte für alle Züge angezeigt werden
- Einschließlich der internen Auflösung von Captures erzeugt ein durchschnittlicher Request 23 WDL-Probes und 70 DTZ-Probes
- Die anfängliche Implementierung parallelisierte zwar die Verarbeitung von Requests insgesamt, führte die Probes innerhalb jedes einzelnen Requests aber seriell aus
- Feinere Parallelität verursacht im Bereich niedriger Latenzen zwar Overhead, senkt aber die Tail-Latenz deutlich
- Auch wenn die Laufwerke physisch nicht viele parallele Reads gleichzeitig abarbeiten können, ist es wahrscheinlicher, dass der I/O-Scheduler die Reads so plant, dass einzelne Requests schneller abgeschlossen werden
- So kann die Reihenfolge der relevanten Plattenzugriffe besser geplant werden, um die Zeit zu verkürzen, bis der Plattenkopf den Sektor des nächsten Requests erreicht
Verifikation in der Produktion und Rohdaten
- Anhand von Antwortzeit-Charts wurde geprüft, ob die Optimierungen aus dem Benchmark-Szenario auch der realen Produktion helfen
- Die Rohdaten sind in lila-tablebase-bench veröffentlicht
Noch keine Kommentare.