Dela via


Prestandajustering i Azure Cosmos DB for PostgreSQL

GÄLLER FÖR: Azure Cosmos DB for PostgreSQL (drivs av Citus-databastillägget till PostgreSQL)

Körning av en distribuerad databas med dess fulla potential ger höga prestanda. För att uppnå den prestandan kan det dock krävas vissa justeringar i programkod och datamodellering. Den här artikeln beskriver några av de vanligaste och mest effektiva teknikerna för att förbättra prestanda.

Anslutningspool på klientsidan

En anslutningspool innehåller öppna databasanslutningar för återanvändning. Ett program begär en anslutning från poolen när det behövs, och poolen returnerar en som redan har upprättats om möjligt eller upprättar en ny. När den är klar släpper programmet anslutningen tillbaka till poolen i stället för att stänga den.

Att lägga till en anslutningspool på klientsidan är ett enkelt sätt att förbättra programmets prestanda med minimala kodändringar. I våra mått går det ungefär 24 gånger snabbare att köra infoga-instruktioner på en rad i ett kluster med pooler aktiverade.

Språkspecifika exempel på hur du lägger till pooler i programkod finns i guiden för appstackar.

Kommentar

Azure Cosmos DB for PostgreSQL tillhandahåller även anslutningspooler på serversidan med hjälp av pgbouncer, men det tjänar främst till att öka gränsen för klientanslutning. Ett enskilt programs prestanda drar mer nytta av pooler på klientsidan än på serversidan. (Även om båda typerna av pooler kan användas samtidigt utan skada.)

Omfång för distribuerade frågor

Uppdateringar

När du uppdaterar en distribuerad tabell kan du försöka filtrera frågor på distributionskolumnen – åtminstone när det är vettigt när de nya filtren inte ändrar frågans innebörd.

I vissa arbetsbelastningar är det enkelt. Transaktions-/driftarbetsbelastningar som SaaS-appar för flera innehavare eller Sakernas Internet distribuerar tabeller efter klientorganisation eller enhet. Frågor begränsas till ett klient- eller enhets-ID.

I vår självstudie om flera klientorganisationer har vi till exempel en ads tabell distribuerad av company_id. Det naiva sättet att uppdatera en annons är att peka ut den så här:

-- slow

UPDATE ads
   SET impressions_count = impressions_count+1
 WHERE id = 42; -- missing filter on distribution column

Även om frågan identifierar en rad unikt och uppdaterar den, vet inte Azure Cosmos DB for PostgreSQL vid planeringstillfället vilken shard frågan kommer att uppdatera. Citus-tillägget tar en ShareUpdateExclusiveLock på alla shards för att vara säker, vilket blockerar andra frågor som försöker uppdatera tabellen.

Även om det id var tillräckligt för att identifiera en rad kan vi inkludera ett extra filter för att göra frågan snabbare:

-- fast

UPDATE ads
   SET impressions_count = impressions_count+1
 WHERE id = 42
   AND company_id = 1; -- the distribution column

Azure Cosmos DB for PostgreSQL-frågehanteraren ser ett direktfilter på distributionskolumnen och vet exakt vilken enskild shard som ska låsas. I våra tester ökade prestandan för parallella uppdateringar med 100x genom att lägga till filter för distributionskolumnen.

Kopplingar och CTE:er

Vi har sett hur UPDATE-instruktioner bör begränsas av distributionskolumnen för att undvika onödiga shardlås. Andra frågor drar också nytta av omfång, vanligtvis för att undvika nätverkskostnaderna för onödigt växling av data mellan arbetsnoder.

-- logically correct, but slow

WITH single_ad AS (
  SELECT *
    FROM ads
   WHERE id=1
)
SELECT *
  FROM single_ad s
  JOIN campaigns c ON (s.campaign_id=c.id);

Vi kan påskynda frågan genom att filtrera på distributionskolumnen, , company_idi CTE-instruktionen och select-instruktionen.

-- faster, joining on distribution column

