Delen via


Best practices voor Apache Phoenix-prestaties

Het belangrijkste aspect van de prestaties van Apache Phoenix is het optimaliseren van de onderliggende Apache HBase. Phoenix maakt een relationeel gegevensmodel boven op HBase waarmee SQL-query's worden geconverteerd naar HBase-bewerkingen, zoals scans. Het ontwerp van uw tabelschema, de selectie en volgorde van de velden in uw primaire sleutel en het gebruik van indexen zijn allemaal van invloed op de prestaties van Phoenix.

Tabelschemaontwerp

Wanneer u een tabel maakt in Phoenix, wordt die tabel opgeslagen in een HBase-tabel. De HBase-tabel bevat groepen kolommen (kolomfamilies) die samen worden geopend. Een rij in de tabel Phoenix is een rij in de HBase-tabel, waarbij elke rij bestaat uit versies van cellen die zijn gekoppeld aan een of meer kolommen. Logisch gezien is één HBase-rij een verzameling sleutel-waardeparen, die elk dezelfde rijsleutelwaarde hebben. Dat wil gezegd: elk sleutel-waardepaar heeft een rijsleutelkenmerk en de waarde van dat rijsleutelkenmerk is hetzelfde voor een bepaalde rij.

Het schemaontwerp van een Phoenix-tabel bevat het primaire-sleutelontwerp, het kolomfamilieontwerp, het afzonderlijke kolomontwerp en hoe de gegevens worden gepartitioneerd.

Ontwerp van primaire sleutel

De primaire sleutel die is gedefinieerd voor een tabel in Phoenix bepaalt hoe gegevens worden opgeslagen in de rijsleutel van de onderliggende HBase-tabel. In HBase is de enige manier om toegang te krijgen tot een bepaalde rij met de rijsleutel. Daarnaast worden gegevens die zijn opgeslagen in een HBase-tabel, gesorteerd op de rijsleutel. Phoenix bouwt de rijsleutelwaarde op door de waarden van elk van de kolommen in de rij samen te vouwen, in de volgorde waarin ze zijn gedefinieerd in de primaire sleutel.

Een tabel voor contactpersonen heeft bijvoorbeeld de voornaam, achternaam, telefoonnummer en adres, allemaal in dezelfde kolomfamilie. U kunt een primaire sleutel definiëren op basis van een toenemend volgnummer:

rowkey Adres telefoon firstName lastName
1000 1111 San Gabriel Dr. 1-425-000-0002 Jan Dole
8396 5415 San Gabriel Dr. 1-230-555-0191 Calvin Raji

Als u echter vaak query's uitvoert op lastName, presteert deze primaire sleutel mogelijk niet goed, omdat voor elke query een volledige tabelscan nodig is om de waarde van elke achternaam te lezen. In plaats daarvan kunt u een primaire sleutel definiëren voor de kolommen lastName, FirstName en Burgerservicenummer. Deze laatste kolom is om twee bewoners op hetzelfde adres met dezelfde naam te ontdubbelen, zoals een vader en zoon.

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

Met deze nieuwe primaire sleutel zijn de rijsleutels die door Phoenix worden gegenereerd:

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

In de eerste rij van de opgegeven tabel worden de gegevens voor de rijsleutel weergegeven zoals weergegeven:

rowkey sleutel waarde
Dole-John-111 Adres 1111 San Gabriel Dr.
Dole-John-111 telefoon 1-425-000-0002
Dole-John-111 firstName Jan
Dole-John-111 lastName Dole
Dole-John-111 socialSecurityNum 111

Met deze rijsleutel wordt nu een dubbele kopie van de gegevens opgeslagen. Houd rekening met de grootte en het aantal kolommen dat u in uw primaire sleutel opneemt, omdat deze waarde is opgenomen in elke cel in de onderliggende HBase-tabel.

Als de primaire sleutel waarden bevat die monotonisch toenemen, moet u ook de tabel met zoutbuckets maken om te voorkomen dat schrijf hotspots worden gemaakt. Zie Partitiegegevens.

Ontwerp van kolomfamilie

Als sommige kolommen vaker worden geopend dan andere, moet u meerdere kolomfamilies maken om de vaak gebruikte kolommen te scheiden van zelden gebruikte kolommen.

Als bepaalde kolommen meestal samen worden geopend, plaatst u deze kolommen ook in dezelfde kolomfamilie.

