Direkt zum Inhalt

Föderation braucht Fehlertoleranz an den Instanzgrenzen

Gespeichert von dadmin02 am

Wie langsame oder fehlerhafte Gegenstellen die eigene Instanz belasten können – und warum ein föderiertes Netzwerk mehr ist als die Summe gut administrierter Server

Föderierte Netzwerke leben von ihrer Dezentralität. Jede Instanz wird eigenständig betrieben, unterschiedlich ausgestattet und unterschiedlich administriert. Genau das gehört zu den Stärken des Fediverse.

Diese Unabhängigkeit hat jedoch eine technische Konsequenz, die aus unserer Sicht zu wenig Beachtung findet:

Eine föderierte Instanz muss nicht nur selbst zuverlässig funktionieren. Sie muss auch damit umgehen können, dass andere Instanzen langsam, überlastet, fehlerhaft oder zeitweise nicht erreichbar sind.

Wir haben diese Fragestellung in den vergangenen Wochen an unserer eigenen Friendica-Instanz sehr konkret untersucht.

Ein zunächst schwer greifbares Lastproblem

Auf unserem Server traten wiederholt ungewöhnliche Lastsituationen auf. Auffällig war dabei insbesondere PHP-FPM. Innerhalb kurzer Zeit konnte die Zahl der Prozesse erheblich ansteigen.

Das Problem war zunächst schwer einzugrenzen.

Der Server selbst bot keine offensichtliche Erklärung:

  • genügend Arbeitsspeicher,
  • keine dauerhafte CPU-Überlastung,
  • MariaDB überwiegend unauffällig,
  • Redis ohne erkennbare Verzögerungen,
  • keine einzelne angreifende IP-Adresse,
  • keine dauerhaft überfüllte Friendica-Worker-Warteschlange.

Auch die Lastspitzen waren nicht kontinuierlich vorhanden. Sie kamen schubweise und verschwanden teilweise wieder, bevor eine detaillierte Untersuchung möglich war.

Deshalb haben wir die PHP-FPM-Diagnose schrittweise erweitert.

Der PHP-FPM-Slowlog brachte die erste Spur

Mit einem temporär aktivierten PHP-FPM-Slowlog konnten wir feststellen, dass sehr viele langsame Requests im gleichen Bereich endeten:

curl_exec()

Die dazugehörigen Friendica-Aufrufketten führten unter anderem durch ActivityPub-Komponenten wie HTTPSignature und APContact.

Damit entstand eine erste plausible Erklärung:

Ein eingehender Request konnte dazu führen, dass Friendica während dessen Bearbeitung selbst eine externe Gegenstelle kontaktierte. Antwortete diese Gegenstelle langsam, blieb das PHP-FPM-Kind entsprechend lange gebunden.

Das allein war zunächst nur eine Interpretation des PHP-Stacks.

Wir wollten wissen, ob sich dieses Verhalten auch auf Betriebssystemebene nachweisen lässt.

ss und strace machten das Problem sichtbar

Während eines passenden Ereignisses konnten wir einen betroffenen PHP-FPM-Prozess gleichzeitig mit ss und strace beobachten.

Dabei zeigte sich ein besonders interessanter Fall.

Ein PHP-FPM-Prozess wartete über rund 14 Sekunden wiederholt in:

poll()

auf Daten eines externen HTTPS-Sockets.

Über ss ließ sich genau derselbe File-Descriptor derselben Prozess-ID einer externen TCP-Verbindung zuordnen.

Gleichzeitig bestanden mehrere weitere PHP-FPM-Verbindungen zu denselben wenigen Gegenstellen.

Das ergab erstmals eine direkt beobachtbare Kette:

eingehender Request
        ↓
PHP-FPM-Prozess
        ↓
externer HTTPS-Aufruf
        ↓
Gegenstelle antwortet langsam
        ↓
PHP-FPM-Prozess wartet
        ↓
weitere Requests benötigen weitere PHP-FPM-Prozesse

