Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【JUGナイトセミナー】検証では成功した Java のパッチが商用でコケた件

Avatar for Takaichi00 Takaichi00
August 26, 2020

【JUGナイトセミナー】検証では成功した Java のパッチが商用でコケた件

Avatar for Takaichi00

Takaichi00

August 26, 2020
Tweet

More Decks by Takaichi00

Other Decks in Technology

Transcript

  1. 検証では成功した Java のパッチが商用でコケた件 #JJUG 髙市 智章 (Tomoaki Takaichi) Aug, 26,

    2020 【オンライン】 JJUGナイトセミナー「おうちで!ビール片手にLT大会!」
  2. 自己紹介 @Takaichi00 tomoaki.takaichi.5 ・髙市 智章(タカイチ トモアキ) ・Java / Node でのシステム開発

    ・CI / CD ・Container / k8s ・アジャイル開発実践 共著: クリーンなコードへの SonarQube即効活用術 http://u0u0.net/RSvx
  3. 本日お話すること ❏ 以前、検証では成功した Java のプログラムが商用で失敗 するということを経験した ❏ Java 初心者の方や教育されている方に、プログラミング 言語として

    Java を知っているだけでは痛い目にあってし まうという一例になれば ※ 内容は発表に向けてカスタマイズしています
  4. ❏ 以下のような java のプログラムを作成したかった やりたかったこと id name address 1 aaa

    NULL 2 bbb NULL ... … ... name register_ event aaa 1 aaa 2 bbb 1 .jar ① `address` が NULL の一覧取得 ② `name` に対応する `register_event` を取得 / 判定処理 insert.sql ④ TABLE_A に `name` に対 応する `address` を insert する SQL ファイルを作成 TABLE_A TABLE_B register_ event address 1 123 2 456 ... ... TABLE_C ③ `register_event` に対応する `address` を取得 / 判定処理
  5. ❏ 実装したプログラムの概要は以下の通り 問題の実装コードと実行コマンド public static void main(String[] args) throws SQLException

    { List<UpdateAttribute> updateAttributes = null; try (Connection con = DriverManager.getConnection(connectionUrl, "user", "pass")) { updateAttributes = new ArrayList<>(); String sql1 = "SELECT id,name,address FROM TABLE_A WHERE address is NULL"; PreparedStatement stmt1 = con.prepareStatement(sql1); ResultSet rs1 = stmt1.executeQuery(sql1); // SELECT したすべてのデータを List に格納 while (rs1.next()) { updateAttributes.add(new UpdateAttribute(rs1.getInt("id"), rs1.getString("name"), null)); } for (UpdateAttribute updateAttribute : updateAttributes) { // List をループして更に別テーブルに SELECT 文を実行 while (rs2.next()) { // なんらかの業務ロジック … 省略 } public static void main(String[] args) throws SQLException { … 省略 for (UpdateAttribute updateAttribute : updateAttributes) { // List をループして更に別テーブルに SELECT 文を実行 while (rs2.next()) { // なんらかの業務ロジック ... String sql3 = "SELECT name,address FROM TABLE_B WHERE name='" + <ロジックから取得できた値> + "'"; while (rs3.next()) { // なんらかの業務ロジック ... updateAttribute.setAddress(rs3.getString("address")); } } } // SQL ファイル出力処理 ... } catch () … // 例外処理 }
  6. ❏ 複数回実行されていた PreparedStatement や ResultSet は それぞれ close されていなかった ❏

    SELECT 文で取得できた ResultSet の結果を List に一括代入 ❏ 実行する際は起動オプションを指定せず、単純に「java -jar ~~.jar」と実行 問題の実装コードと実行コマンド
  7. ❏ java -jar コマンド実行時の引数に -Xlog:gc* を追加して GC ログの詳細を表示してみる ❏ すると

    FullGC が多発していることがわかった GC ログを出してみる $ java -Xlog:gc* -jar hoge.jar ... [81.318s][info][gc ] GC(29) Pause Full (G1 Evacuation Pause) ... [81.318s][info][gc,cpu ] GC(29) User=0.05s Sys=0.00s Real=0.02s
  8. ❏ JVM にはエルゴノミクスというプロセスがあり、マシンの スペックに応じてヒープサイズが自動決定される ❏ デフォルトでは以下の設定となっている ヒープオプションを追加する 初期ヒープ・サイズ 物理メモリーの 1/64

    (最大1GB) 最大ヒープ・サイズ 物理メモリの 1/4 (最大1GB) ❏ jar 実行時に、-Xms512M -Xmx512M のようにヒープサ イズを設定することで、時間はかかったが処理を完了させ ることができた
  9. ⇒ 検証するため、再度似たようなプログラムを作成して GC Viewer を用いて解析をしてみる ❏ 仮説1: PreparedStatement / ResultSet

    の close 忘れ ❏ 仮説2: 最初に SELECT で一気にテーブルの値を List に格 納してしまったことでヒープサイズを圧迫 そもそも実装コードが悪い
  10. ❏ PreparedStatement / ResultSet を close するよう処理 を修正したところ FullGC は発生せず適切にメモリが開放

    されて処理が継続できた (データ量約1万件) 仮説1: PreparedStatement / ResultSet の close 忘れ
  11. まとめ ❏ ヒープサイズは指定するようにする (エルゴノミクス) ❏ close 処理を怠らない ❏ DB からデータを取得して

    Java メモリ上に大量のデータ を格納するような処理はしない ❏ GC ログや GC Viewer の利用方法を知っておくと便利 ❏ できるなら商用環境のデータ量でテストを実施する
  12. ❏ エルゴノミクス (Oracle 公式) ❏ Java開発の性能改善! その2 GCログの解析とHeapの設定 ❏ JavaのGCの仕組みを整理する

    ❏ JavaのGCに関するオプションについてまとめてみた。 ❏ JVM入門 -Javaプログラムが動く仕組み- ❏ Statement の解放漏れには気をつけよう ❏ MySQL Connector/J (JDBC ドライバ)の罠まとめ ❏ MySQL+Connector/Jを使って、大量データのSELECT⇒INSERTした時の挙動を確認する ❏ JDBC setFetchSize() ではまった話 ❏ JDBC経由で100万件取得・追加してみた ❏ Java いまふたたびのJDBC 参考文献