FCM Async Thread Pool 설계 가이드

AWS 프리티어에서 실제 트래픽 기반으로 스레드 풀 크기를 결정하기

덕지의 FCM 푸시 알림을 비동기로 처리하기 위해 스레드 풀을 설정해야 했는데, 정확히 어떤 크기로 설정해야 할지 몰랐습니다.

구글링 또는 대부분의 검색 결과는 대부분 추상적으로 작성되어 있었습니다.

“CPU 코어 수의 2-3배” ”프로파일링 결과에 따라 다릅니다.” ”시스템 리소스에 기반하여 적당히 설정”

이러한 검색 결과는 크게 도움이 되지 않았습니다. 서비스마다 특성이 다를텐데, 저의 서비스에서 정호가히 몇 개를 설정해야할 지 판단할 수 있는 기준이 없었습니다. 그래서 운영중인 덕지 서비스의 New Relic 실측 데이터를 기반으로, AWS Free Tier 인스턴스 환경에 딱 맞는 스레드 풀 수치를 산출하는 과정을 공유하고자 합니다.

1단계: 운영 데이터 수집

덕지 서비스는 NewRelic 모니터링 도구를 사용하고 있으며 이를 통해 트래픽을 측정합니다.

추상적인 값이 아닌 실제 데이터로 결정하기로 했습니다.

사용자가 가장 많던 시기를 분석하고, NewRelic의 NRQL(New Relic Query Language)을 사용해 7일간의 API 트래픽을 수집했습니다.

측정 결과, 핵심 수치는 다음과 같습니다.

시간대

RPM

RPS

의미

피크 (23-24시)

448

7.47

최대 트래픽

피크 시간대 평균 (21-01시)

300-450

5-7.5

저녁부터 자정

활동 시간대 평균 (17-08시)

50-150

0.83-2.5

일반적인 활동 시간

야간 (0-6시)

30-50

0.5-0.83

최소 트래픽

단위 해석:

  • RPM (Requests Per Minute) = 분당 요청 수

  • RPS (Requests Per Second) = 초당 요청 수

  • 변환: RPS = RPM ÷ 60

오후 11시부터 자정까지 급격하게 증가하는 패턴을 볼 수 있습니다.

이 피크 시간대(7.47 RPS)를 안정적으로 처리할 수 있는 스레드 수를 계산하면, 다른 모든 시간대의 트래픽은 자동으로 처리 가능합니다.

2단계: 필요 스레드 수 계산

스레드 풀의 크기를 결정하는 기본 공식은 다음과 같습니다.

이 공식을 이해하기 위해 먼저 용어를 정의합니다.

  • 처리량(RPS): 초당 처리해야 할 요청 수 (예: 7.47 RPS = 초당 7.47건)

  • 작업 시간: 한 작업이 얼마나 오래 걸리는가 (예: FCM API 호출은 약 200ms)

  • Core 스레드: 작업을 처리하는 데 항상 유지되는 스레드 수 (대기 중이라도 종료되지 않음)

  • Max 스레드: 작업 처리를 위한 생성할 수 있는 최대 스레드 수

덕지 서비스 인프라 조건을 확인하고 공식에 대입해 보겠습니다.

조건:

Core 스레드 계산:

  • 피크 시간에 초당 7.47건을 처리합니다.

  • 각 작업은 0.2초가 필요합니다.

  • 따라서 최소 1.49개의 스레드가 필요합니다.

  • 안전을 위해 2배 여유를 두면 Core 2개

Core 스레드 처리 능력:

즉, Core 2개는 초당 최대 10건을 처리할 수 있고, 우리의 피크 처리량은 7.47 RPS이므로 여유가 있습니다.

3단계: 여러 트래픽 상황에서의 검증

실제 운영 환경에서는 여러 상황이 발생합니다. 각 시나리오별로 설정이 충분한지 검증해 보겠습니다.

시나리오 1: 피크 시간 (오후 11시-12시, 448 rpm)

항목

설명

필요 처리량

7.47 RPS

피크 시간 실측 데이터

Core 2개 처리 능력

10 RPS

2 ÷ 0.2초

스레드 사용률

74.7%

7.47 ÷ 10

여유율

25.3%

충분함

판정

안정적으로 처리 가능

추가 스레드 불필요

피크 시간에도 Core 2개만으로 25% 여유를 두고 처리할 수 있습니다.

시나리오 2: 피크 시간대 평균 (오후 9시-새벽 1시, 375 rpm)

항목

필요 처리량

6.25 RPS

여유율

37.5%

판정

더욱 안정적 처리 가능

시나리오 3: 활동 시간대 평균 (오후 5시-새벽 8시, 100 - 150 rpm)

항목

필요 처리량

1.67 RPS

여유율

83.3%

판정

매우 안정적 처리 가능

시나리오 4: FCM API 지연 상황 (응답 지연 2배 증가)

이 상황은 최악의 시나리오로 가정했습니다.

만약 FCM API 호출 응답이 느려져서, 응답 시간이 0.2초 → 0.4초로 증가한다면 어떻게 될까요?

Core 스레드로 가능한 처리량:

Core 2개만으로는 필요 처리량을 달성하기에는 부족합니다. 이때 MaxPoolSize가 역할이 필요합니다.

MaxPoolSize=4로 설정했을 때:

FCM API 응답이 2배 지연되도 MaxPoolSize 4개로 안정적으로 처리할 수 있습니다.

4단계: 큐(Queue) 크기 결정

스레드 풀은 다음과 같이 동작합니다.

  1. Core 스레드(2개)가 작업을 처리

  2. Core가 모두 바쁘면, 들어오는 작업을 Queue에 대기

  3. Queue가 가득 차면, Max 스레드까지 추가 생성

  4. Max도 가득 차면, RejectedExecutionPolicy 정책에 따라 처리

Queue 크기 설정 기준은, 애플리케이션 종료 시점으로 설정하여 큐 크기를 계산했습니다.

애플리케이션 종료 시 데이터 유실 방지를 고려한 설정 값 결정

덕지의 서버 애플리케이션은 도커 컨테이너로 구동됩니다.

그리고 도커 컨테이너는 컨테이너가 종료될 때 graceful shutdown 시간을 갖습니다.

  • Dokcer 컨테이너 강제 종료까지 10초

  • Spring aswaitTerminationSeconds 설정 8초

이 8초 동안 Queue에 남아있는 모든 작업을 처리해야 합니다.

계산 과정

  1. 비동기 스레드 처리 속도

  1. Queue 50개의 처리 시간

  1. 검증

Queue에 최대 50개 작업이 쌓여있어도, 애플리케이션 종료 전에 안정적으로 모두 처리될 수 있고, 이는 데이터 유실 없으 처리할 수 있음을 의미합니다.

Last updated