sql >> Database >  >> RDS >> PostgreSQL

Op PostgreSQL gebaseerde applicatieprestaties:latentie en verborgen vertragingen

Goldfields Pipeline, door SeanMac (Wikimedia Commons)

Als u de prestaties van uw PostgreSQL-gebaseerde toepassing probeert te optimaliseren, concentreert u zich waarschijnlijk op de gebruikelijke tools:UITLEG (BUFFERS, ANALYSE) , pg_stat_statements , auto_explain , log_statement_min_duration , enz.

Misschien onderzoek je slotconflicten met log_lock_waits , het bewaken van de prestaties van uw checkpoint, enz.

Maar heb je nagedacht over netwerklatentie ? Gamers weten wat netwerklatentie is, maar dacht u dat dit van belang was voor uw applicatieserver?

Latentie is belangrijk

Typische client/server-retournetwerklatenties kunnen variëren van 0,01ms (localhost) tot ~0,5ms van een geschakeld netwerk, 5ms wifi, 20ms ADSL, 300ms intercontinentale routering en zelfs meer voor zaken als satelliet- en WWAN-links .

Een triviale SELECTEER kan in de orde van 0,1 ms duren om server-side uit te voeren. Een triviale INSERT kan 0,5 ms duren.

Elke keer dat uw toepassing een query uitvoert, moet deze wachten tot de server reageert met succes/mislukking en mogelijk een resultatenset, metagegevens van de query, enz. Dit leidt tot ten minste één vertraging van het netwerk-round trip.

Als u met kleine, eenvoudige query's werkt, kan de netwerklatentie aanzienlijk zijn in verhouding tot de uitvoeringstijd van uw query's als uw database niet op dezelfde host staat als uw toepassing.

Veel applicaties, met name ORM's, zijn erg vatbaar voor het uitvoeren van lots van vrij eenvoudige vragen. Als uw Hibernate-app bijvoorbeeld een entiteit ophaalt met een lui opgehaalde @OneToMany relatie tot 1000 onderliggende items, zal het waarschijnlijk 1001 zoekopdrachten uitvoeren dankzij het n+1 select-probleem, zo niet meer. Dat betekent dat het waarschijnlijk 1000 keer uw netwerk-retourlatentie besteedt, gewoon wachten . Je kunt join fetch links laten om dat te voorkomen... maar dan zet je de bovenliggende entiteit 1000 keer over in de join en moet je deze dedupliceren.

Evenzo, als u de database vult vanuit een ORM, doet u waarschijnlijk honderdduizenden triviale INSERT s... en na elk wachten tot de server bevestigt dat het goed is.

Het is gemakkelijk om u te concentreren op de uitvoeringstijd van query's en die te optimaliseren, maar u kunt maar zoveel doen met een triviale INSERT INTO ...VALUES ... . Laat wat indexen en beperkingen vallen, zorg ervoor dat het in een transactie wordt gegroepeerd en je bent zo goed als klaar.

Hoe zit het met het wegwerken van alle netwerkwachten? Zelfs op een LAN beginnen ze duizenden zoekopdrachten op te tellen.

KOPIE

Een manier om latentie te voorkomen, is door COPY . te gebruiken . Om de COPY-ondersteuning van PostgreSQL te gebruiken, moet uw toepassing of stuurprogramma een CSV-achtige reeks rijen produceren en deze in een continue volgorde naar de server streamen. Of de server kan worden gevraagd om uw applicatie een CSV-achtige stream te sturen.

Hoe dan ook, de app kan een COPY niet tussenvoegen met andere query's, en kopieer-inserts moeten rechtstreeks in een bestemmingstabel worden geladen. Een veelgebruikte benadering is om COPY in een tijdelijke tabel, doe dan een INSERT INTO ... SELECT ... , UPDATE ... VAN .... , VERWIJDEREN VAN ... GEBRUIKEN... , enz. om de gekopieerde gegevens te gebruiken om de hoofdtabellen in één handeling te wijzigen.

Dat is handig als u rechtstreeks uw eigen SQL schrijft, maar veel toepassingsframeworks en ORM's ondersteunen dit niet, en het kan alleen eenvoudige INSERT rechtstreeks vervangen . Uw applicatie, framework of client driver heeft te maken met conversie voor de speciale representatie die nodig is voor COPY , zoek zelf de vereiste type metadata op, enz.

