
Mein Name ist Juri, ich bin Leiter der Systemadministrationsgruppe bei Citymobil. Heute möchte ich meine Erfahrungen mit der Technologie des Thin Provisioning von Linux-Dateisystemen teilen und erläutern, wie sie in den CI/CD-Prozessen unseres Unternehmens eingesetzt werden kann. Wir werden die Situation betrachten, in der wir für die automatische Codeüberprüfung bei der Bereitstellung in die Produktion so schnell wie möglich Kopien der MySQL-Datenbank benötigen, die der 'Produktionsversion' so nah wie möglich sind und sowohl les- als auch schreibbar sind.
Einführung: Warum schädliche Ratschläge geben?
Eine berechtigte Frage, schließlich gibt es bewährte Mechanismen für Migrationen von Datenbankschemata in Testumgebungen. Warum sollte man die Hauptdatenbank überhaupt bis zu solchen Größen bringen? Außerdem benötigen wir für Tests nicht alle Daten. Ich werde versuchen, es zu erklären.
Vor etwa einem Jahr, angesichts des starken Wachstums unseres Taxiaggregators (2018 haben wir uns in Bezug auf abgeschlossene Fahrten ungefähr verzehnfacht), sind die Datenmengen, die Serverlast und die Frequenz der Deployments gestiegen. Wir befanden uns in folgender Situation:
- Die Hauptdatenbank MySQL wuchs auf etwa 1000 Tabellen mit einem Gesamtvolumen von 2,5 TB und wuchs weiter.
- Es war nicht möglich, schnell zu shardieren und die Datenbank zu verteilen. Der alte Ansatz ‚Ich schreibe in die Datenbank, was ich will und wie ich will‘, viele JOINs und interne Abhängigkeiten zwischen den Tabellen haben dies nicht erlaubt.
- Es gab keinen Mechanismus für migrationsfähige Datenbankschemata in Testumgebungen.
- Es gab keine automatisierte Codeprüfung bei der Bereitstellung in die Produktion.
Die letzte Herausforderung sollte so schnell wie möglich gelöst werden. Es wurden bereits Postman-Tests zur Überprüfung des Haupt-PHP-Monolithen geschrieben, aber es fehlte an einer aktuellen Datenbank. Wir konnten nachts keine Replik erstellen, sie zum Master machen und tagsüber zur Verfügung stellen: Eine große Anzahl von Bereitstellungen und Änderungen, sowohl an den Daten als auch am Datenbankschema, hätte die Testumgebung bereits gegen Mittag unbrauchbar gemacht. Außerdem wäre es ineffizient gewesen, Bereitstellungen nur auf den Arbeitstag zu beschränken.
Dennoch wurde die Aufgabe erfüllt: Wir erhielten die erste produktive Testumgebung bereits nach zwei Wochen. Im vergangenen Jahr hat sie viele Veränderungen durchgemacht und wird weiterhin genutzt.
Ich werde nun alle Schritte und Phasen der Entwicklung unserer Lösung im Detail beschreiben. Sie werden sehen, dass diese Methode ihr Daseinsrecht verdient.
Was versteht man unter ‚feiner Reservierung‘?
Dies ist eine Hardware- oder Software-Technologie (auch bekannt als sparse volumes), die es ermöglicht, mehr benötigte Ressourcen bereitzustellen, als tatsächlich vorhanden sind. Dabei muss das zugewiesene Volumen den Kriterien just-enough (so viel, wie nötig) und just-in-time (zum notwendigen Zeitpunkt) entsprechen. In der Regel wird Thin Provisioning in verschiedenen Speichersystemen eingesetzt, um Speicherplatz in erforderlichen Mengen bereitzustellen, die die tatsächlich verfügbaren Kapazitäten überschreiten. Diese Technologie wird von verschiedenen Dateisystemen unterstützt, wie z. B. LVM2, ZFS, BTRFS. Sie wird häufig in Virtualisierungshypervisoren verwendet. Mit Thin Provisioning konnten wir schnell aus Snapshots des primären Datenbereichs so viele Kopien dieses Bereichs erstellen, wie wir benötigten (data-Direktorium der MySQL-Datenbank).
Erstes Testsystem, Thin LVM-Technologie
Dieses Kapitel kann auch als „Wie man mit , die Stabilität des Dateisystems und der MySQL-Datenbank auf unverschämte Werte reduziert“ betitelt werden.
Da wir bereits LVM für die Erstellung der Hauptpartitionen des Betriebssystems genutzt haben, beschlossen wir, genau damit zu beginnen. Zunächst benötigten wir eine separate physische Maschine – eine replizierte Instanz unserer Haupt-MySQL-Datenbank, auf der wir nach Bedarf einen Snapshot der Replik und einen separaten MySQL-Instanz hochfahren konnten. Während der Testphase erlaubten wir es, auf dieser Instanz Änderungen vorzunehmen, 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 Adaptec RAID-Controller in RAID-10
Über die Wahl zwischen RAID-Controllern und softwarebasiertem RAID MD könnte man einen eigenen Artikel schreiben. Ich sage nur, dass unsere Entscheidung von zwei Faktoren beeinflusst wurde:
- Zu dem Zeitpunkt, als die Aufgabe formuliert wurde, haben wir alle Datenbanken auf RAID-Controllern eingerichtet, sodass man sagen kann, dass dies historisch so gewachsen ist.
- Der Leistungsunterschied bei synthetischen Tests des Dateisystems und bei Tests mit verschiedenen Operationen in MySQL war minimal.
Wir haben das resultierende RAID-10 aufgeteilt: Wir haben eine zusammenhängende Volume Group (VG) für das gesamte Volumen eingerichtet (mit einem Overhead von etwa 6,7 GB) und eine logische Partition (Logical Volume, LV) für das System mit 50 GB erstellt. In der Regel würden wir den restlichen Platz für eine Partition mit MySQL nutzen. Aufgrund der Notwendigkeit von Thin Provisioning 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 erwarteten Datenbankvolumen) angelegt haben:
lvcreate -l 100%FREE -T vga/thin
lvcreate -V 3.5T -T vga/thin -n mysqlWir haben die Partition mit ext4 formatiert, sie gemountet, eine Kopie erstellt und den ursprünglichen Stand erhalten. Anschließend haben wir eine API-Bindung erstellt, die Snapshots erstellen, eine MySQL-Datenbankinstanz auf einem bestimmten Port hochziehen und die erstellte Instanz entfernen soll. Da hierbei ausschließlich Systemaufrufe verwendet werden, haben wir als Scriptsprache herkömmliches Bash gewählt und für die API-Bindung HTTP → Bash eine Open-Source-Lösung bereitgestellt. , geschrieben in Go.
Irgendwann werden wir unsere Bash-Skripte als Open Source bereitstellen, aber vorerst werde ich einfach den grundlegenden Algorithmus beschreiben:
Erstellung des Haupt-Snapshots snapmain:
- Wir stoppen die Hauptkopie.
- Wir setzen eine Sperre für die Operationen mit dem Snapshot snapmain.
- Wir erstellen einen neuen Snapshot snapmain.
- Wir starten MySQL und entfernen die Sperre.
Datenbankerstellung auf einem beliebigen Port aus snapmain:
- Wir setzen eine Sperre für eine bestimmte Instanz der Datenbank (Port).
- Wir prüfen, ob eine Sperre für die Erstellung des Hauptsnapshots vorhanden ist. Falls ja, warten wir und überprüfen alle 5 Sekunden erneut.
- Wir überprüfen, ob es eine alte LV-Partition der Instanz gibt.
3.1 Falls ja, stoppen wir die MySQL-Instanz mit kill -9 und löschen die LV-Partition. - Wir erstellen aus snapmain eine neue Instanz.
- Wir richten die Verzeichnisse für diese Instanz ein und montieren sie.
- Wir entfernen die Slave-Zeichen (Dateien) und starten die MySQL-Instanz.
- Wir machen daraus einen Master.
- Wir heben die Sperre auf.
Datenbanklöschung auf einem beliebigen Port:
- Wir setzen eine Sperre für eine bestimmte Instanz der Datenbank (Port).
- Wir beenden die MySQL-Instanz mit kill -9.
- Wir demontieren die Verzeichnisse.
- Wir löschen die LV-Partition und heben die Sperre auf.
Beispielbefehle zum Klonen der Partitionen der 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 möchte ich über das Hauptproblem sprechen, das wir beim Einsatz von Thin Provisioning festgestellt haben. Wir stießen auf Leistungseinschränkungen der SSD-Laufwerke. Dies geschah aufgrund der Eigenschaften von Thin LVM: Es arbeitet auf der Geräteebene mit low-level Chunks, die standardmäßig 4 MB groß sind. So sah das aus:
- Wir erstellen einen Snapshot aus der Hauptpartition /var/lib/mysql.
- Wir starten die Replikation, um den Master aufzuholen.
- Jede Änderung in den Tabellen der Replikat zwingt dazu, die alten, unveränderten Datenchunks im Snapshott-Bereich zu speichern.
- Jede Änderung im hochgefahrenen Testexemplar zwingt dazu, die alten, unveränderten Datenchunks im Bereich des geklonten Snapshots für dieses Exemplar zu speichern.
- Wir erreichen eine 100%ige Belastung der Eingabe- und Ausgabeoperationen des Geräts, was zu einer Verlangsamung aller Vorgänge und einem schrittweisen Rückstand der Replikat führt.
- Am Ende des Arbeitstags haben wir ein Standbein, das mehrere Stunden hinterherhinkt.
Wie wir dagegen angegangen sind, um ein akzeptableres Ergebnis zu erzielen (die wichtigsten Punkte):
RAID-Controller:
- Wir haben standardmäßig alle Arten von Caching deaktiviert.
- Wir haben den Writeback aktiviert (bei Daten im Puffer wird der Schreibvorgang abgeschlossen, bevor die tatsächliche Speicherung auf der Festplatte erfolgt).
Dateisystem:
- An dem Einhängepunkt /var/lib/mysql haben wir eingetragen: noatime,nodiratime,data=writeback
- Wir haben das Journalen von ext4 mit tune2fs deaktiviert.
MySQL:
- Wir haben eingetragen: innodb_flush_method = O_DSYNC (wir haben die Schreibgeschwindigkeit erhöht und damit die Zuverlässigkeit verringert).
- Wir haben das Journalen deaktiviert, Logs sind für uns nicht erforderlich.
- Wir haben eingetragen: innodb_buffer_pool_size = 4G (je kleiner die Größe des InnoDB-Pools, desto schneller wird MySQL beim Stopp heruntergefahren, und desto schneller erstellen wir einen Snapshot).
Dies ist bei weitem nicht die vollständige Liste, insbesondere für MySQL. Die anderen Änderungen sind jedoch geringfügig und oft nicht immer und nicht genau anwendbar. Zum Beispiel haben wir in dem Versuch, die Festplatten zu entlasten, sogar innodb_parallel_doublewrite_path nach /dev/shm verschoben, was in einigen Fällen beim Start eines nicht korrekt beendeten Instanz uns bis zu 5 Sekunden einsparen konnte.
Warum stoppen wir MySQL, bevor wir einen Snapshot erstellen? Wir könnten ihn auch von einer aktiven Replik machen. Das ist korrekt, aber eine neue Datenbankinstanz aus diesem Snapshot wird standardmäßig als beschädigt betrachtet und benötigt beim Starten einen vollständigen Scan. Das Stoppen der Replik ist definitiv schneller, auch wenn dies letztendlich die zeitaufwändigste Operation im gesamten Prozess ist.
Infolgedessen haben wir akzeptablere Zeiten und eine funktionsbereite Umgebung erhalten. Obwohl, wie das überzeugendste Diagramm der Verzögerung der primären Replik zeigt, die Situation noch weit von ideal entfernt ist:

