Share 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. Men att uppnå den prestandan kan göra vissa justeringar i programkod och datamodellering. Den här artikeln beskriver några av de vanligaste – och 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 vid behov, 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 körningen av enradsinfogningsinstruktioner ungefär 24 gånger snabbare i ett kluster med poolning aktiverat.

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

Anteckning

Azure Cosmos DB for PostgreSQL tillhandahåller även anslutningspooler på serversidan med hjälp av pgbouncer, men det bidrar främst till att öka gränsen för klientanslutningar. Ett enskilt programs prestanda drar mer nytta av pooler på klientsidan än på serversidan. (Även om båda formerna av poolning 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 logiskt när de nya filtren inte ändrar frågans innebörd.

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

I vår självstudie om flera klientorganisationer har vi till exempel en ads tabell som distribueras 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 fragment som 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 id det 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

Frågehanteraren för Azure Cosmos DB for PostgreSQL ser ett direktfilter i distributionskolumnen och vet exakt vilket enskilt fragment som ska låsas. I våra tester ökade tillägg av filter för distributionskolumnen parallell uppdateringsprestanda med 100 x.

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ånget, vanligtvis för att undvika nätverkskostnaderna för onödig blandning 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_id, i CTE och main 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 försöker du i allmänhet inkludera distributionskolumnen i kopplingsvillkoren. Men när du kopplar mellan en distribuerad tabell och en referenstabell krävs det inte, eftersom referenstabellinnehåll replikeras över alla arbetsnoder.

Om det verkar obekvämt att lägga till de extra filtren 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ått 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ällningen 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 INGEN 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 de onormalt långa frågorna

Anteckning

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 ger 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;

wait_event_type Null 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 en fråga till exempel 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 sker just nu, utan även historiska mönster, kan du samla in lås i PostgreSQL-loggarna. Mer information finns i inställningen för log_lock_waits server i PostgreSQL-dokumentationen. En annan bra resurs är sju tips för att hantera lås på Citus-databloggen.

Vanliga problem och lösningar

DDL-kommandon

DDL-kommandon som truncate, dropoch create index alla tar skrivlås och blockerar skrivningar i hela tabellen. Om du minimerar sådana åtgärder minskar du 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. Ett 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 sedan 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 problem för Azure Cosmos DB for 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 Portal, 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 öka UUID:erna monotont.

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