WITH single_ad AS (
  SELECT *
    FROM ads
   WHERE id=1 and company_id=1
)
SELECT *
  FROM single_ad s
  JOIN campaigns c ON (s.campaign_id=c.id)
 WHERE s.company_id=1 AND c.company_id = 1;

När du ansluter till distribuerade tabeller kan du i allmänhet försöka inkludera distributionskolumnen i kopplingsvillkoren. När du ansluter mellan en distribuerad tabell och en referenstabell krävs det dock inte, eftersom referenstabellinnehåll replikeras över alla arbetsnoder.

Om det verkar obekvämt att lägga till extra filter i alla dina frågor bör du tänka på att det finns hjälpbibliotek för flera populära programramverk som gör det enklare. Här följer instruktioner:

Effektiv databasloggning

Loggning av alla SQL-instruktioner hela tiden medför extra tillhörande information. I våra mätningar förbättrades transaktionerna per sekund med 10x jämfört med fullständig loggning med hjälp av mer en omdömesgill loggningsnivå.

För effektiv daglig drift kan du inaktivera loggning förutom fel och onormalt långvariga frågor:

inställning värde orsak
log_statement_stats OFF Undvik omkostnader för profilering
log_duration OFF Behöver inte känna till varaktigheten för normala frågor
log_statement INGET Logga inte frågor utan en mer specifik orsak
log_min_duration_statement Ett värde som är längre än vad du tror att vanliga frågor bör ta Visar onormalt långa frågor

Kommentar

De loggrelaterade inställningarna i vår hanterade tjänst tar hänsyn till rekommendationerna ovan. Du kan lämna dem som de är. Vi har dock ibland sett kunder ändra inställningarna för att göra loggningen aggressiv, vilket har lett till prestandaproblem.

Låskonkurrens

Databasen använder lås för att hålla data konsekventa under samtidig åtkomst. Vissa frågemönster kräver ändå alltför mycket låsning, och det finns snabbare alternativ.

Systemhälsa och lås

Innan vi går in på vanlig låsineffektivitet ska vi se hur du visar lås och aktivitet i hela databasklustret. Vyn citus_stat_activity ger en detaljerad vy.

Vyn visar bland annat hur frågor blockeras av "väntehändelser", inklusive lås. Gruppering efter wait_event_type målar upp en bild av systemets hälsa:

-- general system health

SELECT wait_event_type, count(*)
  FROM citus_stat_activity
 WHERE state != 'idle'
 GROUP BY 1
 ORDER BY 2 DESC;

En NULL wait_event_type innebär att frågan inte väntar på något.

Om du ser lås i statistikaktivitetens utdata kan du visa de specifika blockerade frågorna med hjälp av citus_lock_waits:

SELECT * FROM citus_lock_waits;

Om till exempel en fråga blockeras på en annan som försöker uppdatera samma rad visas de blockerade och blockerande instruktionerna:

-[ RECORD 1 ]-------------------------+--------------------------------------
waiting_gpid                          | 10000011981
blocking_gpid                         | 10000011979
blocked_statement                     | UPDATE numbers SET j = 3 WHERE i = 1;
current_statement_in_blocking_process | UPDATE numbers SET j = 2 WHERE i = 1;
waiting_nodeid                        | 1
blocking_nodeid                       | 1

Om du vill se att låsen inte bara händer just nu, utan även historiska mönster, kan du samla in lås i PostgreSQL-loggarna. Mer information finns i inställningen log_lock_waits server i PostgreSQL-dokumentationen. En annan bra resurs är sju tips för att hantera lås på Citus Data Blog.

Vanliga problem och lösningar

DDL-kommandon

DDL-kommandon som truncate, dropoch create index alla tar skrivlås och blockerar skrivningar i hela tabellen. Att minimera sådana åtgärder minskar låsningsproblemen.

Tips:

  • Försök att konsolidera DDL i underhållsperioder eller använd dem mindre ofta.

  • PostgreSQL har stöd för att skapa index samtidigt, för att undvika att ta ett skrivlås på tabellen.

  • Överväg att ange lock_timeout i en SQL-session innan du kör ett tungt DDL-kommando. Med lock_timeoutavbryter PostgreSQL DDL-kommandot om kommandot väntar för länge på ett skrivlås. Ett DDL-kommando som väntar på ett lås kan leda till en kö bakom det med senare frågor.

