1 Punkte von GN⁺ 2024-07-14 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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.

Noch keine Kommentare.