Osvědčené postupy pro Apache Phoenix z hlediska výkonu

Nejdůležitějším aspektem výkonu Apache Phoenixu je optimalizace základního Apache HBase. Phoenix vytvoří relační datový model atop HBase, který převádí dotazy SQL na operace HBase, jako jsou kontroly. Návrh schématu tabulky, výběr a řazení polí v primárním klíči a použití indexů ovlivní výkon Phoenixu.

Návrh schématu tabulky

Při vytváření tabulky v Phoenixu je tato tabulka uložena v tabulce HBase. Tabulka HBase obsahuje skupiny sloupců (rodin sloupců), ke kterým se přistupuje společně. Řádek v tabulce Phoenix je řádek v tabulce HBase, kde se každý řádek skládá z buněk s verzí přidružených k jednomu nebo více sloupcům. Jeden řádek HBase je logicky kolekce párů klíč-hodnota, z nichž každá má stejnou hodnotu klíče řádku. To znamená, že každý pár klíč-hodnota má atribut rowkey a hodnota tohoto atributu rowkey je stejná pro konkrétní řádek.

Návrh schématu tabulky Phoenix zahrnuje návrh primárního klíče, návrh rodiny sloupců, návrh jednotlivých sloupců a způsob rozdělení dat.

Návrh primárního klíče

Primární klíč definovaný v tabulce v Phoenixu určuje, jak se data ukládají v klíči řádku podkladové tabulky HBase. Jediným způsobem přístupu k určitému řádku v HBase je klíč řádku. Kromě toho se data uložená v tabulce HBase seřadí podle klíče řádku. Phoenix vytvoří hodnotu rowkey tím, že zřetězením hodnot jednotlivých sloupců v řádku v pořadí, v jakém jsou definovány v primárním klíči.

Tabulka kontaktů má například křestní jméno, příjmení, telefonní číslo a adresu ve stejné rodině sloupců. Primární klíč můžete definovat na základě rostoucího pořadového čísla:

rowkey adresa telefon firstName lastName
1000 1111 San Gabriel Dr. 1-425-000-0002 John Dole
8396 5415 San Gabriel Dr. 1-230-555-0191 Calvin Raji

Pokud se však často dotazujete podle lastName, nemusí tento primární klíč dobře fungovat, protože každý dotaz vyžaduje úplnou kontrolu tabulky ke čtení hodnoty každého lastName. Místo toho můžete definovat primární klíč ve sloupcích lastName, firstName a social security number. Tento poslední sloupec spočívá v nejednoznačnosti dvou obyvatel na stejné adrese se stejným jménem, jako je otec a syn.

rowkey adresa telefon firstName lastName socialSecurityNum
1000 1111 San Gabriel Dr. 1-425-000-0002 John Dole 111
8396 5415 San Gabriel Dr. 1-230-555-0191 Calvin Raji 222

S tímto novým primárním klíčem budou klíče řádků vygenerované Phoenixem:

rowkey adresa telefon firstName lastName socialSecurityNum
Dole-John-111 1111 San Gabriel Dr. 1-425-000-0002 John Dole 111
Raji-Calvin-222 5415 San Gabriel Dr. 1-230-555-0191 Calvin Raji 222

V prvním řádku dané tabulky jsou data pro klíč řádku reprezentována takto:

rowkey key hodnota
Dole-John-111 adresa 1111 San Gabriel Dr.
Dole-John-111 telefon 1-425-000-0002
Dole-John-111 firstName John
Dole-John-111 lastName Dole
Dole-John-111 socialSecurityNum 111

Tento klíč řádku teď ukládá duplicitní kopii dat. Vezměte v úvahu velikost a počet sloupců, které zahrnete do primárního klíče, protože tato hodnota je zahrnuta do každé buňky v podkladové tabulce HBase.

Pokud má primární klíč také hodnoty, které se monotonicky zvyšují, měli byste vytvořit tabulku se solnými kbelíky , abyste se vyhnuli vytváření hotspotů zápisu – viz data oddílu.

Návrh rodiny sloupců

Pokud se některé sloupce používají častěji než jiné, měli byste vytvořit více rodin sloupců, abyste rozdělili často používané sloupce od zřídka používaných sloupců.

Pokud mají určité sloupce tendenci být přístupné dohromady, vložte tyto sloupce do stejné rodiny sloupců.

Návrh sloupce

  • Udržujte sloupce VARCHAR pod 1 MB kvůli nákladům na vstupně-výstupní operace velkých sloupců. Při zpracování dotazů HBase materializuje buňky v plném rozsahu před jejich odesláním klientovi a klient je obdrží v plném rozsahu před předáním kódu aplikace.
  • Hodnoty sloupců můžete ukládat pomocí kompaktního formátu, jako je protobuf, Avro, msgpack nebo BSON. Json se nedoporučuje, protože je větší.
  • Zvažte kompresi dat před úložištěm, abyste snížili latenci a vstupně-výstupní náklady.

