Ladění výkonu Hyperscale (Citus)

PLATÍ PRO: Azure Database for PostgreSQL – Hyperscale (Citus)

Provozování distribuované databáze v plném rozsahu nabízí vysoký výkon. Dosažení toho výkonu ale může provést určité úpravy v kódu aplikace a modelování dat. Tento článek se zabývá některými nejběžnějšími technikami, které zlepšují výkon.

Sdružování připojení na straně klienta

Fond připojení obsahuje otevřená připojení k databázi pro opakované použití. Aplikace požaduje připojení z fondu v případě potřeby a fond vrátí, který je již zaveden, pokud je to možné, nebo vytvoří novou. Po dokončení aplikace uvolní připojení zpět do fondu, a ne jeho zavření.

Přidání fondu připojení na straně klienta je snadný způsob, jak zvýšit výkon aplikace s minimálními změnami kódu. V našich měřeních probíhá spouštění příkazů vložení s jedním řádkem přibližně 24krát rychleji ve skupině serverů Hyperscale (Citus) s povoleným sdružováním.

Příklady přidání sdružování do kódu aplikace najdete v průvodci zásobníky aplikací.

Poznámka

Hyperscale (Citus) také poskytuje sdružování připojení na straně serveru pomocí nástroje pgbouncer, ale slouží hlavně ke zvýšení limitu připojení klienta. Výkon jednotlivých aplikací přináší větší výhody z klientského fondu, nikoli z sdružování na straně serveru. (I když lze obě formy sdružování používat najednou bez poškození.)

Rozsah distribuovaných dotazů

Aktualizace

Při aktualizaci distribuované tabulky zkuste filtrovat dotazy na distribuční sloupec– aspoň v případě, že to dává smysl, když nové filtry nezmění význam dotazu.

V některých úlohách je to snadné. Transakční a provozní úlohy, jako jsou aplikace SaaS s více tenanty nebo internet věcí, distribuují tabulky podle tenanta nebo zařízení. Dotazy jsou vymezeny na id tenanta nebo zařízení.

Například v našem kurzu s více tenanty máme tabulku distribuovanou ads .company_id Naïve způsob, jak aktualizovat reklamu, je určit ho takto:

-- slow

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

I když dotaz jednoznačně identifikuje řádek a aktualizuje ho, Hyperscale (Citus) v době plánování, která horizontální dělení dotaz aktualizuje. Citus přebírá ShareUpdateExclusiveLock na všech horizontálních oddílech, aby byl bezpečný, což blokuje další dotazy, které se snaží aktualizovat tabulku.

I když id byla dostatečná k identifikaci řádku, můžeme zahrnout další filtr, abychom mohli dotaz zrychlit:

-- fast

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

Plánovač dotazů Hyperscale (Citus) vidí přímý filtr distribučního sloupce a přesně ví, který jeden horizontální oddíl se má uzamknout. V našich testech přidání filtrů pro distribuční sloupec zvýšilo výkon paralelní aktualizace o 100x.

Spojení a CTE

Viděli jsme, jak mají příkazy UPDATE oborovat podle distribučního sloupce, aby se zabránilo zbytečným zámkům horizontálních oddílů. Jiné dotazy také využívají rozsah, obvykle proto, aby se zabránilo režii sítě zbytečně prohazováním dat mezi pracovními uzly.

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

Dotaz můžeme urychlit filtrováním distribučního sloupce , company_idv CTE a hlavním příkazu SELECT.

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

Obecně platí, že při připojování distribuovaných tabulek zkuste zahrnout distribuční sloupec do podmínek spojení. Při připojování mezi distribuovanou a referenční tabulkou se ale nevyžaduje, protože obsah referenční tabulky se replikuje napříč všemi pracovními uzly.

Pokud se zdá být nevhodné přidat další filtry do všech dotazů, mějte na paměti, že existují pomocné knihovny pro několik oblíbených aplikačních architektur, které usnadňují práci. Tady jsou pokyny:

Efektivní protokolování databáze

Protokolování všech příkazů SQL neustále přidává režii. V našich měřeních se díky vhodnější úrovni protokolu zlepšila transakce za sekundu o 10x vs. úplné protokolování.

Pro efektivní každodenní provoz můžete protokolování zakázat kromě chyb a neobvykle dlouhotrvajících dotazů:

Nastavení hodnota reason
log_statement_stats OFF Vyhněte se režijním nákladům na profilaci
log_duration OFF Není potřeba znát dobu trvání běžných dotazů.
log_statement ŽÁDNÝ Protokolovat dotazy bez konkrétnějšího důvodu
log_min_duration_statement Hodnota delší než to, co si myslíte, že normální dotazy by měly trvat Zobrazuje neobvykle dlouhé dotazy.

Poznámka

Nastavení související s protokoly v naší spravované službě bere v úvahu výše uvedená doporučení. Můžete je nechat, jak jsou. Někdy jsme ale viděli, že zákazníci změní nastavení tak, aby protokolování byly agresivní, což vedlo k problémům s výkonem.

Uzamčení kolizí

Databáze používá zámky k zachování konzistentních dat v rámci souběžného přístupu. Některé vzory dotazů ale vyžadují nadměrné množství uzamčení a existují rychlejší alternativy.

Stav a zámky systému

