|
8 Min. Lesezeit
|

Aidbox HTTP Queue: Verhalten, Fehlerszenarien und Steuerung

Diesen Artikel zusammenfassen mit:
ChatGPTPerplexityClaudeGrok

Die Datenaufnahme ist eine der ersten großen Herausforderungen für jeden FHIR-Anwender — egal ob Sie gerade starten, eine neue Umgebung aufsetzen oder von einem bestehenden Server migrieren. Ist die Datenaufnahme schlecht konzipiert, kämpfen Teams mit langsamen Migrationen, fehlerhaften Referenzen und Systemen, die unter Last zunehmend instabil werden.

Dies ist Teil einer Artikelserie, die wir zur FHIR-Datenaufnahme in Aidbox veröffentlichen. Jeder Artikel beleuchtet einen spezifischen Aspekt des Problems. In diesem Beitrag konzentrieren wir uns auf die Aidbox HTTP Queue: wie sie funktioniert und worauf Sie achten sollten.

Aidbox lässt sich als asynchroner, nicht-blockierender Webserver betrachten, der auf einer Request-Queue und einer festen Anzahl von Worker-Threads (BOX_WEB_THREAD) aufbaut. Jede eingehende Anfrage wird zunächst in die Queue eingereiht und dann zur Verarbeitung übernommen.

Übersteigt die eingehende Anfragerate die Anzahl der verfügbaren Worker, beginnen Anfragen sich in der Queue anzusammeln. Infolgedessen reagiert der Server nicht mehr in Echtzeit, und die Latenz steigt. Bei anhaltender Last können einige Anfragen nicht rechtzeitig verarbeitet werden und schlagen schließlich aufgrund von clientseitigen Timeouts fehl.

Das Vorhandensein von Einträgen in der HTTP-Queue zeigt an, dass das System unter Druck steht und mehr gleichzeitige Anfragen erhält, als es sofort verarbeiten kann. Eine nahezu leere Queue entspricht typischerweise niedriger Latenz, während eine kleine Queue akzeptabel und für eine effiziente Ressourcennutzung sogar vorteilhaft sein kann. Ein anhaltend wachsendes Queue-Volumen oder eine steigende Queue-Zeit ist jedoch ein deutliches Signal für Überlast — es bedeutet, dass die Worker dauerhaft ausgelastet sind und mit dem eingehenden Traffic nicht Schritt halten können.

Um diese Dynamiken anhand konkreter Metriken zu veranschaulichen, haben wir drei verschiedene technische Szenarien entwickelt. Durch die Analyse dieser Beispiele lassen sich wirksame Strategien identifizieren, um typische Fallstricke zu vermeiden und ein leistungsfähiges Client-Server-Kommunikationsmodell zu etablieren. Alle praktischen Empfehlungen finden Sie am Ende des Artikels.

Alle Szenarien basieren auf demselben Lastprofil, um Vergleichbarkeit zu gewährleisten:

  • Fixer Datensatz von 1.200 Bundles (z. B. 100 pro Batch)
  • Bundle-Größe und -Struktur bleiben über alle Tests konstant
  • Der Client sendet kontinuierlich Anfragen bis zum konfigurierten Parallelitätslimit

So lässt sich der Effekt von Parallelität und Timeout-Verhalten auf die HTTP-Queue isolieren, ohne dass Variabilität durch den Workload selbst entsteht.

Szenario 1: Unbegrenzte Parallelität + kurzes Client-Timeout

In diesem Setup ist Aidbox mit 8 Worker-Threads (BOX_WEB_THREAD) auf einer einzelnen CPU konfiguriert, während der Client bis zu 64 parallele Anfragen mit einem Timeout von 10 Sekunden generiert. Auf den ersten Blick mag dies wie ein vertretbarer Lastfluss wirken, doch in der Praxis entsteht dadurch ein klassisches Überlastmuster, das durch unausgewogene Parallelität und Timeout-Verhalten verursacht wird.

Scenario 1: queue growth and timeout-driven retry storm

Da die eingehende Rate höher ist als die Verarbeitungsrate, beginnt die Queue zu wachsen. Dies wirkt sich direkt auf die Latenz aus ≈ (Queue-Zeit + Verarbeitungszeit): Jede Anfrage muss nun länger in der Queue warten, bevor ein Worker sie übernimmt. Schließlich überschreitet die Wartezeit den clientseitigen Timeout-Schwellenwert von 10 Sekunden.