Weitere Nachteile sind die praktisch unmögliche Überwachung des Thin LVM-Pools: Neben den systeminternen Standardfunktionen wie iostat ist es unmöglich zu verstehen, welches Element des Pools gerade die größte Belastung für das Dateisystem verursacht.
Ein großer Nachteil, der mit der oben beschriebenen Optimierung verbunden ist, sollte besonders hervorgehoben werden: Wir haben einen YOLO-Stand erhalten. Etwa alle ein bis zwei Monate hielt ext4 diesen Misshandlungen nicht stand und fiel unwiederbringlich aus, was eine Neuformatierung und das erneute Hochladen des Abbilds erforderte. Während wir an Geschwindigkeit gewonnen haben, haben wir die Stabilität hoffnungslos aufs Spiel gesetzt.
Auf welche Metriken sollte während des Betriebs von Thin LVM geachtet werden:
- Thin Pool Daten-%
- Thin Pool Metadaten-%
Wenn unser Stand den Mangel an Speicherplatz für Daten übersteht (es reicht, die Festplatten zu bereinigen), wird der Mangel an Speicherplatz für Metadaten zum vollständigen Zusammenbruch des Pools und der Notwendigkeit führen, ihn von Grund auf neu zu erstellen.
Das Dateisystem innerhalb des Pools fragmentiert sich im Laufe der Zeit stark. Ich empfehle, einmal täglich über Cron den Befehl auszuführen fstrim -v /var/lib/mysql.
Zwischenbilanz:
- Die Technologie ist einfach anwendbar, ebenso wie LVM, und erfordert kein besonderes Fachwissen des Ingenieurs.
- Sie eignet sich gut für kleine und nicht übermäßig beanspruchte Datenbanken. Je kleiner die Datenbank, desto weniger Chunks werden innerhalb des Pools im Dateisystem verschoben, was die Belastung der Festplatten verringert.
- Für unsere Aufgabe haben wir nach anderen Lösungen gesucht, die im nächsten Abschnitt behandelt werden.
Zweite Lösung: ZFS-Technologie
Vor langer Zeit hatte ich Erfahrungen mit dem Dateisystem ZFS, das damals vor allem auf seinem nativen Betriebssystem Solaris gut funktionierte. Es gab eine FreeBSD-portierte Version mit einer ausreichend guten Implementierung. Zudem existierte ein unfertiger Port für Linux, der nur selten verwendet wurde. Aufgrund der B-Baum-Datenstruktur (übrigens die gleiche, die auch InnoDB von MySQL nutzt) schnitt ZFS in Installationen mit einer sehr hohen Anzahl von Dateien schlecht ab. All dies zusammen mit der notwendigen Einarbeitung in die Materie führte dazu, dass ich dieses Dateisystem langfristig aus meiner Praxis verbannt habe. Es kamen ext4 und xfs auf, die zum Standard wurden. Aber da ZFS für unsere Aufgabenstellung mehr als geeignet ist und die Linux-Version, laut den Rückmeldungen, sich zu einem durchaus brauchbaren Produkt entwickelt hat (wenngleich nicht mit vollständiger Unterstützung, weshalb man ein System auf ZFS nur mit verschiedenen Tricks von Grund auf installieren kann), haben wir beschlossen, es auszuprobieren.
Aus verständlichen Gründen haben wir ein ähnliches Konfiguration gewählt (mit Ausnahme des RAID-Controllers). Wir haben acht SSDs mit jeweils 1920 GB installiert. Da wir keine Lust hatten, unser eigenes Netzwerk-Image für die Einrichtung des Servers auf nacktem ZFS zu schreiben, haben wir von allen Festplatten jeweils 50 GB abgezweigt und ein MD RAID-10 für das System erstellt. Die verbleibenden 1950 GB auf jeder Festplatte haben wir in ein ZFS-ähnliches RAID-10 integriert:
zpool create zpool mirror /dev/sda2 /dev/sdb2 mirror /dev/sdc2 /dev/sdd2 mirror /dev/sde2 /dev/sdf2 mirror /dev/sdg2 /dev/sdh2Wir haben Partitionen für MySQL erstellt:
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 Datensicherung mit gzip aktiviert haben. Auf unserem Server haben wir viele CPU-Ressourcen, die nicht vollständig genutzt werden. Infolgedessen hat sich unsere 3 TB große Datenbank auf 1,6 TB reduziert, und da die maximalen Festplattengeschwindigkeiten, wie auch im vorherigen Fall, das schwächste Glied sind, ist weniger Datenmenge besser – wir profitieren von Anfang an enorm von ZFS! Zu Spitzenzeiten können bis zu 4 Kerne für die gzip-Verarbeitung benötigt werden, aber damit haben wir kein Problem.
Die Implementierung verlief anschließend schneller. Wir haben die MySQL-Replikationskonfiguration eins zu eins vom LVM-Setup übernommen. Es hat einige Zeit in Anspruch genommen, die Skripte in ZFS-Kommandos umzuschreiben, aber insgesamt blieben die Algorithmen unverändert. Beispiel zur 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: Wir haben die ZFS-Partitionen mit Metadaten und Logs in den l2arc und zil-Speicher ausgelagert. Wie sich später herausstellte, war dies für unsere Aufgabe überflüssig, aber wir haben diese Optimierung vorerst beibehalten, da sie bei Bedarf leicht geändert werden kann. Nachteilig ist, dass die entsprechenden Speicherbereiche nach einem Neustart des Servers neu erstellt werden 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, das Setup zu testen, und erzielten hervorragende Ergebnisse: Mit zwei gleichzeitig arbeitenden DB-Instanzen (und einer aktiven Hauptreplik) lag die Festplattenauslastung bei 50-60% auf Snapshots.
Wir haben unser Hauptproblem gelöst, was im Diagramm zur Replikationsverzögerung deutlich wird (im Vergleich mit dem vorherigen Diagramm im Abschnitt Thin LVM):

Darüber hinaus haben wir damit in allen Operationen erheblich an Geschwindigkeit gewonnen: Die vollständige Erstellung eines Snapshots mit dem Stoppen und Starten der Replik dauert bis zu 40 Sekunden, während das Bereitstellen eines neuen MySQL-Instances aus dem Snapshot bis zu 20 Sekunden in Anspruch nimmt. Das entspricht voll und ganz unseren Erwartungen sowie den Tests unseres Codes.
Zwischenbilanz:
- Die Ergebnisse haben unsere Anforderungen an die Erstellung einer Kopie der Produktionsdatenbank für die Code-Tests vollständig erfüllt.
- Die Technologie erfordert ein gewisses Verständnis: Man muss wissen, was ZFS ist und wie man damit arbeitet.
- Wir haben den aktuellen Status von ZFS mit einer großen Anzahl (über 1 Million) kleiner Dateien nicht getestet. Wir vermuten jedoch, dass das Problem weiterhin besteht, weshalb ich dieses Dateisystem nicht für die Nutzung in Dateispeicherlösungen empfehlen würde.
Wie geht es weiter?
Im Rahmen des Standes möchten wir nichts weiter unternehmen, das Ergebnis ist zufriedenstellend. Vielleicht fügen wir später Ausnahmen für Tabellen in den Replikationskonfigurationen hinzu, die für Tests nicht erforderlich sind, was das Datenbankvolumen weiter reduzieren wird. Wir haben das BTRFS-System und seine Implementierung der Thin Provisioning-Technologie nicht getestet. Allerdings steht diese Aufgabe nicht mehr im Vordergrund, da das Hauptziel erreicht wurde. Insgesamt streben wir natürlich eine Abkehr von dem oben beschriebenen Ansatz an – wir möchten funktionierende Datenbankmigrationen in die Testumgebung implementieren, einen separaten Test-Datenbankkreis erstellen und uns mit der Shardierung der Hauptdatenbank beschäftigen. Vieles davon setzen wir bereits um, und wir werden darüber in zukünftigen Artikeln berichten.
Ergebnisse
Die ursprüngliche Aufgabe wurde gelöst, wenn auch auf ungewöhnliche Weise. In den Zwischenergebnissen wurden die Vor- und Nachteile jeder der eingesetzten Technologien beschrieben, also lassen Sie uns entscheiden, welche Technologie wann eingesetzt werden kann:
- Thin LVM – bei kleinen Datenbanken und wenn man ZFS nicht erlernen möchte oder nicht die Zeit dazu hat.
- ZFS – wenn man Erfahrung im Umgang mit ihr hat oder bereit ist, Zeit in ihr Studium zu investieren, in allen Situationen.
Auf einer höheren Ebene betrachtet, handelt es sich bei diesem Artikel nicht nur um einen Vergleich der Technologien zweier Dateisysteme. Die zentrale Idee, die ich vermitteln und festigen möchte, ist, dass man nicht zögern sollte, unkonventionell zu denken in geschäftskritischen Situationen und sich nicht nur auf fertige Rezepte zu stützen. Früher hätte unser gesamtes Technikteam den Kopf geschüttelt und gesagt, dass die Aufgabe, dreiterabyte große Datenbankkopien in weniger als einer Minute zu erstellen, unmöglich ist und wir keine riskanten Technologien benötigen, sondern es richtig machen sollten. Das wäre möglich gewesen, aber wir hätten etwa ein halbes Jahr bis ein Jahr verloren und viele Kundenreisen (Reisen sind unser Hauptgeschäftsindikator) ohne Tests und während der Implementierung. Indem wir unkonventionell vorgingen, verloren wir nicht viel Zeit in der Einführung, sammelten Erfahrungen mit neuen und in Vergessenheit geratenen alten Technologien und boten Tests genau in dem Moment an, als wir sie dringend benötigten. Ohne Zweifel hatte dies positive Auswirkungen auf alle unsere Kennzahlen. Die Entscheidung liegt immer bei Ihnen, und wir werden von unserer Seite weiterhin in unserem Blog über interessante aktuelle und zukünftige Erfolge berichten.
Quelle: habr.com
