
Ich heiße Yuri und bin Teamleiter der Systemadministration bei Citymobil. Heute teile ich meine Erfahrungen mit der Thin Provisioning-Technologie von Linux-Dateisystemen und erkläre, wie sie in den technologischen CI/CD-Prozessen des Unternehmens eingesetzt werden kann. Wir werden die Situation analysieren, in der wir für automatisierte Codeausführungen in die Produktion schnellstmöglich Kopien der MySQL-Datenbank benötigen, die der "Produktivversion" so nahe wie möglich kommen und sowohl lesbar als auch beschreibbar sind.
Einführung: Warum schädliche Ratschläge geben?
Das ist eine berechtigte Frage, denn es gibt bewährte Mechanismen zur Migration von Datenbankschemata in Testumgebungen. Warum sollte man die Hauptdatenbank ohne Sharding auf solche Volumina bringen? Und für Tests werden nicht alle Daten benötigt. Ich werde es erklären.
Etwa vor einem Jahr, angesichts des aktiven Wachstums unseres Taxiaggregators (wir wuchsen 2018 bei den abgeschlossenen Fahrten um etwa das 15-fache), nahmen die Datenmengen, die Serverlast und die Häufigkeit von Rollouts zu. Wir befanden uns in der folgenden Situation:
- Die Hauptdatenbank MySQL wuchs auf etwa 1000 Tabellen mit einem Gesamtvolumen von 2,5 TB und setzte ihr Wachstum fort.
- Es gab keine Möglichkeit, sich schnell zu sharden und die Datenbank zu verteilen. Dies ließ den alten Ansatz "Ich schreibe in die Datenbank, was ich will und wie ich will" nicht zu, mit einer Vielzahl von JOINS und internen Tabellenabhängigkeiten.
- Es gab keinen Mechanismus zur Migration von Datenbankschemata in Testumgebungen.
- Es gab keine automatischen Tests des Codes beim Rollout in die Produktion.
Das letzte Problem wollte ich so schnell wie möglich lösen. Es wurden bereits Postman-Tests zur Überprüfung des Haupt-PHP-Monolithen geschrieben, aber es fehlte an einer aktuellen Datenbank. Wir konnten auch nachts kein Replica erstellen, es zum Master machen und tagsüber zur Verfügung stellen: Bei der hohen Zahl von Rollouts und Änderungen, sowohl in den Daten als auch im Datenbankschema, wäre die Testumgebung bis zur Mittagszeit unbrauchbar gewesen. Außerdem wäre es ineffektiv gewesen, Rollouts nur auf den Arbeitstag zu beschränken.
Dennoch wurde die Aufgabe erfolgreich abgeschlossen: Die erste funktionale Testumgebung erhielten wir bereits nach zwei Wochen. Im vergangenen Jahr hat sie viele Änderungen erfahren und wird weiterhin genutzt.
Im Folgenden werde ich alle Schritte und Phasen der Entwicklung unserer Lösung detailliert beschreiben. Sie werden feststellen, dass diese Methode das Recht auf Existenz verdient.
Was ist „Thin Provisioning“?
Es ist eine Hardware- oder Softwaretechnologie (auch bekannt als sparse volumes), die es ermöglicht, mehr Ressourcen bereitzustellen, als tatsächlich verfügbar sind. Dabei muss das bereitgestellte Volumen den Kriterien just-enough (so viel wie nötig) und just-in-time (innerhalb der erforderlichen Zeit) entsprechen. Thin Provisioning wird hauptsächlich in verschiedenen Speicherlösungen (SAN) eingesetzt, um Speicherplatz in benötigten Mengen bereitzustellen, die die tatsächlich verfügbaren überschreiten. Die Technologie wird von verschiedenen Dateisystemen unterstützt, z. B. LVM2, ZFS, BTRFS. Sie wird häufig in Virtualisierungs-Hypervisoren verwendet. Mit Hilfe von Thin Provisioning konnten wir schnell aus Snapshots des Hauptdatenvolumens so viele Kopien dieses Volumens erstellen, wie wir benötigten (data-Verzeichnis der MySQL-Datenbank).
Erste Testumgebung, Thin LVM-Technologie
Dieses Kapitel könnte man auch „Wie man mit Thin LVM maximal schnelle Snapshots großer Datenmengen erstellt, , wodurch die Stabilität des Dateisystems und der MySQL-Datenbank auf unakzeptable Werte gesenkt wurde.
Da wir bereits LVM zum Aufbau der Hauptpartitionen des Betriebssystems verwendet hatten, beschlossen wir, genau damit zu beginnen. Zunächst benötigten wir eine separate physische Maschine – ein Abbild unserer Hauptdatenbank MySQL, auf der wir Snapshots der Replikation erstellen und sie neben einem separaten MySQL-Instanz hochfahren konnten. Während der Testphase erlaubten wir, dass auf dieser Instanz ändernde Operationen durchgeführt wurden, und nach Abschluss der Tests wurde sie erfolgreich gelöscht. Die Serverkonfiguration war wie folgt:
- 2 x Intel Silver 4114 (10×2,2 GHz HT)
- 8 x 32 GB DDR4
- 8 x 1920 GB Intel SSD im RAID-Controller Adaptec in RAID-10
Zum Thema Wahl zwischen RAID-Controller und Software-RAID MD könnte man einen eigenen Artikel schreiben. Ich sage nur, dass unsere Wahl von zwei Faktoren beeinflusst wurde:
- Zur Zeit der Aufgabenstellung haben wir alle Datenbanken auf RAID-Controllern installiert, daher kann man sagen, dass es historisch so gewachsen ist.
- Der Unterschied in der Leistung bei synthetischen Tests des Dateisystems und Tests mit verschiedenen Operationen in MySQL war minimal.
Wir haben das entstandene RAID-10 aufgeteilt: Wir haben eine einheitliche Volume-Gruppe (VG) für den gesamten Speicher (unter Berücksichtigung von overhead von etwa 6,7 GB) erstellt und eine logische Partition (Logical Volume, LV) für das System mit 50 GB angelegt. In einer normalen Situation würden wir den restlichen Speicherplatz für die Partition mit MySQL verwenden. Aber wir benötigten Thin Provisioning, daher haben wir zunächst einen sogenannten Pool erstellt, in dem wir eine Partition für \/var\lib\/mysql mit 3,5 TB (basierend auf den geschätzten Datenbankgrößen) angelegt haben:
lvcreate -l 100%FREE -T vga\/thin
lvcreate -V 3.5T -T vga\/thin -n mysqlWir haben die Partition in ext4 formatiert, sie eingehängt, ein Snapshot erstellt und die ursprüngliche Umgebung erhalten. Dann haben wir ein API um die Erstellung von Snapshots herum erstellt, um eine MySQL-Datenbankinstanz auf einem bestimmten Port zu starten und die erstellte Instanz zu entfernen. Da dafür ausschließlich Systemaufrufe verwendet werden, haben wir als Sprache für die Skripterstellung einfach Bash gewählt und als API-Verbindung HTTP → Bash eine Open-Source-Lösung implementiert. , geschrieben in Go.
Irgendwann werden wir unsere Bash-Skripte als Open Source veröffentlichen, aber ich werde vorerst den grundlegenden Algorithmus beschreiben:
Erstellung des Hauptsnapshots snapmain:
- Wir stoppen die Hauptinstanz.
- Wir setzen eine Sperre für Operationen mit dem Snapshot snapmain.
- Wir erstellen einen neuen Snapshot snapmain.
- Wir starten MySQL und heben die Sperre auf.
Erstellung einer Datenbank auf einem beliebigen Port aus snapmain:
- Wir setzen eine Sperre auf eine bestimmte Datenbankinstanz (Port).
- Wir prüfen, ob eine Sperre für die Erstellung des Hauptsnapshots existiert. Wenn ja, warten wir und überprüfen alle 5 Sekunden erneut.
- Wir prüfen, ob es eine alte LV-Partition der Instanz gibt.
3.1 Wenn ja, stoppen wir die MySQL-Instanz mit kill -9 und löschen die LV-Partition. - Wir erstellen aus snapmain eine neue Instanz.
- Wir bereiten die Verzeichnisse für diese Instanz vor und hängen sie ein.
- Wir entfernen die Hinweise auf Slave (Dateien) und starten die MySQL-Instanz.
- Wir machen sie zu einem Master.
- Wir heben die Sperre auf.
Löschen einer Datenbank auf einem beliebigen Port:
- Wir setzen eine Sperre auf eine bestimmte Datenbankinstanz (Port).
- Wir beenden die MySQL-Instanz mit kill -9.
- Wir hängen die Verzeichnisse ab.
- Wir löschen die LV-Partition und heben die Sperre auf.
Beispielbefehle zum Klonen der Partitionen einer neuen Datenbankinstanz:
lvcreate -n stage_3307 -s vga\/snapmain
lvchange -ay -K vga\/stage_3307
mount -o noatime,nodiratime,data=writeback \/dev\/mapper\/vga-stage_3307 \/mnt\/stage_3307Jetzt erzähle ich von dem Hauptproblem, mit dem wir bei der Verwendung von Thin Provisioning konfrontiert waren. Wir waren mit der Leistung der SSDs konfrontiert. Das geschah aufgrund der Besonderheiten von Thin LVM: Es arbeitet auf der Geräteebene mit low-level Chunks von standardmäßig 4 MB. So sah es aus:
- Wir erstellen einen Snapshot aus der Hauptpartition /var/lib/mysql.
- Wir starten die Replikation, um den Master einzuholen.
- Jede Änderung in den Tabellen der Replikat zwingt dazu, die alten, unveränderten Daten-Chunks im Snapshot-Abschnitt zu speichern.
- Jede Änderung in der getesteten Instanz zwingt dazu, die alten, unveränderten Daten-Chunks im Abschnitt des geklonten Snapshots für diese Instanz zu speichern.
- Wir erreichen eine IO-Belastung von 100 % auf dem Gerät, was alle Operationen verlangsamt und dazu führt, dass die Replikat allmählich hinterherhinkt.
- Am Ende des Arbeitstags haben wir ein Stand, das mehrere Stunden hinterherhinkt.
Wie wir damit umgegangen sind, um ein vernünftigeres Ergebnis zu erzielen (die wichtigsten Punkte):
RAID-Controller:
- Wir haben alle Arten der Zwischenspeicherung standardmäßig deaktiviert.
- Wir haben Writeback eingestellt (wenn Daten in den Puffer gelangen, wird der Schreibvorgang abgeschlossen, bevor die tatsächliche Speicherung auf der Festplatte erfolgt).
Dateisystem:
- Im Einhängepunkt /var/lib/mysql haben wir eingetragen noatime,nodiratime,data=writeback
- Wir haben das Journal von ext4 mit tune2fs deaktiviert.
MySQL:
- Wir haben eingetragen innodb_flush_method = O_DSYNC (dadurch wird die Schreibgeschwindigkeit erhöht, wodurch die Zuverlässigkeit verringert wird).
- Wir haben das Journal deaktiviert, Protokolle benötigen wir nicht.
- Wir haben eingetragen innodb_buffer_pool_size = 4G (je kleiner die Größe des InnoDB-Pools, desto schneller wird MySQL beim Herunterfahren abgeschaltet, und desto schneller erstellen wir einen Snapshot).
Dies ist bei weitem keine vollständige Liste, insbesondere nicht für MySQL. Die übrigen Änderungen sind jedoch geringfügig und oft nicht immer und nicht genau zutreffend. Zum Beispiel haben wir versucht, die Festplatten zu entlasten, und sogar innodb_parallel_doublewrite_path in /dev/shm verschoben, was in einigen Fällen beim Start einer nicht korrekt beendeten Instanz bis zu 5 Sekunden einsparen konnte.
Warum stoppen wir MySQL, bevor wir einen Snapshot erstellen? Denn wir könnten ihn von einer laufenden Replikat aufnehmen. Das ist richtig, allerdings wird eine neue DB-Instanz auf diesem Snapshot standardmäßig als beschädigt angesehen und benötigt beim Start ein vollständiges Scannen. Die Replikat zu stoppen, ist eindeutig schneller, auch wenn dies letztendlich die längste Operation im gesamten Prozess ist.
Infolgedessen haben wir akzeptablere Zeitpläne und einen einsatzbereiten Stand erhalten. Obwohl, wie man anhand des aussagekräftigsten Diagramms zur Überholzeit der Hauptreplikation sieht, die Situation noch weit vom Ideal entfernt ist:

