Ich stelle den MariaDB Thread Cache gezielt ein, um Verbindungsaufbau und Thread-Erzeugung zu entschärfen. So senke ich Latenz und spare CPU‑Overhead, besonders bei vielen kurzen Sessions und hoher Verbindungsrate.
Zentrale Punkte
Die folgenden Aspekte bilden die Leitplanken für eine wirksame Einstellung und Messung des Caches. Ich konzentriere mich auf klare Werte und umsetzbare Schritte.
- Wirkprinzip: Wiederverwendung beendeter Threads statt teurer Neuerzeugung
- Relevanz: Nützlich bei vielen kurzen Verbindungen pro Sekunde
- Messung: Threads_created, Connections, Threads_cached
- Grenzen: Ignoriert, wenn Thread-Pool aktiv ist
- Vorgehen: Klein starten, messen, behutsam erhöhen
So arbeitet der MariaDB Thread Cache
Nach dem Trennen einer Verbindung legt MariaDB den Thread in einen Cache, solange das Limit nicht erreicht ist. Neue Verbindungen können diesen Thread wiederverwenden, was die teure Erstellung spart und die Antwortzeit senkt. Das wirkt besonders bei vielen Logins pro Sekunde und Workloads mit kurzen Sessions, in denen Erstellen und Zerstören von Threads zum spürbaren Kostenfaktor wird. Der Cache leert sich nach rund fünf Minuten Inaktivität, wodurch der Server keine unnötigen Altlasten anbindet. Ohne Thread‑Pool liegt der Standardwert häufig bei 256, was einen kleinen Puffer für typische Spitzen bietet. Ich beachte zusätzlich, dass die Wiederverwendung nicht alle Probleme löst: Schlechte Anbindung oder fehlerhafte Client‑Strategien bleiben sichtbar und erfordern eigene Korrekturen.
Wann sich das Tuning lohnt
Ich erhöhe den Cache, wenn die Anwendung viele kurze Verbindungen erzeugt und der Zähler Threads_created rasant wächst. Ein deutliches Signal ist eine hohe Quote aus Threads_created geteilt durch Connections, denn dann verfehlt die Wiederverwendung zu oft ihr Ziel. In diesem Fall drücken neue Threads die CPU und zerren an der Antwortzeit, während Reuse den Pfad verkürzt. Ich prüfe aber stets, ob die Ursache nicht beim Client liegt, etwa durch unnötige Reconnects. Bringt ein sauberes Connection‑Handling Ruhe in die Last, braucht der Cache oft nur eine moderate Anpassung. Wer blind maximiert, zahlt schnell mit Speicherverbrauch und übersieht echte Stellschrauben in der Applikationslogik.
Messwerte, die ich vorab prüfe
Für eine saubere Diagnose nutze ich wenige, aber aussagekräftige Kennzahlen mit klaren Formeln. Zu Beginn lese ich Threads_created, Connections, Threads_cached und Threads_connected aus und überprüfe Trends. Die einfache Quote Threads_created/Connections zeigt mir, wie oft die Datenbank neu baut statt wiederzuverwenden. Sehr hilfreich ist zudem die Nähe von Threads_cached zur üblichen Spitzenzahl gleichzeitiger Verbindungen. Bleiben Cache und Spitzenferne groß, verschenke ich Ressourcen oder treffe die Last nicht. Die folgende Tabelle bündelt wichtige Kennzahlen und ihre direkte Aussage:
| Kennzahl | Bedeutung | Interpretation | Aktion |
|---|---|---|---|
| Threads_created | Neu erzeugte Threads seit Start | Schnelles Wachstum deutet auf häufige Neuerzeugung | Cache prüfen, Client‑Reconnects reduzieren |
| Connections | Gesamte Verbindungsanzahl | Basis für Quote und Trendbewertung | Entwicklung zur Lastspitze beobachten |
| Threads_cached | Threads im Cache | Niedrig trotz hoher Frequenz kann zu klein sein | Cache in kleinen Schritten erhöhen |
| Threads_connected | Aktuell aktive Verbindungen | Anhaltspunkt für sinnvolle Cache‑Größe | Cache nahe typischer Spitzen dimensionieren |
Schrittweise Anpassung in der Praxis
Ich beginne mit Messung unter realistischer Last und erfasse die Kennzahlen vor jeder Änderung. Danach prüfe ich den aktuellen Wert mit SHOW VARIABLES LIKE 'thread_cache_size' und notiere die Basis für spätere Vergleiche. Ich erhöhe anschließend in kleinen Stufen und beobachte, ob Threads_created langsamer steigt und die Verbindungszeiten ruhiger werden. Eine einzelne große Änderung verschleiert Ursachen, daher setze ich bewusst auf kleine, überprüfbare Schritte. Nach jeder Anpassung warte ich auf eine aussagekräftige Lastphase, damit der Effekt belastbar bleibt. Erst wenn mehrere Lastfenster das Bild bestätigen, fasse ich den nächsten Schritt ins Auge.
Empfohlene Einstellungslogik und Ausgangswerte
Ein universeller Idealwert existiert nicht, daher richte ich mich nach typischen Spitzen und der Historie. Für ruhige oder mittlere Verbindungsraten genügt oft ein kleiner bis mittlerer Cache, besonders nahe dem Standard von 256. Bei stark schwankender Last und vielen Verbindungen pro Sekunde hilft eine höhere Spanne, solange die Wiederverwendung tatsächlich steigt. Ich halte den Cache etwas unterhalb der üblichen Spitzen von Threads_connected, damit ich keine unnötigen Ressourcen binde. Wer den Cache riesig macht, verschwendet Speicher, ohne Vorteile einzusammeln. Zusätzlich schaue ich auf begleitende Hintergrundfäden wie die Page Cleaner Threads, denn auch sie prägen das Gesamtverhalten bei hoher I/O‑Aktivität.
Speicherbedarf pro Thread und Auswirkungen der Cache‑Größe
Ich kalkuliere die Speicherwirkung des Caches bewusst ein. Ein gecachter Thread hält primär seinen thread_stack und geringe Thread‑Metadaten vor. Per‑Verbindungs‑Puffer wie sort_buffer_size, join_buffer_size oder der Netzpuffer werden beim Disconnect freigegeben und lasten den Cache nicht dauerhaft aus. Der Stack hingegen bleibt an den Thread gebunden. Als Faustwert setze ich an: Cache‑Speicher ≈ thread_cache_size × thread_stack (zuzüglich etwas Overhead). Bei einem thread_stack um 256–320 KB und einem Cache von 512 ergibt das schon in der Größenordnung von 130–170 MB gebundenen Speicher. Wer den Stack erhöht oder sehr große Caches fährt, sollte diesen Effekt im Blick behalten und gegen wichtigere Puffer (z. B. InnoDB‑Buffer) abwägen.
Ich prüfe deshalb stets:
SHOW VARIABLES LIKE 'thread_stack';um den pro Thread gebundenen Speicher zu kennen- Die Nähe von
Threads_cachedzur 95. Perzentil‑Spitze vonThreads_connected - Ob die Cache‑Vergrößerung die Quote
Threads_created / Connectionstatsächlich verbessert
Bleibt der Nutzen aus, reduziere ich wieder. Ein zu großer Cache zeigt sich daran, dass Threads_cached dauerhaft über der üblichen Verbindungs‑Spitze liegt, ohne dass Latenzen weiter sinken.
Betrieb auf Linux und in Containern: Limits und Stolpersteine
Ich verifiziere systemweite Limits, bevor ich den Cache hochsetze. Die Erstellung von Threads kann an OS‑Grenzen scheitern, lange bevor die Datenbank selbst an maximale Verbindungen stößt. Dabei prüfe ich:
- Prozess‑/Thread‑Grenzen:
ulimit -u(max. Prozesse/Threads),/proc/sys/kernel/threads-maxund/proc/sys/kernel/pid_max - Stack‑Limit:
ulimit -sbeeinflusst den pro Thread reservierten Stack – in Summe relevant für große Caches - cgroups im Container:
pids.maxund Memory‑Limits; zu enge PIDs‑Grenzen bremsen Bursts aus - Scheduler‑Druck: Bei sehr vielen Threads ohne Pool kann Kontextwechsel‑Overhead steigen; hier kann der Thread‑Pool oder App‑Pooling sinnvoller sein
Auf Multi‑Socket‑ oder NUMA‑Hosts beobachte ich zusätzlich, ob Threads über Nodes springen und dadurch Speicherfernzugriffe verursachen. In solchen Umgebungen sind stabile Pools oft effizienter als ständig neue Threads, die vom Scheduler breit verteilt werden.
Häufige Missverständnisse rund um den Thread Cache
Ich räume gängige Irrtümer aus dem Weg, um zielgerichtet zu optimieren:
- „Mehr Cache = immer schneller.“ Nur wenn tatsächlich viele neue Threads erzeugt werden, gewinnt der Cache. Sonst binde ich Speicher ohne Nutzen.
- „Der Cache beschleunigt die Authentifizierung.“ Der Cache spart primär die OS‑Thread‑Erstellung. Authentifizierung, TLS‑Handshake und ggf. DNS‑Lookups finden pro Verbindung statt und bleiben unabhängig optimierungsbedürftig.
- „Per‑Thread‑Puffer bleiben belegt.“ Nach dem Disconnect werden diese Puffer freigegeben; im Cache verbleibt hauptsächlich der Stack des Threads.
- „Ein hoher Cache ersetzt App‑Pooling.“ Serverseitiger Cache mildert Kosten, doch App‑seitiges Pooling vermeidet sie. Ich ziehe App‑Pooling immer als ersten Hebel in Betracht.
Einfluss von TLS, DNS und Authentifizierung
Ich werte Verbindungszeiten differenziert aus, da der Cache nicht jeden Anteil adressiert. Hohe Handshake‑Zeiten deute ich häufig als TLS‑Thema (Zertifikatsprüfung, fehlende Wiederaufnahme) oder DNS‑Rückwärtsauflösung. Mit skip_name_resolve=ON vermeide ich teure Reverse‑Lookups und stütze mich auf IP‑basierte Grants. Auch Wahl und Konfiguration des Auth‑Plugins beeinflussen den Login‑Pfad. Der Thread‑Cache reduziert dagegen primär die Kosten von Thread‑Erstellung und -Zerstörung. Sehe ich trotz großem Cache unverändert hohe Connect‑Latenzen, fokussiere ich auf TLS‑Parameter, DNS und das Client‑Verbindungsmanagement.
Entscheidungslogik: Cache, Thread‑Pool oder App‑Pooling?
Ich entscheide entlang eines einfachen Pfads:
- App‑Pooling verfügbar? Wenn ja, sauber dimensionieren. Sinkt
Threads_createddeutlich, reicht ein kleiner bis mittlerer Cache als Puffer. - Thread‑Pool aktiv? Dann greift
thread_cache_sizenicht. Ich tune den Pool und messe Wartezeiten, bevor ich andere Stellschrauben bewege. - Viele kurze Verbindungen ohne Pool? Cache moderat erhöhen. Ziel: spürbar fallende Quote
Threads_created / Connectionsund ruhigere Connect‑Zeiten. - Sehr hohe Parallelität und Scheduler‑Druck? Ich prüfe den Wechsel auf den Thread‑Pool, der Work‑Stealing und engere Worker‑Kontingente liefern kann.
Wichtig ist die Rückfallebene: Funktioniert ein Ansatz nicht messbar besser, nehme ich die letzte Änderung zurück. So bleibe ich nah an den Daten und vermeide Komplexität ohne Gegenwert.
Messmethodik mit Beispielabfragen
Ich nutze reproduzierbare Abfragen, um Fortschritte zu belegen. Für eine Momentaufnahme:
SHOW GLOBAL STATUS LIKE 'Threads\_%';liefertThreads_created,Threads_cached,Threads_connectedSHOW GLOBAL STATUS LIKE 'Connections';für die Basis der QuoteSHOW VARIABLES LIKE 'thread\_%';umthread_cache_sizeundthread_stackzu prüfen
Die Quote berechne ich z. B. so:
SELECT
ROUND(tc.variable_value+0 / NULLIF(c.variable_value+0,0), 4) AS threads_created_per_connection
FROM information_schema.GLOBAL_STATUS tc
JOIN information_schema.GLOBAL_STATUS c
ON tc.variable_name='Threads_created' AND c.variable_name='Connections'; Für Lasttests setze ich auf Zeitfenster. Ich nehme zwei Snapshots (Start und Ende eines 5‑ bis 10‑minütigen Intervalls) und bilde die Differenzwerte. Optional nutze ich in einer isolierten Testumgebung FLUSH STATUS, um Zähler zurückzusetzen – in Produktion meide ich das, um andere Auswertungen nicht zu stören. Neben der Quote speichere ich 95. und 99. Perzentile der Verbindungsdauer aus dem Client‑Monitoring, denn genau dort zeigen sich die Effekte auf die Latenz‑Spitzen.
Erkennen: Der Cache ist zu klein
Ich sehe einen zu kleinen Cache oft daran, dass Threads_created bei konstanter Last stark anzieht. Gleichzeitig bleiben Threads_cached niedrig, obwohl das System viele Verbindungen verarbeitet und die Quote schlecht ausfällt. Die Folge sind schwankende Latenzen und unnötige CPU‑Last durch häufige Thread‑Erstellung. Steigt der Cache moderat und stabilisiert die Kennzahlen, bestätigt das die Diagnose. Werden die Zeiten ruhiger und die Quote deutlich besser, habe ich die richtige Richtung eingeschlagen. Bleibt der Effekt aus, suche ich gezielt nach Client‑Ursachen, Netzproblemen oder Engpässen beim Storage.
Erkennen: Der Cache ist zu groß
Ein zu großer Cache fällt seltener auf, kann aber Speicher binden, der anderen Pufferzielen fehlt. Ich messe dann eine bereits gute Quote, doch mehr Cache ändert kaum etwas und belastet nur die Ressourcen. Wenn Threads_cached dauerhaft weit über der üblichen Spitze liegt, verpufft der Nutzen. Ich reduziere schrittweise und prüfe, ob sich Kennzahlen oder Antwortzeiten verändern. Bleibt alles stabil, setze ich auf das kleinere, effizientere Setup. So halte ich die Instanz schlank und lasse Raum für wichtigere Speicherbereiche wie InnoDB‑Buffer und Query‑Cache‑Ersatzstrukturen.
Besonderheit: Thread‑Pool aktiv
Sobald der Thread‑Pool läuft, ignoriert MariaDB die Variable thread_cache_size vollständig. In diesem Modus steuert ein Pool wenige Worker, die viele Verbindungen bedienen und so Wartezeiten für neue Threads verhindern. Ich entscheide anhand des Lastprofils, ob Pooling der richtige Ansatz ist oder der Cache mehr Flexibilität liefert. Stark parallelisierte Workloads profitieren oft vom Pool, während klassische Login‑Spitzen mit Cache‑Reuse gut fahren. Wer den Pool nutzt, fokussiert auf dessen Parameter und lässt thread_cache_size außen vor. Ein guter Startpunkt ist die Lektüre zum MariaDB Thread‑Pool, bevor ich weitere Tuning‑Schritte plane.
Interaktion mit Connection Pooling der Anwendung
Ich bevorzuge einen App‑seitigen Pool, weil er Verbindungen offenhält und den Datenbankserver entlastet. Bleibt Threads_created trotz hoher Last niedrig, zeigt das wirksames Pooling und einen geringen Bedarf an zusätzlichem Cache. In diesem Setup reicht oft ein kleiner Cache, der vereinzelte Spitzen abfedert und keine Ressourcen verschwendet. Sehe ich dagegen ständige Reconnects, wirkt zuerst sauberes App‑Pooling und erst dann die Vergrößerung der Datenbankeinstellung. Ein Blick auf Idle‑Zeiten und Pool‑Größen hilft, den Sweetspot für gleichmäßige Last zu finden. Nützliche Einstiegsinfos liefert ein Praxisleitfaden zu Connection Pooling, den ich parallel zur Cache‑Optimierung heranziehe.
Beispiel: Konfiguration und Kontrolle
Ich prüfe zunächst die aktuelle Einstellung mit SHOW VARIABLES LIKE 'thread_cache_size' und dokumentiere die Last. Danach setze ich testweise einen moderaten Wert wie SET GLOBAL thread_cache_size = 256; oder 512, je nach Spitzen. Wichtig ist, dauerhaft in der Konfigurationsdatei zu ändern, etwa in my.cnf unter [mysqld], damit ein Neustart die Anpassung behält. In den folgenden Lastfenstern beobachte ich Threads_created und die zugehörige Quote, bis ich einen klaren Trend erkenne. Fällt die Neuerzeugung deutlich, trifft der Cache sein Ziel. Bei unveränderten Zahlen suche ich Ursachen im Verbindungsmanagement, bevor ich den Wert weiter erhöhe.
Praxis‑Playbook: Sizing mit Leitplanken
Ich arbeite mit belastbaren Richtwerten, statt blind zu maximieren:
- Start: Aktuelle Spitzen von
Threads_connectedbeobachten (über mehrere typische Lastfenster). - Erste Dimensionierung: Cache ≈ 70–90 % der üblichen Spitze, zusätzlich gedeckelt durch eine Obergrenze wie max_connections / 2 als Sicherheitslimit.
- Schrittweite: In kleinen Stufen von 64–128 erhöhen und die Quote
Threads_created / Connectionsprüfen. - Zielkorridor: Deutlich sinkende Quote und ruhigere 95. Perzentile bei Verbindungszeiten; bleibt der Effekt aus, Cache zurücknehmen.
- Persistenz: Ab MariaDB‑Versionen mit
SET PERSISTsichere ich getestete Werte direkt serverseitig, andernfalls inmy.cnf. - Rollback: Vor jeder Änderung lege ich den vorherigen Wert fest, um im Zweifel schnell zurückzudrehen.
In Umgebungen mit sehr unterschiedlichen Tag/Nacht‑Profilen empfehle ich eine konservative Dimensionierung, die Peaks glättet, ohne nachts unnötig viel Speicher zu binden. Für Sonderlasten (Deployments, Cron‑Wellen) plane ich bewusst Puffer ein.
Troubleshooting‑Checkliste
Ich kontrolliere zuerst, ob der Thread‑Pool aktiv ist und damit den Cache aushebelt. Anschließend messe ich die Quote aus Threads_created/Connections in mehreren Zeitfenstern, statt nur einen Schnappschuss zu nehmen. Danach vergleiche ich Threads_cached mit der Spitze von Threads_connected, um Über- oder Unterdimensionierung zu erkennen. Bleibt die Leistung schwach, untersuche ich Applikations‑Reconnects, Netzwerk‑Latenzen und Storage‑Signale wie erhöhte I/O‑Wartezeiten. Zum Schluss prüfe ich konkurrierende Einstellungen, die Threads beeinflussen, und liefere wiederholbare Testszenarien. Nur so ziehe ich klare Schlüsse und vermeide Aktionismus ohne tragfähige Datenbasis.
Kurzfassung für Eilige
Ich setze den Thread‑Cache ein, um Threads wiederzuverwenden und die Erstellungskosten zu senken. Wirkung entfaltet das bei hoher Verbindungsfrequenz, während ein aktiver Thread‑Pool die Variable ignoriert. Messbar wird Erfolg über eine sinkende Quote aus Threads_created zu Connections und ruhigere Verbindungszeiten. Ich starte klein, messe konsequent und erhöhe nur, wenn Zahlen und Profil das rechtfertigen. Client‑seitiges Pooling bleibt oft der größere Hebel, daher prüfe ich dort zuerst. So erreiche ich mehr Performance mit weniger Overhead und halte die Konfiguration schlank.


