Supabase 'max clients reached in session mode' auf Vercel, und die Lösung, die es schlimmer machte
Der Fehler lautet (EMAXCONNSESSION) max clients reached in session mode - max clients are limited to pool_size: 15. Unsere API lieferte ihn plötzlich, während die Marketing-Website völlig gesund blieb. Die Marketing-Website greift nämlich nicht auf Postgres zu. Genau deshalb bleibt diese Art von Fehler unbemerkt. Die Teile, die gut aussehen, sind die Teile ohne Datenbank.
Hier steht, was der Fehler bedeutet, welche zwei Fehler wir bei der Behebung gemacht haben und welcher Connection String von Vercel aus wirklich funktioniert.
Zwei Pooler, zwei Ports
Warum Serverless den Session Mode erschöpft
Jede warme Funktionsinstanz hält ihren eigenen kleinen Pool. Unsere hielten je drei. Ein Pool im Session Mode gibt keine Verbindungen zurück, solange die Instanz warm ist. Fünf warme Instanzen sind fünfzehn Verbindungen, also die Grenze. Die sechste Anfrage, egal von wo, schlägt fehl.
Ein Flotten-Daemon fragte alle paar Sekunden drei Endpunkte ab und hielt so rund um die Uhr Instanzen warm. Daran ist nichts ungewöhnlich. Es ist einfach Arithmetik, gegen die der Session Mode verliert.
Fehler eins: zuerst die falsche Ebene reparieren
Die Notlösung war max: 1 pro Instanz und ein Idle-Timeout von zwanzig Sekunden, weiterhin im Session Mode. Das funktionierte. Ruhige Instanzen gaben ihren Platz zurück, und die API erholte sich innerhalb von Minuten. Dann stellten wir den Connection String auf den Transaction Mode um. Das war richtig. Wir ließen max: 1 stehen. Das war falsch.
Fehler zwei: ein Pool von eins im Transaction Mode
Im Transaction Mode reiht eine einzige Verbindung pro Instanz jede Abfrage dieser Instanz hintereinander in eine Leitung. Die Abfragen des Daemons stauten sich dahinter. Anfragen überschritten ihre Frist von zwanzig Sekunden, und das Symptom wechselte von 500ern zu Hängern. Die Marketing-Website war weiter schnell. Die API antwortete gar nicht mehr.
Der Transaction Mode gibt eine Verbindung nach jeder Transaktion zurück. Er braucht also einen normal großen Pool. Wir setzten acht, und die Timeouts hörten auf.
Wie Sie es kommen sehen
Der Pooler sagt nichts, bis er Sie abweist. Postgres selbst sagt es Ihnen, wenn Sie fragen.
select application_name, state, count(*) from pg_stat_activity where datname = 'postgres' group by 1, 2 order by 3 desc;
Führen Sie das über eine Session-Verbindung aus, während die App normal belastet ist. Kriecht die Zahl auf die Grenze zu und sind die meisten Zeilen idle, sind Sie eine geschäftige Minute vom Fehler entfernt. Im Transaction Mode zeigt dieselbe Abfrage, dass Verbindungen wiederverwendet statt gehortet werden.
Der Connection String, der wirklich funktioniert
postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres
- Der Host ist der Pooler, nicht
db.<ref>.supabase.co. Das Supabase-Dashboard zeigt den direkten Host womöglich unter der Überschrift für den Transaction Pooler. Es bleibt trotzdem der direkte Host. - Der Benutzername ist
postgres.<project-ref>beim Pooler. Ein einfachespostgresfunktioniert nur auf dem direkten Host. - Der direkte Host ist nur per IPv6 erreichbar, außer Sie kaufen das IPv4-Add-on. Er hat einen AAAA-Eintrag und keinen A-Eintrag. Vercel-Funktionen erreichen ihn nicht, und Port 6543 ist dort ohnehin nicht offen.
- Kein Query String nötig für postgres.js. Setzen Sie
prepare: false. Das Flag?pgbouncer=trueist eine Prisma-Konvention. - Lassen Sie Migrationen auf 5432. Drizzle und ähnliche Werkzeuge wollen eine Session-Verbindung. Richten Sie die deployte App auf 6543 und Ihre lokale Migrationsumgebung auf 5432 aus.
$ dig +short A db.<project-ref>.supabase.co # (nothing) $ dig +short AAAA db.<project-ref>.supabase.co # 2600:1f18:... $ nc -z aws-0-us-east-1.pooler.supabase.com 6543 # succeeded
Dimensionierung in einer Tabelle
Eine letzte Sache ist gut zu wissen. Während all das passierte, kam der Daemon, der die API nicht erreichte, zu einem Schluss: Jede Maschine, die er verwaltete, sei tot. Er bereitete vor, sie stromlos neu zu starten. Diese Geschichte steht in wie ein Verbindungslimit der Datenbank fast die Macs meiner Kunden neu gestartet hätte.
Fragen
- Welchen Supabase-Port sollte eine Vercel-App nutzen?
- 6543, den Pooler im Transaction Mode. Der Session Mode auf 5432 hält eine Serververbindung pro Client und begrenzt das Projekt auf 15. Serverless verbraucht das schnell.
- Warum verbindet sich db.<project-ref>.supabase.co nicht von Vercel aus?
- Der direkte Host hat nur eine IPv6-Adresse, außer Sie zahlen für das IPv4-Add-on. Vercel-Funktionen erreichen ihn nicht über IPv6. Nutzen Sie den Pooler-Host, aws-0-<region>.pooler.supabase.com.
- Welchen Benutzernamen braucht der Pooler?
- postgres.<project-ref>, also mit angehängter Projekt-Ref. Ein einfaches postgres funktioniert nur auf dem direkten Host.
- Brauche ich ?pgbouncer=true im Connection String?
- Für Prisma ja. Für postgres.js setzen Sie stattdessen prepare: false in den Client-Optionen. Der Transaction Mode unterstützt keine Prepared Statements.
- Welche Poolgröße sollte jeder Modus nutzen?
- Session Mode: eine oder zwei pro Instanz mit kurzem Idle-Timeout, denn die Grenze liegt bei fünfzehn. Transaction Mode: ein normaler Pool von fünf bis zehn, denn Verbindungen werden nach jeder Transaktion zurückgegeben.
Rechnen Sie selbst nach mit dem Kostenrechner oder mieten Sie einen Runner.