100ä¸ä»¶ãããã®ã¬ã³ã¼ããæ±ã£ããOOMEãåºã話ã
è¦ç´
æè¡çãªè©±ã ãæãã¦ãã¨ããæ¹ã®ããã«å
ã«çµè«ã ãæ¸ãã¦ããã¾ãã¨ãPostgreSQLã¯ã¯ã¨ãªãå®è¡ããæç¹ã§å
¨ã¬ã³ã¼ãã®æ
å ±ãä¸æ°ã«èªãã§ãã¦ãã¼ããåãã¦ãã¾ãå ´åããããã¨ãããã¨è©±ã§ãã
ãã¨ãã°ãResultSet#nextã¡ã½ããã使ããªããå¦çãåããããªã³ã¼ããæ¸ãã¦ãå°ãªããã¼ãã§ãå¦çã§ããããã«ããã®ã¯å¸¸å¥æ段ã ã¨æãã¾ãããããããã³ã¼ããæ¸ãã¦ãã¦ãä¸æ°ã«ãã¼ããæ¶è²»ãã¦ãã¾ããã¨ãããã®ã§ãã詳ããã¯ãã®ããã¥ã¡ã³ããè¦ã¦ãã ããã
https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor
ãã¨ã®çºç«¯
ã¡ãã£ã¨ä»äºã§Java + jOOQ + PostgreSQLã§ãDBã®ãã¼ã¿ãéè¨ãããããªãããå¦çãæ¸ãã¦ã¾ãã¦ããã¡ãã俺æ§ã®æ¸ããã³ã¼ãã«ãã°ãªãã¦ããã¯ãããªãããã§ããã念ã®ããã«æ§è½è©¦é¨ããã¦ãããã¨æãã100ä¸ã¬ã³ã¼ãã®ãµã³ãã«ãã¼ã¿ãæå ¥ãã¦ãããã®ãã¹ããèµ°ãããããOutOfMemoryErrorãèµ·ãããã§ããã
OutOfMemoryErrorãã¾ããã俺ãï¼
æ¸ç°å¥æ´¥åã®åå¹ã£ã½ãã»ãªããé ããããã¤ã¤ãä½åº¦ã試ãã¦ã¿ã¦ããã¯ãOutOfMemoryErrorãèµ·ãã¦ãã¾ãã¾ãããã¼ãã2GBã¨ãã«ããã°å®è¡ã§ãã¾ãããã¬ã³ã¼ãæ°ã50ä¸ã¨ãã«æ¸ããã°åé¡ãªãåãã®ã§ãããããããä½ä¸ã¬ã³ã¼ãæ±ããããã¼ãã大é確ä¿ãããããªã³ã¼ããªãã¦æ¸ãã¦ããªãã¤ããã ã£ãã®ã§ãçé¢ç®ã«åé¡ã確èªãããã¨ã«ãã¾ããã
ä½ããã¼ããåãã¦ããã£ã¦è¨ããã ãï¼
åãç´ äººã§ã¯ãªãã§ããããå³åº§ã«èµ·åå¼æ°ã« -XX:+HeapDumpOnOutOfMemoryError
ãã¤ãã¦ãã¹ããåå®è¡ãããã¼ããã³ããåå¾ãã¾ããããã¼ã£ã¨ãåå¾ãããã¼ããã³ãã£ã¦ä½ã§èªãã®ãè¯ããã§ããã£ãï¼âç´ äººï¼
Java Mission Controlã§èªããã¯ããã¨æã£ã¦ã°ã°ã£ãããä»ã¯ååãå¤ãã£ã¦JDK Mission Controlã«ãªã£ã¦ããã®ã§ããï¼âç´ äººï¼ç´°ãããã¨ã¯æ°ã«ããããã¦ã³ãã¼ããã¦å®è¡ãã¦ãFile - Open Fileãããã¼ããã³ãã®hprofãã¡ã¤ã«ãèªã¿è¾¼ã¿ã¾ããã
âãããªæãã§ãã¼ããå ãããªãã¸ã§ã¯ãã®å¾åãåããã¾ãã
ä¸çªä¸ã«ãã byte[][]
ããã¼ãã®7å²ãå ãã¦ãã¦æããã«æªããã®ã§ãããã«ã¯ãªãã¯ãã¦è©³ç´°ãè¦ã¾ãã
âãããªæãã§ã©ãããåç
§ããã¦ããããåããã¾ãã
ã©ããã org.postgresql.core.Tuple.data
ããåç
§ããã¦ãããåããããã®ããã±ã¼ã¸åãããã¯ãDBããèªã¿è¾¼ãã 100ä¸ã¬ã³ã¼ãããã¼ããåãã¦ããã§ããããã¨ãã»ã¼ç¢ºå®ãã¾ããã
åé¡ã¯ã©ãã§èµ·ãã¦ããã ï¼
ä»åº¦ã¯ã¢ããªã±ã¼ã·ã§ã³ã®æ¹ããåé¡ã追跡ãã¾ãããããã¬ã§å°ã追ã£ã¦ã¿ãã¨ãåé¡ãèµ·ãã¦ããç®æãããã«åããã¾ããã
try (Stream<EMP> stream = dslContext.selectFrom(EMP).stream()) { stream.forEach(e -> { // éè¨å¦ç }) }
dslContextã¨ããã®ã¯jOOQã®DslContextã¯ã©ã¹ã®ã¤ã³ã¹ã¿ã³ã¹ã§ãããã®å¦ç㧠select * from EMP
ãå®è¡ããã¦ããã®çµæãStreamã¨ãã¦åå¾ãã¦ããããã§ãã
ãã ãã㧠// éè¨å¦ç
ã¨æ¸ããé¨åã«ã¯å
¥ã£ã¦ããããforEachãå¼ã³åºããæç¹ã§OutOfMemoryErrorãçºçãã¦ãããã¨ãåããã¾ããã
ãªãã¦ãã£ããjOOQã®streamã¡ã½ããã¯é次å¦çãããããããªãã¦å ¨ä»¶ã¾ã¨ãã¦åã£ã¦ããã¤ããªã®ããï¼ ã©ããªå¹¼ç¨ãªå¦çãæ¸ããããããªããã ãï¼ jOOQã ããã£ã¦ã¸ã§ã¼ã¯ããæ¸ã¾ãªããï¼ ã¨æãã«éããªããããã¤ã¼ãããã¾ããã
jooqã®streamã¡ã½ããã大éã¬ã³ã¼ããæ¤ç´¢ãããã¨ã§foreachå¦çãå§ãããã¨ããã¨ããã¼ããä¸æ°ã«æ¶è²»ãã¡ããã£ã½ããã¡ãã£ã¨ãã¤èªãã§ãããªãã®ï¼
— è°·æ¬ å¿ / CERO-METAL (@cero_t) 2020å¹´8æ11æ¥
å¾ã«ããã¯å®å ¨ã«ã¨ã°ã£ã¡ãã ã£ããã¨ãåãã£ãã®ã§ãããã·ã³ã¬ãã¼ã©ä½¿ãããï¼ã·ã³ã¬ãã¼ã©ã£ã¦ä½ã§ããï¼ï¼ã¨ãjOOQã®å ¬å¼ã¢ã«ã¦ã³ãæ§ããããªãã©ã¤ããã ãã¦ãã¾ãã大å¤æ縮ãã¦ããã¾ããç³ã訳ããã¾ããã§ããã
fetchSize()ãå¹ããªããã ã¨ï¼ï¼
å
¬å¼ã¢ã«ã¦ã³ãæ§ããstreamã¡ã½ããã¯Lazyãã¼ãã£ã³ã°ã ããå ´åã«ãã£ã¦ã¯ fetchSize
ã¡ã½ããã使ãã¨è¯ãããã¨ãã£ãããã®ã§ãã¾ãããã試ãã¦ã¿ã¾ããã
try (Stream<EMP> stream = dslContext.selectFrom(EMP).fetchSize(1000).stream()) { stream.forEach(e -> { // éè¨å¦ç }) }
ãããç¶æ³ã¯å¤ããã¾ãããfetchSizeã1ã«ãã¦ãOOMEãçºçãã¦ãã¾ãã¾ããã
次ã«ã·ã³ã¬ãã¼ã©ä½¿ãããããæãã¦ããã£ã fetchLazy
ã¡ã½ããã試ãã¦ã¿ã¾ããã
var cursor = dslContext.selectFrom(BUDGET_REQUEST).fetchSize(1).fetchLazy(); while (cursor.hasNext()) { // éè¨å¦ç }
ããã§ãç¶æ³ã¯å¤ããã¾ããã ãã®è¾ºãã§ãåé¡ã¯jOOQã§ã¯ãªãJDBCãã©ã¤ãå¨ããªã®ã§ã¯ãªããã¨èãå§ããjOOQãå¤ãã¦æ¤è¨¼ãããã¨ã«ãã¾ããã
çJDBCã§åé¡ãåç¾ãã¡ã¾ã£ãï¼
åãO/Rãããã¼ãèªä½ããã¿ã¤ãã®äººéã§ããããçJDBCã§å¦çãæ¸ãã®ããæã®ç©ã§ãããããªã·ã³ãã«ãªã³ã¼ããæ¸ãã¦ã¿ã¾ããã
try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement("select * from EMP"); ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { // éè¨å¦ç } }
ããã¨ããããã©ã£ãããããããã§ãOOMEã¯æ¶ãã¾ãããããã§jOOQã¯é¢ä¿ãªããPostgreSQLã¨JDBCãã©ã¤ãå¨ãã®åé¡ã ããã¨çµãè¾¼ãã¾ããã
ããã¥ã¡ã³ãããã£ããï¼
å ã«æ¸ããã·ã³ãã«ãªã³ã¼ãã§ããOOMEãçºçãã¦ãã¾ãç¶æ³ã«ãªã£ã¦ããã¨ãç§ãä½ãåéããã¦ããã®ããç¥ããªããªããªãã¦ä¸äººç§°ã¾ã§è¬èã«ãªã£ã¦ãã¾ããä½ãPostgreSQLã§ã¯ç¹å¥ãªãä½æ³ãããã®ã ãããã¨æãããpostgresql jdbc lazy loadingãã§ã°ã°ã£ã¦ã¿ãã¨ãããã¿ããªå¤§å¥½ãStack Overflowã«ãã©ãçãã¾ããã
https://stackoverflow.com/questions/984073/java-jdbc-lazy-loaded-resultset
Another example: here's the documentation for the PostgreSQL behavior. If auto-commit is turned on, then the ResultSet will fetch all the rows at once, but if it's off, then you can use setFetchSize() as expected.
ï¼ï¼ï¼ï¼
ResultSet will fetch all the rows at once
ã¾ãã«ä»åèµ·ãã¦ããç¾è±¡ãã®ãã®ã§ãããªã³ã¯ããã¦ããããã¥ã¡ã³ããèªãã§ã¿ãã¨ãæ確ã«æ¸ããã¦ãã¾ããã
https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor
By default the driver collects all the results for the query at once.
ã¯ããï¼ï¼ ãªãã§ãããªå®è£
ã«ãã¨ãããï¼ï¼ï¼
åé¡ã®åå ãåãã£ãåã³ãããããã®çä¸å°½ãªå®è£
ã«å¯¾ããæãã®æ¹ã大ããã£ãã§ãï¼âçä¸å°½ãªæãï¼
ããã§è§£æ±ºã ãª
ããã¥ã¡ã³ããããé¢ä¿ãããããªé¨åãæç²ãã¾ãã¨ã
Cursor based ResultSets cannot be used in all situations. There a number of restrictions which will make the driver silently fall back to fetching the whole ResultSet at once.
- The Connection must not be in autocommit mode. The backend closes cursors at the end of transactions, so in autocommit mode the backend will have closed the cursor before anything can be fetched from it.
ã©ãããautoCommitãtrueã«ãªã£ã¦ããå ´åã¯ãã«ã¼ã½ã«ã使ããªãããã§ãããããªãã㧠Connection#setAutoCommit(false)
ããã¨ãããããjOOQã® fetchSize()
ã¡ã½ãããå¹ãããã«ãªã£ã¦ãOOMEãçºçããªããªãã¾ããï¼
ãããããªãautoCommitãtrueã«ãªã£ã¦ãããã¨è¨ãã¨ã
- ãããæ¬ä½ã§ã¯ setAutoCommit(false) ãã¦ããå¦çãã¦ãããããã¹ãã§ã¯ãã¸ãã¯ã®ä¸å¿é¨åã ãç´æ¥å¼ã³åºãã¦ãã
- ãã¹ãã§ã¯é¢åã ã£ãã®ã§autoCommitãããã©ã«ãå¤ï¼trueï¼ã®ã¾ã¾ã«ãã¦ãã
ã¨ãããé常ã«éã®æªãç¶æ³ã«ãªã£ã¦ãã¾ããã ããã ã£ã¦ãautoCommitã®true/falseã§ãããªã«æåãå¤ãããªãã¦æ³åããã¦ãªãã£ããã ããã»ã»ã»ã
ããã«ãSpring Boot + jOOQ + PostgreSQLã§æ¸ãã¦ããWebã¢ããªã±ã¼ã·ã§ã³ã«ããã¦ãautoCommitãtrueã®ã¾ã¾ã«ãªã£ã¦ãã¾ããããã©ã³ã¶ã¯ã·ã§ã³ãå¹ãã¦ããéãã¯autoCommitãtrueã®ã¾ã¾ã«ãªããã¨ã¯ããå¾ãªãã®ã§ãããå®ã¯Spring Boot + jOOQã®çµã¿åããã§ã¯ããã¨ã spring-boot-starter-jooq
ã使ã£ã¦ãã¦ãããã®ã¾ã¾ã§ã¯ @Transactional ã¢ããã¼ã·ã§ã³ãå¹ããªãç¶æ
ã«ãªã£ã¦ããã®ã§ããã
https://www.baeldung.com/spring-boot-support-for-jooq
ããã¾ãããSpring Bootã¨ã®é£æºãå ¬å¼ã«è¬³ããã¦ãããããªã©ã¤ãã©ãªã§ã@Transcational ãå¹ããªãã ãªãã¦æ³åããã¦ãªãã£ããã ããã»ã»ã»ã
ã¾ã¨ãããï¼
ãããªããã§ãä»åã®ã¾ã¨ãã§ãã
- PostgreSQLã¯fetchSizeãæå®ããªãã¨ãResultSetã«æ¤ç´¢çµæãå ¨ã¦å ¥ã
- PostgreSQLã¯autoCommitãtrueã ã¨ãfetchSizeãæå®ãã¦ãå¹æããªã
- ãããããµãã£ã¦autoCommitãtrueã®ã¾ã¾ã«ãã¦ãã®ãããã¹ã¦ã®ãã£ããã ã£ãããããã
- ã§ããã®ãããã§Spring Bootã¨jOOQã®ãã©ã³ã¶ã¯ã·ã§ã³ãµãã¼ãã®èª²é¡ãè¦ã¤ãã£ã¦è¯ãã£ããï¼
ã¨ããæãã§ãåå ã®è¿½åã¨å¯¾å¿ã«ä¸¸ä¸æ¥ãããã¾ããããè¯ãçµé¨ã«ãªãã¾ããã