Mittwoch, April 24, 2024

Das böse Büro

Uriel Fanellis Blog in deutscher Sprache

Uriel Fanelli

Fedeverse, (über) das Design.

Ich habe mehrmals darauf hingewiesen, dass ActivityPub ein schreckliches Protokoll ist (was leicht zuzustimmen ist), was ich bisher nicht erwähnt habe, ist, wie schlecht die Implementierung der am häufigsten verwendeten Instanzen ist. Aus Sicht des Architekten sieht es für mich so aus, als würden Entwickler versuchen, eine „Einzelbenutzerinstanz (die – daher – nicht skalieren soll) zu schreiben, während sie tatsächlich missbraucht wird, bis zu Tausenden von Benutzern“.

Zunächst sollten wir eine Grenze zwischen mehreren Arten von Instanzen ziehen. Ich würde sagen, "einzelne oder kleine Benutzer", die nicht skaliert werden müssen, und "riesige Instanzen", die skaliert werden sollen. Oder sie MÜSSEN früher oder später skaliert werden.

Sagen wir:

  • Typ 1: Einzelbenutzer oder wenige Benutzer
  • Typ 2: große, skalierbare Instanzen

Alle Software, die ich sehe, ist Typ 1, die oft von vielen Benutzern missbraucht wird, wie in einigen großen Instanzen mit mehr als 1 Million Benutzern.

Zu Typ 1 gibt es ein wenig zur Implementierung zu sagen (die Qualität des Codes und der Dokumentation scheinen keine Priorität zu haben), daher werde ich nur das größte Problem beschreiben, das ich sehe: nämlich Daten.

Kleine Instanzen werden oft von sqlite3 unterstützt, was sinnvoll ist, aber wir ziehen kein Problem in Betracht. Wir haben grundsätzlich mehrere Arten von Daten zu speichern:

  1. die Datenbank von Benutzern und zugehörigen Token
  2. die Datenbank eingehender Nachrichten im Instanzpostfach
  3. die Datenbank der eingehenden Nachrichten für Benutzer
  4. der Speicher für Anhänge

Diese werden auf unterschiedliche Weise skaliert, während es kaum Möglichkeiten gibt, sie in Größe oder Zugriffszeit einzuschränken. Die gesamte mir bekannte Software (Honk, gotosocial usw.) verwendet eine einzelne sqlite3-Datei, als ob der Entwickler nach Problemen sucht . Wir wissen genau, dass Ereignisse asynchron sind, und wir wissen genau, dass die Anzahl der Schreibvorgänge für die /inbox-API (auch bekannt als „Federated Timeline“) je nach Anzahl und Größe der Server, mit denen wir föderieren, skaliert. Die Anzahl der von unseren Benutzern ausgelösten Operationen skaliert mit unserer Anzahl an Benutzern, mit der Anzahl der Clients, die sie besitzen, und die Menge an eingehenden Nachrichten für unsere Benutzer hängt von einer Mischung aus Benutzerverhalten und Verhalten verbundener Instanzen ab.

Die Größe des Anhangs skaliert tatsächlich hauptsächlich mit der Art des Inhalts (Videos, Bilder, Musik, je nach Art der Instanz) und dann mit der maximal zulässigen Größe und der Anzahl der aktiven Benutzer.

Jede Art von Traffic skaliert anders. /inbox ist ziemlich unabhängig von der Aktivität auf unserer Instanz und sehr empfindlich in Bezug auf die Aktivität von föderierten Instanzen.

Wenn in einem solchen Fall das Ziel darin besteht, sqlite3 zu verwenden, würde ich vorschlagen, mindestens 4 Dateien zu verwenden, anstatt nach Problemen zu suchen und die Parallelität auf der sqlite3-API zu betonen:

  1. ein sqlite3 für föderierte Nachrichten, die von der Instanz /inbox empfangen werden, damit wir schreiben können, ohne unsere Benutzer in der Zwischenzeit zu sperren, oder das Risiko einer fehlerhaften Parallelität eingehen.
  2. eine für die Nachrichten der Benutzer, obwohl ich sogar sagen würde, pro Benutzer eine andere sqlite3 zu verwenden. Dies wäre für mehrere Benutzer nützlich, selbst wenn es sich um kleine Zahlen handelt.
  3. eine weitere sqlite3-Datei für Benutzer und Autorisierungstoken. Dies würde die Daten des Benutzers isolieren und Wettlaufbedingungen verhindern.
  4. letzte Datenbank für Anhänge, um die ID der Datei zuzuordnen.