Bemerkenswert war dabei auch das Gegenbild: MariaDB und Redis antworteten während derselben Untersuchung im Mikro- beziehungsweise Submillisekundenbereich. Auch andere externe HTTPS-Verbindungen wurden schnell aufgebaut und beantwortet.

Das lokale System war in diesem Fall also nicht der eigentliche Engpass.

Rund 99 Prozent der Slowlog-Ereignisse führten zu cURL

Nach mehreren Tagen Messzeit haben wir den Slowlog abschließend ausgewertet.

Für den betrachteten Zeitraum ergaben sich:

  • Slowlog-Ereignisse: 196.466
  • davon mit curl_exec(): 195.069

Damit enthielten rund 99,3 Prozent der aufgezeichneten Slow-Requests einen curl_exec()-Aufruf.

Das ist für uns der deutlichste Befund der gesamten Untersuchung.

Der zugrunde liegende Mechanismus war also nicht primär CPU-, Datenbank- oder Speicherlast, sondern das Warten auf externe HTTP-Kommunikation.

Warum hohe Timeouts problematisch werden können

Ein einzelner langsamer Request wäre kaum relevant.

In einem föderierten Netzwerk treten solche Vorgänge jedoch parallel auf.

Wenn mehrere eingehende Aktivitäten gleichzeitig zusätzliche Profil-, Schlüssel- oder ActivityPub-Abfragen bei entfernten Systemen auslösen, können entsprechend viele PHP-FPM-Kinder gleichzeitig auf externe Antworten warten.

Dabei benötigen sie möglicherweise kaum CPU-Leistung. Trotzdem bleiben sie belegt.

Ein hoher Timeout bedeutet deshalb nicht nur:

Ein einzelner Request darf lange warten.

Sondern unter hoher Parallelität möglicherweise:

Viele PHP-FPM-Prozesse dürfen gleichzeitig lange warten.

Damit wird die maximale Wartezeit selbst zu einem wichtigen Kapazitätsparameter.

Was wir angepasst haben

Wir haben die Konfiguration nicht nach einem vorgegebenen Schema verändert, sondern schrittweise aus den Messungen heraus angepasst.

Im Mittelpunkt standen dabei:

  • kürzere externe Wartezeiten,
  • eine sinnvoll dimensionierte PHP-FPM-Reserve,
  • eine harte Abbruchgrenze für aus dem Ruder laufende Requests,
  • die Beobachtung der normalen und verzögerten Worker-Warteschlangen,
  • sowie die Trennung zwischen eigentlicher Fehlerursache und bloßer Notbremse.

Auf konkrete Zahlen verzichten wir an dieser Stelle bewusst.

Solche Grenzwerte lassen sich aus unserer Sicht nicht sinnvoll verallgemeinern. Sie hängen unter anderem von Instanzgröße, Benutzeraktivität, Federation-Volumen, PHP-FPM-Konfiguration, verfügbarem Arbeitsspeicher und dem Verhalten der Gegenstellen ab.

Entscheidend ist nicht das Kopieren einzelner Parameter, sondern das Verständnis der Wechselwirkungen und die Messung auf der eigenen Instanz.

Die Auswirkungen waren deutlich sichtbar

Nach den Änderungen normalisierte sich auch der Load Average unseres Servers wieder deutlich.

Noch interessanter war die Entwicklung des Slowlogs.

Die Zahl der Slowlog-Ereignisse pro Tag entwickelte sich unter anderem so:

DatumSlowlog-Ereignisse
31.08.70.921
01.09.42.172
02.09.25.516
03.09.29.811
04.09.611
05.09.1.701
06.09.5.830
07.09.3.452

Ab dem 4. September befand sich das System damit auf einem völlig anderen Niveau.

Wichtig ist allerdings die Interpretation:

Der zugrunde liegende Mechanismus ist nicht verschwunden.

Friendica führt weiterhin externe HTTP-Abfragen aus, und entfernte Instanzen können weiterhin langsam antworten.

