Rustango docs
← Anleitungen

Datenbank-Tuning

Jede Datenbankverbindung Ihrer Anwendung stammt aus einem Pool — einer Menge offen gehaltener Verbindungen, die an Requests ausgegeben werden. Die Voreinstellungen des Pools kommen vom Treiber, nicht von Ihrer Last, und sie sind für die meisten Produktivsysteme in mindestens einem Punkt falsch.

Diese Seite erklärt, was jeder Parameter bewirkt, was schiefgeht, wenn man ihn unangetastet lässt, und wo man ihn setzt.

Warum überhaupt tunen

Vier Fehlerbilder, jedes durch eine andere Voreinstellung verursacht:

Ihr zehnter gleichzeitiger Request wartet. Der Standard-Pool des Treibers hält 10 Verbindungen. Request elf schlägt nicht fehl — er wartet in der Schlange, und Ihre p99-Latenz steigt, während die CPU untätig ist. Nichts im Log sagt „Pool erschöpft"; Sie sehen langsame Requests.

Eine nicht erreichbare Datenbank legt den ganzen Server lahm. Kann keine Verbindung geholt werden, wartet der Aufrufer. Die Voreinstellung des Treibers sind 30 Sekunden — eine Zahl für Batch-Werkzeuge: auf dem Request-Pfad bedeutet sie, dass ein Worker pro Request eine halbe Minute blockiert ist. Der Ausfall einer Datenbank sättigt so alle Worker und reißt Oberflächen mit, die diese Datenbank nie anfassen. Rustango setzt deshalb 5 Sekunden als Voreinstellung.

Verbindungen sterben unbemerkt, und der nächste Request zahlt dafür. Ein Load Balancer, eine Firewall oder PostgreSQLs eigenes idle_in_transaction_session_timeout schließt eine untätige Verbindung. Ihr Pool weiß davon nichts. Der nächste Request, der sie ausleiht, bekommt einen Broken Pipe — sporadisch und bei geringer Last, also genau die Sorte Fehler, die sich am schlechtesten reproduzieren lässt.

Ein Failover oder eine Passwortrotation greift nicht. Gepoolte Verbindungen sind bewusst langlebig. Nach einem Failover oder einem rotierten Passwort kann ein Pool weiter Verbindungen zum alten Server oder mit den alten Zugangsdaten benutzen, bis etwas sie schließt.

Wo Sie es einstellen

Drei Orte. Weiter oben gewinnt, damit ein Notfall-Override zur Deploy-Zeit niemals einen Config-Push und einen Neustart Ihrer Config-Pipeline braucht.

wowofür
1UmgebungsvariablenOverrides pro Deployment, Secrets-Manager, Nachjustieren im Notfall
2[database] in Ihrer Settings-Stufedie Werte, mit denen Ihr Projekt normalerweise läuft, in der Versionsverwaltung
3Framework-Voreinstellungenwas Sie bekommen, wenn Sie nichts setzen

In einer Settings-Stufe