Das mag für eine Single-User-Instanz sehr seltsam erscheinen, könnte aber für eine Software wie gotosocial, die viele Benutzer hosten soll, ein Segen sein. Sicher, die Leute werden sagen „aber Sie können gotosocial einrichten, um postgressql zu verwenden“, nur weil sie an den Aberglauben glauben, dass jetzt alles effizient ist, seit der Aufwand auf ein RDBMS verlagert wurde.


Ich möchte ein paar Worte über die Idiotie der Verwendung von PostgresQL für ActivityPUB verlieren. Wenn Sie alt genug sind, um sich an den alten LAMP-Hype zu erinnern, erinnern Sie sich, dass diese Lampe Linux, Apache, MySql, PHP war. Als MySQL unter Oracles Kontrolle war, migrierten die meisten Opensource-Hoplites zu Postgres, nur „weil ja“. Was sie nicht bemerkten, war, dass Postgres eine völlig andere Art von Datenbank war, da ihr Design auf der Idee basierte, OOP-Objekte zu serialisieren, und nicht auf einzelnen, selbstgeprägten JSON-Dateien oder Dokumenten.

Dies führte zu einem hybriden Speicherdesign von Postgres, das eine „automatische Analyse“ benötigt (wie die Entwickler, denke ich), was ich als „leistungsfähig, aber nicht effizient“ bezeichne. Weder Postgres ist einfach zu skalieren. Ich habe mich nur gefreut, als der Entwickler von Mastodon die Datenbank auf eine größere Hardware migrieren musste. Im Ernst, Eugen?

In einer ANGEMESSENEN Datenbank MÜSSEN Sie in der Lage sein, eine weitere Instanz oder einen Server hinzuzufügen, Lasten auszugleichen und dann die Leistung zu VERBESSERN. Spätestens seit der Geburt von IBM360.

Die Tatsache, dass „mehr Hardware“ fast die einzige Lösung für die Kapazitätsprobleme von PostgresQL ist, sollte beispielsweise darauf hindeuten, wie unterschiedlich es sich von MySql unterscheidet. Nicht, dass ich ein großer Fan von MySQL wäre, aber zumindest ist es möglich, Server hinzuzufügen und die Leistung zu verbessern. Und nein, CockroachDB hat das Problem nicht "behoben", da Sie, um einen guten Cluster zu erstellen, KEINE "erweiterten" Funktionen des PostgresQL-Protokolls verwenden können. Es gibt auch „Workarounds“, die Verwendung von Plugins von Drittanbietern, Sharding und mehr oder wie Postgres-XL und Citus, aber Sie müssen sehr, sehr, sehr, sehr selbstbewusst sein, um sie zu verwenden. Außerdem wäre es ein Albtraum, verlorene Daten während der Verwendung solcher Plugins wiederherzustellen, wenn ein Shard verloren geht.

Sicher, PostgresQL kann Daten speichern, sicher kann es problemlos JSON speichern und passt zu den meisten Serialisierungsproblemen, aber es ist eine völlig andere Art von Objekt im Vergleich zu MySql. Wenn Sie denken, dass MySql/MariaDB und Postgres Alternativen zueinander sind, dann:

  1. Sie wissen nichts über Datenbanken
  2. Sie VERSTEHEN Datenbanken nicht.

und auf jeden Fall sollten Sie die IT verlassen und in anderen Bereichen wie Landwirtschaft, Bergbau und / oder mehr arbeiten.

Wenn ich eine Software sehe (und das passiert ziemlich oft), die Postgres unter der Annahme verwendet, dass die Datenmenge etwa Dutzende Megabyte groß sein wird (wie es Misskey tut), weiß ich normalerweise, dass ich einen schrecklichen Programmierer ansehe: zu inkompetent dazu Schreiben Sie eine anständige Logik, um Daten mithilfe des Dateisystems zu handhaben. Sie verschieben das Problem einfach in das RDBMS und denken, „jemand anderes hat den guten Code geschrieben, den ich nicht schreiben kann“.

