# Anslutningspool

## Vad det är

En anslutningspool är en uppsättning öppna databasanslutningar som en
applikation håller kvar och lånar ut igen, i stället för att öppna en ny
anslutning för varje arbetsuppgift och stänga den efteråt. Koden lånar en
anslutning ur poolen, kör sina satser på den och lämnar tillbaka den, och nästa
anropare som behöver en får samma anslutning, redan öppen. SQLAlchemys
dokumentation beskriver pooling som en standardteknik för att hålla långlivade
anslutningar i minnet så att de kan återanvändas effektivt. Samma sida ger
poolen ett uppdrag till: den styr hur många anslutningar applikationen får
använda samtidigt, så att en plötslig trafiktopp väntar på en ledig anslutning i
stället för att öppna fler.

En pool kan ligga på två ställen. Inne i applikationen är den en del av
databasbiblioteket: varje `Engine` i SQLAlchemy har en, och Django kan hålla en
i varje process för PostgreSQL. Utanför applikationen är den ett eget program
som står mellan klienterna och databasservern. PgBouncer är en sådan pooler för
PostgreSQL: applikationerna ansluter till den som om den vore databasen, och den
lånar ut serveranslutningar ur sina egna pooler. De två går att kombinera, och
Djangos databasreferens har ett avsnitt om att köra bakom en transaktionspooler
som PgBouncer.

Varför en anslutning är värd att behålla syns på serversidan. PostgreSQL ger
varje klient en egen process: en överordnad process, postmaster, lyssnar efter
inkommande anslutningar och startar en ny serverprocess för varje begäran om
anslutning, och varje klient är ansluten till exakt en av de processerna. Att
öppna en anslutning innebär alltså att starta en process på databasservern, och
en pool betalar det priset en gång och återanvänder sedan resultatet.

## Varför det spelar roll

Det första skälet är kostnaden för att ansluta. Djangos dokumentation säger det
rakt ut: en beständig anslutning sparar arbetet med att upprätta en ny i varje
HTTP-förfrågan. Standardinställningen gör ändå det motsatta. `CONN_MAX_AGE` är
0, vilket stänger anslutningen i slutet av varje förfrågan; ett positivt antal
sekunder håller den öppen för återanvändning, och `None` håller den öppen utan
gräns. Det är den enklaste formen av återanvändning, en anslutning per tråd som
lever längre än förfrågan, och en pool är den allmänna formen.

Det andra skälet är gränsen på servern. PostgreSQLs `max_connections` är
vanligtvis 100 som standard, kan vara lägre om inställningarna i systemkärnan
inte klarar det, och kan bara ändras vid serverstart. Tre av platserna är som
standard reserverade för superanvändare. Att höja talet är inte heller gratis:
PostgreSQL dimensionerar vissa resurser, bland annat delat minne, direkt efter
`max_connections`. Utan en pool håller varje arbetsprocess sin egen anslutning,
och Djangos dokumentation säger vad det leder till: eftersom varje tråd har sin
egen anslutning måste databasen kunna ta emot minst lika många samtidiga
anslutningar som det finns arbetstrådar. En pool gör det till ett tal som teamet
själv väljer och kan summera.

Det tredje skälet är hygien. En pool är det enda ställe som varje anslutning
passerar mellan två lånare, så det är här en anslutning städas och testas. När
en anslutning kommer tillbaka anropar SQLAlchemys pool `rollback()` på den,
vilket släpper allt transaktionstillstånd, lås inräknade, så att nästa lånare
inte ärver en halvfärdig transaktion. PgBouncer skickar sin
`server_reset_query`, som standard `DISCARD ALL`, när den släpper en
serveranslutning och innan en annan klient kan få den. Och en pool kan testa en
anslutning innan den lånas ut, vilket spelar roll för anslutningar som har stått
oanvända en tid.

## Så ser det ut i praktiken