Kolomontwerp

  • Houd VARCHAR-kolommen onder ongeveer 1 MB vanwege de I/O-kosten van grote kolommen. Bij het verwerken van query's materialiseert HBase cellen volledig voordat ze naar de client worden verzonden en ontvangt de client ze volledig voordat deze worden overgedragen aan de toepassingscode.
  • Sla kolomwaarden op met behulp van een compacte indeling, zoals protobuf, Avro, msgpack of BSON. JSON wordt niet aanbevolen, omdat deze groter is.
  • Overweeg om gegevens te comprimeren voordat opslag latentie en I/O-kosten verminderen.

Partitiegegevens

Phoenix stelt u in staat om het aantal regio's te bepalen waar uw gegevens worden gedistribueerd, waardoor de lees-/schrijfprestaties aanzienlijk kunnen worden verhoogd. Wanneer u een Phoenix-tabel maakt, kunt u uw gegevens zout of vooraf splitsen.

Als u een tafel wilt zouten tijdens het maken, geeft u het aantal zoutbakken op:

CREATE TABLE CONTACTS (...) SALT_BUCKETS = 16

Met deze zouting wordt de tabel verdeeld over de waarden van primaire sleutels, waarbij de waarden automatisch worden gekozen.

Als u wilt bepalen waar de tabelsplitsingen plaatsvinden, kunt u de tabel vooraf splitsen door de bereikwaarden op te geven waarmee de splitsing plaatsvindt. Als u bijvoorbeeld een tabel wilt maken die is gesplitst in drie regio's:

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

Indexontwerp

Een Phoenix-index is een HBase-tabel waarin een kopie van sommige of alle gegevens uit de geïndexeerde tabel wordt opgeslagen. Een index verbetert de prestaties voor specifieke typen query's.

Wanneer u meerdere indexen hebt gedefinieerd en vervolgens een query uitvoert op een tabel, selecteert Phoenix automatisch de beste index voor de query. De primaire index wordt automatisch gemaakt op basis van de primaire sleutels die u selecteert.

Voor verwachte query's kunt u ook secundaire indexen maken door hun kolommen op te geven.

Bij het ontwerpen van uw indexen:

  • Maak alleen de indexen die u nodig hebt.
  • Beperk het aantal indexen voor regelmatig bijgewerkte tabellen. Updates voor een tabel worden omgezet in schrijfbewerkingen naar zowel de hoofdtabel als de indextabellen.

Secundaire indexen maken

Secundaire indexen kunnen de leesprestaties verbeteren door een volledige tabelscan om te zetten in een puntzoekactie, tegen de kosten van opslagruimte en schrijfsnelheid. Secundaire indexen kunnen worden toegevoegd of verwijderd na het maken van de tabel en hoeven geen wijzigingen aan bestaande query's te worden aangebracht. Query's worden gewoon sneller uitgevoerd. Afhankelijk van uw behoeften kunt u overwegen om gedekte indexen, functionele indexen of beide te maken.

Gedekte indexen gebruiken

Gedekte indexen zijn indexen die gegevens uit de rij bevatten, naast de waarden die worden geïndexeerd. Nadat u de gewenste indexvermelding hebt gevonden, hoeft u geen toegang te krijgen tot de primaire tabel.

In de voorbeeldtabel met contactpersonen kunt u bijvoorbeeld een secundaire index maken op alleen de kolom socialSecurityNum. Deze secundaire index versnelt query's die filteren op socialSecurityNum-waarden, maar het ophalen van andere veldwaarden vereist een andere leesbewerking voor de hoofdtabel.

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

Als u echter meestal de voornaam en achternaam opzoekt op basis van de socialSecurityNum, kunt u een gedekte index maken die de voornaam en achternaam als werkelijke gegevens in de indextabel bevat:

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

Met deze gedekte index kan de volgende query alle gegevens ophalen door alleen de tabel met de secundaire index te lezen:

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

Functionele indexen gebruiken

Met functionele indexen kunt u een index maken voor een willekeurige expressie die u verwacht te gebruiken in query's. Zodra u een functionele index hebt en een query die expressie gebruikt, kan de index worden gebruikt om de resultaten op te halen in plaats van de gegevenstabel.

U kunt bijvoorbeeld een index maken waarmee u hoofdlettergevoelige zoekopdrachten kunt uitvoeren op de gecombineerde voornaam en achternaam van een persoon:

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

Queryontwerp

De belangrijkste overwegingen in het queryontwerp zijn:

  • Inzicht in het queryplan en controleer het verwachte gedrag.
  • Efficiënt deelnemen.

Inzicht in het queryplan