Než se podíváme na běžné zamykání nefektivnosti, podívejme se, jak zobrazit zámky a aktivity v rámci databázového clusteru. Zobrazení citus_stat_activity poskytuje podrobné zobrazení.

V zobrazení se mimo jiné dozvíte, jak jsou dotazy blokované "událostmi čekání", včetně zámků. Seskupování podle wait_event_type vykresluje obrázek stavu systému:

-- general system health

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

Hodnota NULL wait_event_type znamená, že dotaz na nic nečeká.

Pokud se ve výstupu aktivity statistiky zobrazí zámky, můžete zobrazit konkrétní blokované dotazy pomocí citus_lock_waits:

SELECT * FROM citus_lock_waits;

Pokud je například jeden dotaz zablokovaný na jiném pokusu o aktualizaci stejného řádku, zobrazí se blokované a blokovací příkazy:

-[ 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

Pokud chcete vidět nejen zámky, které se v tuto chvíli děje, ale historické vzory, můžete zachytávat zámky v protokolech PostgreSQL. Další informace najdete v nastavení serveru log_lock_waits v dokumentaci k PostgreSQL. Další skvělý zdroj je sedm tipů pro práci se zámky na blogu Citus Data.

Běžné problémy a řešení

Příkazy DDL

Příkazy DDL, jako truncatejsou , dropa create index všechny přebírají zámky zápisu a blokují zápisy do celé tabulky. Minimalizace takových operací snižuje problémy s uzamykáním.

Tipy:

  • Zkuste konsolidovat DDL do časových období údržby nebo je používejte méně často.

  • PostgreSQL podporuje indexy vytváření současně, aby se zabránilo zamknutí zápisu v tabulce.

  • Před spuštěním náročného příkazu DDL zvažte nastavení lock_timeout v relaci SQL. Pokud lock_timeoutpříkaz DDL počká příliš dlouho na zámek zápisu, postgreSQL přeruší příkaz DDL. Příkaz DDL, který čeká na zámek, může způsobit, že se pozdější dotazy za sebou zařadí do fronty.

Nečinná v transakčních připojeních

Nečinné (nevytvrzené) transakce někdy zbytečně blokují další dotazy. Příklad:

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!

Pokud chcete ručně vyčistit všechny dlouhé nečinné dotazy na koordinačním uzlu, můžete spustit příkaz takto:

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 také nabízí nastavení idle_in_transaction_session_timeout pro automatizaci ukončení nečinné relace.

Zablokování

Citus detekuje distribuované zablokování a zruší své dotazy, ale situace je méně výkonná, než aby se zabránilo zablokování na prvním místě. Běžný zdroj zablokování pochází z aktualizace stejné sady řádků v jiném pořadí než více transakcí najednou.

Spuštění těchto transakcí například paralelně:

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

Relace 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

Relace A aktualizované ID 1 a 2, zatímco relace B aktualizovala 2 a 1. Pečlivě napište kód SQL pro transakce, abyste aktualizovali řádky ve stejném pořadí. (Pořadí aktualizací se někdy označuje jako "zamykání hierarchie".)

V našem měření hromadná aktualizace sady řádků s mnoha transakcemi byla 3x rychlejší , když se vyhnete zablokování.

Vstupně-výstupní operace během příjmu dat

Kritickým bodem vstupně-výstupních operací je obvykle méně problém s hyperškálováním (Citus) než u PostgreSQL s jedním uzlem kvůli horizontálnímu dělení. Horizontální oddíly jsou jednotlivě menší tabulky s lepšími mírami indexů a přístupů do mezipaměti a poskytují lepší výkon.

I když se ale s Hyperscale (Citus) zvětší tabulky a indexy, může se vstupně-výstupní operace disku stát problémem při příjmu dat. Co je potřeba hledat, jsou rostoucí počet vstupně-výstupních wait_event_type položek, které se zobrazují v citus_stat_activity:

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

Spuštěním výše uvedeného dotazu opakovaně zachyťte informace související s událostmi čekání. Všimněte si, jak se mění počty různých typů událostí čekání.

Podívejte se také na metriky v Azure Portal, zejména na metriku IOPS, která se maxuje.

Tipy:

  • Pokud jsou vaše data přirozeně seřazená, například v časové řadě, použijte dělení tabulek PostgreSQL. V této příručce se dozvíte, jak rozdělit distribuované tabulky v Hyperscale (Citus).

  • Odeberte nepoužívané indexy. Údržba indexu způsobuje amplifikace vstupně-výstupních operací během příjmu dat. Pokud chcete zjistit, které indexy se nepoužívají, použijte tento dotaz.

  • Pokud je to možné, vyhněte se indexování randomizovaných dat. Některé algoritmy generování UUID například nedodržují žádné pořadí. Indexování takové hodnoty způsobuje velkou režii. Zkuste místo toho použít sekvenci bigint nebo monotonicky zvyšující identifikátory UUID.

Souhrn výsledků

V srovnávacích testech jednoduchého příjmu dat pomocí INSERTs, UPDATEs, bloků transakcí jsme zaznamenali následující zrychlení dotazů pro techniky v tomto článku.

Technika Zrychlení dotazů
Otázky týkající se dotazů 100x
Sdružování připojení 24x
Efektivní protokolování 10x
Vyhněte se vzájemnému zablokování 3x

Další kroky