Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Batch Performance 극한으로 끌어올리기

kakao
PRO
December 09, 2022

Batch Performance 극한으로 끌어올리기

#BatchPerformance #SpringBatch #SpringCloudDataFlow #Kubernetes #Redis #Kafka

수 천만 건 이상의 대량 배치처리를 하기 위한 최고의 노하우를 전달드리겠습니다.
카카오페이 정산플랫폼팀은 카카오페이의 빠른 성장과 더불어 늘어나는 데이터를 일괄처리하기 위하여 꾸준히 노력 하고 있습니다.
더 많은 데이터를 처리하기 위해 진화하는 서비스와 개발방식을 소개하고자 합니다.

발표자 : benny.ahn
카카오페이 정산플랫폼팀 베니입니다. 서버 Performance 개선에 관심이 많습니다.

kakao
PRO

December 09, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. 카카오페이 정산플랫폼팀 1억 번 데이터 처리를 위한 노력
    Copyright 2022. Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao.
    Batch Performance


    극한으로 끌어올리기
    안성훈 Benny.Ahn


    카카오페이
    if(kakao)2022

    View Slide

  2. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  3. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  4. When?
    “개발자들은 언제 Batch로 개발할까요?”

    View Slide

  5. 일괄 처리, Batch Application
    흠… 오후 4시에 상품 주문 배송 정보를


    고객들에게 문자로 일괄 전송해야 하는구나. 간단하네!


    Batch로 개발해서 16시에 스케줄을 걸어놔야지!
    커머스 서비스를 개발하는


    개발자 A씨
    특정 시간에 많은 데이터를 일괄 처리, 서버 개발자들이 자주 애용하는 방식, 개발 부담↓

    View Slide

  6. 일괄 수정
    일괄 생성 통계
    READ
    CREATE
    WRITE
    READ
    UPDATE
    WRITE
    SUM READ
    WRITE
    CREATE

    View Slide

  7. 일괄 생성
    DB


    API


    FILE
    READ CREATE WRITE
    주문1 정보
    주문2 정보
    주문1 정보
    주문2 정보
    주문자1 정보
    주문자 2 정보
    사용자 정보

    View Slide

  8. 일괄 수정
    READ UPDATE WRITE
    주문1 정보
    주문2 정보
    주문1 정보
    주문2 정보
    수정된


    주문1 정보
    수정된


    주문2 정보
    배송정보

    View Slide

  9. 통계
    DB


    API


    FILE
    SUM


    READ
    WRITE
    주문1 정보
    주문2 정보
    상품별


    주문 금액 합산
    주로


    GroupBy

    View Slide

  10. BUT
    “Batch Performance 얼마나 신경 써보셨나요?”

    View Slide

  11. 무관심한 Batch Performance
    1년 뒤…
    어? Batch가 아직도 안 끝났네?


    언제 이렇게 주문량이 많아졌지?


    왜 이렇게 알림 발송이 느려졌지?
    커머스 서비스를 개발하는


    개발자 A씨
    Batch 개발을 쉽게 생각하는 경향, 배포 후 관리 소홀, 배치를 지원하는 APM Tool의 부재

    View Slide

  12. 카카오페이 정산플랫폼팀이


    처리하는 데이터량

    View Slide

  13. 카카오페이 정산플랫폼팀은 얼마나 많은 데이터를 처리할까요?
    2022년 현재 하루 평균 데이터 Access 횟수는 1억 번이 넘는다.


    (2017년도에는 하루 평균 25만 번)
    2017 2018 2019 2020 2021 2022
    1시간
    400시간
    Batch 수행시간
    1억
    5천만
    0

    View Slide

  14. 5년 전에도 1시간, 오늘도 1시간
    2017 2018 2019 2020 2021 2022
    1시간
    400시간
    Batch 수행시간
    1억
    5천만
    0
    1시간

    View Slide

  15. HOW?
    “어떻게 Batch Performance 개선이 가능한가요?”

    View Slide

  16. Batch 처리의 순수한 성능 개선 관점
    For. 대량 데이터 처리 Batch 개발자

    View Slide

  17. 개발 환경
    Spring
    Batch
    MySQL Spring
    Cloud


    Data Flow
    Redis Kubernetes

    View Slide

  18. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  19. Batch 성능 개선의 첫걸음 = Reader 개선

    View Slide

  20. Reader
    Writer
    Batch 성능에서 차지하는 비중
    성능 개선의 첫걸음 = Reader 개선

    View Slide

  21. Reader의 복잡한 조회조건
    100만
    100만
    100만 개의 주문 정보 중


    100만 개 모두
    차례대로


    가져가세요~ Batch Application

    View Slide

  22. Reader의 복잡한 조회조건
    100만
    10억
    Batch Application
    잠시만요!


    찾는 중 입니다…
    10억 개의 주문 정보 중


    환불이 발생한 100만 개

    View Slide

  23. Batch에서 데이터를 읽는 절대적인 방법


    Chunk Processing

    View Slide

  24. 읽을 때는 항상 Chunk Processing
    Chunk1 Chunk2 Chunk9999 Chunk10000
    1000만 개의 Item


    1만 개의 chunk
    … … …

    …………..
    1000만 개
    1000개
    Chunk1 Chunk2 Chunk3 Chunk4 Chunk5
    24개의 Item


    5개의 chunk
    굳이…? 그냥 한 번에 처리하면 되는거 아니야?
    1000만 개도 한 번에 처리하려고…? 불가능해!

    View Slide

  25. Chunk Processing의 짝꿍, Pagination Reader
    MySQL
    36번 Page, 100개 주세요
    Limit Offset 사용
    select *


    from orders


    where category = ‘BOOK'


    limit 3600, 100
    JpaPagingItemReader
    RepositoryItemReader
    대량 처리에 매우 부적합

    View Slide

  26. MySQL 쿼리와 기존 ItemReader 문제점
    조회 결과: 1억 건
    조회 결과: 100건, 조회 속도: 매우 빠름
    조회 결과: 100건, 조회 속도: 매우 느림 Offset이 커질수록 느려짐
    Index
    select count(1) from orders where category = ‘BOOK'
    select * from orders where category = 'BOOK' limit 0, 100
    select * from orders where category = 'BOOK' limit 50000000, 100
    Limit Offset이 가지는 태생적인 한계

    View Slide

  27. ZeroOffsetItemReader
    Page1 Page2 Page3 Page4 Page5
    24개의 Item


    5개의 page
    PK : 10
    order by id(PK) asc
    PK : 5235 PK : 76432123 PK : 967678332
    select * from orders where category = 'BOOK' and id > 0 limit 0, 100
    select * from orders where category = 'BOOK' and id > 967678332 limit 0, 100
    select * from orders where category = 'BOOK' and id > 5235 limit 0, 100
    Zero Offset

    View Slide

  28. ZeroOffsetItemReader
    Page1 Page2 Page3 Page4 Page5
    24개의 Item


    5개의 page
    PK : 10
    order by id(PK) asc
    PK : 5235 PK : 76432123 PK : 967678332
    select * from orders where category = 'BOOK' and id > 0 limit 0, 100
    select * from orders where category = 'BOOK' and id > 967678332 limit 0, 100
    select * from orders where category = 'BOOK' and id > 5235 limit 0, 100

    View Slide

  29. QueryDsl + ZeroOffsetItemReader
    QuerydslZeroOffsetItemReader(


    name = “orderQueryDslZeroOffsetItemReader",


    pageSize = 1000,


    entityManagerFactory = entityManagerFactory,


    idAndSort = Asc,


    idField = qOrder.id


    ) {


    it.from(qOrder)


    .innerJoin(qOrder.customer).fetchJoin()


    .select(qOrder)


    .where(qOrder.category.eq(CATEGORY.BOOK))


    }


    QueryDSL : Query Domain Specific Language

    View Slide

  30. 다른 해결법은 없나요?

    View Slide

  31. 데이터를 조금씩 가져오는 Cursor를 사용하자!
    Cursor 동작방식
    Yes
    No
    데이터가 없을 때까지 반복해서 Fetch
    Batch
    Open Fetch Close
    Empty

    View Slide

  32. Cursor를 지원하는 ItemReader
    MySQL Cursor방식


    데이터를 모두 읽고 서버에서 직접 Cursor하는 방식


    ->
    OOM 유발
    MySQL Cursor방식


    MySQL의 Cursor를 사용하여 일정 개수만큼 Fetch하는 방식


    ->
    안전함
    근데… HQL, Native Query 사용하기 싫다…
    JpaCursorItemReader
    JdbcCursorItemReader


    HibernateCursorItemReader

    View Slide

  33. “안전하고 세련된 다른 방법은 없을까요?”

    View Slide

  34. 새로운 방식의 쿼리 구현, Exposed
    JetBrains Kotlin 기반 ORM 프레임워크
    EXPOSED
    • 데이터베이스 Access 방식

    SQL을 매핑한 DSL 방식, 경량화한 ORM DAO 방식


    • 지원하는 데이터베이스

    H2, MySQL, MariaDB, Oracle, PostgreSQL, SQL Server, SQLite


    • Kotlin 호환성 (자바 프로젝트는 사용 불가)

    View Slide

  35. Exposed DSL 도입 이유
    object Orders : LongIdTable(“orders") {


    val customer = reference(“customer_id”, Customers).nullable()


    val name = varchar("name", 50)


    val price = long(“price”).default(0)


    val category = varchar("category", 50)


    val deliveryCompleted = bool(“is_delivery_completed”)


    }


    object Customers : LongIdTable("customer") {


    val name = varchar("name", 20)


    val email = varchar("email", 50)


    val age = long(“age”)


    }


    Kotlin 언어적 특성을 활용해서 세련되게 쿼리 구현이 가능

    View Slide

  36. Select


    Customers.slice(Customers.name)


    .select {


    age eq 25


    }


    Insert


    Customers.insert { customers ->


    customers[name] = “ഘӡز"


    customers[email] = “@kakao.com”


    customers[age] = 25


    }


    Batch Insert


    Customers.batchInsert(data = items) { item ->


    this[Customers.name] = item.name


    this[Customers.email] = item.email


    this[Customers.age] = item.age


    }

    View Slide

  37. Exposed+CursorItemReader
    ExposedCursorItemReader(


    name = "orderExposedCursorItemReader",


    dataSource = dataSource,


    fetchSize = 5000


    ) {


    (Orders innerJoin Customers)


    .slice(Orders.columns)


    .select {


    (Orders.category eq "BOOK") and


    (Customers.age greaterEq 11)


    }


    }


    동작 방식 = JdbcCursorItemReader


    쿼리 = Exposed DSL

    View Slide

  38. 개선된 ItemReader


    그래서 얼마나 개선됐나요?

    View Slide

  39. ItemReader 성능 비교
    Sec
    0
    3,500
    7,000
    10만 50만 100만 300만
    290
    96
    50
    11
    266
    82
    47
    9
    6,752
    842
    235
    18
    JpaPagingItemReader
    QueryDslZeroOffsetItemReader
    ExposedCursorItemReader
    Chunk Size, Page Size, Fetch Size = 1,000개 / Column 개수 = 30개
    (112분)
    (4분 26초)
    (4분 50초)

    View Slide

  40. ItemReader 성능 비교
    Sec
    0
    3,500
    7,000
    10만 50만 100만 300만
    290
    96
    50
    11
    266
    82
    47
    9
    6,752
    842
    235
    18
    JpaPagingItemReader
    QueryDslZeroOffsetItemReader
    ExposedCursorItemReader
    Chunk Size, Page Size, Fetch Size = 1,000개 / Column 개수 = 30개
    (112분)
    (4분 26초)
    (4분 50초)

    View Slide

  41. ItemReader 성능 비교
    Sec
    0
    3,500
    7,000
    10만 50만 100만 300만
    290
    96
    50
    11
    266
    82
    47
    9
    6,752
    842
    235
    18
    JpaPagingItemReader
    QueryDslZeroOffsetItemReader
    ExposedCursorItemReader
    Chunk Size, Page Size, Fetch Size = 1,000개 / Column 개수 = 30개
    (112분)
    (4분 26초)
    (4분 50초)
    5배
    13배

    View Slide

  42. ItemReader 성능 비교
    Sec
    0
    3,500
    7,000
    10만 50만 100만 300만
    290
    96
    50
    11
    266
    82
    47
    9
    6,752
    842
    235
    18
    JpaPagingItemReader
    QueryDslZeroOffsetItemReader
    ExposedCursorItemReader
    Chunk Size, Page Size, Fetch Size = 1,000개 / Column 개수 = 30개
    (112분)
    (4분 26초)
    (4분 50초)
    8배

    View Slide

  43. 300만 건 Heap Space Monitoring
    ExposedCursorItemReader
    QueryDslZeroOffsetItemReader
    JDK11
    -
    Xmx512m JDK11
    -
    Xmx512m

    View Slide

  44. 기존의 ItemReader
    RepositoryItemReader


    JpaPagingItemReader
    JdbcCursorItemReader


    HibernateCursorItemReader
    JpaCursorItemReader
    쿼리 구현 방법
    Query Method


    QueryDSL


    JPQL
    Native Query, HQL JPQL
    동작 방식
    Pagination


    Limit Offset 구문 사용
    Cursor 방식
    애플리케이션에서


    직접 Cursor 처리
    성능
    조회할 데이터가 많다면


    뒷 Page로 갈수록 느려짐
    Cursor 기반이므로


    Fetch size와 DB설정만 제대로


    세팅하면 조회 속도가 매우 빠름
    성능은 매우 우수하나


    OOM 유발

    View Slide

  45. 개선된 ItemReader
    QueryDslZeroOffsetItemReader ExposedCursorItemReader
    쿼리 구현 방법 QueryDSL Kotlin Exposed
    동작 방식
    Offset을 항상 0으로 유지


    PK를 where 조건에 추가하는 방식
    JdbcCursorItemReader와 동일한 방식
    성능
    첫 Page를 읽었을 때와 동일하게


    항상 일관된 조회 성능을 가짐
    Cursor 기반으로 많은 양의 데이터를


    빠르게 가져오며 일관된 조회 성능을 가짐

    View Slide

  46. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  47. 데이터 Aggregation 처리
    서버 개발자 A씨
    통계
    ->
    Batch로 개발
    ->
    GroupBy & Sum
    DB


    API


    FILE
    SUM


    READ
    WRITE
    주문1 정보
    주문2 정보
    상품별


    주문 금액 합산
    주로


    GroupBy

    View Slide

  48. “데이터가 많아지고 쿼리가 복잡해져도


    문제가 없을까요?”

    View Slide

  49. Sum 쿼리에 의존하는 Batch 문제점
    복수 개 테이블 Join & GroupBy


    ->
    잘못된 실행 계획, 까다로운 쿼리 튜닝
    select


    sum(o.amount),


    count(1),


    p.price,


    p.product_id,


    u.age


    from orders o


    inner join price p


    on o.price_id = p.id


    inner join user u


    on o.user_id = u.id


    where o.order_date = ‘2022-10-12'


    group by p.product_id, u.age


    order by p.product_id asc, u.age asc


    limit 0, 100;

    View Slide

  50. Join+GroupBy+Sum 쿼리를 사용하면…
    연산 과정이 쿼리에 의존적


    ->
    Database 부하 증가
    쿼리 튜닝을 위한 과도한 인덱스 추가


    ->
    INSERT, UPDATE 성능 저하,


    저장 용량 차지
    데이터 누적


    ->
    데이터 중복도(카디널리티) 변경


    ->
    쿼리 실행 계획의 변경


    ->
    쿼리 튜닝 난이도

    View Slide

  51. 개선된 ItemReader가 무용지물…
    “쿼리 자체가 너무 느려요”

    View Slide

  52. GroupBy를 포기하자

    View Slide

  53. GroupBy를 포기하자
    쿼리는 단순하게!


    ->
    그냥 GroupBy를 안 쓰면 되겠네


    ->
    직접 Aggregation을 하면 되겠네
    select


    sum(o.amount),


    count(1),


    p.price,


    p.product_id,


    u.age


    from orders o


    inner join price p


    on o.price_id = p.id


    inner join user u


    on o.user_id = u.id


    where o.order_date = ‘2022-10-12'


    group by p.product_id, u.age


    order by p.product_id asc, u.age asc


    limit 0, 100;

    View Slide

  54. 직접 Aggregation하자
    직접 Aggregation 가능한가요?
    1000만 개 데이터
    50만 개 SUM 데이터
    합산
    필요한 최소 공간


    공간이 없어요!

    View Slide

  55. 새로운 Architecture의 필요성

    View Slide

  56. 새로운 Architecture, Redis를 활용한 Sum
    충분한 저장공간! 빠른 연산! Redis를 쓰자
    1000만 개 데이터 50만 개 SUM 데이터
    Chunk 1
    Chunk 2
    Chunk 3
    Chunk 10000
    1000개
    Sum 연산 요청
    Summary 1
    Summary 2
    Summary
    500000
    최종 데이터 저장소
    “기존 것에 더해주세요!”

    View Slide

  57. Redis 도입 이유
    50만 개는 쉽게 저장하는 넉넉한 메모리
    In
    -
    Memory DB


    빠른 저장 O! 영구 저장 X!
    Aggregation Tool로 왜 Redis?
    연산 명령어 hincrby, hincrbyfloat 지원


    메모리 수준에서 합산

    View Slide

  58. 그러나, Redis를 도입해도 해결되지 않는 문제
    50만 개 SUM 데이터
    Chunk 1
    Chunk 2
    Chunk 3
    Chunk 10000
    1천 번 요청
    총 1000만 번 SUM 연산 요청 1000만 번 네트워크 I/O
    =
    1천 번 요청

    View Slide

  59. 그러나, Redis를 도입해도 해결되지 않는 문제
    Network I/O
    요청 한 번 당 1ms * 1000만 번 = 3시간
    Redis 연산
    눈 깜짝할 사이에 끝나긴 하는데…
    전체 성능은 오히려

    View Slide

  60. Redis Pipeline으로 처리하자
    Redis Pipeline: 다수 command를 한 번에 묶어서 처리
    50만 개 SUM 데이터
    Chunk 1
    Chunk 2
    Chunk 3
    Chunk 10000
    한 번 Sum 연산 요청
    총 1만 번 SUM 연산 요청 1만 번 네트워크 I/O
    =
    Batch Application 전용


    Redis Pipeline 대량 처리 라이브러리


    별도 개발하여 사용
    (당연히 Spring Data Redis로 불가능)

    View Slide

  61. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  62. 효과적인 대량 데이터 WRITE
    Reader와 Aggregation까지 개선 완료!


    음… 그래도 1000만 개 처리할 때는 여전히 느려…


    남은 곳은 Writer!
    카카오페이 정산플랫폼팀

    View Slide

  63. “Writer는 어떻게 개선할 수 있을까요?”

    View Slide

  64. Batch Insert 명시적 쿼리
    일괄로 쿼리 요청 필요한 컬럼만 Update


    영속성 컨텍스트를 사용하지 않음

    View Slide

  65. 아쉽지만… JPA는 안녕~

    View Slide

  66. Batch에서 JPA WRITE에 대한 고찰
    Batch 환경에서 JPA가 과연 잘 맞는가?
    Dirty Checking과 영속성 관리
    UPDATE할 때 불필요한 컬럼도 UPDATE
    JPA Batch Insert 지원이 어려운 부분

    View Slide

  67. 쓰기 지연 저장소
    ID 엔터티 스냅샷
    Order1
    Order1


    현 상태
    Order1


    스냅샷
    Order2
    Order2


    현 상태
    Order2


    스냅샷
    Flush
    엔터티&스냅샷 비교
    update sql 생성
    JPA 영속성 관리 Dirty Checking
    Dirty Checking과 영속성 관리
    Detached
    Managed
    New
    Removed
    굳이…? Batch에서는 이런 복잡한 과정들이 필요한가?

    View Slide

  68. Read할 때부터 Dirty Checking과 영속성 버리기
    QuerydslZeroOffsetItemReader(


    name = "readerForShipments",


    pageSize = 1000,


    entityManagerFactory = entityManagerFactory,


    idAndSort = Asc,


    idField = QOrders.orders.id


    ) { query ->


    query.select(


    Projections.constructor(


    ShipmentDto::class.java,


    QOrders.orders.orderNumber,


    QUser.user.id,


    QUser.user.name,


    QUser.user.phoneNumber,


    QUser.user.address,


    )


    )


    .from(QOrders.orders)


    .innerJoin(QOrders.orders.user, QUser.user)


    .where(QOrders.orders.orderDate.eq(LocalDate.now()))
    요점은 Dirty Checking과 영속성 관리를 쓰지 않는 것!


    JPA를 버리거나, 또는 Reader에서 Projections를 쓴다.
    정확히 필요한 컬럼만 가져와 Fetch, Deserialize 시간 단축
    영속성 관리, Dirty Checking하지 않아 성능 개선

    View Slide

  69. UPDATE할 때 불필요한 컬럼도 UPDATE
    원하는 쿼리
    update orders


    set is_completed = 1


    where id = 315;
    실제 동작한 쿼리
    update orders


    set is_completed = 1,


    order_number = ‘23231’,


    price = 49000,


    order_date = ‘2022-01-01’


    where id = 315;
    왜 필요 없는 것도 UPDATE하려고 그래…

    View Slide

  70. UPDATE할 때 불필요한 컬럼도 UPDATE
    But, Dynamic UPDATEܳ ೞӝ ਤ೧


    ز੸ ௪ܻܳ ࢤࢿ
    য়൤۰ ࢿמ ੷ೞ
    원하는 쿼리
    update orders


    set is_completed = 1


    where id = 315;
    실제 동작한 쿼리
    update orders


    set is_completed = 1,


    order_number = ‘23231’,


    price = 49000,


    order_date = ‘2022-01-01’


    where id = 315;
    잠깐! JPA에겐 dynamic UPDATE가 있다고!

    View Slide

  71. 그러나, JPA는 Batch Insert 지원이 어려워요
    JPA에서도 Batch Insert를 지원하지만,


    ID 생성 전략을 IDENTITY로 하게 되면 JPA 사상과 맞지 않는 이유로 Batch Insert를 지원하지 않음
    @Id


    @GeneratedValue(strategy = GenerationType.IDENTITY)


    Long id;

    View Slide

  72. Batch에서 Batch Insert 사용해야 하는 이유
    Network I/O
    너무 오래 걸리고 시간이 아까워요!
    데이터베이스 Insert
    눈 깜짝할 사이에 끝나긴 하는데…
    성능 개선을 위해서 쿼리를 모아서 처리하는 Batch Insert 필수에요!

    View Slide

  73. Batch에서 JPA WRITE에 대한 고찰
    Batch 환경에서 JPA가 과연 잘 맞는가?
    Dirty Checking과 영속성 관리
    UPDATE할 때 불필요한 컬럼도 UPDATE
    JPA Batch Insert 지원이 어려운 부분
    ->
    불필요한 컬럼 UPDATE로 인한 소폭 성능 저하
    ->
    불필요한 check 로직으로 인한 큰 성능 저하
    ->
    Batch Insert 불가한 경우, 매우 큰 성능 저하

    View Slide

  74. Writer에서 JPA를 포기하고


    Batch Insert할 것

    View Slide

  75. JPA vs JDBC Batch Insert
    Sec
    0
    3,000
    6,000
    10만 100만 500만
    105
    21
    2 140
    28
    3
    5,452
    1,163
    108
    JPA 단건 저장
    JDBC 1,000개씩 Batch Insert
    JDBC 10,000개씩 Batch Insert
    (90분)
    (2분 20초)
    (1분 45초)

    View Slide

  76. JDBC Batch Insert 보통 이렇게 구현해요
    sql = “update orders set name = ? where id = ?”


    pstmt = con.prepareStatement(sql);


    pstmt.setString(1, “ۄ੉঱”)


    pstmt.setLong(2, 3241)


    pstmt.addBatch()


    pstmt.setString(1, “୸ध੉”)


    pstmt.setLong(2, 865)


    pstmt.addBatch()


    pstmt.executeBatch()
    update orders set name = ‘ۄ੉঱’ where id = 3241;


    update orders set name = ‘୸ध੉’ where id = 865;

    View Slide

  77. Hello Exposed Batch Insert!
    “JDBC Batch Insert도 충분히 좋지만 Native Query를 사용하고 싶지 않아요”


    ->
    Exposed Batch Insert를 사용
    Customers.batchInsert(data = items) { item ->


    this[Customers.name] = item.name


    this[Customers.email] = item.email


    this[Customers.age] = item.age


    }

    View Slide

  78. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리

    View Slide

  79. “여러분들은 어디서 어떻게


    Batch를 구동하나요?”

    View Slide

  80. 여러분들은 어디서 어떻게 Batch를 구동하나요?
    crontab
    실행 요청


    스케줄


    Batch 관리


    워크 플로우 관리


    모니터링


    히스토리
    다들 만족하시나요?

    View Slide

  81. Batch 구동 환경의 특징
    기존 스케줄 Tool의 아쉬운 점

    View Slide

  82. 자원관리(Resource Control)의 어려움
    A Batch
    B Batch
    C Batch
    D Batch
    E Batch
    0시 24시
    배치 종료 = 자원 미사용
    배치 실행 = 자원 사용

    View Slide

  83. 자원관리(Resource Control)의 어려움
    A Batch
    B Batch
    C Batch
    D Batch
    E Batch
    0시 24시
    4개 Batch 2개 Batch 1개 Batch
    Idle
    12시

    View Slide

  84. Batch 상태 파악(Monitoring)의 어려움
    - Batch에서는 동작 하나하나가 매우 길다.


    - 대부분 스케줄 Tool에서 로그를 볼 수 있지만 로그 정보가 매우 빈약하다.


    - 서비스 상태를 로그로 판단하는 것 자체가 전혀 시각적이지 않다.
    [main] m.b.practice.BatchPracticeApplicationKt : Started BatchPracticeApplicationKt in 6.06 seconds (JVM running for
    6.752)


    [main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters:
    [{}]


    [main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepName]


    [main] o.s.batch.core.step.AbstractStep : Step: [stepName] executed in 10m15s192ms


    [main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters:
    [{}] and the following status: [COMPLETED] in 10m15s250ms
    10분 동안 대체 무슨 일이…

    View Slide

  85. Spring Cloud Data Flow 도입

    View Slide

  86. Spring Cloud Data Flow
    데이터 수집, 분석, 데이터 입/출력과 같은 데이터 파이프라인을 만들고 오케스트레이션
    Stream
    Task(Batch)
    데이터 파이프라인 종류

    View Slide

  87. 오케스트레이션 모니터링
    K8s와 완벽한 연동으로


    Batch 실행 오케스트레이션
    Spring Batch와 완벽한 호환


    유용한 정보 시각적으로 모니터링

    View Slide

  88. Spring Cloud Data Flow 도입
    - 다수 Batch가 상호 간섭 없이 Running (by 컨테이너)


    - K8s에서 Resouce 사용과 반납을 조율
    - Spring Cloud Data Flow 자체 Dashboard 제공


    - 그라파나 연동 가능
    K8s와 완벽한 연동으로 Batch 실행 오케스트레이션
    Spring Batch 유용한 정보 시각적으로 모니터링

    View Slide

  89. Spring Cloud Data Flow 동작과 역할
    스케줄 (cronJob) 관리


    애플리케이션 실행(배포)


    Workflow (pipeline) 컨트롤


    K8s 모든 config 설정 가능


    Batch Pod에 할당할 자원 설정
    SCDF Task Batch


    App 2
    Pod2
    Batch


    App 3
    Pod3
    Batch


    App 1
    Pod1

    View Slide

  90. Application Log


    pod 상태 모니터링


    Batch 상태, 결과 모니터링
    Spring Cloud Data Flow 동작과 역할
    SCDF Task Batch


    App 2
    Pod2
    Batch


    App 3
    Pod3
    Batch


    App 1
    Pod1

    View Slide

  91. Spring Cloud Data Flow
    Task 생성 여러 Batch를 Graph로 표현

    View Slide

  92. Spring Cloud Data Flow
    Task 스케줄 생성

    View Slide

  93. Spring Cloud Data Flow
    Task(Batch) 모니터링 (Dashboard)

    View Slide

  94. Spring Cloud Data Flow
    Task 모니터링 (Step 누적 히스토리)
    평균
    소요시간
    한 번 읽을 때 소요시간
    최소 최대 표준편차
    Read 횟수
    Write 횟수

    View Slide

  95. Spring Cloud Data Flow
    Task 모니터링 (Grafana)

    View Slide

  96. 발표에서 다루고자 하는 내용


    대량 데이터 READ


    데이터 Aggregation 처리


    대량 데이터 WRITE


    Batch 구동 환경


    대량 데이터 처리 방식 총정리(발표 내용 정리)

    View Slide

  97. 대량 데이터 처리 방식 총정리(발표 내용 정리)
    대량 데이터 READ
    - ZeroOffsetItemReader (with QueryDSL)


    - CursorItemReader (with Exposed)
    데이터 Aggregation 처리
    - 쿼리 의존도 ↓


    - Redis(with Pipeline)를 통한 Aggregation
    대량 데이터 WRITE
    - Batch Insert 사용
    Batch 구동 환경
    - Spring Cloud Data Flow


    - Batch 오케스트레이션, 모니터링과 히스토리 강화

    View Slide

  98. 감사합니다

    View Slide

  99. 참고 문헌
    1) Spring Cloud Data Flow 소개 내용, https://www.baeldung.com/spring
    -
    cloud
    -
    data
    -
    flow
    -
    stream
    -
    processing


    2) Spring Cloud Data Flow 소개 화면, https://dataflow.spring.io/docs/ https://dataflow.spring.io/docs/feature
    -
    guides/batch/


    3) Batch Insert 성능측정 결과, https://cheese10yun.github.io/spring
    -
    batch
    -
    batch
    -
    insert/

    View Slide