Dělení dat

Phoenix umožňuje řídit počet oblastí, ve kterých se data distribuují, což může výrazně zvýšit výkon čtení a zápisu. Při vytváření phoenixové tabulky můžete data rozdělit buď solí, nebo předem rozdělit.

Chcete-li při vytváření tabulky solit, zadejte počet solných kbelíků:

CREATE TABLE CONTACTS (...) SALT_BUCKETS = 16

Toto solení rozdělí tabulku podle hodnot primárních klíčů a automaticky zvolí hodnoty.

Pokud chcete určit, kde dochází k rozdělení tabulky, můžete tabulku předem rozdělit zadáním hodnot rozsahu, ve kterých dochází k rozdělení. Pokud například chcete vytvořit tabulku rozdělenou mezi tři oblasti:

CREATE TABLE CONTACTS (...) SPLIT ON ('CS','EU','NA')

Návrh indexu

Phoenix index je tabulka HBase, která ukládá kopii některých nebo všech dat z indexované tabulky. Index zlepšuje výkon pro konkrétní typy dotazů.

Pokud máte definovaných více indexů a potom dotazujete tabulku, Phoenix automaticky vybere nejlepší index dotazu. Primární index se vytvoří automaticky na základě vybraných primárních klíčů.

U očekávaných dotazů můžete také vytvořit sekundární indexy zadáním jejich sloupců.

Při navrhování indexů:

  • Vytvořte jenom indexy, které potřebujete.
  • Omezte počet indexů u často aktualizovaných tabulek. Aktualizace do tabulky se přeloží na zápisy do hlavní i indexové tabulky.

Vytvoření sekundárních indexů

Sekundární indexy můžou zlepšit výkon čtení tím, že změní, co by bylo úplné prohledávání tabulky na bodové vyhledávání, a to za cenu úložného prostoru a rychlosti zápisu. Sekundární indexy je možné přidat nebo odebrat po vytvoření tabulky a nevyžadují změny stávajících dotazů – dotazy se spustí rychleji. V závislosti na vašich potřebách zvažte vytvoření pokrytých indexů, funkčních indexů nebo obojího.

Použití pokrytých indexů

Zahrnuté indexy jsou indexy, které kromě indexovaných hodnot obsahují data z řádku. Po nalezení požadované položky indexu není nutné přistupovat k primární tabulce.

Například v ukázkové tabulce kontaktů můžete vytvořit sekundární index pouze ve sloupci socialSecurityNum. Tento sekundární index by urychlil dotazy, které filtrují hodnoty socialSecurityNum, ale načtení jiných hodnot polí vyžaduje další čtení proti hlavní tabulce.

rowkey adresa telefon firstName lastName socialSecurityNum
Dole-John-111 1111 San Gabriel Dr. 1-425-000-0002 John Dole 111
Raji-Calvin-222 5415 San Gabriel Dr. 1-230-555-0191 Calvin Raji 222

Pokud ale obvykle chcete vyhledat jméno a příjmení s názvem socialSecurityNum, můžete vytvořit pokrytý index, který obsahuje jméno a příjmení jako skutečná data v tabulce indexu:

CREATE INDEX ssn_idx ON CONTACTS (socialSecurityNum) INCLUDE(firstName, lastName);

Tento pokrytý index umožňuje následujícímu dotazu získat všechna data pouze čtením z tabulky obsahující sekundární index:

SELECT socialSecurityNum, firstName, lastName FROM CONTACTS WHERE socialSecurityNum > 100;

Použití funkčních indexů

Funkční indexy umožňují vytvořit index libovolného výrazu, který očekáváte v dotazech. Jakmile použijete funkční index a dotaz tento výraz použije, může být index použit k načtení výsledků místo tabulky dat.

Můžete například vytvořit index, který vám umožní provádět vyhledávání bez rozlišování velkých a malých písmen na kombinované křestní jméno a příjmení osoby:

CREATE INDEX FULLNAME_UPPER_IDX ON "Contacts" (UPPER("firstName"||' '||"lastName"));

Návrh dotazu

Hlavními aspekty návrhu dotazů jsou:

  • Seznamte se s plánem dotazu a ověřte jeho očekávané chování.
  • Efektivně se připojte.

Vysvětlení plánu dotazů

V SQLLine použijte příkaz EXPLAIN následovaný dotazem SQL k zobrazení plánu operací, které Phoenix provádí. Zkontrolujte, že plán:

  • Pokud je to vhodné, použije váš primární klíč.
  • Používá vhodné sekundární indexy místo tabulky dat.
  • Používá funkci RANGE SCAN nebo SKIP SCAN, kdykoli je to možné, místo funkce PROHLEDÁVÁNÍ TABULEK.

