Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Dies ist die Fortsetzung einer langen Geschichte über unseren schwierigen Weg zur Schaffung eines leistungsstarken Hochlastsystems, das den Betrieb der Börse gewährleistet. Der erste Teil ist hier: habr.com/en/post/444300

Mysteriöser Fehler

Nach zahlreichen Tests wurde das aktualisierte Handels- und Clearingsystem in Betrieb genommen und wir stießen auf einen Fehler, über den wir eine detektivisch-mystische Geschichte schreiben konnten.

Kurz nach dem Start auf dem Hauptserver wurde eine der Transaktionen mit einem Fehler verarbeitet. Auf dem Backup-Server war jedoch alles in Ordnung. Es stellte sich heraus, dass eine einfache mathematische Operation zur Berechnung des Exponenten auf dem Hauptserver zu einem negativen Ergebnis gegenüber dem eigentlichen Argument führte! Wir haben unsere Forschung fortgesetzt und im SSE2-Register einen Unterschied in einem Bit gefunden, das für die Rundung bei der Arbeit mit Gleitkommazahlen verantwortlich ist.

Wir haben ein einfaches Testprogramm geschrieben, um den Exponenten mit gesetztem Rundungsbit zu berechnen. Es stellte sich heraus, dass in der von uns verwendeten Version von RedHat Linux ein Fehler bei der Arbeit mit der mathematischen Funktion auftrat, als das unglückliche Bit eingefügt wurde. Wir haben dies RedHat gemeldet, nach einer Weile haben wir einen Patch von ihnen erhalten und ihn ausgerollt. Der Fehler trat nicht mehr auf, aber es war unklar, woher dieses Bit überhaupt kam? Verantwortlich dafür war die Funktion fesetround aus der Sprache C. Wir haben unseren Code sorgfältig auf der Suche nach dem vermeintlichen Fehler analysiert: Wir haben alle möglichen Situationen überprüft; habe mir alle Funktionen angeschaut, die Rundungen verwendeten; versucht, eine fehlgeschlagene Sitzung zu reproduzieren; verschiedene Compiler mit unterschiedlichen Optionen verwendet; Es wurden statische und dynamische Analysen verwendet.

Die Fehlerursache konnte nicht gefunden werden.

Dann begannen sie mit der Überprüfung der Hardware: Sie führten einen Belastungstest der Prozessoren durch; überprüfte den RAM; Wir haben sogar Tests für den sehr unwahrscheinlichen Fall eines Multi-Bit-Fehlers in einer Zelle durchgeführt. Umsonst.

Am Ende haben wir uns für eine Theorie aus der Welt der Hochenergiephysik entschieden: Irgendein hochenergetisches Teilchen flog in unser Rechenzentrum, durchschlug die Gehäusewand, traf den Prozessor und sorgte dafür, dass die Auslöseklinke genau dort feststeckte. Diese absurde Theorie wurde „Neutrino“ genannt. Wenn Sie weit von der Teilchenphysik entfernt sind: Neutrinos interagieren fast nicht mit der Außenwelt und können den Betrieb des Prozessors schon gar nicht beeinflussen.

Da die Ursache des Ausfalls nicht ermittelt werden konnte, wurde der „beleidigende“ Server vorsorglich außer Betrieb genommen.

Nach einiger Zeit begannen wir, das Hot-Backup-System zu verbessern: Wir führten sogenannte „warme Reserven“ (warm) ein – asynchrone Replikate. Sie erhielten einen Strom von Transaktionen, die sich in verschiedenen Rechenzentren befinden könnten, Warms interagierte jedoch nicht aktiv mit anderen Servern.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Warum wurde das gemacht? Wenn der Backup-Server ausfällt, wird der an den Hauptserver gebundene Warmstart zum neuen Backup. Das heißt, nach einem Ausfall bleibt das System bis zum Ende der Handelssitzung nicht bei einem Hauptserver.

Und als die neue Version des Systems getestet und in Betrieb genommen wurde, trat der Rundungsbitfehler erneut auf. Darüber hinaus trat der Fehler mit zunehmender Anzahl warmer Server häufiger auf. Gleichzeitig hatte der Verkäufer nichts vorzuweisen, da es keine konkreten Beweise gab.