Falsch.

Keine Datenbank implementiert die Scheiße an Codelogik, die Sie im Sinn haben. Die Datenbank ist nur ein Brunch von IRGENDWELCHER Software, anständig geschrieben: kein RDBMS ist für ein paar Dutzend Megabyte optimiert. Sicher, es läuft schnell mit wenig Daten: Das liegt nicht an gutem Code, sondern an wenig Daten.

Außerdem ist der Aufwand schrecklich.

Wenn Sie eine Instanz entwerfen, die von einer geringen Datenmenge ausgeht, sind andere Datenbanken wie Redis deutlich besser. Und Redis skaliert und gruppiert auch viel besser als eine Jurasic-Datenbank wie Postgres.

Das ist es. Es gibt tonnenweise bessere Datenbanken, die man für diesen Zweck verwenden könnte. Angesichts der serialisierten Natur der Aktivitäts-Pub-Nutzlast könnten Mongo und ähnliche No-Sql-Funktionen den Zweck viel besser erfüllen, falls Sie auf einem leistungsstarken _whatever_ bestehen.

Ich schließe es hier.


Zurück zum Thema, wir diskutieren jetzt über riesige Instanzen. Wie entwirft man eine skalierbare Instanz?

Der Punkt ist, ActivityPub scheint eine Reihe von APIs zu sein, mit inkompetentem Design, schrecklicher Sicherheit, so rational wie ein betrunkener Affe auf Crack. In gewisser Weise erinnert es mich an einige katastrophale Alpträume aus der Vergangenheit, wie CORBA, in anderer Hinsicht sieht es aus wie ein RPC, zusammen mit einigen Versuchen, SOAP mit JSON neu zu erstellen. Wenn ein Zugunglück im Tschernobyl-Reaktor 4 passiert wäre, hätte es besser aussehen können.

Wie auch immer, wenn Sie genug Tequila trinken, sieht ActivityPub wie eine API aus.

Der Punkt ist, dass einige APIs mit der Anzahl der Verbundserver skaliert werden, einige mit der Anzahl der Benutzer in der lokalen Instanz, einige mit der Anzahl der Zugriffe der Benutzer auf die Instanz, einige mit der Anzahl der Clients Benutzer verbindet sich mit. Einige APIs rufen auch andere Instanzen auf, und die Instanzen sind absolut unvorhersehbar, was bedeutet, dass unsere Instanz auch die API anderer verbraucht. (Im Grunde ist es ein Push-UND-Pull-Protokoll, wie SMTP, während ein Request-Response-Protokoll verwendet wird. AAAARGH!).

Da es sich um eine Reihe von APIs handelt, könnte der erste Ansatz darin bestehen, ein API-Gateway für eingehenden Datenverkehr zu verwenden. Da die API-Funktion die Geschäftslogik nicht vollständig widerspiegelt (aufgrund des schrecklichen, radioaktiven Designs), könnten wir auch eine Orchestrierung wie kubernetes und dann eine routerbasierte Infrastruktur wie traefik verwenden, um verschiedene Lastmodelle zu isolieren.

Wir können jedenfalls sagen:

  1. OAuth2-APIs skalieren den Datenverkehr mit der Anzahl der Benutzer, die die Instanz tatsächlich verwenden. Das heißt, wenn ein einzelner Benutzer 4 Clients hat, bedeutet „Benutzer“ nur „Client“. Jeder mit einem Token, das er von Zeit zu Zeit überprüfen kann.
  2. Die /inbox-APIs der Instanz (AKA „Federated Timeline“) skalieren mit der Anzahl der Server, mit denen das System verbunden ist, angesichts ihrer Größe und Aktivität.
  3. Der Posteingang des Benutzers skaliert mit der Menge an Inhalten, die Benutzer veröffentlichen, mehr als mit der Anzahl der Kunden. Es ist unwahrscheinlich, dass ein Benutzer die Instanz von mehr als einem Client gleichzeitig verwendet.
  4. Einige Aufrufe von „Mastodon“-APIs (z. B. mobile Clients, die die Zeitleisten aktualisieren) skalieren mit der Anzahl der Clients, die ständig eine Aktualisierung der Zeitleisten anfordern.
  5. Einige Aufrufe von „Mastodon“-APIs skalieren auch mit der Größe des Anhangs, wie bei PeerTube mit Videos, oder abhängig von der Größe des Anhangs.
  6. Der ausgehende Datenverkehr zu anderen Instanzen skaliert mit der Menge der Benutzeraktivität zu anderen Instanzen und der Menge der Instanzen, die der Instanz bekannt sind.

