ES /docs

batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/f08cf04288c144f38

RCA: AwsTask.batch_pull! ECS DescribeTasks Rate exceeded

Overview#

What Happened#

cupixworks-workerAwsTask.batch_pull! cron job 이 AWS ECS DescribeTasks API 를 호출하는 과정에서 AWS Throttling(Rate exceeded) 예외가 반복적으로 발생. 2026-07-13 16:06 KST 부터 16:34 KST 까지 약 28분 동안 169건의 error 로그가 기록되었으며, 모든 로그는 동일 host (ip-10-1-17-34.ap-southeast-1.compute.internal) 의 whenever cron runner 에서 발생함.

Quick Facts#

Field Value
exception.class AWS ECS Throttling (rescued as StandardError — 원본 class 미기록)
exception.message Rate exceeded
top_frame app/models/aws_task.rb:87
runtime Ruby (Rails whenever cron runner)
deploy production-ap-southeast-1-20260713T0734Z0-69260da5-cupixworks
env production, ap-southeast-1 (worker) → us-west-2 (ECS API 대상)

Affected Teams#

Team / Domain Error Count Impact
cupixworks-worker (AwsTask cron) 169 ECS task 상태 폴링 지연 — pending/blank 상태 record 가 최신화되지 못함

에러 자체가 사용자 트래픽을 직접 차단하지는 않지만, AwsTask.after_save 콜백을 통해 Job#run_task_stopped_callbacks 를 트리거하는 파이프라인이 지연될 수 있음.

Timeline#

  1. 2026-07-13 16:06 KST — 최초 batch_pull! ... Rate exceeded 발생 (first_seen)
  2. 2026-07-13 16:28–16:34 KST — 초당 여러 건 페이스로 반복 (169건 누적, last_seen 16:34 KST)
  3. 2026-07-13 16:58 KST — 동일 코드 경로에서 별개 오류 (ecs:DescribeTasks not authorized, taskId length should be one of [32,36]) 관측 — 본 클러스터와 별도로 조사 필요
  4. Status boardsvc:cupixworks-worker::unknown 인시던트 2026-07-13-svc-cupixworks-worker--unknown-2 에 포함, 다른 2개 cluster (8a242739..., 1ad7f6fa...) 와 함께 그룹화됨

Error Log#

Datadog Logs

text
batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/f08cf04288c144f384c0a517fc3ec322,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/522564c7ebb14f7a8ce1ba152e821400,arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/ed4c0e4d621e47e89a0d92f933a76d10 - Rate exceeded

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 169
  • 최초 발생: 2026-07-13 16:06 KST
  • 최근 발생: 2026-07-13 16:34 KST

Root Cause Summary#

Cupix::Cron::AwsTask.pull_blank / pull_pending cron 이 2분마다 실행되면서 AwsTask.batch_pull! 를 통해 AWS ECS DescribeTasks API 를 호출하는데, blank scope 가 500 개 이상의 record 를 매번 반환하고 있어 (16:38 KST 로그에서 blank scope 에 15086 ~ 15611 등 526 IDs 관측) BATCH_SIZE = 100 슬라이스 기준으로 한 cron 실행당 6+ 회의 API 호출이 발생. pull_pending 등과 동시 실행되면서 AWS ECS DescribeTasks account/region-level throttling 한도를 초과, Rate exceeded throttling 예외가 반복 발생. 코드의 rescue StandardError 는 로그만 남기고 재시도/backoff 없이 다음 슬라이스로 진행하며, 예외 class 정보(Aws::ECS::Errors::ThrottlingException 등) 도 로그에 남기지 않음.

Technical Analysis#

Code Path#

  • Entry point: lib/cupix/cron/aws_task.rb:3 (pull_blank — whenever every 2 minutes)
  • Batch call site: app/models/aws_task.rb:54-59 (ecs.describe_tasks)
  • Failure point: app/models/aws_task.rb:86-88 (rescue clause)

Cron 스케줄에서 pull_pendingpull_blank 가 2분마다 실행됨:

config/schedule.rb:29-35ruby
every 2.minutes do
  # runner 'Cupix::Cron::AwsTask.pull_all'
  runner 'Cupix::Cron::AwsTask.pull_pending'
  runner 'Cupix::Cron::AwsTask.pull_blank'
  runner 'Cupix::Cron::CopyRequest.run_waiting_copy_reqeusts'
  runner 'Cupix::Cron::SiteinsightsService.publish_polling_events_by_schedule'
end