Bei der nächsten Analyse der Situation entstand die Theorie, dass das Problem mit dem Betriebssystem zusammenhängen könnte. Wir haben ein einfaches Programm geschrieben, das eine Funktion in einer Endlosschleife aufruft fesetround, merkt sich den aktuellen Zustand und überprüft ihn im Ruhezustand, und dies geschieht in vielen konkurrierenden Threads. Nachdem wir die Parameter für den Ruhezustand und die Anzahl der Threads ausgewählt hatten, begannen wir, den Bitfehler nach etwa 5 Minuten der Ausführung des Dienstprogramms konsistent zu reproduzieren. Der Red Hat-Support konnte es jedoch nicht reproduzieren. Tests unserer anderen Server haben gezeigt, dass nur Server mit bestimmten Prozessoren für den Fehler anfällig sind. Gleichzeitig wurde das Problem durch den Wechsel auf einen neuen Kernel behoben. Am Ende haben wir einfach das Betriebssystem ausgetauscht und die wahre Ursache des Fehlers blieb unklar.

Und plötzlich wurde letztes Jahr ein Artikel über Habré veröffentlicht.Wie ich einen Fehler in Intel Skylake-Prozessoren gefunden habe" Die darin beschriebene Situation war unserer sehr ähnlich, aber der Autor ging die Untersuchung weiter und stellte die Theorie auf, dass der Fehler im Mikrocode lag. Und wenn Linux-Kernel aktualisiert werden, aktualisieren die Hersteller auch den Mikrocode.

Weiterentwicklung des Systems

Obwohl wir den Fehler beseitigt haben, zwang uns diese Geschichte dazu, die Systemarchitektur zu überdenken. Schließlich waren wir nicht vor der Wiederholung solcher Fehler geschützt.

Die folgenden Grundsätze bildeten die Grundlage für die nächsten Verbesserungen des Reservierungssystems:

  • Du kannst niemandem vertrauen. Server funktionieren möglicherweise nicht ordnungsgemäß.
  • Mehrheitsvorbehalt.
  • Konsens sicherstellen. Als logische Ergänzung zum Mehrheitsvorbehalt.
  • Doppelausfälle sind möglich.
  • Vitalität. Das neue Hot-Standby-Schema sollte nicht schlechter sein als das vorherige. Der Handel sollte bis zum letzten Server ununterbrochen fortgesetzt werden.
  • Leichter Anstieg der Latenz. Jeder Ausfall bringt enorme finanzielle Verluste mit sich.
  • Minimale Netzwerkinteraktion, um die Latenz so gering wie möglich zu halten.
  • Auswahl eines neuen Master-Servers in Sekundenschnelle.

Keine der auf dem Markt verfügbaren Lösungen passte zu uns und das Raft-Protokoll steckte noch in den Kinderschuhen, also haben wir unsere eigene Lösung entwickelt.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Vernetzung

Zusätzlich zum Reservierungssystem haben wir mit der Modernisierung der Netzwerkinteraktion begonnen. Das I/O-Subsystem bestand aus vielen Prozessen, die den größten Einfluss auf Jitter und Latenz hatten. Da Hunderte von Prozessen TCP-Verbindungen verarbeiten, waren wir gezwungen, ständig zwischen ihnen zu wechseln, und im Mikrosekundenbereich ist dies ein ziemlich zeitaufwändiger Vorgang. Das Schlimmste ist jedoch, dass ein Prozess, wenn er ein Paket zur Verarbeitung empfängt, es an eine SystemV-Warteschlange sendet und dann auf ein Ereignis aus einer anderen SystemV-Warteschlange wartet. Wenn jedoch eine große Anzahl von Knoten vorhanden ist, stellen das Eintreffen eines neuen TCP-Pakets in einem Prozess und der Empfang von Daten in der Warteschlange in einem anderen zwei konkurrierende Ereignisse für das Betriebssystem dar. Wenn in diesem Fall keine physischen Prozessoren für beide Aufgaben verfügbar sind, wird einer bearbeitet und der zweite in eine Warteschlange gestellt. Es ist unmöglich, die Folgen vorherzusagen.

