はじめに
業務でパフォーマンス改善に取り組む中で、JavaのJDBCが提供するsetFetchSizeメソッドを使う機会がありました。
「パフォーマンス改善」と聞くと速度向上をイメージしがちですが、今回はOOM(Out of Memory)を防ぐことが主な目的でした。その経緯も含めて、使い方や挙動について整理します。
setFetchSizeとは
setFetchSizeは、StatementやPreparedStatementオブジェクトに対して呼び出すことができるメソッドです。
デフォルトの動作では、クエリ結果の全件をいっきにメモリに読み込もうとします。件数が多い場合、そのままだとメモリを使い果たしてOOMエラーが発生してしまうことがあります。そこでsetFetchSizeを設定することで、指定した行数ずつ小分けにしてデータを取得できます。
使い方
以下は基本的な使用例です。setFetchSize(1000)と指定することで、1回の通信で1000行ずつ取得するようになります。
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エラーで悩んでいる方はぜひ試してみてください。