I SQLAlchemy 2.1 får varje `Engine` en `QueuePool` som standard, med SQLites
databas i minnet som undantag. Dess `pool_size` är 5 och dess `max_overflow` är
10, så en engine kan ha upp till 15 anslutningar samtidigt, och en anropare som
finner alla upptagna väntar `pool_timeout`, som standard 30 sekunder, innan den
ger upp. Inställningarna är nyckelordsargument till `create_engine()`:

```python
from sqlalchemy import create_engine

# Högst tio anslutningar för den här enginen: tio hålls öppna, inga utöver dem.
engine = create_engine(
    "postgresql+psycopg2://me@localhost/mydb",
    pool_size=10,
    max_overflow=0,
    pool_timeout=10,  # vänta tio sekunder på en ledig anslutning, ge sedan upp
    pool_pre_ping=True,  # testa varje anslutning när den lånas ut
    pool_recycle=3600,  # byt ut anslutningar som är över en timme gamla
)
```

Med `pool_pre_ping` kontrollerar poolen vid utlåning att anslutningen
fortfarande går att använda, via drivrutinens egen ping eller en sats som
motsvarar `SELECT 1`. `pool_recycle` är avsett för servrar som stänger
anslutningar som har stått oanvända en viss tid: med 3600 byts varje anslutning
som har varit öppen i mer än en timme ut vid nästa utlåning.

Django 6.1 har en egen pool för PostgreSQL. Den fungerar bara med drivrutinen
psycopg och kräver paketet `psycopg[pool]`, och den slås på med `"OPTIONS":
{"pool": True}` i databasinställningarna eller med en ordbok med inställningar
för psycopgs `ConnectionPool`. Poolen återanvänder anslutningar mellan trådar,
och varje process har en separat pool för varje databasalias. `CONN_MAX_AGE`
måste stå kvar på 0 när pooling är påslaget; livslängden för en anslutning i
poolen sätts i stället med poolens inställning `max_lifetime`. Under ASGI råder
Djangos dokumentation till att stänga av beständiga anslutningar och använda den
här inbyggda poolen i stället.

PgBouncers `pool_mode` avgör när en serveranslutning kan gå vidare till en annan
klient. I sessionsläge, som är standard, behåller klienten den tills den kopplar
ner. I transaktionsläge har en klient en serveranslutning bara medan en
transaktion pågår, så några dussin serveranslutningar kan betjäna långt fler
klienter. Satsläge släpper anslutningen efter varje fråga och tillåter inte
transaktioner som sträcker sig över flera satser. Varje pool rymmer upp till
`default_pool_size` serveranslutningar per par av användare och databas, som
standard 20, och PgBouncer tar emot upp till `max_client_conn`
klientanslutningar, som standard 100.

Företagets egna anteckningar placerar poolen tidigt i ordningen.
[Optimera databasen innan du shardar den](/sv/notes/optimise-before-you-shard/)
låter läsaren räkna de aktiva sessionerna i `pg_stat_activity` mot antalet
CPU-kärnor: när många gånger fler är aktiva än det finns kärnor ägnar servern
sin tid åt att växla mellan dem, och en anslutningspool löser det innan någon
hårdvara gör det.
[Att sälja den sista biljetten exakt en gång](/sv/notes/selling-limited-stock-without-overselling/)
använder samma tanke på en blixtrea: hundratusen köpare ska bli några dussin
anslutningar genom PgBouncer, inte tusentals serverprocesser som väntar på ett
enda lås.

## Var det går fel

**Poolerna kräver tillsammans fler anslutningar än servern tillåter.** Varje
pool räknar bara sina egna anslutningar. Med SQLAlchemys standardvärden kan en
engine nå 15, så tolv processer kan begära 180 från en server där
`max_connections` vanligtvis är 100 som standard, med tre av dem reserverade för
superanvändare, och Django har en separat pool för varje databasalias i varje
process. Summera det högsta antalet för varje pool som pekar mot databasen, över
alla processer i alla applikationer och alla schemalagda jobb, och håll summan
under `max_connections` minus de reserverade platserna. När det inte går, ställ
PgBouncer framför och låt den styra antalet serveranslutningar.

