はじめに
最近、業務で実際に調査・実施したことを記事としてまとめることで、自分自身の経験を棚卸しするという個人的な取り組みをしています。
今回は、その中でも過去に実施したJavaアプリケーションのヒープメモリのチューニングについて、当時どのような調査を行い、どのように判断して対策を実施したのかを整理してみたいと思います。
なお、本記事では実際の業務で発生した事象をもとにしていますが、業務上の機密情報や具体的なシステム情報については掲載していません。
また、当時の判断が必ずしも最適なチューニング方法だったとは限りません。
そのため、この記事は「この方法が正解」というものではなく、実際の現場でこのように調査・判断してみた、という一例として読んでいただければと思います。
もし、より良い調査方法やチューニング方法、今回の判断について「こうしたほうがよかったのでは?」といったご意見がありましたら、ぜひコメントでアドバイスいただけると幸いです。
1. 大量データのCSV出力でアプリケーションが停止した
過去に、Javaで構築されたオンラインシステムにおいて、大量データをCSV出力する機能を実行した際、アプリケーションのヒープメモリが逼迫し、アプリケーションが停止する事象がありました。
通常のオンライン処理では問題なく動作していましたが、大量のデータを検索・取得してCSVとして出力する処理では、通常時よりも多くのメモリを使用していました。
そこで、
大量データのCSV出力を実行した場合、JVMのヒープメモリはどの程度まで必要になるのか?
という観点から調査を行いました。
当時のヒープサイズは、おおよそ5GBで運用されていました。
一方、通常時のヒープ使用量は2GB前後で推移していたため、通常のオンライン処理だけを見ると、5GBでも十分余裕があるように見えます。
しかし、大量データを扱う処理では、通常時とは異なるメモリ使用量になる可能性があります。
今回は大量データ処理により、Out of memory となっておりました。
2.どのような対策が考えられるか
今回の問題に対して、最初から「ヒープメモリを増やす」と決めていたわけではありません。
大量データ処理によるメモリ使用量の増加に対して、いくつかの対策が考えられました。
例えば、以下のような方法です。
① Java側の処理を改善する
大量データを一度に処理するのではなく、データを分割して取得・処理する方法です。
さらに、分割したデータを並列に取得することで、処理時間の短縮も期待できます。
大量データ
↓
複数に分割
↓
┌────────┬────────┬────────┐
│ データ1 │ データ2 │ データ3 │
└────────┴────────┴────────┘
↓
並列取得・処理
↓
CSV出力
② JVMのヒープサイズを増やす
もう一つは、JVMに割り当てるヒープサイズそのものを増やす方法です。
既存:約5GB
↓
必要な容量を調査
↓
ヒープサイズを増量
ただし、この方法ではOS側のメモリにも余裕があることを確認する必要があります。
③ その他のアプリケーション・システム側の改善
例えば、
- データ取得方法の変更
- SQLの見直し(Oracle側の見直し)
- 画面側の条件の見直し(データ量の抑制)
なども考えられます。
今回は、これらの選択肢について、実際のユーザの使用状況や改修コストなどを踏まえて、どの方法が適切なのかを検討しました。
3. 過去のjstatログからJVMの状態を確認する
Java側の処理方式を変更する以外の方法について検討するため、次にJVMが実際にどの程度メモリを使用していたのかを調査しました。
jstatのログから、JVMのヒープ使用量などを確認し、通常時と大量データ処理時でどの程度差があるのかを調査しました。
また、ログをシェルスクリプトで加工して、必要なデータをCSV形式に加工しました。
※ログ自体を読み込ませるのは社内規則に違反していたため、スクリプトをAIに作成してもらい、CSV形式に加工しました。
過去のjstatログ
↓
シェルスクリプトで加工
↓
CSVファイル
↓
Excelへ取り込み
↓
グラフ化
↓
ヒープ使用量の推移を確認
これにより、1日単位のヒープ使用量の推移などをグラフで確認できるようにしました。
4. 調査結果:GCではなくヒープ使用量のピークに着目
jstatのログを確認して、特に注目したのが以下の2点です。
- ヒープ使用量
- GCの発生状況
今回の調査では、大量データ処理時にヒープ使用量が大きく増加していました。
一方で、GCが異常に頻発しているという状況ではありませんでした。
つまり、
ヒープ使用量が増加
↓
GCが大量発生
という状況ではなく、
大量データCSV出力
↓
一時的に大量のオブジェクトを生成
↓
ヒープ使用量が増加
↓
ピーク時の使用量が大きくなる
という状況でした。
通常時のヒープ使用量は、おおよそ2GB前後でしたが、
大量データをCSV出力する処理では、ピーク時に約8GB程度までヒープ使用量が増加し、oldまではいかないけど、ヒープメモリを食い潰して、Out Of Memoryになったということが考えられます。
5. 実際にどの程度のヒープメモリが必要だったのか
ログを分析した結果、通常時のヒープ使用量はおおよそ2GB前後で推移していました。
一方、大量データをCSV出力する処理を実行すると、ヒープ使用量が大きく増加し、ピーク時には約8GB程度まで増加することが確認できました。
通常時は約2GB程度で推移している一方、大量データ処理では約8GBまで増加していました。
既存のヒープサイズは約5GBだったため、大量データ処理時のピークを考えると、十分な余裕がないことが分かりました。
ここで重要なのは、
「通常時に2GBしか使っていないから、5GBで十分」
とは判断できないということです。
システムを運用するうえでは、通常時だけではなく、負荷が高くなる処理を実行した際のピーク値も確認する必要があると考えました。
6. OS側のメモリ使用量と改修コストも考慮してヒープサイズを変更
JVMのヒープサイズを増やす場合、JVMだけを見て判断することはできません。
OS側のメモリに余裕があるかという視点でも調査しました。
そこで、JVMのログだけではなく、サーバー側に残っていたsarのログも確認し、過去の一定期間におけるOS側のメモリ使用状況を確認しました。
また、キャッシュなども考慮しながら、実際にサーバー側のメモリにどの程度余裕があるのかを確認しました。
今回の調査では、OS側のメモリには十分な余裕があることを確認できました。
この結果から、今回はJava側の大幅な処理変更を行うよりも、ヒープサイズを増やして大量データ処理時のピークに対応する方針としました。
最終的に、ヒープサイズを、
約5GB
↓
約10GB
へ変更しました。
10GBという値は、単純に「8GB使ったから10GB」という理由だけで決めたわけではありません。
実際のピーク時使用量に加えて、
- サーバー全体のメモリ余力
- 大量データ処理の特性
- Java側の改修工数
- 機能要件
- 改修による影響
- 費用対効果
などを考慮して判断しました。
7. まとめ
今回の経験を通して、性能問題に対しては、単純に「メモリを増やす」のではなく、
- 実際のJVMの使用状況を確認する
- 通常時だけでなくピーク時の使用量を確認する
- GCが問題になっていないか確認する
- OS側のリソースにも余裕があるか確認する
- アプリケーション側の改善方法も検討する
- 機能要件や改修コストを考慮する
- 費用対効果を踏まえて対策を決定する
という流れで考えることが重要だと感じました。
今回はヒープサイズを増やすという対応を行いましたが、これはあくまで今回のシステム・処理・リソース状況を踏まえた判断です。
大量データ処理によるヒープ使用量増加に対しては、データ取得方法の変更やストリーム処理、ページングなど、アプリケーション側での改善が適切なケースもあると思います。
本記事をご覧いただいた方で、今回のようなケースに対して、
- JVM側で他に確認すべき項目
- ヒープサイズを決める際の考え方
- GCやメモリ使用量の分析方法
- 大量データ処理に対する別のアプローチ
など、「こうしたほうがよかったのでは?」という知見がありましたら、ぜひコメントで教えていただけると嬉しいです。