Článek přečtěte do 7 min.

Určení ideální velikosti paměti pro váš server nebo virtuální instanci pro spuštění Java aplikace může být náročné. S rostoucími náklady a ekologickým dopadem cloudových instancí je však důležité správně dimenzovat vaše počítače tak, aby zvládly očekávanou zátěž bez předimenzování, a minimalizovaly tak náklady na počítač a snížily jeho ekologický dopad. Pochopení požadavků aplikace na velikost paměti je důležité pro dosažení maximálního výkonu při nejnižších provozních nákladech.

V tomto příspěvku použijeme logovací soubory garbage collectoru (GC) k určení požadované velikosti paměti pro aplikaci. Díky běhovému prostředí Java se můžeme spolehnout na to, že GC vyčistí paměť, která se již nepoužívá, a udrží celkové množství co nejnižší. Přitom může GC vygenerovat logovací soubor s množstvím informací, které nám mohou pomoci najít problémy v kódu a také definovat správné dimenze pro naše servery nebo virtuální prostředí.

Jak otestovat vaši aplikaci

Nejtěžší, ale nejdůležitější částí testu v reálném provozu je provedení opakovatelného zátěžového testu, který napodobuje skutečné použití aplikace. Jedná se o důležitý krok při vývoji a nasazení aplikace a vyžaduje spolupráci mezi vývojářským a DevOps týmem. Při nastavování testu pro definování množství paměti, které aplikace potřebuje, je třeba mít na paměti několik důležitých bodů. Tyto body platí i v jiných případech, například při testování maximální propustnosti.

  • Nespěchejte: Když je spuštěna Java aplikace, JVM překompiluje nejpoužívanější bajtkód (soubory tříd) do nativního kódu. Tento proces trvá určitou dobu (známou jako doba zahřívání), takže je třeba počkat, dokud nebude vaše aplikace dostatečně dlouho používána s typickou zátěží, kterou očekáváte. Aplikace musí volat veškerý kód, který by měla, na základě zátěže, která byla na aplikaci kladena.
  • Buďte opatrní s lokálními testy : Některé testy lze snadno spustit na vašem vlastním počítači, ale mějte na paměti zátěž samotného testu! Spuštění zátěžového testu na stejném počítači, na kterém běží aplikace, může způsobit přetížení CPU a/nebo paměti, což ovlivní výkon testované aplikace.
  • Použijte test z reálného světa: Test je platný pouze tehdy, když můžete simulovat očekávané zatížení v prostředí podobném vašemu produkčnímu systému.
  • Testování v produkčním prostředí : Záznamy GC mají minimální dopad na výkon vašeho systému. V mnoha případech je to mnohem jednodušší a levnější řešení pro získání reálných výsledků záznamů než nastavení plnohodnotného testovacího prostředí.

Experimentování s jarní petclinic

Pro tento příspěvek jsem k shromaždění výsledků testů použil aplikaci Spring Pet Clinic. Zdrojové kódy jsou k dispozici na GitHubu a obsahují i testovací skript pro JMeter.

Spuštění testovací aplikace

Chcete-li tento postup použít, získejte zdrojový kód, zkompilujte aplikaci a spusťte ji pomocí následujících příkazů:

# Get the sources
$ git clone https://github.com/spring-projects/spring-petclinic
$ cd spring-petclinic
 
