Dimensionieren Sie einen Anwendungs-Threadpool danach, wie lange Anfragen arbeiten und wie lange sie warten.
Jeder Thread, der die Datenbank berührt, hält beim Warten eine Verbindung — dieser Pool setzt also die Untergrenze für Ihren Verbindungspool.
Warum der richtige Threadpool kleiner ist als gedacht — bis er es plötzlich nicht mehr ist
Ein Threadpool hat eine Aufgabe: die CPU beschäftigt halten, ohne dass sich Arbeit im Prozess staut. Die dafür passende Größe hängt fast ausschließlich von einem Verhältnis ab — wie lange eine Anfrage auf etwas anderes wartet, verglichen mit der Zeit, die sie wirklich rechnet. Dieses Werkzeug wendet die Standardformel dafür an, prüft sie gegen Little's Law und sagt Ihnen, wann das Ergebnis ein anderes Nebenläufigkeitsmodell verlangt statt einer größeren Zahl.
So wird gerechnet
- Wendet die klassische Formel an: Kerne × Zielauslastung × (1 + Warten ÷ Arbeiten).
- Ermittelt mit Little's Law die Nebenläufigkeit, die Ihr Zieldurchsatz tatsächlich verlangt.
- Nennt den Durchsatz, den der empfohlene Pool trägt, damit Sie sehen, ob er Ihr Ziel erreicht.
- Warnt, wenn die Rechnung über das hinausgeht, was ein Thread-pro-Anfrage-Modell sinnvoll leisten kann.
Threads = Kerne x Auslastung x (1 + Wartezeit / Bearbeitungszeit) Little's Law Nebenlaeufigkeit = Ankunftsrate x Latenz Durchsatzgrenze max_rps = Threads / (Bearbeitungszeit + Wartezeit)
Rechenbeispiel
Ein Dienst auf 8 Kernen, Ziel 80% CPU, bei dem eine Anfrage 20 ms rechnet und 80 ms auf die Datenbank wartet, in der Spitze 300 Anfragen pro Sekunde.
- Warten ÷ Arbeiten = 80 ÷ 20 = 4
- Threads = 8 × 0,8 × (1 + 4) = 32
- Latenz = 20 + 80 = 100 ms
- Little's Law: 300 Anfragen/s × 0,1 s = 30 gleichzeitige Anfragen
- 32 Threads ≥ 30, der Pool erreicht das Ziel also
- Grenze: 32 ÷ 100 ms = 320 Anfragen pro Sekunde
Zweiunddreißig Threads auf acht Kernen. Der Pool ist das Vierfache der Kernzahl, nicht weil Threads gratis wären, sondern weil jeder Thread zu 80% untätig ist — er hält einen Platz in der Warteschlange der Datenbank und nutzt keine CPU. Setzt man die Wartezeit auf null, liefert dieselbe Formel 6, was für nie blockierende Arbeit die richtige Antwort ist.
Das Ergebnis lesen
- Die Formel taugt nur so viel wie die beiden Zeiten, die Sie ihr geben. Bearbeitungszeit ist CPU-Zeit, nicht Wanduhrzeit, und beides wird leicht verwechselt — die Wanduhr enthält das Warten, also die andere Variable. Ein Profiler trennt sie, eine Stoppuhr nicht.
- Jeder Thread, der die Datenbank abfragt, hält beim Warten eine Verbindung. Damit ist der Threadpool die Untergrenze des Verbindungspools, und genau deshalb wird ein Verbindungslimit üblicherweise erreicht: Niemand hat beide zusammen bemessen.
- Jenseits einiger hundert Threads ist das Modell selbst das Problem. Jeder Plattform-Thread kostet etwa ein Megabyte Stack und einen Scheduler-Platz, ein Pool von 1.300 verbringt also mehr Zeit mit Kontextwechseln als mit Arbeit. Bei diesem Verhältnis lautet die Antwort nicht-blockierende E/A oder virtuelle Threads — deshalb sagt das Werkzeug das, statt die Zahl auszugeben und Sie damit allein zu lassen.
- Eine Warteschlange vor dem Pool ist keine zusätzliche Kapazität. Übersteigen die Ankünfte, was der Pool abarbeiten kann, wächst die Schlange unbegrenzt und die Latenz mit ihr — die Anfragen scheitern trotzdem, nur später und nach Speicherverbrauch. Begrenzen Sie die Schlange und werfen Sie Last ab, statt Tiefe für Durchsatz zu halten.
- Auslastungsziele über etwa 80% sind nicht mehr sicher. Warteschlangentheorie ist nahe der Sättigung unbarmherzig: Nähert sich die Auslastung 100%, steigt die Wartezeit unbegrenzt — die letzten 20% eines Kerns kosten weit mehr Latenz, als sie an Durchsatz einbringen.
Häufige Fragen
- Woher stammt die Formel (1 + Warten/Arbeiten)?
- Sie ist das Standardergebnis, um N Kerne beschäftigt zu halten, wenn eine Aufgabe den Kern nur einen Teil ihrer Lebensdauer belegt, bekannt gemacht durch Brian Goetz in Java Concurrency in Practice. Ohne Warten reduziert sie sich auf Kerne × Auslastung, was für CPU-gebundene Arbeit korrekt ist; das Verhältnis berücksichtigt lediglich die jeweils auf E/A geparkten Threads.
- Sollte der Threadpool dem Verbindungspool entsprechen?
- Der Verbindungspool sollte mindestens so groß sein wie die Zahl der Threads, die gleichzeitig in einem Datenbankaufruf stecken können, aber nicht größer, als das Limit der Datenbank über Ihre gesamte Flotte trägt. Widersprechen sich beide, lautet die Lösung weniger Threads oder mehr Anwendungsinstanzen — kein größerer Verbindungspool, der die Konkurrenz nur in die Datenbank verlagert.
- Machen virtuelle Threads das hinfällig?
- Sie verschieben, welche Zahl zählt. Virtuelle Threads sind billig genug für einen je Anfrage, ohne einen Pool zu bemessen — das Warteverhältnis schränkt Sie dann nicht mehr ein. Es bleibt die nachgelagerte Grenze: der Verbindungspool, das Ratenlimit einer fremden API, die CPU selbst. Diese müssen Sie weiterhin bemessen, und diese Rechnung zeigt nach wie vor, wo sie greifen.
- Wie messe ich Bearbeitungs- und Wartezeit?
- Bearbeitungszeit aus CPU-Profiling oder aus der Differenz zwischen Wanduhrzeit und der Summe Ihrer instrumentierten externen Aufrufe. Wartezeit aus den Spans, die Ihr Tracing für Datenbank- und HTTP-Aufrufe ohnehin erzeugt. Fehlt beides, beginnen Sie mit einer fundierten Schätzung und prüfen Sie die Vorhersage: Trägt der Pool deutlich weniger Durchsatz als berechnet, war Ihre Warteschätzung zu niedrig.