ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 프로젝트 회고 - 수업 스케줄 일괄 변경 배치 처리
    Spring 2024. 4. 8. 08:30

    프로젝트 개요

    각 센터마다 스케줄을 생성할 수 있습니다. 이에 대한 일괄 변경 기능을 제공하는 것인데요.
    성능최적화를 통해 빠른 응답시간을 제공하는 것이 목표입니다.

    해당 프로젝트에서 다른 주제는 다음과 같습니다.
    - 적정 ThreadPool Size 조정
    - 적정 BatchSize 조정
    - JMeter 성능 테스트

    요구사항

    - 자동공개 설정의 일괄 변경 기능 제공
    - ASIS와 같이 실시간 처리 기능 제공

    분석

    - 기존 일괄변경 기능 UI에 들어가게 되는데, 저장 건수에 대한 LIMIT이 존재하지 않음
    - 3만건 정도 조회하면 UI가 뻗는 현상

    구현

    제목이 일괄변경 배치 처리라고 해서, SpringBatch를 사용한 것은 아닙니다.
    Legacy 이다 보니, SpringBoot가 아닌 SpringFramewokr4환경에서 개발되었습니다.

    - 전략패턴을 사용해서, 데이터 사이즈 별 처리 전략 구현
    - 멱등성 보장을 위해, 데이터를 read할 때는 단일 쓰레드 처리

    테스트

    ThreadPooSize와, ChunkSize를 몇으로 했을 때 가장 최적의 성능을 내는지 찾아야 합니다.

    테스트 전략

    - 동적 Thread Pool Size 변경
    - 동적 처리 전략 변경(single/chunk/batch)
    - JMeter로 성능 테스트

    테스트를 위해서, 동적으로 ThreadPoolSize 를 변경할 수 있게 API를 만들고,
    파라미터를 받아서 처리전략을 선택할 수 있도록 했습니다.

    그리고, 건수에 따라서, multi Thread로 처리할지, Single Query를 사용할지, 결정하기로 했습니다.

    ThreadPoolSize를 하드코딩 해놓고 테스트 하면, 서버를 계속해서 재부팅해야 하기 때문에, PoolSize를 REST API를 통해서 변경이 가능하게 했습니다.
    그리고, 파라미터로 처리 전략 타입을 받으면, 건수에 상관없이, 구현해 놓은 전략이 실행되도록 했습니다.

    데이터 준비 

    - 9,048건 준비
    사실, 1만건을 준비하려고 했는데, 하다보니 9,048 건이 나와서 이대로 진행했습니다.
    데이터를 직접 만들지 않고, 데이터가 가장 많이 있는 센터를 먼저 찾은 뒤에, 월별 집계를 해서 테스트 대상을 선택했습니다.

    1만건으로 테스트하려고 한 이유는, 3만건의 경우 UI에서 버티질 못했고, 3개월 기간 LIMIT은 기존에 잡혀있었는데, 3개월 동안 스케줄이 가장 많은 센터는, 5~6천건 정도 있는 것으로 확인 됐습니다.
    그리고, 이것은 MAX 값이기 때문에, 전체를 전부 변경하는 곳은 극히 드물것이고, 보통 많아야, 1천건, 간혹 3-4천건 처리될 것이라 예상했습니다.

    성능 측정 결과

    - 성능 측정 서버 : 2core 하이퍼스레딩
    - 실제 운영 서버 : 4core 하이퍼스레딩

    테이블 설명

    - data size : 총 처리 건 수
    - chunk size : 한번에 처리하는 처리 건 수
    - pool size : thread pool 개수
    - query type
      - single : single 쿼리 단일 쓰레드
      - batch : batch 쿼리 단일 쓰레드
      - chunk : batch 쿼리 머리 쓰레드
    - LoopCount : 30회 반복
    - Avarage : 평균 응답시간(밀리초)
    - Throught : 평균 처리량

    JMeter 성능 측정 결과

    성능 측정 결과

    SingleQuery보다는 BatchQuery의 속도가 빨랐고, 단일 쓰레드 보다는 멀티 쓰레드로 실행한 경우의 처리속도가 높았습니다.
    몇의 숫자가 의미있는 숫자 인지 확인하려고 노력했습니다.
    CPU Core가 2코어 이고, 하이퍼 스레딩이 되기 때문에, pool size가 4일 때, 가장 좋은 처리속도를 볼 수 있었습니다.
    또한, pool size가 4보다 커지면 커질수록 속도는 더 느려졌는데요. 이는, 오히려 많은 쓰레드에게 일을 처리하게 함으로 컨텍스트 스위칭이 발생해서, 더 느린 것으로 유추해 볼 수 있습니다. 그리고, 당연히 4명이 일할 수 있는데, 더 적은 사람한테 일을 시키면 더 느리겠죠?

    chunkSize는 결국 한번에 update되는 batch query 1개의 데이터 사이즈 인데요. 250~350개 정도일 때 비슷한 속도를 보였습니다.
    처리속도가 같다면, Table record lock이 조금만 걸리는게 DeadLock을 발생시킬 위험을 낮출 수 있다고 생각해서, 350이 아닌, 250개의 chunkSize를 선택했습니다.

    결론적으로, ThreadPoolSize는 4로 결정했고, chunkSize는 250으로 결정했습니다.
    운영서버는 최대 8개의 쓰레드가 동시에 처리될 수 있기 때문에, 4로 했습니다. 특정시간에 모든 쓰레드를 해당 기능이 점유하도록 하는 것은 다른 기능에 대한 지연시간이 증가할 것으로 판단되었습니다.

    ThreadPool Size가 작으면? 크면?

    너무 적은 Thread는 자원을 효율적으로 사용하지 못합니다. 일할 수 있는 자원이 존재하는데, 사용하지 않는 것입니다.
    너무 많은 Thread는 Context Switching을 많이 발생시켜, 오히려 Latency를 증가시키고, CPU 사용량을 증가 시킵니다.

    BatchQuery Size가 작으면? 크면?

    매번 쿼리를 날리는 것 보다, 하나의 쿼리로 처리하는 것이 데이터가 커지며 커질 수록 빠른 속도를 제공합니다.
    너무 크면 실패할 수도 있습니다. 각 DB는 Network Packet Max Size 를 가지고 있습니다.
    또한, Update는 Update하는 대상에 대해 Lock을 잡게 됩니다.
    많은 데이터를 한번에 Update할 수록 DeadLock의 발생 가능성이 높아지게 됩니다.

    처리 전략 결정

    이 과정을 거쳐서, 다음과 같이 처리하도록 구현했습니다.

    - 1건 : single query
    - 2~250건 : single Query + 단일 쓰레드
    - 250건 초과 : batch query + 멀티 쓰레드

    마치며

    기존 기능의 경우, Single Query의 단일 쓰레드로 처리되고 있었기 때문에, 건수가 많아지면 많아질 수록 상당히 느린 Latency를 보여줬습니다. 5천건 이상되는 경우 수십초가 소요됐었는데요.
    현재 추가된 기능과 개선된 기능은 5,000건을 1초안에 처리하도록 최적화 되었습니다.

    사실, 이런 테스트 과정, 성능최적화를 위한 부수적인 코드 구현들은 귀찮기도 하고 많은 시간을 소요하는데요. 하지만, 이런 행위들이 결국엔 시스템의 안정성 확보와, 성능최적화로 인해 사용자의 만족도를 높일 수 있는 일이라고 생각합니다.

    댓글

Designed by Tistory.