Příklady plánů

Řekněme například, že máte tabulku s názvem LETY, která ukládá informace o zpoždění letů.

Pokud chcete vybrat všechny lety s polem airlineid19805, kde airlineid je pole, které není v primárním klíči nebo v žádném indexu:

select * from "FLIGHTS" where airlineid = '19805';

Spusťte vysvětlený příkaz následujícím způsobem:

explain select * from "FLIGHTS" where airlineid = '19805';

Plán dotazu vypadá takto:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER FLIGHTS
   SERVER FILTER BY AIRLINEID = '19805'

V tomto plánu si všimněte fráze ÚPLNÉ PROHLEDÁVÁNÍ PŘES LETY. Tato fráze označuje, že provádění provede prohledávání tabulek přes všechny řádky v tabulce místo použití efektivnější možnosti OBLAST SCAN nebo SKIP SCAN.

Řekněme, že chcete zadat dotaz na lety 2. ledna 2014 pro dopravce AA , kde jeho let byl větší než 1. Předpokládejme, že v ukázkové tabulce existují sloupce year, month, dayofmonth, carrier a flightnum a jsou součástí složeného primárního klíče. Dotaz by vypadal takto:

select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;

Pojďme se podívat na plán pro tento dotaz:

explain select * from "FLIGHTS" where year = 2014 and month = 1 and dayofmonth = 2 and carrier = 'AA' and flightnum > 1;

Výsledný plán je:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER FLIGHTS [2014,1,2,'AA',2] - [2014,1,2,'AA',*]

Hodnoty v hranatých závorkách jsou rozsahem hodnot primárních klíčů. V tomto případě jsou hodnoty rozsahu pevné s rokem 2014, měsícem 1 a dnem 2, ale umožňují hodnoty letového čísla počínaje 2 a nahoru (*). Tento plán dotazu potvrzuje, že se primární klíč používá podle očekávání.

Dále vytvořte index v tabulce FLIGHTS s názvem carrier2_idx , která je pouze v poli dopravce. Tento index také zahrnuje flightdatesloupce , origintailnuma flightnum jako zahrnuté sloupce, jejichž data jsou také uložena v indexu.

CREATE INDEX carrier2_idx ON FLIGHTS (carrier) INCLUDE(FLIGHTDATE,TAILNUM,ORIGIN,FLIGHTNUM);

Řekněme, že chcete operátora získat spolu s operátorem flightdate a tailnum, jako v následujícím dotazu:

explain select carrier,flightdate,tailnum from "FLIGHTS" where carrier = 'AA';

Měli byste vidět, že se používá tento index:

CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER CARRIER2_IDX ['AA']

Úplný seznam položek, které se dají zobrazit v vysvětlení výsledků plánu, najdete v části Vysvětlit plány v Průvodci laděním Apache Phoenix.

Efektivní spojení

Obecně se chcete vyhnout spojením, pokud není jedna strana malá, zejména u častých dotazů.

V případě potřeby můžete provádět velké spojení s nápovědou /*+ USE_SORT_MERGE_JOIN */ , ale velké spojení je nákladná operace nad obrovským počtem řádků. Pokud by celková velikost všech pravých tabulek překročila dostupnou paměť, použijte nápovědu /*+ NO_STAR_JOIN */ .

Scénáře

Následující pokyny popisují některé běžné vzory.

Úlohy náročné na čtení

V případě případů použití náročných na čtení se ujistěte, že používáte indexy. Pokud chcete ušetřit režijní náklady na čtení, zvažte také vytvoření pokrytých indexů.

Úlohy náročné na zápis

Pro úlohy náročné na zápis, u kterých se primární klíč monotonicky zvyšuje, vytvořte kontejnery soli, které pomáhají vyhnout se hotspotům zápisu, a to na úkor celkové propustnosti čtení kvůli potřebným dalším kontrolám. Při použití funkce UPSERT k zápisu velkého počtu záznamů vypněte funkci autoCommit a zasadíte záznamy do dávek.

Hromadné odstranění

Při odstraňování velké datové sady zapněte funkci autoCommit před vydáním dotazu DELETE, aby si klient nemusel pamatovat klíče řádků pro všechny odstraněné řádky. AutoCommit zabrání klientovi ukládat řádky ovlivněné funkcí DELETE do vyrovnávací paměti, aby je Phoenix mohl odstranit přímo na serverech oblastí bez nákladů na jejich vrácení klientovi.

Neměnné a pouze připojení

Pokud váš scénář upřednostňuje rychlost zápisu před integritou dat, zvažte zakázání protokolu s předstihem při vytváření tabulek:

CREATE TABLE CONTACTS (...) DISABLE_WAL=true;

Podrobnosti o této a dalších možnostech najdete v tématu Apache Phoenix Grammar.

Další kroky