Verbindingsoptimalisatie

Voltooid

Databaseverbindingen zijn dure resources. Elke verbinding verbruikt servergeheugen, vereist verificatieoverhead en telt mee op basis van serverlimieten. Voor AI-toepassingen die frequent vectorquery's uitvoeren, is efficiënt verbindingsbeheer essentieel voor het bereiken van hoge doorvoer zonder de resources uit te putten.

Opmerking

Codevoorbeelden in deze les demonstreren verbindingsbeheerpatronen voor Python (psycopg) en .NET (Npgsql). Deze bibliotheken worden regelmatig bijgewerkt. Ga naar de psycopg-documentatie en npgsql-documentatie voor de huidige API-details.

Verbindingsoverhead

Het maken van een nieuwe PostgreSQL-verbinding omvat meerdere stappen, waarbij elke extra latentie wordt toegevoegd:

  1. TCP-handshake: De netwerkverbinding tot stand brengen (meestal 1-3 retouren)
  2. TLS-onderhandeling: De verbinding versleutelen (vereist voor Azure Database for PostgreSQL)
  3. Verificatie: Referenties verifiëren (wachtwoord- of tokenuitwisseling)
  4. Toewijzing van serverprocessen: PostgreSQL spawnst een back-endproces voor elke verbinding
  5. Sessie-initialisatie: Sessieparameters instellen en configuraties laden

Deze reeks duurt 50-200 milliseconden, afhankelijk van netwerklatentie en serverbelasting. Voor een aanbevelingsengine die duizenden aanvragen per seconde verwerkt, verbruikt het maken van nieuwe verbindingen per aanvraag meer tijd bij het instellen van verbindingen dan bij de werkelijke uitvoering van query's.

Azure Database for PostgreSQL beperkt gelijktijdige verbindingen op basis van de rekenlaag. Burstable B1ms staat 50 verbindingen toe, General Purpose 2 vCores staat 859 verbindingen toe, General Purpose 4 vCores staat 1.718 verbindingen toe, Memory Optimized 4 vCores staat 3.437 verbindingen toe en Memory Optimized 16 vCores staat 5.000 verbindingen toe. Als u deze limieten overschrijdt, worden verbindingsfouten veroorzaakt. Toepassingen die verbindingen per aanvraag maken, kunnen deze limieten snel bereiken tijdens pieken in het verkeer.

Groepsgewijze verbindingen met PgBouncer

PgBouncer is een lichtgewicht verbindingspooler die zich bevindt tussen uw toepassing en PostgreSQL. Het onderhoudt een groep databaseverbindingen en multiplexes-clientverbindingen tussen deze verbindingen, waardoor het aantal werkelijke databaseverbindingen dat nodig is aanzienlijk wordt verminderd.

Azure Database for PostgreSQL bevat ingebouwde PgBouncer-ondersteuning voor de rekenlagen Algemeen gebruik en Geoptimaliseerd voor geheugen. De Burstable-laag biedt geen ondersteuning voor deze functie. Schakel PgBouncer in via Azure Portal of CLI. Als dit is ingeschakeld, maakt u verbinding via poort 6432 (de PgBouncer-poort) in plaats van 5432 (de directe PostgreSQL-poort). De PgBouncer-verbindingsreeks maakt gebruik van postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb.

az postgres flexible-server parameter set \
    --resource-group myResourceGroup \
    --server-name myserver \
    --name pgbouncer.enabled \
    --value true

PgBouncer ondersteunt drie poolmodi, elk met verschillende afwegingen. Sessiemodus betekent dat een client een serververbinding voor de hele sessie bevat (totdat de verbinding is verbroken). Deze modus ondersteunt alle PostgreSQL-functies, maar biedt minimale verbindingsreductie. Transactiemodus betekent dat een client alleen een serververbinding bevat tijdens een transactie. Tussen transacties retourneert de verbinding naar de pool. Deze modus werkt goed voor de meeste toepassingen en vermindert de verbindingsvereisten aanzienlijk. Instructiemodus betekent dat een client alleen een verbinding krijgt voor afzonderlijke instructies. Deze modus biedt maximale verbindingsreductie, maar biedt geen ondersteuning voor transacties met meerdere instructies. Voor vectorzoekworkloads is de transactiemodus doorgaans de beste keuze.

PgBouncer maakt verschillende parameters beschikbaar die het gedrag van de pool, verbindingslimieten en time-outafhandeling beheren. Voor vectorzoekbelasting met bruuske verkeerspatronen helpt het afstemmen van deze parameters om de beschikbaarheid van verbindingen te balanceren tegen het gebruik van resources. Configureren pgbouncer.default_pool_size (20-50 afhankelijk van gelijktijdigheidsbehoeften), pgbouncer.max_client_conn (5000+ voor toepassingen met veel verkeer), pgbouncer.pool_mode (transactie) en pgbouncer.query_wait_timeout (30-120 seconden).