In diesem Moment tritt folgendes Verhalten auf: Der Client schließt die Verbindung und betrachtet die Anfrage als fehlgeschlagen, aber der Server bricht die Arbeit nicht ab. Die Anfrage verbleibt in der Queue (oder wird bereits verarbeitet) und verbraucht weiterhin Ressourcen bis zur Fertigstellung. Aus Sicht des Servers wird nach wie vor sinnvolle Arbeit geleistet — aus Sicht des Clients ist diese Arbeit jedoch bereits irrelevant.

Wenn sich Timeouts häufen, beginnt der Client mit Wiederholungsversuchen, die noch mehr Anfragen erzeugen. Diese neuen Anfragen werden zur bereits wachsenden Queue hinzugefügt, die noch veraltete Arbeit aus zuvor abgelaufenen Anfragen enthält. Dadurch entsteht eine positive Rückkopplungsschleife: Mehr Last führt zu mehr Timeouts, was Wiederholungsversuche auslöst und die Last weiter erhöht.

Szenario 2: Hohe Parallelität + keine Client-Timeouts

In dieser Konfiguration bleibt das Server-Setup unverändert — 8 Worker-Threads auf einer einzelnen CPU — während der Client weiterhin bis zu 64 parallele Anfragen sendet. Der entscheidende Unterschied besteht darin, dass der Client kein Timeout erzwingt, also bereit ist, beliebig lange auf eine Antwort zu warten.

Scenario 2: steady-state queue without timeouts

Wie in Szenario 1 übersteigt die eingehende Anfragerate die Verarbeitungskapazität des Servers. Anfragen werden in die HTTP-Queue eingereiht, und Worker verarbeiten sie mit fester Rate. Da der Client jedoch kein Timeout hat, werden keine Anfragen verworfen und keine Wiederholungsversuche gestartet.

Infolgedessen erreicht das System einen stabilen Zustand. Die Queue wächst zunächst, stabilisiert sich aber schließlich auf einem Niveau, das ungefähr der Differenz zwischen eingehender Parallelität und verfügbaren Workern entspricht (in diesem Fall etwa ~56 Anfragen in der Queue). Anstatt einer unkontrolliert wachsenden Queue haben wir nun einen konstanten Rückstand.

Von außen betrachtet sieht dies wesentlich gesünder aus: keine Timeouts, keine Fehler, keine Retry-Stürme. Diese Stabilität hat jedoch ihren Preis: höhere Latenz. Jede Anfrage muss in der Queue warten, bevor sie verarbeitet wird, was die Ende-zu-Ende-Antwortzeit erheblich verlängert.

Wichtig ist, dass der Durchsatz (RPS) im Vergleich zu Szenario 1 unverändert bleibt. Das System ist nach wie vor durch die Anzahl der Worker-Threads und die CPU-Kapazität begrenzt. Das Einzige, was sich verändert hat, ist die Art und Weise, wie die Überlast bewältigt wird: Anstatt verworfen zu werden (durch Timeouts), wird sie in Latenz umgewandelt.

Das bedeutet, das System arbeitet bei konstanter Sättigung — alle Worker sind dauerhaft ausgelastet, und die Queue dient als Puffer, der die Überlast auf Kosten der Reaktionsfähigkeit abfedert.

In dieser stabilen Konfiguration muss jede eingehende Anfrage den bestehenden Rückstand (ungefähr ~50 Anfragen in der Queue) durchlaufen, bevor sie verarbeitet werden kann. Die Queue wird zu einer Latenzbarriere für den gesamten Traffic.

Da die Queue geteilt wird, unterliegen alle Anfragetypen — einschließlich kritischer wie Health-Checks oder benutzerseitiger Operationen — derselben Verzögerung. Anfragen werden zwar nicht verworfen, ihr Erfolg hängt jedoch davon ab, ob sie diese akkumulierte Wartezeit tolerieren können.

Dies erzeugt ein subtiles, aber wichtiges Risiko: Das System wirkt stabil, aber jede Zunahme der Latenzempfindlichkeit (z. B. durch Einführung von Timeouts oder mehr Traffic) kann es schnell in einen Fehlerzustand treiben.

Szenario 3: Parallelität an Worker-Kapazität angepasst

In dieser Konfiguration ist die Client-Parallelität auf die Verarbeitungskapazität des Servers abgestimmt: 8 parallele Anfragen gegenüber 8 Worker-Threads auf einer einzelnen CPU. Anders als in den vorherigen Szenarien ist das System nicht mehr überlastet — die eingehende Anfragerate entspricht in etwa dem, was der Server verarbeiten kann.

Scenario 3: matched concurrency, low latency, zero buffer