blank scope 는 last_status, desired_status 가 모두 nil 인 record 를 반환:

app/models/aws_task.rb:18ruby
scope :blank, -> { where(archived_at: nil).where(last_status: nil, desired_status: nil) }

각 cron 실행마다 batch_pull! 로 넘어가 100개 단위로 describe_tasks 를 반복 호출:

app/models/aws_task.rb:45-89ruby
def self.batch_pull!(tasks)
  tasks = tasks.to_a
  return if tasks.empty?

  ecs = tasks.first.ecs_client
  cluster_name = $AWS[:ecs][:cluster_name]
  task_ids = tasks.filter_map(&:task_id)
  tasks_by_task_id = tasks.index_by(&:task_id)

  task_ids.each_slice(BATCH_SIZE) do |batch_ids|
    resp = ecs.describe_tasks({
      tasks: batch_ids,
      cluster: cluster_name,
      include: ['TAGS']
    })
    # ... 응답 처리 ...
  rescue StandardError => e
    Cupix::Logger.error("batch_pull! error on batch #{batch_ids.first(3).join(',')} - #{e.message}", class: name, function: __method__)
  end
end

ecs_client 는 매 record 마다 새 Aws::ECS::Client 인스턴스를 생성하며, region 은 환경변수로부터 결정:

app/models/aws_task.rb:91-93ruby
def ecs_client
  Aws::ECS::Client.new region: ENV['CUPIX_REGION'] || 'us-west-2'
end

기대 동작 vs 실제 동작:

  • 기대: blank/pending scope 크기가 소량으로 유지되며 API 호출이 산발적으로 발생
  • 실제: blank scope 에 500+ record 누적, 2분마다 6+ 회의 DescribeTasks 호출, 여기에 pull_pending, pull_running (일 1회), after_commit :pull (task 생성 시마다 개별 호출) 이 겹치면서 AWS ECS DescribeTasks 의 계정/region 단위 throttle 을 초과. AWS SDK 의 default retry (retry_limit: 3, exponential backoff) 이후에도 실패하는 케이스가 rescue 로 흡수됨

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-worker "batch_pull!" "Rate exceeded"

핵심 로그 (raw):

json
{
  "timestamp": "2026-07-13T07:34:22.712Z",
  "status": "error",
  "class": "AwsTask",
  "function": "batch_pull!",
  "message": "batch_pull! error on batch arn:aws:ecs:us-west-2:002596530511:task/cupix-tesla-ece/f08cf04288c144f384c0a517fc3ec322,... - Rate exceeded",
  "host": "ip-10-1-17-34.ap-southeast-1.compute.internal",
  "region": "ap-southeast-1",
  "environment": "production",
  "version": "production-ap-southeast-1-20260713T0734Z0-69260da5-cupixworks"
}

blank scope 크기가 500+ 임을 보여주는 info 로그 (첫 사고 시각 이후 첫 관측치):

text
Task pulling with blank scope : [15086, 15087, 15088, ... 15611]  (526 IDs)
service: cupixworks-worker, class: Cupix::Cron::AwsTask, function: pull_blank
2026-07-13T07:38:21Z

Datadog 쿼리:

text
service:cupixworks-worker "Task pulling"

에러 발생 빈도 (동일 host, 동일 함수): 16:28:52 ~ 16:34:22 KST 사이 약 5분 30초 동안 최소 10건이 2초 간격으로 발생 — each_slice 반복 내에서 슬라이스마다 throttle 됨을 시사.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 blank scope record 누적 + BATCH_SIZE=100 + 2분 cron 으로 인해 AWS ECS DescribeTasks throttle 초과 16:38 KST info 로그에서 blank scope 에 526 IDs 관측; each_slice(BATCH_SIZE) 반복 구조 (app/models/aws_task.rb:54); 에러 메시지가 정확히 AWS Throttling 시그니처 Rate exceeded Confirmed
H2 외부 AWS 리전 장애 Status board 조회 결과 dep:* 인시던트 없음 (svc:cupixworks-worker::unknown-2 만 존재); AWS us-west-2 관련 dep 인시던트 미검출 Rejected
H3 IAM 권한 문제 (not authorized) 가 root cause 동일 코드 경로에서 16:58 KST 부터 not authorized to perform: ecs:DescribeTasks 로그 관측 시간대가 본 클러스터 범위 (16:06–16:34 KST) 이후이며, 에러 메시지 시그니처가 다름 (Rate exceedednot authorized); 별개 사건으로 판단 Rejected (별개 이슈 — 별도 조사 필요)
H4 after_commit :pull 개별 콜백 폭주 app/models/aws_task.rb:29 에서 create 시마다 개별 describe_tasks 호출 발생 가능 발생 로그가 batch_pull! 시그니처만 있고 개별 Task fetched per-record 에러 시그니처 없음; 개별 호출 실패는 pullrescue 로 흡수되어 로그 없음 Inconclusive (기여 가능성 있음)

