TaskScheduler로 배치 작업 개선하기(2/3) - 시나리오 설계 및 가정
들어가며
이전 글에서 @Scheduled
와 TaskScheduler
의 개념 및 사용예시를 살펴보았다.
개념들을 이해했다면 @Scheduled
는 복잡한 스케줄 작업을 하는 데에 한계가 있다는 것을 알아차렸을 것이다.
이 포스팅에서는 @Scheduled
사용을 지양하게 된 시나리오를 보며 TaskScheduler
를 사용해야 하는 근거를 수치로 제시한다.
요구사항
- 알림 발송 기능
- 사용자가 설정한 목표 시간에 도착할 수 있도록 출발 시간을 계산
이는 클라이언트 측에서 예상소요시간을 계산해서 서버 측으로 전달한다.
- 출발해야 하는 시간 30분 전에 알림을 발송
- 사용자가 설정한 목표 시간에 도착할 수 있도록 출발 시간을 계산
- 편의성 제공
- 매번 동일한 출퇴근 경로를 검색하지 않도록 반복 기능을 제공
- 사용자는 원하는 도착 시간만을 설정하여 출발 시간을 생각할 필요가 없도록 한다.
알림 발송 시간
알림이 발송되어야 하는 시간 = 희망도착시간 - 예상소요시간 - 30분
- 계산에 필요한 데이터는 클라이언트 측에서 보내주므로, 알림을 특정 시간에 발송하는 작업에만 집중하면 된다.
시나리오 가정
[쿼리 비용]
쿼리 고정비용 5ms, 조회 데이터 1건당 0.01ms가 추가로 소요된다고 가정한다.
통신하는 데이터의 크기가 클수록 통신 시간은 늘어난다.
[FCM]
Firebase Cloud Messaging
은 단일 발송, 여러건 발송을 모두 지원하고, FCM에게 보내는 요청 자체는 큰 차이가 없다.
FCM에게 보내는 요청은 300ms가 소요된다고 가정한다.
FCM에서 요청을 받아 실제로 알림을 보내는 작업 시간은 고려하지 않는다.
또, 후술하겠지만 FCM에 작업 요청을 보내는 시간도 제외한다.
[선별 작업 비용]
모든 데이터들이 알림 설정된 것은 아니다.
알림 대상 데이터인지 애플리케이션 단에서 선별하는 작업이 필요하다.
1건당 0.05ms라고 가정한다.
[선별 크기]
데이터 10000건 중 동일한 시간에 예약된 작업(초단위)은 100개라고 가정한다.
두 방식의 작업 소요시간 계산
[@Scheduled]
데이터 10000건 불러오는 시간
- 5ms + (0.01ms x 10000) = 105ms
알림 선별 작업시간
- 하나의 쓰레드가 모두 처리하므로 순차적으로 처리한다고 가정한다.
- 0.05ms x 10000 = 500ms
FCM 요청 고정비용
- 300ms
총 작업시간
- 105ms + 500ms + 300ms =
905ms
[TaskScheduler]
병렬 처리 기능을 사용하며, 크기가 10인 쓰레드풀을 가지고 있다고 가정한다.
지정된 시간에 예약된 작업을 쓰레드 하나가 위임받아 처리하므로, 각 작업은 하나의 데이터를 가져온다.
데이터 1건 불러오는 시간
- 5ms + 0.01ms = 5.01ms
알림 1건 선별 작업시간
- 0.05ms
FCM 요청 고정비용
- 300ms
알림 1건에 대한 총 작업시간
- 5.01ms + 0.05ms + 300ms = 305.06ms
전체 알림 100건에 대한 작업시간
- 쓰레드풀에 10개의 쓰레드가 등록되어 있으므로, 동시에 10건 처리가능하다.
- 305.06ms x (100 / 10) =
3050.6ms
FCM 요청비용
사실 구현할 때엔 FCM unicast/multicast 비용 차이가 없다는 것을 인지하지 못하고 있었다.
때문에 알림 1건마다 FCM 단일요청을 보내는 방식이 더 비효율적이라는 것을 정리하면서 깨닫게 되었다..
그러나 큐에 한번에 모아서 multicast 요청으로 개선할 수도 있고, TaskScheduler
적용에 대해 좀 더 초점을 맞추고 싶기에 여기서는 FCM 요청 고정비용을 빼도록 한다.
최종비용
최종 작업비용은 다음과 같다.
@Scheduled
- 105ms + 500ms
-> 605ms
TaskScheduler
- (5.01ms + 0.05ms) x (100 / 10)
-> 50.6ms
개선율
결론
개선율을 끼워맞추긴 했지만, @Scheduled
방식의 오버헤드 자체가 크다.
초단위로 다음 작업이 이루어져야 하는데, 이전 작업이 1초 넘게 실행되는 경우 다음 작업은 건너뛰어질 수 있다.
ex. 0초에 실행한 작업이 1초가 지나서 끝난 경우, 1초에 실행되어야 하는 작업은 실행되지 않음
사용자는 알림 서비스를 믿고 하고 있는 일에 집중했는데, 위와 같은 상황이 발생하여 출발 시간을 놓쳐버린다면 서비스 탈퇴로 이어질 것이다.
따라서 TaskScheduler
를 사용하여 알림을 동적으로 예약하고, 단일 인스턴스의 자원 소모를 줄이고자 여러가지 시도를 진행하였다.
다음 포스팅에서는 TaskScheduler
를 어떻게 사용했는지, 어떤 시도들을 했는지 다뤄보겠다.