Settings liegen in config/*_settings.toml, eine Datei pro Umgebung. Tragen Sie die Werte in die Stufe ein, zu der sie gehören — Entwicklungswerte in dev_settings.toml, Produktionswerte in prod_settings.toml:

[database]
pool_max_size             = 50
pool_min_size             = 5
pool_acquire_timeout_secs = 5
pool_idle_timeout_secs    = 600
pool_max_lifetime_secs    = 1800

Als Umgebungsvariablen

Jeder Parameter hat ein Umgebungs-Override, das Vorrang vor dem TOML hat:

RUSTANGO_DB_MAX_CONNECTIONS=50
RUSTANGO_DB_MIN_CONNECTIONS=5
RUSTANGO_DB_ACQUIRE_TIMEOUT_SECS=5
RUSTANGO_DB_IDLE_TIMEOUT_SECS=600
RUSTANGO_DB_MAX_LIFETIME_SECS=1800

Ein Wert, der keine positive ganze Zahl ist, wird mit einer Warnung ignoriert statt befolgt — ein Tippfehler soll weder stillschweigend eine Grenze auf null setzen noch den Start abbrechen.

Eine Reihenfolgeregel

Die Settings werden von Cli::with_settings(...) auf die Pools angewendet. Rufen Sie das auf, bevor irgendetwas eine Datenbankverbindung öffnet. Ein früher gebauter Pool läuft mit den Umgebungs-Voreinstellungen, und das Framework protokolliert eine Warnung mit der Anzahl betroffener Pools, statt Sie das unter Last entdecken zu lassen.

Die Parameter

SettingUmgebungsvariableVoreinstellungWirkung
pool_max_sizeRUSTANGO_DB_MAX_CONNECTIONS10 (Treiber)Höchstzahl geöffneter Verbindungen. Weitere Requests warten.
pool_min_sizeRUSTANGO_DB_MIN_CONNECTIONS0 (Treiber)Verbindungen, die auch im Leerlauf offen bleiben.
pool_acquire_timeout_secsRUSTANGO_DB_ACQUIRE_TIMEOUT_SECS5Wie lange ein Aufrufer auf eine Verbindung wartet, bevor er scheitert.
pool_idle_timeout_secsRUSTANGO_DB_IDLE_TIMEOUT_SECSTreiber-VoreinstellungSchließt Verbindungen, die so lange untätig waren.
pool_max_lifetime_secsRUSTANGO_DB_MAX_LIFETIME_SECSTreiber-VoreinstellungSchließt Verbindungen ab diesem Alter, unabhängig von der Nutzung.

Einen Parameter nicht zu setzen ist nicht dasselbe, wie ihn auf null zu setzen. Nicht gesetzt heißt: die Voreinstellung des Treibers gilt — also das Verhalten, das Sie hatten, bevor es den Parameter gab.

pool_max_size

Die Obergrenze für gleichzeitige Datenbankarbeit. Erhöhen Sie sie, wenn Requests auf Verbindungen warten; das Symptom ist Latenz, die mit dem Verkehr wächst, während die Datenbank selbst nicht ausgelastet ist.

Es ist eine Obergrenze, kein Zielwert — der Pool öffnet Verbindungen nach Bedarf und nur bis zu dieser Zahl.

Die wichtige Nebenbedingung liegt auf der anderen Seite: Ihr Datenbankserver hat sein eigenes Verbindungslimit (PostgreSQLs max_connections, typischerweise 100). Die Summe aller Pools über alle Repliken muss darunter bleiben, sonst werden neue Verbindungen abgewiesen. Zehn Pods mit pool_max_size = 50 sind 500 Verbindungen gegen einen Server, der 100 erlaubt.

pool_min_size

Verbindungen, die offen bleiben, auch wenn sie niemand benutzt. Es geht um Latenz: bei 0 zahlt der erste Request nach einer ruhigen Phase den vollen TCP-, TLS- und Authentifizierungs-Roundtrip, bevor irgendeine Query läuft.

Bemessen Sie den Wert nach Ihrer Grundlast, nicht nach der Spitze. Offen gehaltene Verbindungen kosten auch auf dem Server Ressourcen.

pool_acquire_timeout_secs

Wie lange ein Aufrufer auf eine Verbindung wartet, bevor er aufgibt — das umfasst sowohl den Aufbau einer neuen Verbindung als auch das Anstehen für eine freie.

Dieser Parameter entscheidet, wie sich Ihre Anwendung verhält, wenn die Datenbank nicht erreichbar ist. Zu hoch, und Worker blockieren an einer Datenbank, die nie antworten wird; zu niedrig, und eine legitime Lastspitze — bei der Anstehen echte, produktive Arbeit ist — wird zu Fehlern.

Die Voreinstellung von 5 Sekunden ist bewusst enger als die 30 des Treibers. Erhöhen Sie sie bei langlaufenden Queries und einem ausgelasteten, aber gesunden Pool; senken Sie sie, wenn Sie Last lieber schnell abweisen.

pool_idle_timeout_secs

Schließt Verbindungen, die ungenutzt herumlagen. Setzen Sie den Wert unter das kürzeste Idle-Timeout auf dem Weg zwischen Anwendung und Datenbank — Load Balancer, Proxy, Connection-Tabelle einer Firewall oder die Idle-Timeouts des Datenbankservers selbst. Schließt etwas anderes die Verbindung zuerst, gibt Ihr Pool eine tote Verbindung aus.

10 Minuten sind ein gängiger Startwert.

pool_max_lifetime_secs

Schließt Verbindungen ab einem festen Alter, ob gesund oder nicht. Das ist der Parameter, der Failover und Passwortrotation überhaupt erst wirksam macht: ohne ihn kann ein Pool weiter mit dem Server sprechen, zu dem er beim Start verbunden hat.

30 Minuten sind ein gängiger Startwert. Kürzer, wenn Sie Zugangsdaten mit kurzer Gültigkeit aus einem Secrets-Manager beziehen.

Backends unterscheiden sich

SQLite ist kein Server, und Pool-Größen bedeuten dort etwas anderes. SQLite serialisiert Schreiber global — immer nur ein Schreiber, unabhängig von der Pool-Größe — ein höheres pool_max_size bringt also Lese-, aber nie Schreibparallelität. Bei einer In-Memory-Datenbank sind zusätzliche Verbindungen schlimmer als nutzlos: jede ist eine eigene leere Datenbank, sofern die URL nicht cache=shared setzt.

PostgreSQL und MySQL erzwingen beide ein serverseitiges Verbindungslimit. Bemessen Sie Ihre Pools an diesem Budget über alle Repliken hinweg, und denken Sie daran, dass Connection-Pooler wie PgBouncer die Rechnung verändern.

Verbindungsoptionen sind etwas anderes

Die obigen Parameter betreffen den Pool. Optionen für eine einzelne Verbindung — TLS, Verbindungs-Timeouts, der Anwendungsname, den der Server sieht — reisen in der Verbindungs-URL mit:

postgres://user:pw@host:5432/db?sslmode=require&connect_timeout=10&application_name=myapp
mysql://user:pw@host:3306/db?ssl-mode=REQUIRED
sqlite://./dev.db?mode=rwc

Diese werden an den Treiber durchgereicht; alles, was dessen URL-Parser akzeptiert, funktioniert.

Tenant-Pools

In einer mandantenfähigen Anwendung bekommt jeder Tenant im Datenbank-Modus seinen eigenen Pool, und diese werden separat über TenantPoolsConfig konfiguriert — siehe Tenant-Pool-Tuning in der manage-Anleitung. Ihre Voreinstellungen weichen von denen des primären Pools ab; insbesondere ist das Acquire-Timeout der Tenants großzügiger, was einen Blick wert ist, wenn Sie viele Tenants gegen Datenbanken betreiben, die unabhängig voneinander ausfallen können.