📚 Notion | Apr 2024 ~ May 2024
- 대량의 영상 시청기록에 대한 통계 및 정산 Batch 작업
- 부하분산 및 장애복구 기능을 포함한 MSA 구조
-
통계 및 정산 기능
a. 2대의 Batch 서버에서 동시 작업
b. 각 Batch 서버에서 가상스레드를 활용
c. DB Partitioning
d. chunk read 동시성 제어
Batch 작업 멀티스레드 도입 : 플랫폼 스레드 vs 가상스레드
수억건의 시청기록 통계 및 정산 : 날짜별 파티션 vs 인덱스 일치검색
I/O 집약적인 프로젝트의 특징에 따른 가설
가설 1. 이 프로젝트는 CPU 사용에 있어 가상 스레드가 우위를 보일 것이다.
가설 2. 이 프로젝트는 메모리 사용에 있어 가상 스레드가 우위를 보일 것이다.
가설 3. 이 프로젝트는 처리량(동일 작업에 대한 Batch 작업 수행시간으로 측정)에 있어 가상 스레드가 우위를 보일 것이다.
검증 및 성능개선 : 플랫폼 스레드 대비 CPU Load 약 10% 절약 & 수행시간 약 24% 단축
-
jdk.CPULoad의 JVM User 지표 : 가상스레드가 유의미한 우위를 보임
-
JVM Heap 사용량 : 가상스레드가 전반적으로 더 많이 사용
-
처리량 : 가상스레드가 전반적으로 더 빠른 시간 내에 작업 처리
-
하루 시청기록 300만건, chunkSize 2000일 때의 비교
jdk.CPULoad
JVM UserJVM Heap used Batch job compeleted Platform Thread 52.2 % 26.8 MiB – 137 MiB 약 9~11초 (9s56ms, 10s922ms) Virtual Thread 42.8 % 27.5 MiB – 190 MiB 약 8~9초 (8s308ms, 8s832ms)
상세기록 : Batch: 가상 스레드 성능개선
통계작업 처리시간 단축 : 22s458ms → 3s402ms
- Singleton 패턴의 전역 캐시객체 도입
- processor 삭제 & writer 단계 전역 캐시 저장
- JobListener 활용, JobScope로 DB 갱신 작업
- 총 100만 행의 테이블, chunk size 20일 때 일간 통계 22s458ms → 3s402ms
상세기록 : 전역 캐시도입 성능개선
복합적인 문제원인
-
날짜별 DB Partition으로 인해 auto increment PK 사용 불가
-
chunk read 동시성 처리를 위한 새로운 sort 방식 필요
a. chunk paging을 위한 추가정렬 : FileSort 발생시 성능 대폭 저하
b. sort의 기준 칼럼이 unique 하지 않을 경우 잘못된 통계 결과 산출
해결방법
-
chunk paging을 위한 별도의 정렬 칼럼 인덱스 별도 생성 및 쿼리 최적화
-
사용중인 인터페이스(JdbcPagingItemReader)가 off-set 방식의 페이징을 하지 않는 것을 확인
상세 기록 : SpringBatch: chunk read 동시성 제어(JdbcPagingItemReader)
문제원인
-
synchronized 블록 안에서
VirtualThread.park()
가 발생하면 가상스레드는 CarrierThread에서 unmount 되지 않는다. -
MySQL JDBC 연결은 synchronized 키워드로 구현된 부분이 많다.
대안모색 및 결정
-
MySQL R2DBC 연결과 대조
-
MariaDB JDBC 연결과 대조
-
대조결과
a. 현재의 환경에서 MariaDB나 R2DBC를 통해 Virtual Thread Pinned가 눈에 띄게 감소하는 일은 없었다.
b. Virtual Thread Pinned 지표와 성능(수행시간, CPU부하 등) 사이에도 유의미한 관계는 발견되지 않았다. -
VirtualThread pinned issue를 완전히 해결하는 것은 시기상조라고 판단
a. DB연결과 관련된 것이 아니더라도, 다른 인터페이스에서 pinning 현상이 발생 가능
b. 예컨대 ConcurrentHashMap 구현체 등의 내부 메서드에서도 synchronized 블록 사용 -
기존의 JDBC 기반 Batch 작업 유지
상세 기록: VirtualThread pinned issue
- 부하가 큰 일부 서비스에 대해 다수 인스턴스 부하분산 기능 구현
- Spring Cloud Gateway & LoadBalancer & Eureka 활용
-
Feign Client로 통신하는 일부 서버에 대해 회복 탄력성 및 예비 서버로의 라우팅 기능 구현
-
resilience4J CircuitBreaker 활용
상세 파라미터
- 최근 100번의 호출에 대해 50% 실패하면 Circut Breaker 개방
- 개방 상태에서 10초 대기 후 반개방 상태로 전환
- 반개방 상태에서 허용되는 호출횟수 3회 제한, 복구 가능성 평가
resilience4j.circuitbreaker: instances: adFeignClient: registerHealthIndicator: true slidingWindowSize: 100 minimumNumberOfCalls: 10 permittedNumberOfCallsInHalfOpenState: 3 automaticTransitionFromOpenToHalfOpenEnabled: true waitDurationInOpenState: 10s failureRateThreshold: 50 eventConsumerBufferSize: 10
- DB main - replica 구조 적용
- main ec2에서 다른 2대의 ec2 인스턴스로 MySQL replication
- 일간, 주간, 월간 조회수 및 재생시간 Top5 조회
- 일간, 주간, 월간 사용자 정산내역 조회
- 주간 Top5 정보 및 주간 사용자 정산내역 pdf 형태로 메일발송