**En anslutning dör medan den väntar i poolen.** En anslutning som har stått
oanvänd kan stängas i andra änden utan att poolen vet om det, och nästa lånare
misslyckas då på sin första sats. Slå på `pool_pre_ping` i SQLAlchemy eller
`CONN_HEALTH_CHECKS` i Django, som testar en återanvänd anslutning en gång per
förfrågan, och sätt `pool_recycle` eller psycopgs `max_lifetime` lägre än den
tid efter vilken servern stänger oanvända anslutningar. Ingen av dem täcker en
anslutning som bryts mitt i en transaktion: SQLAlchemys dokumentation säger
tydligt att pre-ping inte hanterar det fallet, så applikationen måste själv göra
om arbetet.

**En arbetsprocess som startas med fork ärver förälderns anslutningar.** En
webbserver eller en jobbkörare som skapar sin engine och sedan startar sina
arbetsprocesser med fork lämnar varje barn med samma poolanslutningar som
föräldern. SQLAlchemys dokumentation kallar det kritiskt att poolanslutningar
inte delas med en process som startats med fork, eftersom båda processerna då
skulle tala över samma nätverksanslutning. Följ det rekommenderade
tillvägagångssättet och anropa `engine.dispose(close=False)` i varje barns
initializer, så att barnet börjar med en tom pool och lämnar förälderns
anslutningar i fred. Alternativen är `NullPool`, som öppnar och stänger en
anslutning vid varje användning, eller att anropa `engine.dispose()` före
fork-anropet.

**Transaktionspooling förstör sessionstillstånd.** I PgBouncers transaktionsläge
kan två transaktioner från samma klient köras på olika serveranslutningar, så
inget av det en klient ställer in för sin session finns garanterat kvar i dess
nästa transaktion. PgBouncers funktionslista markerar `SET`, `LISTEN`, markörer
deklarerade med `WITH HOLD`, `PREPARE` på SQL-nivå och advisory locks på
sessionsnivå som aldrig stödda i det läget, och sticker inte under stol med att
läget till sin natur bryter några av de antaganden klienter gör om servern. Håll
sessionstillstånd borta från kod som går genom en transaktionspooler, och skicka
de klienter som behöver det till en pool i sessionsläge. I Django ska
`DISABLE_SERVER_SIDE_CURSORS` sättas till `True` för en databas bakom en
transaktionspooler, annars måste frågorna köras inne i `atomic()`.

## Läs mer

- [Connection Pooling](https://docs.sqlalchemy.org/en/21/core/pooling.html)
  — `QueuePool` och dess standardvärden, hantering av brutna anslutningar,
  återställning vid retur och pooler vid fork i SQLAlchemy 2.1.
- [PgBouncer features](https://www.pgbouncer.org/features.html)
  — sessions-, transaktions- och satsläge, och vilka PostgreSQL-funktioner vart
  och ett av dem stöder.
- [PgBouncer configuration](https://www.pgbouncer.org/config.html)
  — `pool_mode`, `default_pool_size`, `max_client_conn` och tidsgränserna med
  sina standardvärden.
- [How Connections Are Established](https://www.postgresql.org/docs/current/connect-estab.html)
  — PostgreSQLs modell med en process per användare och postmaster-processen.
- [Connections and Authentication](https://www.postgresql.org/docs/current/runtime-config-connection.html)
  — `max_connections` och de anslutningsplatser som är reserverade för
  superanvändare och andra roller.
- [Databases](https://docs.djangoproject.com/en/stable/ref/databases/)
  — beständiga anslutningar, PostgreSQL-poolen och transaktionspooling i Djangos
  referens.

<https://engineer.company/sv/glossary/connection-pool/>