Inaktivitet i transaktionsanslutningar

Inaktiva (ej incheckade) transaktioner blockerar ibland andra frågor i onödan. Till exempel:

BEGIN;

UPDATE ... ;

-- Suppose the client waits now and doesn't COMMIT right away.
--
-- Other queries that want to update the same rows will be blocked.

COMMIT; -- finally!

Om du vill rensa frågor som är inaktiva länge manuellt på koordinatornoden kan du köra ett kommando som det här:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'citus'
 AND pid <> pg_backend_pid()
 AND state in ('idle in transaction')
 AND state_change < current_timestamp - INTERVAL '15' MINUTE;

PostgreSQL har också en idle_in_transaction_session_timeout-inställning för att automatisera avslutning av inaktiva sessioner.

Dödlägen

Azure Cosmos DB for PostgreSQL identifierar distribuerade deadlocks och avbryter deras frågor, men situationen ger sämre prestanda än att undvika deadlocks i första hand. En vanlig orsak till deadlock är uppdatering av samma uppsättning rader i en annan ordning från flera transaktioner samtidigt.

Du kan till exempel köra dessa transaktioner parallellt:

Session A:

BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;

Session B:

BEGIN;
UPDATE ads SET updated_at = now() WHERE id = 2 AND company_id = 1;
UPDATE ads SET updated_at = now() WHERE id = 1 AND company_id = 1;

-- ERROR:  canceling the transaction since it was involved in a distributed deadlock

Session Ett uppdaterat ID 1 och sedan 2, medan sessionen B uppdaterade 2 och 1. Skriv SQL-kod för transaktioner noggrant för att uppdatera rader i samma ordning. (Uppdateringsordningen kallas ibland för en "låsningshierarki".)

I vår mätning gick massuppdateringen av en uppsättning rader med många transaktioner 3 gånger snabbare när du undviker dödläge.

I/O under inmatning

I/O-flaskhalsar är vanligtvis mindre av ett problem för Azure Cosmos DB för PostgreSQL än för PostgreSQL med en nod på grund av horisontell partitionering. Shards är individuella mindre tabeller, med bättre index- och cacheträffar, vilket ger bättre prestanda.

Men även med Azure Cosmos DB for PostgreSQL kan disk-I/O bli ett problem för datainmatning när tabeller och index blir större. Saker att hålla utkik efter är ett ökande antal I/O-poster wait_event_type som visas i citus_stat_activity:

SELECT wait_event_type, wait_event count(*)
  FROM citus_stat_activity
 WHERE state='active'
 GROUP BY 1,2;

Kör ovanstående fråga upprepade gånger för att samla in väntehändelserelaterad information. Observera hur antalet olika väntehändelsetyper ändras.

Titta också på mått i Azure-portalen, särskilt IOPS-måttet som maxar ut.

Tips:

  • Om dina data är naturligt ordnade, till exempel i en tidsserie, använder du PostgreSQL-tabellpartitionering. I den här guiden kan du lära dig hur du partitionerar distribuerade tabeller.

  • Ta bort oanvända index. Indexunderhåll orsakar I/O-amplifiering under inmatning. Använd den här frågan om du vill ta reda på vilka index som inte används.

  • Undvik om möjligt indexering av slumpmässiga data. Vissa UUID-genereringsalgoritmer följer till exempel ingen ordning. Indexering av ett sådant värde medför mycket tillhörande information. Prova en bigint-sekvens i stället, eller monotont ökande UUID:er.

Sammanfattning av resultat

I benchmarks för enkel inmatning med INSERT, UPDATEs, transaktionsblock observerade vi följande frågehastigheter för teknikerna i den här artikeln.

Teknik Frågehastighet
Omfångsfrågor 100x
Anslutningspooler 24x
Effektiv loggning 10x
Undvika dödläge 3x

Nästa steg