[Spring Batch] ItemReader cursor vs paging

1. ๊ฐœ์š”

์ด์ „ ํ”„๋กœ์ ํŠธ์—์„œ ์ผ๊ด„์ ์œผ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ–ˆ์—ˆ๋Š”๋ฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ์œ„ํ•ด ์Šคํ”„๋ง ๋ฐฐ์น˜๋ฅผ ํ•™์Šตํ–ˆ์—ˆ๋‹ค. ๊ทธ๋•Œ๋„ ItemReader์˜ ์ข…๋ฅ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์žˆ๋Š” ๊ฑด ์•Œ์•˜์ง€๋งŒ ์šฐ์„  ์•„๋ฌด๊ฑฐ๋‚˜ ์„ ํƒํ•ด ๊ตฌํ˜„ํ–ˆ์—ˆ๋Š”๋ฐ ์ด๋ฒˆ ๊ธฐํšŒ์— ์ œ๋Œ€๋กœ ์•Œ์•„๋ณด๋ ค๊ณ  ํ•œ๋‹ค. 

 

 

 

2. Spring Batch Chunk?

Spring Batch์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ๋ฐ”๋กœ Tasklet ๊ธฐ๋ฐ˜๊ณผ Chunk ๊ธฐ๋ฐ˜์ธ๋ฐ ItemReader์€ Chunk ๋ฐฉ์‹์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์ด๋‹ค. ItemReader์— ํ•™์Šตํ•˜๊ธฐ ์•ž์„œ Chunk์— ๋Œ€ํ•ด ๊ฐ€๋ณ๊ฒŒ ์•Œ์•„๋ณด์ž. 

 

Spring Batch์—์„œ์˜ Chunk๋ž€ ๋ฐ์ดํ„ฐ ๋ฉ์–ด๋ฆฌ๋กœ ์ž‘์—… ํ•  ๋•Œ ๊ฐ ์ปค๋ฐ‹ ์‚ฌ์ด์— ์ฒ˜๋ฆฌ๋˜๋Š” row ์ˆ˜๋ฅผ ์–˜๊ธฐํ•œ๋‹ค.

 

์ฆ‰, Chunk ์ง€ํ–ฅ ์ฒ˜๋ฆฌ๋ž€ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด Chunk๋ผ๋Š” ๋ฉ์–ด๋ฆฌ๋ฅผ ๋งŒ๋“  ๋’ค, Chunk ๋‹จ์œ„๋กœ ํŠธ๋žœ์žญ์…˜์„ ๋‹ค๋ฃจ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

์—ฌ๊ธฐ์„œ ํŠธ๋žœ์žญ์…˜์ด๋ผ๋Š”๊ฒŒ ์ค‘์š”ํ•œ๋ฐ, Chunk ๋‹จ์œ„๋กœ ํŠธ๋žœ์žญ์…˜์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ์—” ํ•ด๋‹น Chunk ๋งŒํผ๋งŒ ๋กค๋ฐฑ์ด ๋˜๊ณ , ์ด์ „์— ์ปค๋ฐ‹๋œ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊นŒ์ง€๋Š” ๋ฐ˜์˜์ด ๋œ๋‹ค.

 

Chunk ์ง€ํ–ฅ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฒฐ๊ตญ Chunk ๋‹จ์œ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๋ฆผ์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

 

์œ„ ์ž‘์—…์—์„œ read() ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒŒ ItemReader ์ด๋‹ค. 

 

Spring Batch์˜ ItemReader๋Š” ๊ผญ DB์˜ ๋ฐ์ดํ„ฐ๋งŒ์ด ์•„๋‹Œ File, XML, Json๋“ฑ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ์˜ ์ž…๋ ฅ์œผ๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

 

3. Spring Batch์˜ ๋Œ€ํ‘œ์ ์ธ Reader

Spring Batch๋Š” ๋Œ€ํ‘œ์ ์œผ๋กœ 2๊ฐœ์˜ Reader ํƒ€์ž…์„ ์ง€์›ํ•œ๋‹ค.

 

 

 

Cursor ํ˜•์‹ 

Database์™€ ์ปค๋„ฅ์…˜์„ ๋งบ์€ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ Streaming ํ•ด์„œ ๋ณด๋‚ธ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Cursor๋ฅผ ํ•œ ์นธ์”ฉ ์˜ฎ๊ธฐ๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. 

 

ํ•˜๋‚˜์˜ Connection์œผ๋กœ Batch๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— Batch๊ฐ€ ๋๋‚˜๊ธฐ ์ „์— Database์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ Connection์ด ๋จผ์ € ๋Š์–ด์งˆ ์ˆ˜ ์žˆ์–ด์„œ ์œ„ํ—˜ํ•˜๋‹ค. ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋งŽ์Šต๋‹ˆ๋‹ค. 

 

Paging 

๋ฐ˜๋ฉด Paging์€ ์ข€ ๋” ๋งŽ์€ ์ž‘์—…์ด ํ•„์š”๋กœ ํ•œ๋‹ค.