In solchen Situationen kann eine dynamische Prozessprioritätssteuerung verwendet werden, dies erfordert jedoch den Einsatz ressourcenintensiver Systemaufrufe. Infolgedessen haben wir mit klassischem Epoll auf einen Thread umgestellt, was die Geschwindigkeit erheblich erhöhte und die Transaktionsverarbeitungszeit verkürzte. Wir haben auch separate Netzwerkkommunikationsprozesse und die Kommunikation über SystemV abgeschafft, die Anzahl der Systemaufrufe erheblich reduziert und begonnen, die Prioritäten der Vorgänge zu steuern. Allein am I/O-Subsystem konnten je nach Szenario etwa 8-17 Mikrosekunden eingespart werden. Dieses Single-Thread-Schema wird seitdem unverändert verwendet; ein Epoll-Thread mit einem Rand reicht aus, um alle Verbindungen zu bedienen.

Transaktionsverarbeitung

Die wachsende Belastung unseres Systems erforderte die Modernisierung fast aller seiner Komponenten. Leider ist es aufgrund der Stagnation des Wachstums der Prozessortaktraten in den letzten Jahren nicht mehr möglich, Prozesse direkt zu skalieren. Aus diesem Grund haben wir beschlossen, den Engine-Prozess in drei Ebenen zu unterteilen. Die geschäftigste davon ist das Risikoprüfungssystem, das die Verfügbarkeit von Geldern auf Konten bewertet und die Transaktionen selbst erstellt. Aber Geld kann in verschiedenen Währungen vorliegen, und es musste herausgefunden werden, auf welcher Grundlage die Bearbeitung der Anfragen aufgeteilt werden sollte.

Die logische Lösung besteht darin, es nach Währungen aufzuteilen: Ein Server handelt in Dollar, ein anderer in Pfund und ein dritter in Euro. Wenn jedoch bei einem solchen Schema zwei Transaktionen zum Kauf verschiedener Währungen gesendet werden, entsteht das Problem der Desynchronisierung der Brieftasche. Aber die Synchronisierung ist schwierig und teuer. Daher wäre es richtig, das Shard getrennt nach Wallets und getrennt nach Instrumenten aufzuteilen. Übrigens haben die meisten westlichen Börsen nicht die Aufgabe, Risiken so akut zu prüfen wie wir, daher geschieht dies meist offline. Wir mussten eine Online-Verifizierung implementieren.

Lassen Sie es uns anhand eines Beispiels erklären. Ein Händler möchte 30 $ kaufen und die Anfrage geht an die Transaktionsvalidierung: Wir prüfen, ob dieser Händler zu diesem Handelsmodus berechtigt ist und ob er über die erforderlichen Rechte verfügt. Wenn alles in Ordnung ist, geht die Anfrage an das Risikoverifizierungssystem, d. h. um zu prüfen, ob die Mittel für den Abschluss einer Transaktion ausreichen. Es gibt einen Hinweis, dass der benötigte Betrag derzeit gesperrt ist. Die Anfrage wird dann an das Handelssystem weitergeleitet, das die Transaktion genehmigt oder ablehnt. Nehmen wir an, die Transaktion wird genehmigt – dann stellt das Risikoüberprüfungssystem fest, dass das Geld freigegeben ist und die Rubel in Dollar umgewandelt werden.

Im Allgemeinen enthält das Risikoprüfsystem komplexe Algorithmen und führt eine Vielzahl sehr ressourcenintensiver Berechnungen durch und prüft nicht einfach nur den „Kontostand“, wie es auf den ersten Blick scheinen mag.

