4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

業務でパフォーマンス改善に取り組む中で、JavaのJDBCが提供するsetFetchSizeメソッドを使う機会がありました。

「パフォーマンス改善」と聞くと速度向上をイメージしがちですが、今回はOOM(Out of Memory)を防ぐことが主な目的でした。その経緯も含めて、使い方や挙動について整理します。


setFetchSizeとは

setFetchSizeは、StatementPreparedStatementオブジェクトに対して呼び出すことができるメソッドです。
デフォルトの動作では、クエリ結果の全件をいっきにメモリに読み込もうとします。件数が多い場合、そのままだとメモリを使い果たしてOOMエラーが発生してしまうことがあります。そこでsetFetchSizeを設定することで、指定した行数ずつ小分けにしてデータを取得できます。


使い方

以下は基本的な使用例です。setFetchSize(1000)と指定することで、1回の通信で1000行ずつ取得するようになります。

FetchSizeExample.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class FetchSizeExample {
    public static void main(String[] args) throws SQLException {
        String url = "jdbc:postgresql://localhost:5432/mydb";
        String user = "user";
        String password = "password";

        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement pstmt = conn.prepareStatement(
                     "SELECT id, name FROM large_table")) {

            // 一度にDBから取得する行数を設定する
            pstmt.setFetchSize(1000);

            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    int id = rs.getInt("id");
                    String name = rs.getString("name");
                    System.out.println(id + ": " + name);
                }
            }
        }
    }
}

コードの構造はシンプルで、executeQuery()の前にsetFetchSizeを呼び出すだけです。ResultSetのループ処理はそのままで、意識せずに小分け取得できます。


必ずしもスピードが上がるわけではない

setFetchSize速度向上のためのメソッドではないという点が重要です。

イメージとしては下図のようになります。

fetch sizeを設定しない場合 fetch sizeを設定した場合
全件を一気にメモリに乗せる 指定件数ずつ小分けにして取得する
メモリ消費量が大きい メモリ消費量を抑えられる
通信回数は1回 通信回数が増える

fetch sizeを大きくしすぎるとメモリを圧迫し、逆に小さくしすぎると通信回数が増えてオーバーヘッドが大きくなります。適切な値はクエリや環境によって異なるので、実測しながら決めるのが現実的です。


実際に試してみた

業務で扱ったクエリは最大200万件ほどでした。fetch sizeを変えながら処理時間を計測してみましたが、検索速度自体はほとんど変わりませんでした

# fetch size 500
[SampleProcess] procedure done(140662 ms)

# fetch size 1000
[SampleProcess] procedure done(143877 ms)

# fetch size 2000
[SampleProcess] procedure done(148372 ms)

速度面での顕著な差は出ませんでしたが、setFetchSizeを設定することでメモリ使用量が抑えられ、OOMエラーが解消されました。今回の目的はそちらだったので、結果としては十分でした。


注意

PostgreSQLでは、autocommitを無効(false)にしないと動作しない。
参考:【PostgreSQL】FetchSizeを設定してもフェッチ処理が動作しない

まとめ

  • setFetchSizeは、1回の通信で取得する行数を制御するメソッド
  • 大量データを扱う際に、OOMを防ぐ手段として有効
  • 速度が必ず向上するわけではなく、fetch sizeが大きいほどメモリを消費する
  • 適切な値はクエリや環境によって異なるため、実測して決めるのがおすすめ

大量データを処理する際に、OOMエラーで悩んでいる方はぜひ試してみてください。

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?