
von St-Pete
Hallo zusammen! Ich bin Mons Anderson, Architekt der Plattform , und ich werde erzählen, wie wir unseren S3-Speicher aufgebaut haben, wie er funktioniert, welche Lösungen erfolgreich waren und welche man ändern sollte, wenn wir solch ein Projekt heute von Grund auf neu starten würden.
Der Artikel basiert auf einem Bericht von Mail.ru Cloud Solutions & Tarantool. In dem Artikel sprechen wir über:
- wie der Mail.ru Speicher eingerichtet war, auf dem wir den S3-Speicher aufgebaut haben;
- was wir hinzugefügt haben, um den Mail.ru Cloud Storage zu schaffen;
- wie das Objektmodell der Speicherung funktioniert und welche Schritte unternommen wurden, um in die Produktion zu gehen;
- über die Verbesserungen des produktiven Systems: Failover und Skalierung;
- wie wir Sharding und Re-Sharding umgesetzt haben;
- und auch über die Arbeit mit SSL-Zertifikaten.
Wenn Sie nicht lesen möchten, können Sie .
Wie der Mail.ru Speicher eingerichtet war, auf dem wir den S3-Speicher aufgebaut haben
Die Entwicklung unseres S3 begann auf dem Speicher der Mail.ru Cloud, daher ist es zunächst wichtig, zu erzählen, wie er aufgebaut ist und was er kann.
Das Mail.ru Cloud-Speicher besteht aus Servern mit Festplatten. Im Durchschnitt ist ein moderner Storage-Server mit 36 Festplatten von je 12–14 Terabyte ausgestattet. Früher waren die Festplatten kleiner, aber im Laufe von drei Jahren sind die Festplattengrößen gewachsen, und heute sind es fast ein halbes Petabyte an Rohdaten.
Festplatten aus verschiedenen Storage-Servern werden in sogenannten „Paaren“ (pair) zusammengefasst. Ein Paar ist eine einzelne Einheit zur Speicherung von Dateien. Es handelt sich im Prinzip um eine Festplatte, die in einem bestimmten Partition und Pfad eingebunden ist, wo Dateien liegen können, die durch Hashes identifiziert sind.
Paar ist ein historischer Begriff, der bis heute erhalten geblieben ist, obwohl es in einem Paar nicht unbedingt nur zwei Festplatten geben muss. Es können auch drei Festplatten oder verschiedene hybride Speicherlösungen wie 3/2 vorhanden sein.

Paare (pair) — Einheiten der Objektlagerung
Alle Paare werden in PairDB gespeichert — einer Anwendung auf Basis von Tarantool. Alle Datenbanken in unserem Speicher, beginnend mit den allerersten, sind Tarantool, andere Datenbanken nutzen wir nicht.
PairDB speichert alle Paare, deren Zustände, den verfügbaren Speicher, die Ausfallmöglichkeiten und die letzten Fehler. Außerdem kann sie selbst zu den Paaren gehen, deren Zustand aktualisieren und überprüfen, ob sie funktionieren oder nicht. Das heißt, PairDB ist eine Art Gesamtübersicht über den Zustand aller Festplatten unseres Systems.

Pair DB: Datenbank mit dem Zustand der Paare
In den Paaren werden Dateien gespeichert, und um zu wissen, welche Datei sich bei welchem Paar befindet, ist eine weitere Datenbank nötig – FileDB. Sie speichert das Mapping, die Zuordnung: Diese Datei wird bei diesem Paar gespeichert, sowie eine kleine Anzahl benötigter Attribute.
File DB: Ort, an dem die Datei gespeichert wird
Ein weiteres wichtiges Glied ist der Dienst Nylon, ein Router für die Arbeit mit Datenbanken. Er ist der einzige Einstiegspunkt und ermöglicht die Arbeit über eine einheitliche Schnittstelle sowohl mit PairDB als auch mit FileDB. Dies ist ein stateless-Dienst, er führt die Lastverteilung der Anfragen durch, versteht, zu welchem Shard FileDB navigiert werden muss, und weiß, welche Paare aktiv sind und welche nicht.

Nylon: Router für die Arbeit mit Datenbanken
Außerdem muss der Inhalt irgendwie in das Speicher gespeichert werden. Dafür gibt es den Dienst – Streamer. Er bietet zwei HTTP-Methoden an: die PUT-Methode, um Inhalte in den Speicher hochzuladen, und die GET-Methode, um sie dort abzurufen. HTTP ist ein ziemlich beliebtes und bequemes Protokoll für die Datenübertragung.
Wenn wir Streamer ansprechen, ruft er über Nylon PairDB auf, um herauszufinden, auf welches Paar die Datei hochgeladen werden kann, und überträgt die Daten dann über WebDAV an dieses Paar.
Im Grunde genommen ist jeder Storage-Server nginx plus die Platten, die an vorgegebene Pfade eingebunden sind. Wir können über den Streamer eine Datei in den Speicher hochladen, sie löschen, umbenennen oder ihre Integrität überprüfen. Das heißt, dies ist eine bequeme Schnittstelle für die niedrigstufige Interaktion mit dem Speicher.

Streamer: Einstiegspunkt in den Speicher
Was wir hinzugefügt haben, um die S3-Speicherung zu erstellen
Also, wir haben die grundlegende Struktur des Speichers betrachtet, als wir bereit waren, S3-Speicher zu implementieren. Mithilfe der PUT-Methode konnten wir dort beliebige Inhalte ablegen und als Identifikator dieser Daten einen Hash erhalten. Mit diesem Identifikator konnte man später zurückkehren und die ursprüngliche Datei abholen. Aber das reicht nicht für die Implementierung von S3. Im S3-Protokoll gibt es neben der eigentlichen Speicherung der Objekte auch:
- Speicherung von Metadaten – zusätzlichen Eigenschaften der Objekte;
- Zugriffsorganisation auf Objekte über HTTP;
- Gruppierung von Objekten in Sammlungen – Buckets;
- HTTP-S3-Endpunkt. S3 organisiert Daten in bestimmte Strukturen – Buckets, von denen jeder einen Einstiegspunkt für die Speicherung von Dateien bietet.
Um diese Logik umzusetzen, war ein separater Dienst erforderlich. Auch wollte man gleich die Architektur für das zukünftige Wachstum des Dienstes mit linearer Skalierbarkeit voraussehen.
Erste Komponenten
Ein Demon, der die S3-API implementiert. Dies ist die Standard-S3-API von Amazon, die die Arbeit mit XML für Metadaten unterstützt und es ermöglicht, Inhalte direkt zu übertragen. Wir mussten nichts neu erfinden, alles ist beschrieben und dokumentiert.
Vor dem Dienst haben wir auch Nginx bereitgestellt. Diesen haben wir für die SSL-Terminierung, Lastverteilung sowie für einige Logik in Lua (Metriken, Logging und Tracing) verwendet.
Für die Speicherung der S3-Metadaten haben wir ebenfalls Tarantool gewählt. In der ersten Version ging der S3-Demon für Metadaten in diese Datenbank; die Inhalte wurden in einem großen Speicher über Streamer aufbewahrt.

Nginx + S3 API + Metadaten
Objektmodell für die Speicherung
Schauen wir uns an, wie S3 funktioniert. Der Benutzer kann einen Bucket erstellen – eine Sammlung von Objekten. Der Bucket wird über den Hostnamen adressiert und ist ein Subdomain des Dienstes. Innerhalb des Buckets kann der Benutzer Objekte erstellen. Der Identifikator des Objekts ist die URL. Der Inhalt des Objekts ist ein Blob, ein Array von Binärdaten, das wir im Speicher aufbewahren werden. Auch hat das Objekt Attribute: den Namen – die eben genannte URL, ACL (Zugriffssteuerungsliste), sowie andere zusätzliche oder benutzerdefinierte Attribute – all dies wird in den Metadaten gespeichert.
Das normalisierte Schema dieser Daten könnte so aussehen: Es gibt Projekte, die Buckets besitzen, die Objekte besitzen können, und Objekte können zusammengesetzt sein. Da ein Objekt auf verschiedene Teile hochgeladen werden kann, gibt es dafür zwei Hilfstabellen: uploads und chunks. Auch haben Projekte Zugangsdaten und Abrechnungen.

Datenschema
Da wir einen B2B-Dienst mit kostenpflichtigem Zugang erstellt haben, war in diesem Schema eine Abrechnung erforderlich.
Den Abrechnungsdienst haben wir ebenfalls in Tarantool umgesetzt.

Verbesserungen am S3-Speicher: Schritte zur Produktion
Wir haben bereits ein funktionierendes Modell erstellt, das verwendet werden kann: Objekte und Metadaten wurden gespeichert, aber für den Produktionsstart fehlten noch einige Punkte.
Zunächst das System der Ratenbegrenzung. Wenn man den Dienst ohne dieses System starten würde, könnten wir bei hoher Auslastung unvorhersehbare Überlastungen in einem Teil des Systems verursachen. Die Ratenbegrenzung sollte so funktionieren: Jeder S3-Anfrage wird an einen bestimmten Host gesendet, dieser Host ist der Identifikator des Buckets, und der Bucket gehört einem bestimmten Kunden. Wir müssen eine Funktion für den Bucket definieren, die es uns ermöglicht, die Ratenbegrenzung zu berechnen.
Außerdem sollte das System der Ratenbegrenzung leistungsfähig genug sein, um die Last, die auf S3 einwirkt, zu bewältigen.
Hier haben wir erneut Tarantool verwendet. Die Ratenbegrenzungen sind ein Cluster aus 21 Instanzen, die Instanzen sind in Gruppen aufgeteilt, über drei physische Knoten verteilt und zu einem großen topologischen Cluster zusammengefasst. Konfigurationsänderungen werden automatisch darauf verteilt: Ratenbegrenzungen, Standardwerte und Konfiguration werden festgelegt. Jeder Bucket wird strikt von einer einzigen Instanz bedient. Wenn eine Anfrage für einen bestimmten Bucket eingeht, wird die zuständige Instanz für diesen Bucket ermittelt. Innerhalb dieses Knotens wird die aktuelle Anfragerate nach einem Algorithmus berechnet, der dem Token-Bucket ähnelt. Anschließend teilt das System der Ratenbegrenzung, basierend auf den aktuellen Lastwerten und den für den spezifischen Bucket festgelegten Eigenschaften, mit, ob die Anfrage ausgeführt werden kann oder nicht. Die Überprüfung der Limits erfolgt in der frühesten Phase der Ausführung der S3-Anfrage, wodurch alle anderen Elemente des Systems vor übermäßiger Belastung geschützt werden.

Auch unter Last ist es ziemlich schwierig, ohne Cache auszukommen. In S3 wird mehrfach auf dieselben Objekte zugegriffen, was bedeutet, dass es sich um einen heißen Speicher handelt. Normalerweise wird der Zugriff auf eine einzelne Datei über den gesamten Prozess bedient: Streamer, FileDB, PairDB, Storage. Bei mehrfachen Zugriffen auf eine Datei optimieren wir jedoch den Zugriff auf diesen Inhalt mithilfe eines lokalen Caches.
Der Cache ist mehrschichtig und wird mit nginx, lokalen, SSD- und RAM-Laufwerken realisiert. Hier haben wir Tarantool nicht verwendet, da es bequemer ist, Objekte aus dem Dateisystem auszuliefern, so können wir ein Cache-Tiering durchführen. Außerdem haben wir große Objekte mit einer maximalen Größe von 32 Gigabyte, während in Tarantool nur kleine Objekte zwischengespeichert werden können.

Dieses erste System, mit dem wir gestartet sind, hatte eine berechnete Kapazität, die für die Erforschung und das Verständnis des Produkts ausreichte, sowie für die Gewissheit, dass es funktionieren wird.
Verbesserungen am Kampfsystem: Failover und Skalierung
Das System war bereits im Einsatz, wobei wir anfangs einige Punkte verpasst hatten – es war notwendig, Failover und Skalierung hinzuzufügen.
Unser S3-Dämon holte Metadaten über das Tarantool-Protokoll ab. Anstelle der ursprünglichen Datenbank setzten wir Tarantool ein, der als Proxy-Router für Anfragen nach Metadaten fungierte. Aus der Sicht der Anwendung, die die API umsetzt, hat sich nichts geändert – sie greift weiterhin über das Tarantool-Protokoll auf die Datenbank zu, aber der Router konnte aktives Failover bereitstellen. Das heißt, wir konnten die Verfügbarkeit des Knotens überwachen, Wartezeiten bei Umschaltungen und Ausfällen einhalten und so weiter. Die Anwendung selbst wurde dabei nicht modifiziert.

Mehr über die Implementierung der Shardierung
Die nächste Herausforderung, um die wir uns kümmern mussten, war die Shardierung. Das System wuchs, die Anzahl der Objekte stieg, und es war notwendig, Möglichkeiten für weiteres Wachstum zu schaffen.
Kehren wir zum Datenschema zurück: Es gibt Projekte, Buckets, Credenzen und Billing. Das sind Objekte, die mit hoher Wahrscheinlichkeit in naher Zukunft weder hinsichtlich des Volumens noch der Anfragen über den Rahmen einer einzelnen Instanz hinauswachsen werden. Daher macht es keinen Sinn, sie zu sharden, und wir haben sie in eine separate Instanz ausgelagert, die ungeshardet bleibt. Dies ermöglicht ein konsistenteres Management von Projekten und Buckets, da es einen einheitlichen ungeshardeten Punkt gibt.

Außerdem gibt es im Schema Objekte, die linear wachsen – zunächst waren es Hunderttausende, jetzt misst man ihre Anzahl in mehreren Milliarden. Solche Objekte und ihre Teile mussten in einen sharded Cluster verschoben werden.

Wir haben das Schema aufgeteilt, aber die Objekte müssen weiterhin mit den Buckets arbeiten: Ein Objekt gehört immer zu einem bestimmten Bucket, und auf dem Bucket wird ACL angewendet. Daher halten wir für jedes Shard mit Objekten eine Schattenkopie jedes Buckets. Zudem ist es während der Modifikation von Objekten und bei Anfragen notwendig, das Volumen für die Abrechnung zu berücksichtigen, weshalb auf jedem Shard Zähler für die Abrechnung vorhanden sind.
Wir haben auch einige zusätzliche Tabellen und Komponenten hinzugefügt:
- einen Papierkorb zum Löschen alter Projekte, die gelöscht oder eingefroren werden.
- Warteschlange für Hintergrundaufgaben, das heißt, der primäre Speicher kann Hintergrundaufgaben durchführen, die auf dem Cluster erforderlich sind;
- Lifecycle-Unterstützung – ein Mechanismus, der es ermöglicht, mit Objekten zu arbeiten und deren Lebenszyklus zu verwalten.

Da ein Teil der Daten auf Shards verteilt wurde, war ein sharding Proxy erforderlich. Man hätte den Router für diese Rolle wiederverwenden können, aber ein separater sharding Proxy, der nur für das Sharding der Daten verantwortlich ist, ermöglicht es dem Router, die Daten insgesamt abzurufen, ohne über das Sharding nachdenken zu müssen.

Ich werde separat erläutern, warum wir keine fertige Lösung übernommen haben, sondern eine benutzerdefinierte Sharding-Funktion erstellen wollten.
Schauen wir uns an, wie es aufgebaut ist. Wir haben 256 verfügbare Shards. Für jeden Bucket bestimmen wir einen Bereich mit einer konsistenten Funktion. Es ist einfach – genau wie Sie mit einer konsistenten Funktion die Zugehörigkeit zu einem Shard bestimmen, legen Sie den Startshard fest und weisen einen Bereich zu:
f(bucket, shards) = subset
Das heißt, wenn Sie einen Bucket nehmen, können Sie sagen, dass er und seine Daten immer auf einer bestimmten Teilmenge aller Shards liegen werden. Dies verringert den Einfluss einzelner Buckets auf andere und vereinfacht die Durchführung von Map-Reduce-Abfragen, wenn beispielsweise eine Auflistung der Objekte eines Buckets erstellt werden muss. Dazu müssen alle Shards abgefragt werden, auf denen diese Objekte gespeichert sind. Wenn die Objekte auf allen Shards wären, würde jede Auflistung die gesamte Systemleistung beeinträchtigen, aber hier wird nur eine bestimmte Teilmenge betroffen.
Weiterhin gehört jedes Objekt zu einem bestimmten Bucket, daher greifen wir, wenn wir auf ein Objekt zugreifen, auf das Objekt anhand des Namens in einem bestimmten Bucket zu. Das heißt, wir können eine Funktion für ein Objekt nicht aus dem gesamten verfügbaren Bereich der Shards, sondern nur aus der Teilmenge seines Buckets definieren:
f(object, subset) = shard
Wir nehmen ein bestimmtes Objekt und übergeben als Argumente für die Funktion nicht alle Shards, sondern die Teilmenge seines Buckets – und erhalten den spezifischen Shard.

So, das Sharding ist implementiert, es gibt einen sharding Proxy. Als nächstes bleibt es, vom Router und der Metadaten-Datenbank auf den sharding Proxy zuzugreifen. Zum Beispiel bei der Erstellung von Objekten von Schattenkopien – wenn wir einen Bucket erstellen, muss der primäre Speicher einen Vertreter dieses Buckets auf allen Shards erstellen, auf denen er vorhanden sein soll.

Wie wir Resharding implementiert haben
Das größte Problem beim Sharding ist das Re-Sharding. Es war uns wichtig, dies ohne Ausfallzeiten zu schaffen, da das System bereits in Produktion war. Ich werde zeigen, wie wir das Problem an einem ähnlichen Beispiel mit der Live-Datenmigration von einem Projekt zu einem anderen gelöst haben.
Unten ist das Schema unseres Clusters, das nach der Implementierung des Shardings entstanden ist. Wir haben nginx, die S3 API, einen Router, eine primäre Datenbank mit Projekten, einen Sharding-Proxy und die Shards selbst.

Ich habe oben den Punkt ausgelassen, dass es zu einem bestimmten Zeitpunkt im Projekt eine produktbezogene Aufgabe gab: "Ein weiteres Speicher, Icebox, zu starten – wie Hotbox, nur für kalte Daten". Im Grunde genommen handelt es sich um dasselbe Speicher, nur unter anderen URLs und ohne Caches.

Icebox wurde weniger genutzt als Hotbox, daher kam es relativ lange ohne Sharding aus. Letztendlich haben wir beschlossen, darauf zu verzichten und Hotbox und Icebox zu einem Dienst zusammenzuführen, indem wir die Speicherklassen einfach trennten.
Die Buckets in den Storages überschneiden sich nicht, sie konnten einfach zusammengeführt und verschoben werden, aber die Kunden nutzten sowohl das eine als auch das andere Storage, was bedeutete, dass wir das Problem ohne Ausfallzeiten lösen mussten. Wir konnten nicht einfach abschalten und kopieren. Wir führten die Migration in mehreren Phasen durch.
Zunächst synchronisierten wir die primären Storages. Wir hatten Tarantool und konnten bei der Erstellung eines Objekts Folgendes tun:
- Eine Anfrage zur Erstellung eines Buckets geht an die Datenbank, zum Beispiel in Hotbox;
- Tarantool überprüft in einer anderen Datenbank (in diesem Fall in Icebox), dass es einen solchen Bucket nicht gibt;
- Wenn der Bucket vorhanden ist, teilt die Datenbank mit, dass er nicht erstellt werden kann, und er synchronisierte sich als bestehend.

Synchronisation der Buckets
In dem Storage, das alle Daten aufnehmen sollte, wurde für Projekte und Buckets ein Kennzeichen eingeführt, das angibt, wo sich dieses Objekt befindet. Es konnte lokal in Hotbox gespeichert werden, in Icebox – dann gibt es im neuen Storage keine Daten davon, oder es konnte sich im Zustand der Migration befinden.
Wenn für ein Projekt oder einen Bucket das Kennzeichen 'Migrating' gesetzt war, wurde die Anfrage während der Migration zunächst an das neue Storage gesendet, in dem die Daten gespeichert sein sollten. Falls diese dort nicht vorhanden waren, wurden die Anfragen an das alternative Storage umgeleitet.
Danach haben wir den Traffic umgeschaltet. Da die API sowohl Anfragen an Icebox als auch an Hotbox bedienen konnte, konnten wir den Traffic ohne Ausfallzeiten umschalten, indem wir einfach die Hosts verschoben und die entsprechenden Einträge in Nginx hinzugefügt haben.
Nachdem der Traffic umgeleitet wurde, konnten Nginx und die API von Icebox entfernt werden.
Dann haben wir Icebox nginx und S3 API entfernt – und alles funktionierte:

Anschließend haben wir einen Hintergrundprozess für die Migration gestartet, der innerhalb der Datenbank läuft – er durchläuft alle Projekte und deren Buckets elementweise, setzt für sie das Zeichen Migrating, überträgt die Daten und setzt nach Abschluss der Übertragung das Zeichen Local.

Nach der Datenübertragung benötigen wir das alte Speicher nicht mehr, und wir entfernen die verbleibenden Teile des alten Systems sowie die Unterstützung für den Migrationsstatus aus dem Code.

Nach den gleichen Prinzipien wurde auch der Resharding aus dem alten Speicher in den shardierten Speicher durchgeführt:
- Wir haben alle Buckets als
Non-shardedmarkiert. Alle Anfragen an sie gingen in den ursprünglichen, nicht shardierten Speicher. - Neue Buckets wurden sofort im Status
Sharded. - erstellt. Wir haben Buckets einzeln genommen, den Status
Migratinggesetzt und die Daten übertragen.
Die Anfragen wurden dabei nach folgendem Prinzip bearbeitet:
- Zuerst im neuen lesen, dann im alten.
- Erstellen nur im neuen.
- Bei der Aktualisierung in zwei Phasen: Wenn im neuen nichts vorhanden ist, übertragen wir von alt nach neu, dann aktualisieren wir.
Arbeiten mit SSL-Zertifikaten
Im Frontend verwenden wir Nginx. In unserem Fall ist es nicht das gewöhnliche Nginx, sondern OpenResty, Nginx mit LuaJIT-Unterstützung.
Ein weiteres Teil des Systems ist die Arbeit mit SSL-Zertifikaten. Im S3-Speicher können Sie eine eigene Domain für den Zugriff auf einen bestimmten Bucket einrichten, einfach durch CNAME. Aber ohne HTTPS geht es heutzutage nicht: Eine eigene Domain setzt ein eigenes SSL-Zertifikat voraus.
Wie ich bereits sagte, ist Nginx für das Load Balancing und die SSL-Terminierung zuständig. In unserem Fall ist es nicht das normale Nginx, sondern OpenResty, Nginx mit LuaJIT-Unterstützung.
Das hat es uns ermöglicht, Nginx relativ einfach dazu zu bringen, beliebige Zertifikate auszugeben. Darüber hinaus war es notwendig, die Zertifikate dynamisch auszugeben (ohne sie in der Konfigurationsdatei festzuschreiben). Wir haben das Modul ssl_certificate_by_luaverwendet, das es erlaubt, Zertifikate aus beliebigen Quellen direkt während des TLS-Handshakes zu lesen. Als Zertifikatsspeicher haben wir ebenfalls Tarantool verwendet: das ermöglicht eine zentrale Verwaltung der Zertifikate und sorgt für eine extrem schnelle Bereitstellung.
Außerdem wurde ein separater Daemon implementiert, dessen Aufgabe es ist, regelmäßig die Zertifikate zu aktualisieren, die durch Let’s Encrypt ausgestellt wurden.

Was ich gespeichert hätte und was ich anders gemacht hätte, wenn ich das Speicherreservoir von Grund auf neu entwickeln würde
Was von Anfang an hätte genutzt werden müssen
Sharding sofort. Relativ viele Probleme wurden durch das Resharding verursacht. Es ist leicht umzusetzen, aber, dennoch, wenn man Projekte startet, die skalierbar sein müssen, sollte man sofort einen shardierten Cluster wählen, selbst wenn es nur eine minimale Anzahl von Knoten ist. Die Umsetzung von Sharding zu Beginn ist fast kostenlos im Vergleich zur Implementierung von Sharding in ein laufendes System.
Arbeiten mit Tarantool über Load Balancer. Jetzt schließen wir alle neuen Datenbanken sofort über Load Balancer in den Betrieb ein. Dies ermöglicht es, die Funktionalität zu erweitern und eine höhere Ausfallsicherheit zu erreichen.
Auto-Failover. Ich hätte alle notwendigen Werkzeuge für das Auto-Failover installiert, da die ersten Misserfolge nach dem Start auf dessen Fehlen zurückzuführen waren. Nach der Erfahrung mit S3 wurden alle folgenden Produkte unter Berücksichtigung dessen gestartet.
S3-Funktion „Versionierung“. Zunächst schien es, dass dies eine nicht sehr gefragte Funktionalität ist. Diese Möglichkeit in die Architektur eines funktionierenden Systems zu integrieren, ist äußerst schwierig.
Getrennte Abrechnung. Die Art, wie wir die Abrechnung in unser System integriert haben, hat sich zu Beginn als sehr gut erwiesen, wurde jedoch später störend; es wäre besser gewesen, dies als einen völlig separaten Dienst bereitzustellen.
Was eine erfolgreiche Entscheidung war
Datenmodell. Die Geschichte hat gezeigt, dass wir mit der Entwicklung des Dienstes recht genau mit dem Datenmodell von Amazon übereinstimmen, daher können wir die Funktionen implementieren, die dort vorhanden sind.
Shardierungsschema. Ich würde ähnliche Bereichs-Shardings nach Buckets unterstützen, da dies ermöglicht, Anfragen von verschiedenen Buckets gut über einen großen Cluster zu verteilen.
Verwendung von Tarantool. Tarantool hat bei der Entwicklung des Dienstes und seiner Modifikation erheblich geholfen; wir haben problemlos mit den Daten gearbeitet, transformiert und das Speicherreservoir shardiert, ohne auf die Anwendungsebene steigen zu müssen.
Dieser Bericht wurde erstmals auf by Mail.ru Cloud Solutions&Tarantool. Siehe weitere Vorträge und abonnieren Sie die Ankündigungen von Veranstaltungen auf Telegram .
Sie können auch meinen alten Vortrag über S3 ansehen oder den Artikel meines Kollegen über Blockspeicher lesen.
- .
- .
Quelle: habr.com