Als wir begannen, den Engine-Prozess in Ebenen zu unterteilen, stießen wir auf ein Problem: Der zu diesem Zeitpunkt verfügbare Code nutzte in den Validierungs- und Verifizierungsphasen aktiv dasselbe Datenarray, was ein Umschreiben der gesamten Codebasis erforderte. Als Ergebnis haben wir eine Technik zur Verarbeitung von Anweisungen von modernen Prozessoren übernommen: Jeder von ihnen ist in kleine Phasen unterteilt und mehrere Aktionen werden parallel in einem Zyklus ausgeführt.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Nach einer kleinen Anpassung des Codes haben wir eine Pipeline für die parallele Transaktionsverarbeitung erstellt, in der die Transaktion in 4 Phasen der Pipeline unterteilt wurde: Netzwerkinteraktion, Validierung, Ausführung und Veröffentlichung des Ergebnisses

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Schauen wir uns ein Beispiel an. Wir verfügen über zwei Verarbeitungssysteme, seriell und parallel. Die erste Transaktion kommt an und wird zur Validierung in beide Systeme gesendet. Die zweite Transaktion trifft sofort ein: In einem parallelen System wird sie sofort zur Arbeit gebracht, und in einem sequentiellen System wird sie in eine Warteschlange gestellt, in der sie darauf wartet, dass die erste Transaktion die aktuelle Verarbeitungsstufe durchläuft. Das heißt, der Hauptvorteil der Pipeline-Verarbeitung besteht darin, dass wir die Transaktionswarteschlange schneller verarbeiten.

So sind wir auf das ASTS+-System gekommen.

Zwar läuft auch bei Förderbändern nicht alles so reibungslos. Nehmen wir an, wir haben eine Transaktion, die sich auf Datenarrays in einer benachbarten Transaktion auswirkt. Dies ist eine typische Situation für einen Austausch. Eine solche Transaktion kann nicht in einer Pipeline ausgeführt werden, da sie andere beeinflussen könnte. Diese Situation wird als Datengefährdung bezeichnet und solche Transaktionen werden einfach separat verarbeitet: Wenn die „schnellen“ Transaktionen in der Warteschlange aufgebraucht sind, stoppt die Pipeline, das System verarbeitet die „langsame“ Transaktion und startet die Pipeline dann erneut. Glücklicherweise ist der Anteil solcher Transaktionen am Gesamtfluss sehr gering, sodass die Pipeline so selten stoppt, dass die Gesamtleistung dadurch nicht beeinträchtigt wird.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Dann begannen wir, das Problem der Synchronisierung von drei Ausführungsthreads zu lösen. Das Ergebnis war ein System, das auf einem Ringpuffer mit Zellen fester Größe basierte. In diesem System hängt alles von der Verarbeitungsgeschwindigkeit ab, Daten werden nicht kopiert.

  • Alle eingehenden Netzwerkpakete gelangen in die Zuteilungsphase.
  • Wir platzieren sie in einem Array und markieren sie als verfügbar für Stufe 1.
  • Die zweite Transaktion ist eingetroffen, sie ist wieder für Stufe Nr. 1 verfügbar.
  • Der erste Verarbeitungsthread sieht die verfügbaren Transaktionen, verarbeitet sie und verschiebt sie in die nächste Stufe des zweiten Verarbeitungsthreads.
  • Anschließend wird die erste Transaktion verarbeitet und die entsprechende Zelle markiert deleted — es steht nun für eine neue Nutzung zur Verfügung.

Auf diese Weise wird die gesamte Warteschlange abgearbeitet.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Die Verarbeitung jeder Stufe dauert Einheiten oder zehn Mikrosekunden. Und wenn wir Standard-OS-Synchronisierungsschemata verwenden, verlieren wir mehr Zeit bei der Synchronisierung selbst. Deshalb haben wir angefangen, Spinlock zu verwenden. Dies ist jedoch in einem Echtzeitsystem eine sehr schlechte Form, und RedHat empfiehlt dies strikt nicht. Daher wenden wir 100 ms lang einen Spinlock an und wechseln dann in den Semaphormodus, um die Möglichkeit eines Deadlocks auszuschließen.

Dadurch erreichten wir eine Leistung von etwa 8 Millionen Transaktionen pro Sekunde. Und buchstäblich zwei Monate später Artikel Über LMAX Disruptor haben wir eine Beschreibung einer Schaltung mit der gleichen Funktionalität gesehen.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Jetzt könnte es mehrere Ausführungsthreads gleichzeitig geben. Alle Transaktionen wurden einzeln und in der Reihenfolge ihres Eingangs verarbeitet. Dadurch stieg die Spitzenleistung von 18 auf 50 Transaktionen pro Sekunde.