(Opmerkelijke chauffeurs die doen ondersteuning KOPIE omvatten libpq, PgJDBC, psycopg2 en de Pg gem ... maar niet noodzakelijkerwijs de frameworks en ORM's die erop zijn gebouwd.)

PgJDBC – batchmodus

De JDBC-driver van PostgreSQL heeft een oplossing voor dit probleem. Het vertrouwt op ondersteuning die aanwezig is in PostgreSQL-servers sinds 8.4 en op de batchfuncties van de JDBC API om een ​​batch te verzenden aantal vragen naar de server en wacht dan slechts één keer op bevestiging dat de hele batch goed is verlopen.

Nou ja, in theorie. In werkelijkheid beperken sommige implementatie-uitdagingen dit, zodat batches op zijn best alleen kunnen worden gedaan in brokken van een paar honderd zoekopdrachten. Het stuurprogramma kan ook alleen query's uitvoeren die resultaatrijen in batch-blokken retourneren als het van tevoren kan achterhalen hoe groot de resultaten zullen zijn. Ondanks deze beperkingen is het gebruik van Statement.executeBatch() kan een enorme prestatieverbetering bieden aan applicaties die taken uitvoeren zoals het in bulk laden van externe database-instanties.

Omdat het een standaard-API is, kan het worden gebruikt door applicaties die in meerdere database-engines werken. Hibernate kan bijvoorbeeld JDBC-batch gebruiken, hoewel dit niet standaard is.

libpq en batchverwerking

De meeste (alle?) andere PostgreSQL-stuurprogramma's hebben geen ondersteuning voor batchverwerking. PgJDBC implementeert het PostgreSQL-protocol volledig onafhankelijk, terwijl de meeste andere stuurprogramma's intern de C-bibliotheek gebruiken libpq dat wordt geleverd als onderdeel van PostgreSQL.

libpq ondersteunt geen batchverwerking. Het heeft wel een asynchrone niet-blokkerende API, maar de klant kan nog steeds maar één zoekopdracht "in vlucht" tegelijk hebben. Het moet wachten tot de resultaten van die zoekopdracht zijn ontvangen voordat het een andere kan verzenden.

De PostgreSQL server ondersteunt batching prima, en PgJDBC gebruikt het al. Dus ik heb batchondersteuning geschreven voor libpq en diende het in als kandidaat voor de volgende PostgreSQL-versie. Omdat het alleen de client verandert, zal het nog steeds sneller gaan als je verbinding maakt met oudere servers, als het wordt geaccepteerd.

Ik zou erg geïnteresseerd zijn in feedback van auteurs en gevorderde gebruikers van libpq -gebaseerde clientstuurprogramma's en ontwikkelaars van libpq gebaseerde toepassingen. De patch past prima bovenop PostgreSQL 9.6beta1 als je hem wilt uitproberen. De documentatie is gedetailleerd en er is een uitgebreid voorbeeldprogramma.

Prestaties

Ik dacht dat een gehoste databaseservice zoals RDS of Heroku Postgres een goed voorbeeld zou zijn van waar dit soort functionaliteit nuttig zou zijn. Vooral het toegang krijgen tot hen vanaf onze eigen netwerken laat echt zien hoeveel latentie pijn kan doen.

Bij ~320ms netwerklatentie:

  • 500 inserts zonder batchverwerking:167.0s
  • 500 inzetstukken met batchverwerking:1.2s

... wat meer dan 120x sneller is.

U zult uw app meestal niet uitvoeren via een intercontinentale link tussen de app-server en de database, maar dit dient om de impact van latentie te benadrukken. Zelfs over een unix-socket naar localhost zag ik een prestatieverbetering van meer dan 50% voor 10000 inserts.

Batchen in bestaande apps

Het is helaas niet mogelijk om batching automatisch in te schakelen voor bestaande applicaties. Apps moeten een iets andere interface gebruiken waar ze een reeks vragen sturen en pas daarna om de resultaten vragen.

Het zou vrij eenvoudig moeten zijn om apps aan te passen die al de asynchrone libpq-interface gebruiken, vooral als ze de niet-blokkerende modus en een select() gebruiken /poll() /epoll() /WaitForMultipleObjectsEx lus. Apps die de synchrone libpq . gebruiken interfaces vereisen meer wijzigingen.

Batchen in andere clientstuurprogramma's

Evenzo hebben clientstuurprogramma's, frameworks en ORM's over het algemeen interface- en interne wijzigingen nodig om het gebruik van batching mogelijk te maken. Als ze al een gebeurtenislus en niet-blokkerende I/O gebruiken, moeten ze vrij eenvoudig te wijzigen zijn.

Ik zou graag zien dat gebruikers van Python, Ruby, enz. toegang hebben tot deze functionaliteit, dus ik ben benieuwd wie er geïnteresseerd is. Stel je voor dat je dit kunt doen:

import psycopg2
conn = psycopg2.connect(...)
cur = conn.cursor()

# this is just an idea, this code does not work with psycopg2:
futures = [ cur.async_execute(sql) for sql in my_queries ]
for future in futures:
    result = future.result  # waits if result not ready yet
    ... process the result ...
conn.commit()

Asynchrone batchuitvoering hoeft niet ingewikkeld te zijn op clientniveau.

COPY is het snelst

Waar praktische klanten nog steeds de voorkeur geven aan COPY . Hier zijn enkele resultaten van mijn laptop:

inserting 1000000 rows batched, unbatched and with COPY
batch insert elapsed:      23.715315s
sequential insert elapsed: 36.150162s
COPY elapsed:              1.743593s
Done.

Het batchen van het werk levert een verrassend grote prestatieverbetering op, zelfs op een lokale unix-socketverbinding... maar KOPIE laat beide afzonderlijke inzetstukbenaderingen ver achter zich in het stof.

Gebruik KOPIE .

De afbeelding

De afbeelding voor dit bericht is van de Goldfields Water Supply Scheme-pijpleiding van Mundaring Weir bij Perth in West-Australië naar de goudvelden in het binnenland (woestijn). Het is relevant omdat het zo lang duurde om het af te maken en onder zo'n hevige kritiek stond dat de ontwerper en belangrijkste voorstander, C. Y. O'Connor, 12 maanden voordat het in gebruik werd genomen zelfmoord pleegde. Lokaal zeggen mensen vaak (ten onrechte) dat hij stierf na de pijpleiding werd aangelegd toen er geen water stroomde – omdat het zo lang duurde, ging iedereen ervan uit dat het pijpleidingproject was mislukt. Weken later stroomde het water eruit.


  1. Waarom primaire sleutels belangrijk zijn en hoe u er een kiest?

  2. is er een functie voor het vertalen van gegevens in sql

  3. Het mengen van expliciete en impliciete joins mislukt met Er is een item voor tabel ... maar er kan niet naar worden verwezen vanuit dit deel van de query

  4. PostgreSQL - stel een standaard celwaarde in volgens een andere celwaarde