De transactiewijze retourneert verbindingen met de pool nadat elke transactie is bevestigd of teruggedraaid. Dit is van invloed op verschillende PostgreSQL-functies. Sessievariabelen worden tussen transacties opnieuw ingesteld, zodat SET toegepaste instellingen niet behouden blijven. Gebruik SET LOCAL binnen transacties of configureer de standaardinstellingen aan de serverzijde. Voorbereide instructies werken mogelijk niet omdat benoemde voorbereide instructies zijn gekoppeld aan verbindingen. In transactiemodus kan een voorbereide instructie die in één transactie is gemaakt, mogelijk niet beschikbaar zijn in de volgende transactie als er een andere verbinding is toegewezen. LISTEN/NOTIFY werkt niet omdat deze functies permanente verbindingen vereisen en niet compatibel zijn met transactiepooling. Voor vectorzoektoepassingen zijn deze beperkingen zelden problematisch, omdat query's doorgaans eenvoudige selecties zijn zonder sessiespecifieke status.

Groepsgewijze verbindingen op toepassingsniveau

Naast (of in plaats van) PgBouncer kan uw toepassing verbindingsgroepen rechtstreeks beheren. Dit biedt een nauwkeurigere controle over de levenscyclus van verbindingen en kan worden geïntegreerd met toepassingsframeworks.

Het psycopg_pool pakket biedt groepsgewijze verbindingen voor psycopg. Pools op toepassingsniveau bieden u controle over de levenscyclus van verbindingen, time-outgedrag voor inactiviteit en statuscontrole. Ze kunnen ook op natuurlijke wijze worden geïntegreerd met de foutafhandeling en logboekregistratie van uw toepassing. In combinatie met PgBouncer verwerken toepassingsgroepen lokaal verbindingsbeheer terwijl PgBouncer multiplexing aan serverzijde afhandelt. De with pool.connection() contextbeheerder retourneert automatisch de verbinding met de pool bij het afsluiten van het blok, zelfs als er een uitzondering optreedt.

from psycopg_pool import ConnectionPool

# Create a connection pool
pool = ConnectionPool(
    conninfo="postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb",
    min_size=5,      # Minimum connections to maintain
    max_size=20,     # Maximum connections allowed
    max_idle=300,    # Close idle connections after 5 minutes
    max_lifetime=3600  # Recycle connections after 1 hour
)

