ログ日記

作業ログと日記とメモ

S2JDBCのiterate()で OutOfMemoryError

S2JDBCを使って DBのデータ数十万件をまとめてDLしようとしたらエラーになった。
getResultList()じゃなくてiterate()使ったら結果をまとめて保持しなくなるからメモリを使わなくなるんじゃないの?と思ったけどダメ。
WicketのResourceStream系がキャッシュしてるのか自分で書いたオブジェクトを書き出す処理がミスってたのか何か使い方が間違ってたのか分からずハマった。
Eclipse Memory Analyzer でダンプを見たら、どこかでデータを全件保持している模様。



2/29 ここから追記
S2JDBC+PostgreSQLだとs2jdbc.diconのfetchSizeプロパティの設定+トランザクションをきちんと開始する、で解決しそう。(とりあえずローカル環境でエラーの再現 → エラー修正の確認まではできた)

PostgreSQL: http://old.postgresql.jp/wg/jpugdoc/jdbc/jdbc-8.3dev-600/query.html#query-with-cursor http://ml.postgresql.jp/pipermail/pgsql-jp/2005-May/010531.html
MySQL: http://dev.mysql.com/doc/refman/5.1/ja/connector-j-reference-implementation-notes.html


更新のときだけトランザクションを真面目にやって、表示系は無しでいいやと思って前に変えたのだった… *1



見ているサイトは情報が古すぎ or 誤読 or 間違いでしたorz
大昔のサーバー(PostgreSQL7.3とか?)を使っているのでない限り、昨日書いた記述は間違い。各々のDB&バージョンごとに注意点があるようなのでそれぞれチェックする必要があるっぽい。日記コメント、ブクマコメントの指摘助かりました。


追記おわり 以下間違い

おそらく、原因はこれ。

S2JDBC を使わずに直接 JDBC API を呼び出してみても,
PreparedStatement#executeQuery() の呼び出し中に
OutOfMemoryError になってしまい,ResultSet が
返ってきません.
このことから,S2JDBC は関係ないように思われます.

これが MySQL の問題なのか Connector/J の設定なのか
よく分かりませんが,まずは JDBC 直接呼び出しで
結果を取得する方法を調べてみてはいかがでしょうか?

http://ml.seasar.org/archives/seasar-user/2008-July/015066.html

基礎となるサーバーがスクロール可能なカーソルをサポートしていないの
で、スクロール可能性は別のレイヤーでOracle JDBC によって実装する必要
があります。
この機能は、スクロール可能な結果セットの行をクライアント側のメモリ
ー・キャッシュに格納することにより、実現されていることに注意してくだ
さい。

重要:スクロール可能な結果セットの行はすべて、クライアント側の
キャッシュに格納されます。そのため、結果セットに多くの行、多くの列
または非常に大きな列が含まれていると、クライアント側のJava Virtual
Machine に障害が発生する可能性があります。大きな結果セットにはスク
ロール可能性を指定しないでください。

Oracle サーバーのスクロール可能なカーソル、すなわちサーバー側のキャッ
シュは、将来のOracle リリースでサポートされます。

http://www.oracle.co.jp/forum/thread.jspa?messageID=11006931

ResultSet


SELECTを実行した結果は、ResultSetで受け取る。

next()を使って結果の各レコードを処理する。レコード内の項目の値は、(データ型に応じて)getString()等を使って取得する。

PostgreSQLJDBCが返すResultSetでは、複数レコードを取得するSQLの場合は 全結果を一度に取り込もうとするらしい。
レコード数が無茶苦茶多い(何十万件とか何百万件とかだ)と、OutOfMemoryExceptionが発生する。

http://www.ne.jp/asahi/hishidama/home/tech/postgres/jdbc.html

全体的にJDBCはそういう設計?


つまり、いくらS2JDBCやアプリでメモリを使わないようにしてもJDBCのResultSetが全件溜め込んでいるのでアウト。
仕方がないので全件取得するところをlimitとoffsetで分割したものを繋げてストリームに書き出すことにした。
なんか微妙だ。