Share via


Metodtips för prestanda för Apache Phoenix

Den viktigaste aspekten av Apache Phoenix-prestanda är att optimera den underliggande Apache HBase. Phoenix skapar en relationsdatamodell ovanpå HBase som konverterar SQL-frågor till HBase-åtgärder, till exempel genomsökningar. Utformningen av tabellschemat, markeringen och ordningen på fälten i primärnyckeln och din användning av index påverkar alla Phoenix-prestanda.

Tabellschemadesign

När du skapar en tabell i Phoenix lagras tabellen i en HBase-tabell. HBase-tabellen innehåller grupper med kolumner (kolumnfamiljer) som nås tillsammans. En rad i Phoenix-tabellen är en rad i HBase-tabellen, där varje rad består av versionsceller som är associerade med en eller flera kolumner. Logiskt sett är en enda HBase-rad en samling nyckel/värde-par som var och en har samma rowkey-värde. Det innebär att varje nyckel/värde-par har ett rowkey-attribut, och värdet för det rowkey-attributet är detsamma för en viss rad.

Schemadesignen för en Phoenix-tabell innehåller primärnyckeldesign, kolumnfamiljedesign, enskild kolumndesign och hur data partitioneras.

Design av primärnyckel

Den primära nyckeln som definieras i en tabell i Phoenix avgör hur data lagras i radnyckeln i den underliggande HBase-tabellen. I HBase är det enda sättet att komma åt en viss rad med radnyckeln. Dessutom sorteras data som lagras i en HBase-tabell efter radnyckeln. Phoenix skapar värdet rowkey genom att sammanfoga värdena för var och en av kolumnerna i raden, i den ordning de definieras i den primära nyckeln.

Till exempel har en tabell för kontakter förnamn, efternamn, telefonnummer och adress, allt i samma kolumnfamilj. Du kan definiera en primärnyckel baserat på ett ökande sekvensnummer:

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

Men om du ofta frågar efter lastName kanske den här primärnyckeln inte fungerar bra, eftersom varje fråga kräver en fullständig tabellgenomsökning för att läsa värdet för varje lastName. I stället kan du definiera en primärnyckel i kolumnerna lastName, firstName och personnummer. Den sista kolumnen är att skilja två boende på samma adress med samma namn, till exempel en far och son.

rowkey Adress phone 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

Med den här nya primärnyckeln skulle radnycklarna som genererats av Phoenix vara:

rowkey Adress phone 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

På den första raden i den angivna tabellen representeras data för radnyckeln enligt följande:

rowkey key värde
Dole-John-111 Adress 1111 San Gabriel Dr.
Dole-John-111 phone 1-425-000-0002
Dole-John-111 firstName John
Dole-John-111 lastName Dole
Dole-John-111 socialSecurityNum 111

Den här radnyckeln lagrar nu en duplicerad kopia av data. Överväg storleken och antalet kolumner som du inkluderar i primärnyckeln, eftersom det här värdet ingår i varje cell i den underliggande HBase-tabellen.

Om primärnyckeln dessutom har värden som monotont ökar bör du skapa tabellen med salt buckets för att undvika att skapa skrivpunkter – se Partitionsdata.

Kolumnfamiljedesign

Om vissa kolumner används oftare än andra bör du skapa flera kolumnfamiljer för att skilja de kolumner som används ofta från kolumner som används sällan.

Om vissa kolumner tenderar att nås tillsammans placerar du kolumnerna i samma kolumnfamilj.

Kolumndesign

  • Behåll VARCHAR-kolumner under cirka 1 MB på grund av I/O-kostnaderna för stora kolumner. Vid bearbetning av frågor materialiserar HBase celler i sin helhet innan de skickas till klienten, och klienten tar emot dem i sin helhet innan de överlämnas till programkoden.
  • Lagra kolumnvärden med ett kompakt format som protobuf, Avro, msgpack eller BSON. JSON rekommenderas inte eftersom det är större.
  • Överväg att komprimera data före lagring för att minska svarstiden och I/O-kostnaderna.

Partitionera data

Med Phoenix kan du styra antalet regioner där dina data distribueras, vilket avsevärt kan öka läs-/skrivprestanda. När du skapar en Phoenix-tabell kan du antingen salta eller dela dina data i förväg.

Om du vill salta en tabell under skapandet anger du antalet salt bucketar:

CREATE TABLE CONTACTS (...) SALT_BUCKETS = 16

Den här saltningen delar upp tabellen längs värdena för primära nycklar och väljer värdena automatiskt.

Om du vill kontrollera var tabelldelningarna sker kan du dela tabellen i förväg genom att ange de intervallvärden som delningen sker längs. Om du till exempel vill skapa en tabell uppdelad i tre regioner:

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

Indexdesign

Ett Phoenix-index är en HBase-tabell som lagrar en kopia av vissa eller alla data från den indexerade tabellen. Ett index förbättrar prestandan för specifika typer av frågor.

När du har definierat flera index och sedan kör frågor mot en tabell väljer Phoenix automatiskt det bästa indexet för frågan. Det primära indexet skapas automatiskt baserat på de primära nycklar som du väljer.

För förväntade frågor kan du också skapa sekundära index genom att ange deras kolumner.

När du utformar dina index:

  • Skapa bara de index du behöver.
  • Begränsa antalet index i tabeller som uppdateras ofta. Uppdateringar till en tabell översätts till skrivningar till både huvudtabellen och indextabellerna.

Skapa sekundära index

Sekundära index kan förbättra läsprestanda genom att omvandla vad som skulle vara en fullständig tabellsökning till en punktsökning, på bekostnad av lagringsutrymme och skrivhastighet. Sekundära index kan läggas till eller tas bort när tabellen har skapats och kräver inte ändringar i befintliga frågor – frågor körs bara snabbare. Beroende på dina behov bör du överväga att skapa täckta index, funktionella index eller både och.