# Generate a JAR and run, use CTRL+C to stop the application
$ ./mvnw package
$ java -Xlog:gc,safepoint:gc.log::filecount=0 -jar target/*.jar

Vaše aplikace je nyní nakonfigurována tak, aby ukládala protokolování garbage collection do jednoho souboru. Toto nastavení je ideální pro test v tomto příspěvku. Při povolování GC Log v produkčním prostředí byste však měli používat rolovací soubory, abyste zabránili tomu, aby se soubor příliš zvětšil a zaplnil váš úložný prostor. Například pomocí -Xlog:gc,safepoint:gc.log::filecount=10,filesize=100Mnastavte rotaci protokolu na maximálně 10 souborů o velikosti 100 MB každý. Pokud nedefinujete filecountfilesize, výchozí hodnota je pět souborů o velikosti 20 MB každý, takže GC Logging nepoužije více než 100 MB.

O JMeteru

Projekt Spring Petclinic obsahuje test JMeter. Takový test lze spustit pomocí Apache JMeter, 100% čisté open-source aplikace v Javě určené k zátěžovému testování funkčního chování a měření výkonu. Původně byl navržen pro testování webových aplikací, ale od té doby se rozšířil i na další testovací funkce. Zkontrolujte nejnovější verzi a stáhněte si ji.

$ cd ~/Downloads
$ wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.zip
$ unzip apache-jmeter-5.6.3.zip
$ rm apache-jmeter-5.6.3.zip

Testy JMeteru lze spustit pomocí aplikace s grafickým uživatelským rozhraním, ale nedoporučuje se to, protože to představuje riziko, že grafické uživatelské rozhraní ovlivní výkon testu. Grafické uživatelské rozhraní by se mělo používat pouze k vytvoření testu nebo jeho spuštění za účelem ověření jeho konfigurace.

Vytvoření testu s grafickým rozhraním JMeteru

  • Spusťte grafickou aplikaci Apache JMeter:
$ java -jar ~/Downloads/apache-jmeter-5.6.3/bin/ApacheJMeter.jar
  • V uživatelském rozhraní klikněte na Soubor > Otevřít a vyberte soubor spring-petclinic/src/test/jmeter/petclinic_test_plan.jmx.
  • Test pro ověření konfigurace můžete spustit stisknutím tlačítka Start, které spustí vlákna pro simulaci 500 uživatelů.
  • Nechte to běžet, dokud test není dokončen. Počet aktivních vláken klesne z 500 na 0.

Spuštění zátěžového testu pomocí JMeteru v bezhlavém režimu

Pro samotný test spustíme JMeter v headless režimu. V mém případě test spouštím na stejném počítači, na kterém běží aplikace, protože má dostatek paměti a CPU pro zpracování obou. Ujistěte se, že to platí i pro váš test, když používáte stejný přístup! 

Spusťme test a vygenerujme zprávu s následujícími možnostmi:

  • -n : spustit v headless režimu (bez GUI)
  • -t : cesta k testovacímu skriptu .jmx, který má být spuštěn
  • -l : cesta k souboru .jtl pro uložení nezpracovaných výsledků
  • -o : cesta k výstupní složce pro generování řídicího panelu sestavy po zátěžovém testu, která musí být prázdná
  • -e : vygenerovat dashboard sestavy po zátěžovém testu
$ java -jar ApacheJMeter.jar -n -t spring-petclinic/src/test/jmeter/petclinic_test_plan.jmx -l jmeter.jtl -o jmeter-report/ -e

I když tuto možnost nepřidáte -e, můžete i tak později vygenerovat HTML sestavu na základě souboru .jtl vytvořeného během testovacího běhu.

  • -g : cesta k souboru .jtl vygenerovanému během testu
  • -o : složka pro uložení HTML sestavy
$ java -jar ApacheJMeter.jar -g jmeter.jtl -o jmeter-report/

Protože každá nová verze běhového prostředí Java přináší vylepšení výkonu, je důležité vědět, která verze se používá ve vašem produkčním systému. Své testy jsem provedl s Azul Zulu Builds OpenJDK verze 21.0.3.

$ java -version
#openjdk version "21.0.3" 2024-04-16 LTS
#OpenJDK Runtime Environment Zulu21.34+19-CA (build 21.0.3+9-LTS)
#OpenJDK 64-Bit Server VM Zulu21.34+19-CA (build 21.0.3+9-LTS, mixed mode, sharing)

Čtení reportu JMeteru

V adresáři HTML reportů JMeteru ( jmeter-report/v mém případě, jak je specifikováno parametrem -o), najdete webovou stránku s výsledky testu JMeteru. Nenajdete zde žádné informace týkající se paměti, ale výsledky testů, které jsou definovány v testovacím souboru JMeteru. Například: Percentily doby odezvy, Propustnost v Hits Per Second atd.

Kontrola výsledků protokolu GC

Tento gc.logsoubor je „místem, kde se dozvíme více o využití paměti naší aplikací. Pomocí nástroje Azul GC Log Analyzer můžeme tento soubor číst a vizualizovat sadu grafů v čase (nástěnné hodiny a doba provozuschopnosti), abychom mohli zkontrolovat garbage collector, JIT kompilátor, systémové metriky a další. Následující grafy ukazují, že doba pauzy garbage collectoru zůstává po počátečním načtení pod 10 ms a velikost haldy po garbage collection zůstává kolem 64 MB. Doporučujeme použít dvojnásobek této hodnoty pro dimenzování systému. V tomto případě by tedy aplikace byla schopna zvládnout stejnou zátěž, jaká byla generována během testu, se 128 MB paměti.

Stejný princip můžete použít i u své aplikace a po změně nastavení – Xmxběhového prostředí Java nebo konfigurace paměti virtuálního prostředí znovu zkontrolovat dobu trvání pauz a využití haldy.

Rozdíly v GC logech mezi sestaveními OpenJDK pro Azul Zing a Zulu

S jiným interním benchmarkem jsme vytvořili několik dalších souborů protokolu, abychom demonstrovali rozdílné výsledky poskytované verzí 17 buildů OpenJDK pro Azul Zulu a Zing.

Výsledky se Zulu

Když generujeme protokol GC pomocí Zulu, což je sestavení OpenJDK, získáme v souboru protokolu stejná data jako většina ostatních distribucí. Následující grafy ukazují, že doba pauzy Garbage Collectoru zůstává pod 80 ms a využití haldy po garbage collectoru se udržuje kolem 1 GB pro dlouhodobě fungující objekty ve staré generaci a 2 GB v nové generaci pro dočasné objekty. V tomto konkrétním testovacím případě -Xmx4Gje celkem dostatečné a skutečně využité , ale obvykle by standardním doporučením bylo nastavit -Xmxna dvojnásobek pozorovaného využití haldy; zde by to bylo -Xmx6G.

Výsledky se Zingem

Stejný test jsme zopakovali se Zingem, alternativním běhovým prostředím Java založeným na OpenJDK, které má lepší JIT kompilátor (Falcon) a další garbage collector (C4, Continuously Concurrent Compacting Collector). Grafy vypadají mírně odlišně kvůli dodatečným informacím poskytovaným garbage collectorem C4 . U souběžných GC je často důležitější doba souběžného běhu GC, kdy je aktivní paralelně s aplikací. Nepozastaví to aplikaci, ale spotřebovává to trochu času CPU. 100 % neznamená, že to spotřebovává 100 % veškerého času CPU, protože základních 100 % je celkový počet vláken GC, což je méně než počet jader CPU, ale delšímu setrvání na 100 % by se mělo zabránit zvětšením velikosti haldy. Většinu tohoto času GC obvykle stráví zpracováním dočasných objektů. V tomto konkrétním testovacím případě byl výkon aplikace stále lepší se Zingem ve srovnání se Zulu se stejným -Xmx4G. Pro obecné dimenzování je důležitý také graf Live Set pro Zing, protože ukazuje počet živých objektů, tj. bez neodkazovaných objektů, známých také jako garbage (odpad).

Závěrem

Protokolování Garbage Collectoru poskytuje správné metriky pro kontrolu toho, kolik paměti aplikace potřebuje. Je zásadní, aby bylo možné aplikaci testovat ve stejném prostředí a s podobnou zátěží jako produkční systém. Možná by „testování v produkčním prostředí“ mohlo být nejjednodušším způsobem, jak toho dosáhnout.