Börsenrisikomanagementsystem

Der Perfektion sind keine Grenzen gesetzt, und schon bald begannen wir erneut mit der Modernisierung: Im Rahmen von ASTS+ begannen wir, Risikomanagement- und Abwicklungsbetriebssysteme in autonome Komponenten zu überführen. Wir haben eine flexible moderne Architektur und ein neues hierarchisches Risikomodell entwickelt und versucht, die Klasse wo immer möglich zu verwenden fixed_point statt double.

Doch sofort tauchte ein Problem auf: Wie kann man die gesamte seit vielen Jahren funktionierende Geschäftslogik synchronisieren und in das neue System übertragen? Infolgedessen musste die erste Version des Prototyps des neuen Systems aufgegeben werden. Die zweite Version, die derzeit in Produktion ist, basiert auf demselben Code, der sowohl im Handels- als auch im Risikoteil funktioniert. Während der Entwicklung war das Git-Merge zwischen zwei Versionen das Schwierigste. Unser Kollege Evgeniy Mazurenok führte diese Operation jede Woche durch und jedes Mal fluchte er sehr lange.

Bei der Auswahl eines neuen Systems mussten wir sofort das Problem der Interaktion lösen. Bei der Auswahl eines Datenbusses musste auf stabilen Jitter und minimale Latenz geachtet werden. Hierfür war das InfiniBand-RDMA-Netzwerk am besten geeignet: Die durchschnittliche Verarbeitungszeit ist viermal kürzer als in 4-G-Ethernet-Netzwerken. Aber was uns wirklich faszinierte, war der Unterschied in den Perzentilen – 10 und 99.

Natürlich hat InfiniBand seine Herausforderungen. Erstens eine andere API – Ibverbs statt Sockets. Zweitens gibt es fast keine allgemein verfügbaren Open-Source-Messaging-Lösungen. Wir haben versucht, unseren eigenen Prototypen zu erstellen, aber es stellte sich als sehr schwierig heraus, also haben wir uns für eine kommerzielle Lösung entschieden – Confinity Low Latency Messaging (ehemals IBM MQ LLM).

Dann stellte sich die Aufgabe, das Risikosystem richtig aufzuteilen. Wenn Sie einfach die Risk Engine entfernen und keinen Zwischenknoten erstellen, können Transaktionen aus zwei Quellen gemischt werden.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Die sogenannten Ultra Low Latency-Lösungen verfügen über einen Reordering-Modus: Transaktionen aus zwei Quellen können beim Eingang in die gewünschte Reihenfolge gebracht werden; dies wird über einen separaten Kanal zum Austausch von Informationen über die Reihenfolge umgesetzt. Wir verwenden diesen Modus jedoch noch nicht: Er verkompliziert den gesamten Prozess und wird in einigen Lösungen überhaupt nicht unterstützt. Darüber hinaus müssten jeder Transaktion entsprechende Zeitstempel zugewiesen werden, und in unserem Schema ist es sehr schwierig, diesen Mechanismus korrekt zu implementieren. Daher haben wir das klassische Schema mit einem Message Broker verwendet, also mit einem Dispatcher, der Nachrichten zwischen der Risk Engine verteilt.

Das zweite Problem bezog sich auf den Client-Zugriff: Wenn mehrere Risk Gateways vorhanden sind, muss sich der Client mit jedem von ihnen verbinden, was Änderungen an der Client-Ebene erfordert. Wir wollten dies an dieser Stelle vermeiden, daher verarbeitet das aktuelle Risk Gateway-Design den gesamten Datenstrom. Dadurch wird der maximale Durchsatz stark eingeschränkt, die Systemintegration jedoch erheblich vereinfacht.

Vervielfältigung

Unser System sollte keinen einzigen Fehlerpunkt haben, das heißt, alle Komponenten müssen dupliziert werden, einschließlich des Nachrichtenbrokers. Dieses Problem haben wir mit dem CLLM-System gelöst: Es enthält einen RCMS-Cluster, in dem zwei Dispatcher im Master-Slave-Modus arbeiten können und bei Ausfall eines Dispatchers automatisch auf den anderen umgeschaltet wird.