In SQLLine gebruikt u EXPLAIN gevolgd door uw SQL-query om het plan weer te geven van bewerkingen die Phoenix uitvoert. Controleer of het plan:

  • Gebruikt uw primaire sleutel indien van toepassing.
  • Gebruikt de juiste secundaire indexen in plaats van de gegevenstabel.
  • Maakt gebruik van RANGE SCAN of SKIP SCAN indien mogelijk, in plaats van TABLE SCAN.

Voorbeelden van plannen

Stel dat u een tabel hebt met de naam VLUCHTEN waarin vluchtvertragingsgegevens worden opgeslagen.

Als u alle vluchten wilt selecteren met een airlineid van 19805, waar airlineid bevindt zich een veld dat zich niet in de primaire sleutel of in een index bevindt:

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

Voer de uitgelegde opdracht als volgt uit:

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

Het queryplan ziet er als volgt uit:

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

In dit plan noteert u de zin VOLLEDIGE SCAN OVER VLUCHTEN. Deze woordgroep geeft aan dat de uitvoering een TABLE SCAN uitvoert voor alle rijen in de tabel, in plaats van de efficiëntere optie BEREIKSCAN of SKIP SCAN te gebruiken.

Stel dat u wilt zoeken naar vluchten op 2 januari 2014 voor de vervoerder AA waar het vluchtnummer groter is dan 1. Stel dat het kolommenjaar, de maand, de dagvanmonth, de vervoerder en het vluchtnummer in de voorbeeldtabel bestaan en allemaal deel uitmaken van de samengestelde primaire sleutel. De query ziet er als volgt uit:

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

Laten we het plan voor deze query bekijken met:

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

Het resulterende plan is:

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

De waarden tussen vierkante haken vormen het bereik van waarden voor de primaire sleutels. In dit geval worden de bereikwaarden vastgesteld met jaar 2014, maand 1 en dayofmonth 2, maar staan waarden toe voor vluchtnummer beginnend met 2 en hoger (*). Dit queryplan bevestigt dat de primaire sleutel wordt gebruikt zoals verwacht.

Maak vervolgens een index op de tabel VLUCHTEN met de naam carrier2_idx alleen in het veld Carrier. Deze index bevat flightdateook , tailnumen originflightnum als gedekte kolommen waarvan de gegevens ook in de index worden opgeslagen.

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

Stel dat u de provider samen met de flightdate en tailnum, zoals in de volgende query, wilt ophalen:

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

U ziet dat deze index wordt gebruikt:

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

Zie de sectie Uitlegplannen in de Apache Phoenix Tuning Guide voor een volledig overzicht van de items die kunnen worden weergegeven in de resultaten van het uitlegplan.

Efficiënt deelnemen

Over het algemeen wilt u joins vermijden, tenzij één kant klein is, met name bij frequente query's.

Indien nodig kunt u grote joins uitvoeren met de /*+ USE_SORT_MERGE_JOIN */ hint, maar een grote join is een dure bewerking ten opzichte van een groot aantal rijen. Als de totale grootte van alle tabellen aan de rechterkant het beschikbare geheugen overschrijdt, gebruikt u de /*+ NO_STAR_JOIN */ hint.

Scenario's

In de volgende richtlijnen worden enkele veelvoorkomende patronen beschreven.

Leesintensieve workloads

Voor leesintensieve gebruiksvoorbeelden moet u ervoor zorgen dat u indexen gebruikt. Als u ook leestijdoverhead wilt besparen, kunt u overwegen om gedekte indexen te maken.

Schrijfintensieve workloads

Voor schrijfintensieve werkbelastingen waarbij de primaire sleutel monotonisch toeneemt, maakt u zoutbuckets om schrijf hotspots te voorkomen, ten koste van de totale leesdoorvoer vanwege de extra scans die nodig zijn. Als u UPSERT gebruikt om een groot aantal records te schrijven, schakelt u autoCommit uit en voert u een batch van de records uit.

Bulksgewijs verwijderen

Wanneer u een grote gegevensset verwijdert, schakelt u AutoCommit in voordat u de DELETE-query uitgeeft, zodat de client de rijsleutels voor alle verwijderde rijen niet hoeft te onthouden. AutoCommit voorkomt dat de client de rijen die worden beïnvloed door de DELETE buffert, zodat Phoenix ze rechtstreeks op de regioservers kan verwijderen zonder dat ze ten koste gaan van het retourneren naar de client.

Onveranderbaar en alleen toevoegen

Als uw scenario de voorkeur geeft aan schrijfsnelheid ten opzichte van gegevensintegriteit, kunt u overwegen om het write-ahead-logboek uit te schakelen bij het maken van uw tabellen:

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

Zie Apache Phoenix Grammar voor meer informatie over deze en andere opties.

Volgende stappen