Paging ๊ฐœ๋…์€ ํŽ˜์ด์ง€๋ผ๋Š” Chunk๋กœ Database์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ํ•œ๋‹ค. ์ฆ‰, ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•ด์˜ค๋Š” ๋ฐฉ์‹์ธ๋ฐ, ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ DB Connection์„ ๋งบ๊ธฐ ๋•Œ๋ฌธ์— ์—ฐ๊ฒฐ ์‹œ๊ฐ„์ด ์ƒ๋Œ€์ ์œผ๋กœ ์ ์€ ํŽธ์ด๋‹ค. ๋˜ํ•œ ํŽ˜์ด์ง• ๋‹จ์œ„์˜ ๊ฒฐ๊ณผ๋งŒ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋Œ€์ ์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ ๋‹ค.

 

์ฃผ์˜์‚ฌํ•ญ์œผ๋กœ Paging์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ์ •๋ ฌ์ด ๋˜์–ด์žˆ์–ด์•ผ ํ•œ๋‹ค.

 

๊ฒŒ์‹œํŒ์˜ ํŽ˜์ด์ง•์„ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ํŽ˜์ด์ง•์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ฐ ์ฟผ๋ฆฌ์— ์‹œ์ž‘ ํ–‰ ๋ฒˆํ˜ธ (offset) ์™€ ํŽ˜์ด์ง€์—์„œ ๋ฐ˜ํ™˜ ํ•  ํ–‰ ์ˆ˜ (limit)๋ฅผ ์ง€์ •ํ•ด์•ผํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. Spring Batch์—์„œ๋Š” offset๊ณผ limit์„ PageSize์— ๋งž๊ฒŒ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด ์ค€๋‹ค.

 

๋‹ค๋งŒ ๊ฐ ์ฟผ๋ฆฌ๋Š” ๊ฐœ๋ณ„์ ์œผ๋กœ ์‹คํ–‰ํ•œ๋‹ค๋Š” ์ ์„ ์œ ์˜ํ•ด์•ผํ•œ๋‹ค. ๊ฐ ํŽ˜์ด์ง€๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฏ€๋กœ ํŽ˜์ด์ง•์‹œ ๊ฒฐ๊ณผ๋ฅผ ์ •๋ ฌํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค. 

 

 

Cursor, Paging ๊ธฐ๋ฐ˜์˜ ๋™์ž‘ ๋ฐฉ์‹

 

 

 

 

4. JdbcPagingItemReader

JdbcPagingItemRedaer๋Š” JdbcCursorItemReader์™€ ๊ฐ™์€ JdbcTemplate ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•œ PagingItemReader์ด๋‹ค. ์˜ˆ์ œ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

// Cursor ๊ธฐ๋ฐ˜ ItemReader ๊ตฌํ˜„์ฒด
@Bean
public JdbcCursorItemReader<Schedule> allReadReader() {
    return new JdbcCursorItemReaderBuilder<Schedule>()
            .verifyCursorPosition(false)
            .fetchSize(FETCH_SIZE)
            .dataSource(dataSource)
            .rowMapper(new BeanPropertyRowMapper<>(Schedule.class))
            .sql("select * from schedules order by id")
            .name("jdbcCursorItemReader")
            .build();
}
// Paging ๊ธฐ๋ฐ˜ ItemReader ๊ตฌํ˜„์ฒด
@Bean
public JdbcPagingItemReader<Schedule> allReadPagingReader(
        PagingQueryProvider queryProvider) {
    return new JdbcPagingItemReaderBuilder<Schedule>()
            .pageSize(CHUNK_SIZE)
            .fetchSize(FETCH_SIZE)
            .dataSource(dataSource)
            .rowMapper(new BeanPropertyRowMapper<>(Schedule.class))
            .queryProvider(queryProvider)
            .name("jdbcCursorItemReader")
            .build();
}

@Bean
public PagingQueryProvider queryProvider() throws Exception {
    SqlPagingQueryProviderFactoryBean queryProvider = new SqlPagingQueryProviderFactoryBean();
    queryProvider.setDataSource(dataSource);
    queryProvider.setSelectClause("*");
    queryProvider.setFromClause("from schedules");

    Map<String, Order> sortKeys = new HashMap<>(1);
    sortKeys.put("id", Order.ASCENDING);

    queryProvider.setSortKeys(sortKeys);
    return queryProvider.getObject();
}

 

JdbcPagingItemReader ๊ณผ JdbcCursorItemReader ์˜ ๊ฐ€์žฅ ํฐ ์ฐจ์ด์ ์€ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๋ฐฉ์‹์ด๋‹ค. Paging ๋ฐฉ์‹์—์„  queryProvider ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. 

 

์ด๋Š” ๊ฐ DB์—์„œ Paging์„ ์ง€์›ํ•˜๋Š” ์ž์ฒด์ ์ธ ์ „๋žต๋“ค์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. 

 

๊ฐ DB์˜ Paging ์ „๋žต์— ๋งž์ถ˜ Provider๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

