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

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

Takaichi00
August 26, 2020

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

Takaichi00

August 26, 2020
Tweet

More Decks by Takaichi00

Other Decks in Technology

Transcript

  1. 検証では成功した Java のパッチが商用でコケた件
    #JJUG
    髙市 智章 (Tomoaki Takaichi)
    Aug, 26, 2020
    【オンライン】 JJUGナイトセミナー「おうちで!ビール片手にLT大会!」

    View Slide

  2. 自己紹介
    @Takaichi00
    tomoaki.takaichi.5
    ・髙市 智章(タカイチ トモアキ)
    ・Java / Node でのシステム開発
    ・CI / CD
    ・Container / k8s
    ・アジャイル開発実践
    共著: クリーンなコードへの
    SonarQube即効活用術
    http://u0u0.net/RSvx

    View Slide

  3. 本日お話すること
    ❏ 以前、検証では成功した Java のプログラムが商用で失敗
    するということを経験した
    ❏ Java 初心者の方や教育されている方に、プログラミング
    言語として Java を知っているだけでは痛い目にあってし
    まうという一例になれば
    ※ 内容は発表に向けてカスタマイズしています

    View Slide

  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` を取得 / 判定処理

    View Slide

  5. ❏ 実装したプログラムの概要は以下の通り
    問題の実装コードと実行コマンド
    public static void main(String[] args) throws SQLException {
    List 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 () … // 例外処理
    }

    View Slide

  6. ❏ 複数回実行されていた PreparedStatement や ResultSet は
    それぞれ close されていなかった
    ❏ SELECT 文で取得できた ResultSet の結果を List に一括代入
    ❏ 実行する際は起動オプションを指定せず、単純に「java -jar
    ~~.jar」と実行
    問題の実装コードと実行コマンド

    View Slide

  7. ❏ ローカル PC (メモリ 16GB) から、検証用のデータが入っ
    た検証用 DB へ向けてこのパッチを起動したところ、問題
    なく実行が完了した (データ量は数十件程度)
    検証の構成では成功
    検証環境
    成功
    メモリ: 16GB

    View Slide

  8. ❏ 商用環境では VM 上 (メモリ 1GB) で作成したパッチを実
    行した。しかし処理が全然完了しなかった
    ❏ 商用のデータ量は検証環境よりは多いが、たかだか1万件
    未満程度のもの
    商用環境では失敗
    商用環境
    失敗
    メモリ: 1GB

    View Slide

  9. ❏ 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

    View Slide

  10. ❏ JVM にはエルゴノミクスというプロセスがあり、マシンの
    スペックに応じてヒープサイズが自動決定される
    ❏ デフォルトでは以下の設定となっている
    ヒープオプションを追加する
    初期ヒープ・サイズ 物理メモリーの 1/64 (最大1GB)
    最大ヒープ・サイズ 物理メモリの 1/4 (最大1GB)
    ❏ jar 実行時に、-Xms512M -Xmx512M のようにヒープサ
    イズを設定することで、時間はかかったが処理を完了させ
    ることができた

    View Slide

  11. ⇒ 検証するため、再度似たようなプログラムを作成して GC
    Viewer を用いて解析をしてみる
    ❏ 仮説1: PreparedStatement / ResultSet の close 忘れ
    ❏ 仮説2: 最初に SELECT で一気にテーブルの値を List に格
    納してしまったことでヒープサイズを圧迫
    そもそも実装コードが悪い

    View Slide

  12. ❏ 似たようなプログラムをヒープサイズを指定し、GC ログを
    出力するようにして実行した結果、処理が進むに連れ FullGC
    が発生して最終的に OOME が発生した (データ量約1万件)
    問題のプログラムを解析する
    $ java -Xlog:gc:./gc.log \
    -Xlog:gc* \
    -Xms20M -Xmx20M -jar hoge.jar

    View Slide

  13. ❏ PreparedStatement / ResultSet を close するよう処理
    を修正したところ FullGC は発生せず適切にメモリが開放
    されて処理が継続できた (データ量約1万件)
    仮説1: PreparedStatement / ResultSet の close 忘れ

    View Slide

  14. ❏ close 処理を実施し、データ件数を1万件から2万件に変更し
    て実行したが、初期のヒープサイズがやや増加したのみ。
    よって今回ではクリティカルな問題ではなかった。
    仮説2: List に全件のデータを格納してしまった

    View Slide

  15. まとめ
    ❏ ヒープサイズは指定するようにする (エルゴノミクス)
    ❏ close 処理を怠らない
    ❏ DB からデータを取得して Java メモリ上に大量のデータ
    を格納するような処理はしない
    ❏ GC ログや GC Viewer の利用方法を知っておくと便利
    ❏ できるなら商用環境のデータ量でテストを実施する

    View Slide

  16. まとめ
    ❏ 今回ではプログラミング言語として Java を知っているだ
    けでは痛い目にあうという一例を紹介した
    ❏ Java をこれから始めようとされている方 / Java を教えて
    いる方などの参考になれば幸いです

    View Slide

  17. ❏ エルゴノミクス (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
    参考文献

    View Slide

  18. ご清聴ありがとうございました

    View Slide