# Use connections from the pool
def search_similar_products(query_embedding, limit=10):
    with pool.connection() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, name, embedding <=> %s AS distance
                FROM products
                ORDER BY embedding <=> %s
                LIMIT %s
            """, (query_embedding, query_embedding, limit))
            return cur.fetchall()

Npgsql bevat een ingebouwde verbinding pool die standaard is ingeschakeld, dus u hebt geen afzonderlijk pakket nodig. De pool beheert automatisch het maken, hergebruiken en verwijderen van verbindingen op basis van parameters die u in de verbindingsreeks opgeeft. Elke unieke verbindingsreeks onderhoudt een eigen pool, dus consistente verbindingsreeksen in uw toepassing zorgen voor efficiënt poolgebruik. Wanneer u conn.Close() aanroept of de verbinding wordt vrijgegeven, keert deze terug naar de pool in plaats van vernietigd te worden. Groeperen configureren via verbindingsreeksparameters zoals Minimum Pool Size=5;Maximum Pool Size=20;Connection Idle Lifetime=300;Connection Lifetime=3600.

De grootte van de pool is van invloed op zowel prestaties als resourceverbruik. Als u de pool te klein instelt, worden aanvragen gewacht op beschikbare verbindingen, waardoor de latentie toeneemt. Als u het te groot instelt, wordt geheugen verspild en kan de databaseserver overbelasten. De juiste grootte is afhankelijk van uw verkeerspatronen, queryduur en het aantal toepassingsexemplaren dat de database deelt. Houd de minimale grootte groot genoeg om basislijnverkeer te verwerken zonder te wachten. Maximaliseer de grootte op basis van wat de database aankan, gedeeld door het aantal toepassingsexemplaren. Als u 10 applicatie-instanties hebt en uw database 1000 verbindingen ondersteunt, stelt u maximaal 100 per instantie in (om ruimte over te houden). Recycle verbindingen periodiek (om de 30-60 minuten) om de gezondheid te behouden, omdat langdurige verbindingen geheugenlekken kunnen verzamelen of oude plannen in de cache kunnen bevatten.

Sessiebeheer voor AI-workloads

Sommige vectorquery's profiteren van instellingen op sessieniveau die meer resources toewijzen aan de query dan de standaardinstellingen voor de hele server toestaan.

Vector-similariteitquery's die grote resultatensets sorteren, profiteren van verbeterde work_mem. Stel deze in voor specifieke sessies of transacties met behulp van SET LOCAL work_mem = '256MB'. SET LOCAL is alleen van toepassing binnen de huidige transactie. Wanneer de transactie eindigt, wordt de instelling teruggezet naar de standaardwaarde, wat veilig is voor gegroepeerde verbindingen.

Pas hnsw.ef_search of ivfflat.probes queries aan voor queries met verschillende nauwkeurigheidsvereisten. Gebruik SET LOCAL hnsw.ef_search = 200 voor hogere recall in query's waar nauwkeurigheid cruciaal is, of SET LOCAL hnsw.ef_search = 20 voor snellere query's waar benaderende resultaten acceptabel zijn. Met dit patroon kunt u nauwkeurigheid en snelheid verdelen op basis van de specifieke use case zonder dat dit van invloed is op andere query's.

Efficiënte SDK-gebruikspatronen

Naast verbindingsbeheer is de structuur van databaseinteracties van invloed op de prestaties.

Netwerkrondes voegen latentie toe aan elke databasebewerking. Wanneer u meerdere stukjes gegevens nodig hebt, elimineert het ophalen ervan in één query de overhead per query van netwerkoverdracht, het parseren van query's en het serialiseren van resultaten. Voor AI-toepassingen die insluitingen voor meerdere items ophalen, kan batchverwerking de totale latentie van honderden milliseconden tot enkele cijfers verminderen. In plaats van meerdere rondes te maken met afzonderlijke query's, gebruikt u één query met WHERE id = ANY(%s) door een lijst met id's door te geven.

Voor het laden van grote aantallen vectoren is de PostgreSQL-opdracht COPY aanzienlijk sneller dan afzonderlijke INSERT instructies. COPY streamt gegevens rechtstreeks naar de tabel in een binaire of tekstindeling, waardoor de overhead van het parseren van afzonderlijke SQL-instructies wordt overgeslagen. Bij het laden van insluitgegevens uit pijplijnen voor batchverwerking of initiële gegevensmigraties, COPY kunnen laadtijden van uren tot minuten worden verminderd. COPY kan honderden duizenden rijen per seconde laden, terwijl afzonderlijke invoegingen beperkt zijn tot duizenden per seconde.

Wanneer uw toepassing werk kan parallelliseren, verbeteren asynchrone databasebewerkingen de doorvoer door meerdere query's gelijktijdig uit te voeren zonder threads te blokkeren. Dit patroon is waardevol voor AI-workloads die tegelijkertijd in meerdere vectorverzamelingen moeten zoeken of vectorzoekopdrachten moeten combineren met andere gegevens ophalen. Asynchrone pools beheren verbindingen efficiënt voor gelijktijdige bewerkingen, terwijl de limieten voor de poolgrootte worden gerespecteerd. Gebruik AsyncConnectionPool van psycopg_pool en asyncio.gather om meerdere zoekopdrachten gelijktijdig uit te voeren.

Verbindingsweerstandigheid

Netwerkproblemen, opnieuw opstarten van de server en failovers kunnen databaseverbindingen onderbreken. Robuuste toepassingen verwerken deze probleemloos.

Tijdelijke fouten, zoals netwerklips, opnieuw instellen van verbindingen en korte serveronbeschikbaarheid tijdens onderhoud, zijn onvermijdelijk in cloudomgevingen. Het implementeren van logica voor opnieuw proberen met exponentieel uitstel helpt uw toepassing probleemloos te herstellen van deze tijdelijke problemen zonder de server te overweldigen met onmiddellijke nieuwe pogingen. Voeg willekeurige jitter toe om te voorkomen dat meerdere toepassingsexemplaren tegelijkertijd opnieuw proberen. Ondervang OperationalError uitzonderingen, bereken de wachttijd als (2 ** attempt) + random.uniform(0, 1)en probeer het opnieuw tot een maximum aantal pogingen.

Time-outs voorkomen dat uw toepassing voor onbepaalde tijd wacht wanneer de database traag of onbereikbaar is. Verbindingstime-outs beperken hoe lang moet worden gewacht bij het tot stand brengen van nieuwe verbindingen, terwijl instructietime-outs de uitvoeringstijd van queries beperken. Voor vectorzoektoepassingen kiest u time-outs die geschikt zijn voor uw traagste legitieme query's, terwijl ze snel mislukken bij query's die de acceptabele latentie overschrijden. Configureer time-outs in uw verbindingsreeks met behulp van parameters zoals connect_timeout=10 en options=-c statement_timeout=30000. Voor vectorqueries stelt u time-outs voor queries in zodat ze geschikt zijn voor de traagste acceptabele queries. Een time-out van 30 seconden is redelijk voor complexe vectorzoekopdrachten; interactieve toepassingen kunnen lagere waarden gebruiken.

Wanneer alle poolverbindingen in gebruik zijn en er nieuwe aanvragen binnenkomen, moet de pool de verzoeken in de wachtrij plaatsen (waardoor er latentie ontstaat) of ze onmiddellijk afwijzen. Geen van beide opties is ideaal, dus het bewakingspoolgebruik helpt u te schalen voordat uitputting frequent wordt. Wanneer uitputting optreedt, helpt het retourneren van een duidelijk foutbericht clients hun eigen logica voor opnieuw proberen te implementeren in plaats van onvoorspelbaar te timen. Behandel PoolTimeout uitzonderingen door een nette fout terug te geven, zoals {"error": "Service temporarily busy, please retry"}. Bewaak het poolgebruik en schaal als uitputting vaak plaatsvindt.

Aanvullende bronnen