Spring batch์—์„  SqlPagingQueryProviderFactoryBean์„ ํ†ตํ•ด Datasource ์„ค์ •๊ฐ’์„ ๋ณด๊ณ  ์œ„ ์ด๋ฏธ์ง€์—์„œ ์ž‘์„ฑ๋œ Provider์ค‘ ํ•˜๋‚˜๋ฅผ ์ž๋™์œผ๋กœ ์„ ํƒํ•œ๋‹ค. 

 

 

 

 

 

5. JpaPagingItemReader

๊ฒฐ๊ณผ์ ์œผ๋กœ ๋‚ด ์„ ํƒ์€ JpapagingItemReader ์ด๋‹ค.

AWS EC2์— ๋ฐฐํฌํ•ด๋‘๊ณ  ์ž‘์€ ๋ฉ”๋ชจ๋ฆฌ ์ŠคํŽ™์„ ๊ฐ€์ง„ ํ™˜๊ฒฝ์— ์„œ ์„œ๋ฒ„๊ฐ€ ๋Œ๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋• ์„ฑ๋Šฅ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, DB์ปค๋„ฅ์…˜์„ ์งง๊ฒŒ ์œ ์ง€ํ•˜๊ณ  ์•ˆ์ •์ ์ธ Paging ๋ฐฉ์‹์„ ํƒํ–ˆ๊ณ , JPA๋ฅผ ์‚ฌ์šฉ์ค‘์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— JpapagingItemReader ๋ฅผ ์„ ํƒํ–ˆ๋‹ค. 

 

JpapagingItemReader ๋กœ Item์„ ์ฝ์–ด๋“œ๋ฆฌ๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ์ด๋‹ค. 

@Bean
public ItemReader<Apply> applyReader() {
    LocalDate tomorrow = LocalDate.now().plusDays(1);
    return new RepositoryItemReaderBuilder<Apply>()
            .name("applyReader")
            .repository(applyRepository)
            .methodName("findNeedToCancel")
            .pageSize(CHUNK_SIZE)
            .arguments(List.of(tomorrow))
            .sorts(Collections.singletonMap("id", Sort.Direction.ASC)) // ํ•„์ˆ˜
            .build();
}

 

์œ„์—์„œ๋„ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ์ •๋ ฌ์€ ๋ฌด์กฐ๊ฑด ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค. 

๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ •๋ ฌํ•˜๋ ค๋ฉด full scan์ด ์ผ์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— pk๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ์„ฑ๋Šฅ์— ์ง€์žฅ์ด ๊ฐ€์ง€ ์•Š๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค. 

 

 

 

6. JPAPagingItemReader -  RepositoryItemReaderBuilder ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

JPAPagingItemReader๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ช‡ ๊ฐ€์ง€ ์ฃผ์˜ํ•ด์•ผ ํ•  ๋ถ€๋ถ„๋“ค์ด ์žˆ๋‹ค. 

 

1. Hibernate, JPA ๋“ฑ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ Reader ์‚ฌ์šฉ์‹œ fetchSize์™€ ChunkSize๋Š” ๊ฐ™์€ ๊ฐ’์„ ์œ ์ง€ํ•ด  ์•ผ ํ•œ๋‹ค.

 

2. Pagepable์„ ๊ผญ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. ๋‹น์—ฐํžˆ Paging์„ ์‚ฌ์šฉํ•ด์„œ ์ฝ์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— Paging์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์žก์•„์ฃผ์ง€ ์•Š์œผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค.

 

3. 2๋ฒˆ๊ณผ ๋น„์Šทํ•˜๊ฒŒ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ Page์—ฌ์•ผ ํ•œ๋‹ค. List๋กœ ์„ค์ •ํ•  ์‹œ ์—๋Ÿฌ๊ฐ€ ๋œฌ๋‹ค.

 

4. where ์กฐ๊ฑด ๋ฌธ์— ๋“ค์–ด๊ฐ€๋Š” ํ•„๋“œ์˜ ๊ฐ’์ด ๋ฐ”๋€” ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ๋‚ด์šฉ์ด ๊ธธ์–ด์ ธ ์•„๋ž˜ ๊ธ€์—์„œ ๋‹ค๋ฃจ์—ˆ๋‹ค. 

https://dgjinsu.tistory.com/47

 

[Spring Batch] Paging ๊ธฐ๋ฐ˜์˜ ItemReader ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ์ดํ„ฐ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ํ˜„์ƒ

1. ๋ฌธ์ œ ์ƒํ™ฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ธฐ์ค€์œผ๋กœ ItemReader์˜ ๊ตฌํ˜„์ฒด๋ฅผ ๋‚˜๋ˆŒ ๋•Œ, Cursor Based ItemReader๊ณผ Paging Based ItemReader์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค. Cursor ๊ธฐ๋ฐ˜์˜ ItemReader์˜ ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปค๋„ฅ์…˜์„ ์ง€์†์ ์œผ๋กœ

dgjinsu.tistory.com