Fix Recommendation#

즉시 조치 (Critical)#

  • app/models/aws_task.rb:86-88 rescue 세분화 및 재시도 추가

    • Aws::ECS::Errors::ThrottlingException (그리고 SDK 가 최종 반환하는 Aws::Errors::ServiceError 하위 throttling 계열) 을 별도로 rescue 해 warn 레벨로 다운그레이드하고, exponential backoff + jitter 로 슬라이스 단위 재시도 수행
    • rescue StandardError 는 남겨두되 원본 exception class 를 함께 로깅 (e.class.name) — 현재는 e.message 만 남겨 후속 분석이 어려움
    • AWS SDK 의 기본 retry 설정 (retry_limit, retry_base_delay) 이 이 케이스에 대해 충분한지 검토 — Aws.config.update (config/environments/production.rb:129-135) 에 retry_limit, retry_mode: 'adaptive' 명시적 설정 고려
  • config/schedule.rb:29-35 cron 빈도 조정

    • pull_blank 를 별도 슬롯으로 분리하고 실행 간격을 5분 이상으로 늘려 pull_pending 과 동일 분에 겹치지 않도록 함
    • 또는 pull_blank 는 record 개수가 임계값 (예: 200개) 을 초과할 때만 실행하도록 guard 추가

단기 개선 (1주 이내)#

  • blank scope 누적 원인 조사
    • blank scope 는 last_statusdesired_status 가 모두 nil 인 record — 정상 흐름에서는 create 직후 after_commit :pull 로 상태가 채워져야 하지만 526 개가 남아있는 것은 pull! 실패 record 가 archive/cleanup 되지 않고 축적됨을 의미
    • AwsTask#pull (app/models/aws_task.rb:100-106) 이 실패시 조용히 false 반환하고 record 를 방치 — 실패 이력 추적 필드 추가 (예: pull_failed_at, pull_attempts) 또는 일정 시간 이상 blank 상태인 record 자동 archive
  • ecs_client 인스턴스 캐싱
    • batch_pull! 내부에서 tasks.first.ecs_client 로 한 번만 생성되므로 문제 없음; 다만 개별 after_commit :pull 경로는 record 마다 새 Aws::ECS::Client 인스턴스를 생성 (app/models/aws_task.rb:91-93) — thread-local / process-level 캐싱 고려

장기 개선 (재발 방지)#

  • 폴링 → 이벤트 기반 전환: AWS ECS Task state change 는 EventBridge event 로 push 가능. cron polling 대신 EventBridge → SQS/Lambda 로 상태 반영 시 API 호출을 근본적으로 줄일 수 있음
  • Cross-region 호출 감시: 로그의 host region (ap-southeast-1) 과 ECS ARN region (us-west-2) 이 상이 — 의도된 구성인지 확인 필요; 아니라면 region 별 workload 분리
  • Throttling 관측성: AWS ECS API 호출 count 및 throttling rate 를 Datadog 대시보드에 노출해 임계값 초과 전 알림

Monitoring#

  • 추가할 메트릭/알림
text
sum:trace.aws.ecs.errors{service:cupixworks-worker,error_type:throttling}.as_count()
text
logs("service:cupixworks-worker @class:AwsTask @function:batch_pull! \"Rate exceeded\"").index("*").rollup("count").by("host").last("15m")
text
logs("service:cupixworks-worker @class:Cupix::Cron::AwsTask @function:pull_blank \"Task pulling with blank scope\"").index("*").rollup("count").last("30m")

Note: 위 log-based query 들은 dashboard timeseries widget 에서 사용 가능한 형식. blank scope 실행 로그 카운트가 record 크기와 함께 급증할 때 알림.

Risk Assessment#

  • Risk level: medium (사용자 트래픽 직접 영향 없음, 그러나 task 상태 최신화 지연으로 Job#run_task_stopped_callbacks 지연 발생 가능)
  • 예상 복잡도: standard (rescue 세분화 + backoff 로직 추가는 표준적 변경, cron 간격 조정도 low-risk. blank scope cleanup 은 신중 필요)