Ein weiterer Nachteil ist die praktische Unmöglichkeit, den Thin LVM-Pool zu überwachen: neben den systemeigenen Standardfunktionen von iostat ist es unmöglich zu verstehen, welches Element des Pools derzeit die größte Last auf das Dateisystem erzeugt.
Ein großer Nachteil, der mit der oben beschriebenen Optimierung verbunden ist, soll gesondert erwähnt werden: Wir haben einen YOLO-Stand erhalten. Etwa alle ein bis zwei Monate hielt ext4 solchen Misshandlungen nicht stand und brach unwiderruflich zusammen, was eine Neuformatierung und das Wiederherstellen der Replik erforderte. Während wir an Geschwindigkeit gewannen, haben wir hoffnungslos die Stabilität ruiniert.
Welche Metriken sollten während des Betriebs von Thin LVM überwacht werden:
- Thin pool Daten %
- Thin pool Metadaten %
Wenn unser Stand nur ein volles Datenvolumen übersteht (es reicht aus, die Festplatten zu reinigen), führt ein volles Metadatenvolumen jedoch zum vollständigen Zusammenbruch des Pools und der Notwendigkeit, ihn von Grund auf neu zu erstellen.
Das Dateisystem innerhalb des Pools fragmentiert im Laufe der Zeit stark. Ich empfehle, täglich über cron den Befehl auszuführen fstrim -v /var/lib/mysql.
Zwischenergebnisse:
- Die Technologie ist einfach anwendbar, ebenso wie LVM selbst, und erfordert keine besondere Qualifikation des Ingenieurs.
- Sie eignet sich gut für kleine Datenbanken und moderate Lasten. Je kleiner die Datenbank, desto weniger Chunks bewegen sich im Dateisystem innerhalb des Pools, und desto geringer ist die Belastung der Festplatten.
- Für unsere Aufgabe begannen wir, nach anderen Lösungen zu suchen, worüber im nächsten Abschnitt gesprochen wird.
Der zweite Stand, die Technologie ZFS
Vor langer Zeit hatte ich mit dem Dateisystem ZFS zu tun, aber damals funktionierte ZFS zuverlässig gut auf seinem eigenen Betriebssystem Solaris. Es gab eine auf FreeBSD portierte Version mit einem akzeptablen Implementierungsgrad. Außerdem gab es ein unfertiges Port auf Linux, das kaum Verwendung fand. Aufgrund der B-Baum-Datenstruktur (übrigens hat auch InnoDB MySQL diese Struktur) zeigte ZFS bei Installationen mit einer sehr großen Anzahl von Dateien schwache Leistungen. All dies zusammen mit der Notwendigkeit, die technischen Grundlagen vor der Nutzung zu lernen, führte dazu, dass ich dieses Dateisystem lange Zeit aus meiner Praxis verbannt habe. Es erschienen ext4 und xfs, die zum Standard wurden. Aber da ZFS nach wie vor perfekt für unsere Anforderungen geeignet ist und die Linux-Version laut Berichten zu einem durchaus brauchbaren Produkt gewachsen ist (auch wenn sie nicht voll unterstützt wird, weshalb die Installation eines Systems auf ZFS von Grund auf nur mit verschiedenen Tricks möglich ist), haben wir uns entschieden, es auszuprobieren.
Aus verständlichen Gründen wählten wir einen Stand mit einer ähnlichen Konfiguration (außer dem RAID-Controller). Wir installierten acht SSDs mit jeweils 1920 GB. Da wir keine Lust hatten, ein eigenes Netzwerk-Image für die Installation des Servers auf reinem ZFS zu schreiben, nahmen wir von allen Platten jeweils 50 GB und machten daraus ein MD RAID-10 für das System. Die verbleibenden 1950 GB auf jeder Platte kombinierten wir in einem ZFS-Äquivalent von RAID-10:
zpool create zpool mirror /dev/sda2 /dev/sdb2 mirror /dev/sdc2 /dev/sdd2 mirror /dev/sde2 /dev/sdf2 mirror /dev/sdg2 /dev/sdh2Wir machten Partitionen für MySQL:
zfs create zpool/mysql
zfs set compression=gzip zpool/mysql
zfs set recordsize=128k zpool/mysql
zfs set atime=off zpool/mysql
zfs create zpool/mysql/data
zfs set recordsize=16k zpool/mysql/data
zfs set primarycache=metadata zpool/mysql/data
zfs set mountpoint=/var/lib/mysql zpool/mysql/dataBitte beachten Sie, dass wir die standardmäßige Datenkompression gzip aktiviert haben. Unsere Server-Ressourcen sind reichlich vorhanden und nicht vollständig ausgelastet. Infolgedessen verwandelten sich 3 TB unserer Datenbank in 1,6 TB, und da die maximale Leistung der Platten, wie auch im letzten Fall, das schwächste Glied ist, gilt: Je weniger Daten, desto besser. Von Anfang an profitieren wir also großartig von ZFS! Zu Spitzenzeiten beansprucht die vollständige Auslastung zur Unterstützung von gzip bis zu 4 Kerne, aber das ist uns nicht zu viel.
Dann ging die Implementierung schneller voran. Wir übertrugen die MySQL-Replikationseinstellungen eins zu eins von dem LVM-Stand. Es war nötig, etwas Zeit für die Umschreibung der Skripte in ZFS-Befehle aufzuwenden, aber im Großen und Ganzen blieben die Algorithmen unverändert. Beispiel für die Erstellung eines Snapshots:
zfs set snapdir=visible zpool/mysql/data
zfs create zpool/stage_3307
zfs clone zpool/mysql/data@snapmain zpool/stage_3307/data
zfs set mountpoint=/mnt/stage_3307 zpool/stage_3307/dataVon zusätzlichem Tuning: Die ZFS-Partitionen mit Metadaten und Logs l2arc und zil wurden in den Speicher ausgelagert. Bei unserer Aufgabe stellte sich später heraus, dass dies überflüssig war, doch vorerst haben wir diese Optimierung beibehalten, eine Anpassung ist bei Bedarf unkompliziert. Zu den negativen Effekten gehört, dass wir nach einem Serverneustart die entsprechenden Speicherbereiche neu erstellen müssen. Die Daten gehen dabei nicht verloren. Auszug zpool status:
logs
/dev/shm/zil_slog.img ONLINE 0 0 0
cache
/dev/shm/l2arc.img ONLINE 0 0 0In dieser Konfiguration haben wir begonnen, die Testumgebung zu evaluieren, und erzielten hervorragende Ergebnisse: Mit zwei gleichzeitig betriebenen DB-Instanzen (und einer aktiven Hauptreplik) auf Snapshots hatten wir eine Auslastung der Festplatten von 50-60 %.
Wir haben unser Hauptproblem beseitigt, was im Diagramm der Replikationsverzögerung deutlich wird (im Vergleich zur vorherigen Grafik im Abschnitt Dünnes LVM):