Aufgrund dieser Abstimmung werden Anfragen von Workern fast sofort nach ihrem Eintreffen übernommen. Die HTTP-Queue bleibt nahezu leer, und die Queue-Verzögerung ist faktisch eliminiert. Jede Anfrage durchläuft das System mit minimaler Wartezeit, was zu niedriger und vorhersehbarer Latenz führt.

Aus Durchsatz-Perspektive arbeitet das System an seiner natürlichen Grenze: Der RPS entspricht der Worker-Kapazität, und es gibt keine künstliche Inflation oder Degradation durch Wiederholungsversuche oder Rückstau. Das System erscheint stabil, effizient und ausgewogen.

Dieses Setup hat jedoch eine wichtige Einschränkung. Da alle Worker kontinuierlich mit Client-Traffic ausgelastet sind, gibt es keine freie Kapazität, um zusätzliche Last abzufangen. Externe Anfragen — wie Health-Checks, Hintergrundaufgaben oder benutzerseitiger Traffic — konkurrieren sofort um dieselben Worker und beginnen, sich in der Queue anzusammeln.

Mit anderen Worten: Das System arbeitet bei voller Auslastung ohne Puffer. Dies minimiert zwar die Latenz unter kontrollierten Bedingungen, macht das System jedoch in realen Umgebungen anfällig, wo Traffic selten perfekt vorhersehbar ist.

Dieses Setup ist in Bezug auf Effizienz nahezu ideal — das System verarbeitet Anfragen mit minimaler Latenz und ohne Queue-Aufbau. Es arbeitet jedoch direkt an der Grenze seiner Kapazität.

Da alle Worker kontinuierlich ausgelastet sind, gibt es effektiv keinen Puffer, um zusätzliche Last abzufangen. Bereits ein geringer Anstieg des Traffics oder das Auftreten von Nebenanfragen wird das System sofort in Queueing und erhöhte Latenz treiben.

Mit anderen Worten: Das System ist perfekt ausbalanciert — aber sehr eng, ohne Spielraum für Variabilität oder reale Bedingungen.

Empfohlenes Lastmuster

Dieses Modell ist bewusst vereinfacht und keine genaue Darstellung des tatsächlichen Systemverhaltens, aber es ist nützlich, um ein grundlegendes Verständnis aufzubauen. In der Praxis kann ein einzelner Worker manchmal mehr als eine Anfrage gleichzeitig verarbeiten, abhängig von der CPU-Verfügbarkeit und der Art (Größe und Komplexität) des Workloads.

Als Faustregel empfiehlt es sich, in Begriffen der Abstimmung zu denken: Die Anzahl der parallelen Client-Anfragen an Aidbox sollte generell unterhalb der Anzahl der konfigurierten Web-Threads bleiben. Je mehr Neben- oder Hilfsanfragen (Health-Checks, Hintergrundaufgaben, Integrationen) das System verarbeiten muss, desto mehr Puffer sollten Sie einplanen — das bedeutet weniger parallele clientseitige Anfragen im Verhältnis zu den verfügbaren Workern.

Recommended load pattern: client concurrency below worker capacity

Mit anderen Worten: Nicht alle Worker sollten für eine einzige Traffic-Quelle „reserviert" sein; ein Teil der Kapazität sollte für alles andere verfügbar bleiben, was das System verarbeiten muss.

Eine effektive Konfiguration setzt robuste Observability voraus. Durch die Überwachung wichtiger Metriken wie Anfragevolumen, Queue-Tiefe, Latenz, RPS und Timeouts können Sie das optimale Gleichgewicht für Ihren spezifischen Workload ermitteln. Dieser datengesteuerte Ansatz ermöglicht es Ihnen, die Anzahl der Aidbox-Worker und die zugewiesenen CPU-Ressourcen für Ihre Umgebung präzise abzustimmen.

Weitere Aspekte der Aidbox-Skalierbarkeit und des Workload-Managements werden wir in kommenden Artikeln behandeln.

Konfiguration in Aidbox

Wenn Sie mit hochvolumiger FHIR-Datenaufnahme arbeiten, können Sie die Worker-Konfiguration in Aidbox anpassen. Performance-Tuning umfasst jedoch weit mehr als nur die Erhöhung der Worker-Anzahl — wir empfehlen, den Artikel zu lesen, der erklärt, wie Web-Worker und Datenbankverbindungen (db) gemeinsam zur Aidbox-Performance beitragen.

Starten Sie in Sekunden Ihre eigene Aidbox-Instanz, um weiter zu experimentieren, oder nehmen Sie Kontakt auf, um dieses Setup zu besprechen.

Diesen Artikel teilen
Comments
Comments
Sign in
Loading comments...
Subscribe to our blog

Get the latest articles on FHIR, interoperability, and healthcare IT.