Dadurch werden mindestens 6 Microservices im Back-End identifiziert. Jeder MUSS unabhängig skaliert werden, während die verbleibenden möglicherweise keine Anforderungen zu erfüllen haben. Es macht absolut keinen Sinn, alles zusammen zu skalieren, nur weil ein Satz von APIs auf 6 eine Spitzenlast hat.

Wenn wir zum Beispiel an so etwas wie Autoskalierung in AWS denken, wäre die Skalierung insgesamt eine Verschwendung von Ressourcen. Es ist also besser, jeden Microservice auf unabhängige Weise zu skalieren, wie es eine Kubernetes-ähnliche Orchestrierung tun kann. (mit geeigneten Ressourcenrichtlinien).

Über OAuth2 verfügen viele API-Gateways, insbesondere die mit einem „Entwicklerportal“ (wie Apigee oder Kong), über einige OAuth2-Funktionen. Andernfalls benötigen wir eine Instanz einer Vault-ähnlichen Software, die OAth2-fähig ist.

Zum zweiten Problem brauchen wir einen Microservice, der den /inbox-Aufruf bereitstellt und nur in eine Back-End-Datenbank schreibt.

Gleiches gilt für 3 und 4, mit der guten Nachricht, dass die meisten API-Gateways das API-Ergebnis zwischenspeichern können, wodurch die „4“-Last verringert wird, wenn viele Clients dieselben Zeitleisten lesen.

Microservice 5 sollte eine Art Objektspeicher sein, wie minio. Dies würde nur Anhänge speichern. Oder schreiben Sie in eine Cloud wie S3.

Microservice 6 ist ein irgendwie „seltsamer“ Microservice, denn aus Sicht des API-Gateways wäre dies ein Aufruf an eine „externe“ API.

Eine ähnliche Infrastruktur würde so weit skalieren, wie die Datenbank und der Objektspeicher skalierbar sind. Warum? Denn das Skalieren des Microservice ohne Skalieren der Backend-Datenbank führt in Situationen mit hoher Last zu einer Art DOS für die DB selbst. Ziemlich häufiger Fehler (ein skalierendes Frontend/Backend mit nicht skalierender DB zu haben), aber immer noch sehr, sehr, dummes Designmuster.

Und mein Punkt hier ist: Haben wir etwas Ähnliches? Nein. Keine der SWs, die wir im Fediverse verwenden, passt auf dieses Design. Also, ich sehe eine Menge Ärger in der Zukunft voraus. Bisher wissen wir, wie schwierig es ist, Mastodon zu skalieren, und wir sprechen von 1,3 Millionen Benutzern in der größeren Instanz.

Verstehen wir, was passieren könnte, wenn eine von Amateuren entworfene Software wie Mastodon einen Höchststand von 10.000.000 Benutzern oder mehr erreicht? Nun, Eugen wird versuchen, riesige Instanzen für die Datenbank zu verwenden, wie er es bereits getan hat, und dann größer und wieder größer, bis kein Server mehr passt, der von irgendeiner Cloud angeboten wird. Und dann? Die Idee von Cloud ist es, die Leistung zu parallelisieren und horizontal zu skalieren, was Postgres nicht wirklich kann, zumindest ohne Tricks und sehr empfindliche Add-Ons.

Nehmen wir an, Postgres gibt vor, auf Multi-Server-Situationen skalieren zu können, aber … ich sehe nicht, wie eine Jurasic-Software die Belastung einer WIRKLICH großen Instanz überstehen kann. Wenn 50 % der Twitter-Nutzer auf das Fediverse migrieren würden, könnte das Problem der „Skalierung“ das Ganze wahrscheinlich zunichte machen.