Arbeiten mit einem Backup-Rechenzentrum

InfiniBand ist für den Betrieb als lokales Netzwerk optimiert, also für den Anschluss von Rack-Geräten, und ein InfiniBand-Netzwerk kann nicht zwischen zwei geografisch verteilten Rechenzentren verlegt werden. Deshalb haben wir einen Bridge/Dispatcher implementiert, der über reguläre Ethernet-Netzwerke eine Verbindung zum Nachrichtenspeicher herstellt und alle Transaktionen an ein zweites IB-Netzwerk weiterleitet. Wenn wir von einem Rechenzentrum migrieren müssen, können wir jetzt auswählen, mit welchem ​​Rechenzentrum wir arbeiten möchten.

Ergebnisse

Das alles wurde nicht auf einmal erledigt; die Entwicklung einer neuen Architektur erforderte mehrere Iterationen. Wir haben den Prototyp in einem Monat erstellt, aber es dauerte mehr als zwei Jahre, bis er funktionsfähig war. Wir haben versucht, den besten Kompromiss zwischen einer Erhöhung der Transaktionsverarbeitungszeit und einer Erhöhung der Systemzuverlässigkeit zu erreichen.

Da das System stark aktualisiert wurde, haben wir die Datenwiederherstellung aus zwei unabhängigen Quellen implementiert. Wenn der Nachrichtenspeicher aus irgendeinem Grund nicht ordnungsgemäß funktioniert, können Sie das Transaktionsprotokoll aus einer zweiten Quelle beziehen – der Risk Engine. Dieser Grundsatz wird im gesamten System beachtet.

Unter anderem konnten wir die Client-API beibehalten, sodass weder für Broker noch für andere Personen wesentliche Überarbeitungen für die neue Architektur erforderlich waren. Wir mussten einige Schnittstellen ändern, wesentliche Änderungen am Betriebsmodell waren jedoch nicht erforderlich.

Wir haben die aktuelle Version unserer Plattform Rebus genannt – als Abkürzung für die beiden auffälligsten Neuerungen in der Architektur, Risk Engine und BUS.

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Ursprünglich wollten wir nur den Clearing-Teil zuweisen, aber das Ergebnis war ein riesiges verteiltes System. Kunden können jetzt entweder mit dem Trade Gateway, dem Clearing Gateway oder beiden interagieren.

Was wir letztendlich erreicht haben:

Die Entwicklung der Architektur des Handels- und Clearingsystems der Moskauer Börse. Teil 2

Die Latenz wurde reduziert. Bei einem geringen Transaktionsvolumen funktioniert das System genauso wie die Vorgängerversion, hält aber gleichzeitig einer deutlich höheren Belastung stand.

Die Spitzenleistung stieg von 50 auf 180 Transaktionen pro Sekunde. Eine weitere Steigerung wird durch den einzigen Stream des Order Matchings erschwert.

Es gibt zwei Möglichkeiten zur weiteren Verbesserung: Parallelisierung des Abgleichs und Änderung der Funktionsweise mit Gateway. Jetzt arbeiten alle Gateways nach einem Replikationsschema, das bei einer solchen Belastung nicht mehr normal funktioniert.

Abschließend kann ich denjenigen, die Unternehmenssysteme fertigstellen, einige Ratschläge geben:

  • Seien Sie jederzeit auf das Schlimmste vorbereitet. Probleme treten immer unerwartet auf.
  • Es ist normalerweise unmöglich, Architektur schnell neu zu gestalten. Vor allem, wenn Sie maximale Zuverlässigkeit über mehrere Indikatoren hinweg erreichen müssen. Je mehr Knoten vorhanden sind, desto mehr Ressourcen werden für die Unterstützung benötigt.
  • Alle kundenspezifischen und proprietären Lösungen erfordern zusätzliche Ressourcen für Forschung, Support und Wartung.
  • Schieben Sie die Lösung von Problemen der Systemzuverlässigkeit und Wiederherstellung nach Ausfällen nicht auf, sondern berücksichtigen Sie sie bereits in der ersten Entwurfsphase.

Source: habr.com

Kommentar hinzufügen