Zusätzlich und dank dessen haben wir alle Operationen deutlich beschleunigt: Die vollständige Erstellung eines Snapshots mit Stopp und Neustart der Replik dauert bis zu 40 Sekunden, das Bereitstellen einer neuen MySQL-Instanz aus einem Snapshot benötigt bis zu 20 Sekunden. Dies ist sowohl für uns als auch für unsere Programmcode-Tests mehr als ausreichend.
Zwischenergebnisse:
- Die Ergebnisse haben unseren Bedarf an einer Kopie der Produktionsdatenbank für die Code-Tests vollständig erfüllt.
- Die Technologie erfordert ein Einarbeiten: Man muss verstehen, was ZFS ist und wie man damit arbeitet.
- Wir haben den aktuellen Status der ZFS-Arbeit mit einer großen Anzahl (ab 1 Million) kleiner Dateien nicht getestet. Wir gehen jedoch davon aus, dass das Problem weiterhin besteht, weshalb ich dieses Dateisystem nicht für irgendwelche Dateispeicher empfehlen würde.
Was folgt jetzt?
Innerhalb des Standes gibt es nichts weiter zu tun, das Ergebnis ist für uns zufriedenstellend. Möglicherweise fügen wir in Zukunft Ausnahmen für Tabellen, die für Tests nicht erforderlich sind, in die Replikationseinstellungen ein; dies würde das Volumen der Datenbank weiter reduzieren. Wir haben das BTRFS-System und dessen Implementierung für Thin Provisioning nicht getestet. Diese Aufgabe steht jedoch nicht mehr zur Debatte, da das Hauptziel erreicht wurde. Insgesamt wünschen wir uns natürlich, von dem oben beschriebenen Ansatz abzugehen — funktionierende Datenbankmigrationen in die Testumgebung zu realisieren, einen separaten Testkreis für die Datenbank zu schaffen und uns mit Sharding der Hauptdatenbank zu befassen. Vieles davon setzen wir bereits in die Praxis um, worüber wir in zukünftigen Artikeln unbedingt berichten werden.
Ergebnisse
Die ursprüngliche Aufgabe wurde gelöst, auch wenn auf ungewöhnliche Weise. In den Zwischenberichten wurden die Vor- und Nachteile jeder eingesetzten Technologie beschrieben, also lassen Sie uns entscheiden, welche Technologie wann verwendet werden kann:
- Thin LVM — bei kleinen Datenbanken und wenn man ZFS nicht lernen möchte oder keine Zeit hat.
- ZFS — wenn man Erfahrung im Umgang damit hat oder bereit ist, in jeder Situation Zeit in das Lernen zu investieren.
Auf einer höheren Ebene betrachtet, ist dieser Artikel nicht nur ein Vergleich von zwei Dateisystemtechnologien. Die Hauptidee, die ich vermitteln und festigen möchte, ist, dass man in geschäftskritischen Situationen keine Angst haben sollte, unkonventionell zu denken und sich nicht nur auf fertige Rezepte zu stützen. Früher hätten wir als gesamtes Technikteam nur den Kopf schütteln und sagen können, dass die Aufgabe, dreiterabyte große Datenbankkopien in weniger als einer Minute zu erstellen, unmöglich sei und wir keine riskanten Technologien brauchen, also lassen Sie es uns richtig machen. Das wäre möglich gewesen, aber wir hätten dabei etwa ein halbes Jahr bis ein Jahr und viele Kundenreisen (Reisen sind unser wichtigster Geschäftsfaktor) ohne Tests beim Rollout verloren. Indem wir unkonventionell handelten, verloren wir nicht viel Zeit mit der Implementierung, sammelten Erfahrungen mit neuen und in Vergessenheit geratenen alten Technologien und ermöglichten Testen genau in dem Moment, als wir es dringend benötigten. Dies hatte zweifellos einen positiven Einfluss auf alle unsere Kennzahlen. Die Wahl liegt immer bei Ihnen, und wir werden unsererseits weiterhin in unserem Blog über interessante aktuelle und zukünftige Errungenschaften berichten.
Quelle: habr.com