Aber die Auswirkungen dieser Wartezustände auf die eigene Instanz lassen sich begrenzen.

Genau darin liegt aus unserer Sicht die entscheidende Erkenntnis.

Das schwächste Glied ist nicht die richtige Betrachtung

Unsere ursprüngliche Überlegung lautete sinngemäß:

Ein föderiertes Netzwerk ist nur so gut wie sein schwächstes Glied.

Diese Aussage greift zu kurz.

In einem dezentralen Netzwerk wird es zwangsläufig unterschiedlich leistungsfähige Instanzen geben. Manche laufen auf großen Servern, andere auf kleinen VPS-Systemen oder privaten Anschlüssen. Manche werden hervorragend administriert, andere weniger gut.

Diese Unterschiede können und sollen durch Föderation nicht beseitigt werden.

Die entscheidende Frage lautet vielmehr:

Wie muss eine Instanz mit stärkeren und schwächeren Gegenstellen umgehen, damit deren Zustand die eigene Funktionsfähigkeit möglichst wenig beeinflusst?

Das ist für uns inzwischen die wesentlich wichtigere Fragestellung.

Robustheit muss an der Instanzgrenze entstehen

Ein Administrator kann eine fremde Instanz weder reparieren noch optimieren.

Er kann und muss aber beeinflussen können, wie seine eigene Instanz auf deren Verhalten reagiert.

Zu einer robusten föderierten Architektur gehören deshalb aus unserer Sicht Mechanismen wie:

  • sinnvolle und abgestufte Timeouts,
  • asynchrone Verarbeitung, wo immer dies möglich ist,
  • Warteschlangen und Retry-Mechanismen,
  • Backoff bei wiederholt gestörten Gegenstellen,
  • Begrenzung paralleler Verbindungen,
  • gegebenenfalls Circuit-Breaker-Mechanismen,
  • und möglichst wenig synchrone externe Kommunikation innerhalb eines Benutzerrequests.

Die zentrale Forderung lässt sich einfach formulieren:

Die Schwäche einer fremden Instanz darf nicht proportional Ressourcen auf der eigenen Instanz binden. 

Föderation ist mehr als „meine Instanz“

In Diskussionen über den Betrieb föderierter Systeme begegnet uns gelegentlich eine sehr lokale Sichtweise:

Meine Instanz funktioniert. Für die anderen bin ich nicht verantwortlich.

Der erste Satz ist richtig. Der zweite greift technisch zu kurz.

Natürlich ist kein Administrator für fremde Server verantwortlich.

Aber eine föderierte Instanz kommuniziert permanent mit fremden Systemen. Deren Antwortzeiten, Fehlerzustände und Ausfälle werden damit zwangsläufig Teil des eigenen Betriebsumfeldes.

Die Verantwortung besteht deshalb nicht darin, andere Instanzen zu administrieren.

Sie besteht darin, die eigene Instanz auf ein heterogenes und teilweise fehlerhaftes Netzwerk vorzubereiten.

Genau darin sehen wir einen wesentlichen Unterschied zwischen einem normalen Webserver und einem Server innerhalb eines föderierten Systems.

Unser Fazit

Unsere Untersuchung begann mit einem ungewöhnlich anwachsenden PHP-FPM-Pool.

Am Ende führte sie zu einer wesentlich allgemeineren Erkenntnis.

Föderation bedeutet nicht nur, dass Server miteinander kommunizieren können. Föderationsfähigkeit bedeutet auch, dass eine Instanz die Unzuverlässigkeit anderer Instanzen verkraften kann. 

Je größer und vielfältiger das Fediverse wird, desto wichtiger dürfte genau diese Eigenschaft werden.

Die technische Qualität eines föderierten Systems zeigt sich deshalb nicht nur darin, wie gut es unter idealen Bedingungen arbeitet.

Sie zeigt sich insbesondere darin, wie gut es mit den unvermeidbaren Schwächen des Netzes umgeht.