Und das liegt daran, dass das Design von Mastodon nur Amateur ist, es sei denn, jemand kommt mit einer „Mastodon Enterprise“, die auf einem besseren Design basiert, wie dem, das ich oben beschrieben habe.


Heutzutage ist der Punkt: Wir haben nichts dergleichen. Hier möchte ich eine Software namens Pleroma vorstellen, die von einem PHP-Javascript-bewussten Team geschrieben wurde, das Elixir für cool hielt. Lassen Sie uns seine paradoxe Unskalierbarkeit diskutieren.

Und sicher, Elixir KANN cool sein, wenn die Leute aufhören, es zu benutzen, als wäre es PHP oder, schlimmer noch, Javascript. Das liegt nicht an der Sprache selbst (ist eindeutig eine vogonisch orientierte Sprache wie Erlang): Es liegt daran, dass sie mit der virtuellen Maschine von Erlang läuft. Und die virtuelle Maschine von Erlang ist cool.

Als Person, die mit Telekommunikationsunternehmen und Netzwerken arbeitet, kenne ich unzählige Ericsson-Produkte. Daher kenne ich die virtuelle Maschine von ERlang sehr gut. Ich weiß, was es kann. Und wenn ich vergleiche, was Pleroma NICHT tut, was die virtuelle Maschine von ERlang sofort kann, kann ich nur eines sagen: „Lain“, wer auch immer er ist, ist nur ein anmaßender Angeber ohne wirkliche Fähigkeiten gute Entwicklung.

Pleroma ist gut, weil es einfach zu installieren und ressourcenschonend ist. Aus diesem Grund verwende ich es auch. Alles andere in dieser Software ist mehr oder weniger inkompetenter Müll, oder wenn Sie es vorziehen: PHP/Javascript übersetzt in Elixir. Dieser Typ DENKT in PHP (oder vielleicht in einer albernen Art, Javascript zu schreiben?), er hat keine Ahnung, was die ERlang-VM kann, also erfindet Pleroma das Rad neu, und das Ergebnis ist ein quadratisches Rad.

Nur fürs Protokoll: Pleroma kann nicht horizontal skalieren. Was so absurd sein kann, wenn Sie denken, dass Erlang VM mit einem unglaublich effizienten System zum Erstellen von Clustern ausgestattet ist.

https://www.erlang.org/doc/reference_manual/distributed.html

Außerdem verfügt die Erlang-VM auch über eine verteilte Datenbank, sowohl auf Speicher- als auch auf Festplattenbasis:

https://www.erlang.org/faq/mnesia.html

Wenn ich jetzt eine Erlang-VM-Software sehe, die das Backend nicht wirklich skalieren kann, weil die verwendete Datenbank jurasisch ist (wie Postgres) und die Software verteiltes Erlang nicht nutzen kann, verstehe ich eine einfache Sache:

Der Entwickler hat nie ein Handbuch des Tools gelesen, für das er entwickelt hat.

Da fallen mir Worte ein wie: „falsch“, „Amateur“, „unprofessionell“, „Scharlatan“. Wenn wir uns fragen, welche Fediverse-Software besser skalieren KÖNNTE, lautet die Antwort mit Sicherheit „Pleroma hat das größte Skalierungspotenzial, da es auf der skalierbarsten und zuverlässigsten VM der Welt läuft, die häufig in Carrier-Grade-Anwendungen für die Telekommunikationsbranche verwendet wird.“ .

Aber wenn wir sehen, was das Ergebnis war, sieht es so aus, als hätte "Lain" nicht einmal versucht, die VM auszunutzen. Es verwendet Elixir, als wäre es nur eine bloße Schulübung, und nutzt KEINE seiner Funktionen. Die interessantesten.

Da Pleroma jedoch so wenig Platz benötigt, können wir versuchen, es so zu verwenden, als wäre es skalierbar, indem wir mehr Instanzen ausführen und nur APIs isolieren. Ich werde es einfach halten, Sie können hinzufügen, was Sie brauchen.

Stellen Sie sich vor, wir haben einen Schwarmstapel, auf dem Pleroma plus Traefik läuft. Was wir in einem solchen Stack tun würden, ist ungefähr so:

version: '3.9'

services:
pleroma:
hostname: bbs-whatever
restart: always
image: repo.org/pleroma:latest

...
ports:
- "4000:4000"

...

...

deploy:
labels:
- traefik.http.routers.pleroma.rule=Host(`ourinstance.net`)

.....

.....

alles andere in der Datei ist interessant, bezieht sich aber nicht auf das Problem. Was ich bemerken möchte, ist, dass sich eine Instanz um ALLE APIs in „ourinstance.net“ kümmert.

Jetzt wollen wir ZWEI Instanzen von Pleroma haben, um eine Problemumgehung für die „horizontale Skalierung“ zu versuchen, wenn wir mehr von einem Server haben. Was könnten wir tun?

version: '3.9'

services:
pleroma-users:
hostname: bbs-whatever-users
restart: always
image: repo.org/pleroma:latest

...
ports:
- "4000:4000"

...

...

deploy:
labels:
- traefik.http.routers.pleroma.rule=Host(`ourinstance.net`)

.....

.....

pleroma-inbox:
hostname: bbs-whatever-inbox
restart: always
image: repo.org/pleroma:latest

...
ports:
- "4000:4000"

...

...

deploy:
labels:
- traefik.http.routers.pleroma.rule=Host(`ourinstance.net`) && PathPrefix(`/inbox`)"

.....

.....

Im Grunde führen wir also zwei Instanzen aus, die sich dieselbe Datenbank teilen (was immer noch ein Problem ist, aber Postgres ist Postgres), mit dem einzigen Unterschied, dass einer der beiden Container die gesamte Last im Zusammenhang mit dem föderierten Posteingang der Instanz übernimmt, während die Der erste bekommt den Rest. Das heißt, wenn die Datenbank dem gesamten Datenverkehr widerstehen kann, wirkt sich die Last des föderierten Posteingangs nicht auf die Benutzererfahrung der Clients aus.

Sie können dieses Schema wiederholen und isolieren, wie viele APIs Sie möchten, wie „/api“ oder noch detaillierter. Dies würde natürlich erfordern, dass Sie der Datenbank weitere Verbindungen hinzufügen, da jeder Container die vollständige Anzahl repliziert, aber auf der anderen Seite könnte jeder Container auf einem anderen Server des Schwarms laufen und die Last irgendwie „verteilen“.

Dies ist die einzige Möglichkeit, diese Art von Systemen zu „skalieren“, und es könnte auch für Mastodon selbst funktionieren, nicht nur für Pleroma.

Sicher, wir könnten das gesamte Mastodon oder das gesamte Pleroma auf den gesamten Datenverkehr replizieren: Der Aufwand, alles andere zu replizieren, nur weil der /inbox-Datenverkehr zugenommen hat, ist sehr dumm, da /inbox anders skaliert als alles andere.

Wenn Sie ABCD skalieren, indem Sie einfach replizieren, weil B unter Last steht, erhalten Sie Folgendes:

ABCD

ABCD

ABCD

...

ABCD

während, wenn Sie A, B, C, D in separaten Instanzen skalieren, weil B unter Last steht, erhalten Sie:

A

BBB...B

C

D

Vermeidung von zu viel Overhead oder Duplizierung nutzloser Threads.

Natürlich können Sie sagen, dass der erste Weg (alles skalieren) nicht zu dramatisch erscheint, da ein Thread, der nichts tut, nicht viel Ressourcen verbraucht. Ich antworte Ihnen mit einem einzigen Wort: „Mastodon“ oder, wenn Sie es vorziehen, „Rubin“.


Im Allgemeinen KÖNNTE das Fediverse skalieren, aber um dies zu tun, müssen sowohl das Protokoll als auch die Software, die wir verwenden, neu geschrieben werden, wobei an eine Skalierung um mehrere Größenordnungen mehr als jetzt gedacht wird.

Andernfalls, wenn ein großer Akteur das Föderale in seine Protokolle aufnehmen würde, wäre das Ergebnis: Alle kleinen Instanzen werden ausgeknockt oder zur Deföderation gezwungen, nur weil sie sich die föderierte Zeitachse allein nicht leisten können.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert