Deze gastpost van Intel Java performance architect Eric Kaczmarek (oorspronkelijk hier gepubliceerd) onderzoekt hoe Java garbage collection (GC) kan worden afgestemd op Apache HBase, gericht op 100% YCSB reads.
Apache HBase is een Apache open source-project dat NoSQL-gegevensopslag biedt. HBase wordt vaak samen met HDFS gebruikt en wordt wereldwijd veel gebruikt. Bekende gebruikers zijn Facebook, Twitter, Yahoo en meer. Vanuit het perspectief van de ontwikkelaar is HBase een "gedistribueerde, geversioneerde, niet-relationele database gemodelleerd naar Google's Bigtable, een gedistribueerd opslagsysteem voor gestructureerde gegevens". HBase kan gemakkelijk een zeer hoge doorvoer aan door ofwel op te schalen (d.w.z. implementatie op een grotere server) of uit te schalen (d.w.z. implementatie op meer servers).
Vanuit het oogpunt van de gebruiker is de latentie voor elke afzonderlijke query erg belangrijk. Terwijl we met gebruikers werken om HBase-workloads te testen, af te stemmen en te optimaliseren, komen we nu een aanzienlijk aantal tegen die echt 99e percentiel latenties willen. Dat betekent een retourtje, van het verzoek van de klant naar het antwoord terug naar de klant, allemaal binnen 100 milliseconden.
Verschillende factoren dragen bij aan variatie in latentie. Een van de meest verwoestende en onvoorspelbare latency-indringers zijn de "stop the world"-pauzes van de Java Virtual Machine (JVM's) voor het verzamelen van afval (geheugenopruiming).
Om dat aan te pakken, hebben we enkele experimenten geprobeerd met Oracle jdk7u21 en jdk7u60 G1 (Garbage 1st) collector. Het serversysteem dat we gebruikten was gebaseerd op Intel Xeon Ivy-bridge EP-processors met Hyper-threading (40 logische processors). Het had 256 GB DDR3-1600 RAM en drie 400 GB SSD's als lokale opslag. Deze kleine opstelling bevatte een master en een slave, geconfigureerd op een enkel knooppunt met de juiste schaal van de belasting. We gebruikten HBase-versie 0.98.1 en een lokaal bestandssysteem voor HFile-opslag. De HBase-testtabel was geconfigureerd als 400 miljoen rijen en was 580 GB groot. We gebruikten de standaard HBase-heapstrategie:40% voor blockcache, 40% voor memstore. YCSB werd gebruikt om 600 werkthreads aan te sturen die verzoeken naar de HBase-server stuurden.
In de volgende grafieken wordt jdk7u21 gedurende één uur 100% gelezen met behulp van -XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
. We hebben de te gebruiken vuilnisophaler, de heapgrootte en de gewenste "stop de wereld"-pauzetijd van de vuilnisophaaldienst gespecificeerd.
Figuur 1:Wilde schommelingen in GC Pauzetijd
In dit geval kregen we wild slingerende GC-pauzes. De GC-pauze had een bereik van 7 milliseconden tot 5 volledige seconden na een eerste piek die wel 17,5 seconden bereikte.
De volgende grafiek toont meer details tijdens een stabiele toestand:
Afbeelding 2:Details van GC-pauze, tijdens stabiele toestand
Figuur 2 vertelt ons dat de GC-pauzes eigenlijk in drie verschillende groepen voorkomen:(1) tussen 1 en 1,5 seconde; (2) tussen 0,007 seconden tot 0,5 seconden; (3) pieken tussen 1,5 seconden en 5 seconden. Dit was heel vreemd, dus we hebben de meest recent uitgebrachte jdk7u60 getest om te zien of de gegevens anders zouden zijn:
We hebben dezelfde 100% leestests uitgevoerd met exact dezelfde JVM-parameters:-XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
.
Afbeelding 3:sterk verbeterde verwerking van pieken in de pauzetijd
Jdk7u60 heeft het vermogen van G1 om pieken in de pauzetijd te verwerken na de eerste piek tijdens het settelen aanzienlijk verbeterd. Jdk7u60 maakte 1029 jonge en gemengde eindklasseringen tijdens een run van een uur. GC gebeurde ongeveer elke 3,5 seconden. Jdk7u21 maakte 286 GC's, waarbij elke GC ongeveer elke 12,6 seconden plaatsvond. Jdk7u60 kon de pauzetijd tussen 0,302 en 1 seconde beheren zonder grote pieken.
Afbeelding 4 hieronder geeft ons een kijkje bij 150 GC-pauzes tijdens stabiele toestand:
Figuur 4:Beter, maar niet goed genoeg
Tijdens de stabiele toestand kon jdk7u60 de gemiddelde pauzetijd rond de 369 milliseconden houden. Het was veel beter dan jdk7u21, maar het voldeed nog steeds niet aan onze vereiste van 100 milliseconden gegeven door –Xx:MaxGCPauseMillis=100
.
Om te bepalen wat we nog meer konden doen om onze 100 miljoen seconden pauzetijd te krijgen, moesten we meer weten over het gedrag van het geheugenbeheer van de JVM en de G1 (Garbage First) garbage collector. De volgende afbeeldingen laten zien hoe G1 werkt aan de Young Gen-collectie.
Figuur 5:Dia uit de 2012 JavaOne-presentatie door Charlie Hunt en Monica Beckwith:"G1 Garbage Collector Performance Tuning"
Wanneer de JVM start, op basis van de JVM-startparameters, vraagt het het besturingssysteem om een groot continu geheugenblok toe te wijzen om de heap van de JVM te hosten. Dat geheugenblok wordt door de JVM in regio's verdeeld.
Figuur 6:Dia uit de 2012 JavaOne-presentatie door Charlie Hunt en Monica Beckwith:"G1 Garbage Collector Performance Tuning"
Zoals figuur 6 laat zien, komt elk object dat het Java-programma toewijst met behulp van de Java API eerst naar de Eden-ruimte in de jonge generatie aan de linkerkant. Na een tijdje raakt het Eden vol en wordt een jonge generatie GC geactiveerd. Objecten waarnaar nog steeds wordt verwezen (d.w.z. "levend") worden gekopieerd naar de Survivor-ruimte. Wanneer objecten meerdere GC's in de jonge generatie overleven, worden ze gepromoveerd naar de ruimte van de oude generatie.
Wanneer Young GC plaatsvindt, worden de threads van de Java-toepassing gestopt om live-objecten veilig te markeren en te kopiëren. Deze stops zijn de beruchte "stop-the-world" GC-pauzes, waardoor de applicaties niet reageren totdat de pauzes voorbij zijn.
Figuur 7:Dia uit de 2012 JavaOne-presentatie door Charlie Hunt en Monica Beckwith:"G1 Garbage Collector Performance Tuning"
De oude generatie kan ook druk worden. Op een bepaald niveau—gecontroleerd door -XX:InitiatingHeapOccupancyPercent=?
waar de standaard 45% van de totale heap is, wordt een gemengde GC geactiveerd. Het verzamelt zowel Young gen als Old gen. De pauzes van het gemengde eindklassement worden bepaald door hoe lang het duurt voordat het jonge gen opruimt wanneer het gemengde eindklassement plaatsvindt.
Dus we kunnen in G1 zien dat de "stop de wereld" GC-pauzes worden gedomineerd door hoe snel G1 levende objecten uit de Eden-ruimte kan markeren en kopiëren. Met dit in gedachten zullen we analyseren hoe het HBase-geheugentoewijzingspatroon ons zal helpen om G1 GC af te stemmen om onze gewenste pauze van 100 milliseconden te krijgen.
In HBase zijn er twee in-memory-structuren die het grootste deel van de heap verbruiken:de BlockCache
, het cachen van HBase-bestandsblokken voor leesbewerkingen en de Memstore die de laatste updates in de cache opslaat.
Figuur 8:In HBase verbruiken twee in-memory-structuren het grootste deel van de heap.
De standaardimplementatie van HBase's BlockCache
is de LruBlockCache
, die eenvoudigweg een grote bytearray gebruikt om alle HBase-blokken te hosten. Wanneer blokken worden "uitgezet", wordt de verwijzing naar het Java-object van dat blok verwijderd, waardoor de GC het geheugen kan verplaatsen.
Nieuwe objecten die de LruBlockCache
. vormen en Memstore
ga eerst naar de Eden-ruimte van de jonge generatie. Als ze lang genoeg leven (d.w.z. als ze niet uit LruBlockCache
worden gezet of weggespoeld uit Memstore), en na verschillende jonge generaties GC's, vinden ze hun weg naar de oude generatie van de Java-hoop. Wanneer de vrije ruimte van de Oude generatie minder is dan een gegeven threshOld
(InitiatingHeapOccupancyPercent
om te beginnen), gemengde GC activeert en verwijdert enkele dode objecten in de oude generatie, kopieert levende objecten van de jonge generatie en herberekent de Eden van de jonge generatie en de HeapOccupancyPercent
van de oude generatie . Uiteindelijk, wanneer HeapOccupancyPercent
een bepaald niveau bereikt, een FULL GC
gebeurt, waardoor enorme "stop de wereld" GC pauzeert om alle dode objecten in de oude generatie op te ruimen.
Na bestudering van het GC-logboek geproduceerd door "-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
", merkten we HeapOccupancyPercent
nooit groot genoeg geworden om een volledige GC te induceren tijdens HBase 100% lezen. De GC-pauzes die we zagen, werden gedomineerd door Young-gen "stop the world"-pauzes en de toenemende referentieverwerking in de loop van de tijd.
Na het voltooien van die analyse hebben we drie groepen wijzigingen aangebracht in de standaard G1 GC-instelling:
- Gebruik
-XX:+ParallelRefProcEnabled
Wanneer deze vlag is ingeschakeld, gebruikt GC meerdere threads om de toenemende referenties te verwerken tijdens Young en mixed GC. Met deze vlag voor HBase wordt de GC-opmerkingstijd met 75% verminderd en de algehele GC-pauzetijd met 30% verminderd. Set -XX:-ResizePLAB and -XX:ParallelGCThreads=8+(logical processors-8)(5/8)
Tijdens de Young-inzameling wordt gebruik gemaakt van Promotion Local Allocation Buffers (PLAB's). Er worden meerdere draden gebruikt. Elke thread moet mogelijk ruimte toewijzen voor objecten die worden gekopieerd in Survivor- of Old-ruimte. PLAB's zijn vereist om concurrentie van threads voor gedeelde datastructuren die vrij geheugen beheren te voorkomen. Elke GC-thread heeft één PLAB voor Survival-ruimte en één voor Old-ruimte. We willen het formaat van PLAB's niet langer aanpassen om de hoge communicatiekosten tussen GC-threads en variaties tijdens elke GC te voorkomen. 5/8). Deze formule is onlangs aanbevolen door Oracle. Met beide instellingen zijn we in staat om vloeiendere GC-pauzes te zien tijdens de run.- Wijzig
-XX:G1NewSizePercent
standaard van 5 tot 1 voor 100GB heapGebaseerd op de uitvoer van-XX:+PrintGCDetails and -XX:+PrintAdaptiveSizePolicy
, merkten we dat de reden waarom G1 niet aan onze gewenste 100GC-pauzetijd voldeed, de tijd was die nodig was om Eden te verwerken. Met andere woorden, G1 had tijdens onze tests gemiddeld 369 milliseconden nodig om 5 GB Eden leeg te maken. Vervolgens hebben we de Eden-maat gewijzigd met-XX:G1NewSizePercent=
vlag van 5 omlaag naar 1. Met deze wijziging zagen we de GC-pauzetijd teruggebracht tot 100 milliseconden.
Uit dit experiment kwamen we erachter dat de snelheid van G1 om Eden op te schonen ongeveer 1 GB per 100 milliseconden is, of 10 GB per seconde voor de HBase-configuratie die we gebruikten.
Op basis van die snelheid kunnen we -XX:G1NewSizePercent=
instellen dus de Eden-grootte kan rond de 1 GB worden gehouden. Bijvoorbeeld:
- 32 GB hoop,
-XX:G1NewSizePercent=3
- 64 GB hoop, –
XX:G1NewSizePercent=2
- 100 GB en meer heap,
-XX:G1NewSizePercent=1
- Dus onze laatste opdrachtregelopties voor de HRegionserver zijn:
-XX:+UseG1GC
-Xms100g -Xmx100g
(Hoopgrootte gebruikt in onze tests)-XX:MaxGCPauseMillis=100
(Gewenste GC-pauzetijd in tests)- –
XX:+ParallelRefProcEnabled
-XX:-ResizePLAB
-XX:ParallelGCThreads= 8+(40-8)(5/8)=28
-XX:G1NewSizePercent=1
Hier is de GC-pauzetijdgrafiek voor het uitvoeren van 100% leesbewerking gedurende 1 uur:
Figuur 9:De hoogste initiële bezinkingspieken werden met meer dan de helft verminderd.
In deze grafiek werden zelfs de hoogste initiële bezinkingspieken teruggebracht van 3.792 seconden naar 1.684 seconden. De meeste initiële pieken waren minder dan 1 seconde. Na de schikking kon GC de pauzetijd rond de 100 milliseconden houden.
De onderstaande grafiek vergelijkt jdk7u60-uitvoeringen met en zonder afstemming, tijdens stabiele toestand:
Afbeelding 10:jdk7u60 werkt met en zonder afstemming, tijdens stabiele toestand.
De eenvoudige GC-afstemming die we hierboven hebben beschreven, geeft ideale GC-pauzetijden, ongeveer 100 milliseconden, met een gemiddelde 106 milliseconden en een standaarddeviatie van 7 milliseconden.
Samenvatting
HBase is een applicatie die cruciaal is voor responstijd en waarvoor de GC-pauzetijd voorspelbaar en beheersbaar moet zijn. Met Oracle jdk7u60, gebaseerd op de GC-informatie gerapporteerd door -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
, kunnen we de GC-pauzetijd afstemmen op de door ons gewenste 100 milliseconden.
Eric Kaczmarek is een Java-prestatiearchitect in Intel's Software Solution Group. Hij leidt de inspanningen bij Intel om Big Data-frameworks (Hadoop, HBase, Spark, Cassandra) voor Intel-platforms mogelijk te maken en te optimaliseren.
Software en workloads die in prestatietests worden gebruikt, zijn mogelijk geoptimaliseerd voor alleen prestaties op Intel-microprocessors. Prestatietests, zoals SYSmark en MobileMark, worden gemeten met behulp van specifieke computersystemen, componenten, software, bewerkingen en functies. Elke wijziging in een van deze factoren kan ertoe leiden dat de resultaten variëren. U dient andere informatie en prestatietests te raadplegen om u te helpen bij het volledig evalueren van uw overwogen aankopen, inclusief de prestaties van dat product in combinatie met andere producten.
Intel-processornummers zijn geen maatstaf voor prestaties. Processornummers onderscheiden functies binnen elke processorfamilie. Niet tussen verschillende processorfamilies. Ga naar:http://www.intel.com/products/processor_number.
Copyright 2014 Intel Corp. Intel, het Intel-logo en Xeon zijn handelsmerken van Intel Corporation in de VS en/of andere landen.