Använda täckta index

Täckta index är index som innehåller data från raden utöver de värden som indexeras. När du har hittat den önskade indexposten behöver du inte komma åt den primära tabellen.

I exempelkontakttabellen kan du till exempel skapa ett sekundärt index för kolumnen socialSecurityNum. Det här sekundära indexet skulle påskynda frågor som filtreras efter socialSecurityNum-värden, men för att hämta andra fältvärden krävs ytterligare en läsning mot huvudtabellen.

rowkey Adress phone 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

Men om du vanligtvis vill slå upp firstName och lastName med tanke på socialSecurityNum kan du skapa ett täckt index som innehåller firstName och lastName som faktiska data i indextabellen:

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

Med det här indexet kan följande fråga hämta alla data bara genom att läsa från tabellen som innehåller det sekundära indexet:

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

Använda funktionella index

Med funktionella index kan du skapa ett index för ett godtyckligt uttryck som du förväntar dig ska användas i frågor. När du har ett funktionellt index på plats och en fråga använder det uttrycket kan indexet användas för att hämta resultaten i stället för datatabellen.

Du kan till exempel skapa ett index så att du kan göra skiftlägesokänsliga sökningar på en persons kombinerade förnamn och efternamn:

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

Frågedesign

De viktigaste övervägandena i frågedesignen är:

  • Förstå frågeplanen och verifiera dess förväntade beteende.
  • Anslut effektivt.

Förstå frågeplanen

I SQLLine använder du EXPLAIN följt av DIN SQL-fråga för att visa den åtgärdsplan som Phoenix utför. Kontrollera att planen:

  • Använder din primära nyckel när det är lämpligt.
  • Använder lämpliga sekundära index i stället för datatabellen.
  • Använder RANGE SCAN eller SKIP SCAN när det är möjligt, i stället för TABLE SCAN.

Planexempel

Anta till exempel att du har en tabell med namnet FLYG som lagrar information om flygförsening.

Så här väljer du alla flygningar med ett airlineid av 19805, där airlineid är ett fält som inte finns i primärnyckeln eller i något index:

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

Kör det förklarande kommandot på följande sätt:

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

Frågeplanen ser ut så här:

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

I den här planen noterar du frasen FULLSTÄNDIG GENOMSÖKNING ÖVER FLYG. Den här frasen anger att körningen gör en TABELLGENOMSÖKNING över alla rader i tabellen, i stället för att använda det mer effektiva ALTERNATIVET INTERVALLSÖKNING eller SKIP SCAN.

Anta nu att du vill fråga efter flyg den 2 januari 2014 för det flygbolag AA där dess flygnummer var större än 1. Anta att kolumnerna year, month, dayofmonth, carrier och flightnum finns i exempeltabellen och alla ingår i den sammansatta primära nyckeln. Frågan skulle se ut så här:

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

Nu ska vi undersöka planen för den här frågan med:

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

Den resulterande planen är:

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

Värdena inom hakparenteser är intervallet med värden för de primära nycklarna. I det här fallet är intervallvärdena fasta med år 2014, månad 1 och dagtid 2, men tillåter värden för flightnum som börjar med 2 och uppåt (*). Den här frågeplanen bekräftar att den primära nyckeln används som förväntat.

Skapa sedan ett index i tabellen FLIGHTS med namnet carrier2_idx som endast finns på transportföretagets fält. Det här indexet innehåller flightdateäven , tailnum, originoch flightnum som täckta kolumner vars data också lagras i indexet.

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

Anta att du vill hämta transportören tillsammans med flightdate och tailnum, som i följande fråga:

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

Du bör se att det här indexet används:

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

En fullständig lista över de objekt som kan visas i förklara planresultat finns i avsnittet Förklara planer i Apache Phoenix Tuning Guide.

Anslut effektivt

I allmänhet vill du undvika kopplingar om inte ena sidan är liten, särskilt vid frekventa frågor.

Om det behövs kan du göra stora kopplingar med tipset /*+ USE_SORT_MERGE_JOIN */ , men en stor koppling är en dyr åtgärd över ett stort antal rader. Om den totala storleken på alla tabeller på höger sida skulle överskrida det tillgängliga minnet använder du tipset /*+ NO_STAR_JOIN */ .

Scenarier

Följande riktlinjer beskriver några vanliga mönster.

Läsintensiva arbetsbelastningar

Kontrollera att du använder index för läsintensiva användningsfall. Om du vill spara lästid kan du dessutom överväga att skapa täckta index.

Skrivintensiva arbetsbelastningar

För skrivintensiva arbetsbelastningar där primärnyckeln monotont ökar skapar du salt buckets för att undvika skrivpunkter, på bekostnad av det totala läsdataflödet på grund av de ytterligare genomsökningar som behövs. När du använder UPSERT för att skriva ett stort antal poster inaktiverar du även autoCommit och batchar upp posterna.

Massborttagningar

När du tar bort en stor datauppsättning aktiverar du autoCommit innan du utfärdar DELETE-frågan, så att klienten inte behöver komma ihåg radnycklarna för alla borttagna rader. AutoCommit förhindrar att klienten buffrar de rader som påverkas av DELETE, så att Phoenix kan ta bort dem direkt på regionservrarna utan att behöva returnera dem till klienten.

Oföränderlig och endast tillägg

Om ditt scenario prioriterar skrivhastighet framför dataintegritet bör du överväga att inaktivera loggen för framåtskrivning när du skapar tabellerna:

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

Mer information om detta och andra alternativ finns i Apache Phoenix Grammar.

Nästa steg