ES /docs

VoxelService 503 — downstream throttling/concurrency limit

RCA: failed to get captured area - error: 503 Service Unavailable

Overview#

What Happened#

2026-06-16 01:33–01:53 KST 동안 cupixworks-workerCupix::Cron::Facility.flush_stale_captured_size cron이 실행한 Cupix::VoxelService.captured_area! 호출이 voxel service($CUPIX_VOXEL_SERVICE_URL/captured_area)로부터 503 Service Unavailable 을 반환받았다. Cupix::HttpClient.put 의 자동 재시도(최대 3회, exponential backoff)에도 불구하고 끝까지 실패한 facility가 10건 발생했고, 결과적으로 해당 facility들의 captured_size 갱신이 한 cron 사이클에서 누락되었다.

Quick Facts#

Field Value
exception.class Cupix::Errors::System (code SYS20000)
exception.message failed to get captured area - error: 503 Service Unavailable
top_frame app/services/cupix/voxel_service.rb:97
env production, region us-west-2, tenant cupix
upstream $CUPIX_VOXEL_SERVICE_URL/captured_area (voxel-service)

Affected Teams#

Team / Domain Error Count Impact
voxel-service (upstream) 10 Partial unavailability of /captured_area endpoint
cupixworks-worker (Sidekiq cron) 10 captured_size stale flush skipped for 10 facilities in this cycle

확인된 영향 facility ID: 13933, 17291, 17815, 17821, 17839, 17842, 17853 (그 외 일부는 cron 시작 시점 facility ID 미확인).

Timeline#

  1. 2026-06-16 01:33 KST — 첫 503 발생 (captured_area!, occurrence 시작)
  2. 2026-06-16 01:42 KST — 일부 요청은 정상 응답 (Completed - total: 10788/34759); voxel service는 완전 다운 아님, 부분 불가용
  3. 2026-06-16 01:33–01:53 KST — 약 2분 간격으로 cron이 facility 1건씩 처리, 매번 503 발생 (총 10건)
  4. 2026-06-16 01:53 KST — 마지막 503 (Facility 17853)

Error Log#

Datadog Logs

text
failed to get captured area - error: 503 Service Unavailable

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 10
  • 최초 발생: 2026-06-16 01:33 KST
  • 최근 발생: 2026-06-16 01:53 KST
  • 사용자 영향: 직접적인 사용자 노출은 없음 (백그라운드 cron). 영향받은 facility의 captured_size 값이 stale 상태로 남았다가 다음 주기에 다시 시도됨. 단, voxel-service /captured_area 부분 불가용이 길어지면 facility footprint/billing 지표가 지연될 수 있음.

Root Cause Summary#

근본 원인은 upstream voxel-service의 /captured_area endpoint 부분 불가용이다. Cupix::HttpClient.put 이 이미 503을 retriable 코드로 등록하고 최대 3회 exponential backoff 재시도를 수행함에도 불구하고 모든 재시도가 503으로 실패했다. 같은 시각 (01:42 KST) voxel-service가 다른 captured_area 요청에는 정상 응답한 점(Completed - total: 10788)에서 voxel-service 인스턴스/파드 일부만 unhealthy 였거나, 특정 facility의 데이터가 처리에 실패해 503을 일관되게 반환했을 가능성이 높다. tesla 측 코드에는 새로운 결함이 식별되지 않았다.

Infra-side 기여 요인 (revision 1 추가): data-pipeline repo 의 cupix-data/services/voxel/main.tf 에서 captured_area Lambda timeout(300s, line 430)과 API Gateway integration timeout(30000ms, line 336)이 일치하지 않는 상태가 production 에 배포되어 있다. 과거 PR 83809(TSLA-11391 voxel 503 error 해결, 2025-12-18 머지)에서 두 timeout 을 300s/120s 로 맞춰 503 을 해결하려 했으나, PR 84305 (2026-01-07 머지)에서 revert 되어 API Gateway timeout 이 다시 30s 로 돌아간 상태이다. 즉 Lambda 가 30s 를 초과해 실행되면 API Gateway 가 integration 응답을 받지 못하고 5xx 를 반환할 수 있으며, 본 인시던트의 503 도 해당 timeout mismatch 의 재발 가능성이 있다 (확정은 voxel-service Lambda duration 로그 / API Gateway access log 대조 필요).

API Gateway 종류와 hard limit 확인 (revision 2 추가): voxel-service 는 terraform-aws-modules/apigateway-v2/aws 모듈을 사용하는 HTTP API (API Gateway v2) 이다 (cupix-data/services/modules/authorized_apigateway/main.tf:2-3, variable.tf:9-12 protocol_type default "HTTP"). HTTP API 의 integration timeout 은 30초가 hard limit 이며, REST API 와 달리 service quota 요청으로도 증액 불가능하다 (AWS 공식 문서). 따라서 PR 83809 에서 시도했던 timeout_milliseconds = 120000 설정은 Terraform plan 단계에서는 통과해도 실제 적용 시 무시되거나 30000ms 로 cap 되었을 가능성이 높고, 30s 를 초과해 실행되는 captured_area Lambda 호출은 API Gateway 단에서 5xx 로 끊긴다. 이 때문에 단순 timeout 값 조정만으로는 본 인시던트를 해결할 수 없으며, 아래 Fix Recommendation 에서 제시하는 (a) Lambda 실행 시간 단축, (b) 비동기 처리 패턴 전환, 또는 (c) API Gateway 우회(Lambda function URL / ALB / 직접 invoke) 중 하나가 필요하다.

Lambda 가 30s 동안 실제로 한 작업 (revision 3 추가): Datadog 로그(source:lambda print 출력)로 실패 facility nr0m13 의 invocation 한 건을 끝까지 추적했다 (captured_area.py 의 print 문은 lambda_handler 진입 시 / Athena 호출 시 / 결과 도착 시점에 모두 남는다).

시각 (KST) 단계 로그
01:50:57.0 Lambda 진입 (event 출력) "Voxel::CapturedArea | event: {...facility_key:nr0m13...}"
01:50:57.x Phase A — start_query_execution (partition_query) query id 2e117436-3ed2-49d9-b570-76a73d68d93c
01:50:57.x Phase A 완료, state: SUCCEEDED (ALTER TABLE ... ADD IF NOT EXISTS PARTITION — partition 이 이미 있으면 즉시 SUCCEEDED)
01:50:58.0 Phase B — start_query_execution (captured_area_query) query id 8f21d5d1-40cc-4261-a91f-394e650c208d
01:50:58 ~ 01:51:27 Phase C — while True: get_query_execution(...) busy-poll, sleep 없음 (captured_area.py:49-52) (이 시간 동안 Athena 가 tb_tesla_reality_captures × tb_raw_voxel join + COUNT(DISTINCT CONCAT(...)) aggregation 실행 중)
~01:51:27 API Gateway 가 30s integration timeout 초과로 연결 끊고 503 반환 Lambda 는 강제 종료, 따라서 state: SUCCEEDED 로그도, handler_response 로그도 남지 않음

증거: 동일 query id 8f21d5d1-40cc-4261-a91f-394e650c208d 으로 검색했을 때 state: 또는 result: 로그가 존재하지 않는다 (1건만 hit — 시작 로그). tesla 측 4회 재시도(첫 시도 + backoff 1s/2s/4s) 의 타임스탬프 간격이 32–34s 인 것도 매번 30s API Gateway timeout 에 정확히 맞아떨어진다 (01:50:57 → 01:51:29 → 01:52:01 → 01:52:35).

대조군 (성공 facility 3zyucx, level 73064, captured_area=10788): Lambda 진입(01:42:23) → handler_response 200(01:42:24) = 약 1초. ResultReuseConfiguration(captured_area.py:38-43, 12시간 cache) cache hit 또는 데이터량이 작아 Athena 실행이 1s 내에 끝난 케이스. 즉, 실패/성공의 차이는 코드 경로가 아니라 facility 별 Athena 쿼리 실행 시간이다.

4회 재시도 전체에 걸친 Phase A vs Phase B 시간 분해 (revision 4 추가): Revision 3 에서는 1회 invocation 만 트레이스했으나, 본 revision 에서는 nr0m13 facility 의 4회 재시도 모두에서 Phase A(partition_query) 와 Phase B(captured_area_query) 가 어디에서 시간을 소비하는지 동일하게 분해해 패턴 일관성을 확인했다.

재시도 Lambda 진입 Phase A 종료 (partition_query SUCCEEDED) Phase B 시작 (captured_area_query) Phase B 종료 / handler_response Lambda 강제 종료까지
#1 01:50:57 01:50:57 (2e117436-… SUCCEEDED, 동일 초) 01:50:58 (8f21d5d1-…) 로그 부재 ~30s 후 API GW 503
#2 01:51:29 01:51:29 (6e0a427b-… SUCCEEDED, 동일 초) 01:51:29 (215cbf3a-…) 로그 부재 ~30s 후 API GW 503
#3 01:52:01 01:52:02 (5256f235-… SUCCEEDED, 1s) 01:52:02 (18949946-…) 로그 부재 ~30s 후 API GW 503
#4 01:52:35 01:52:36 (f947b57e-… SUCCEEDED, 1s) 01:52:36 (8d926492-…) 로그 부재 ~30s 후 API GW 503

(데이터 출처: Datadog 쿼리 "Voxel::CapturedArea" 16:50–17:00Z, 그리고 4개 captured_area_query id 각각에 대한 단독 검색 — 4건 모두 state: / result: 로그가 한 줄도 hit 되지 않음.)

이로부터 다음을 단정할 수 있다:

  • Phase A 는 항상 0–1s 에 끝난다. 4회 모두 partition_query 가 동일 second 또는 +1s 에 SUCCEEDED. ALTER TABLE … ADD IF NOT EXISTS PARTITION 은 partition 이 이미 존재하므로 사실상 no-op (captured_area.py:104-107).
  • Phase B 는 4회 모두 결과 미반환 상태로 강제 종료. captured_area_querystate: SUCCEEDED / result: / handler_response - status_code: print 출력(captured_area.py:53, 58, 21) 가 4회 모두 부재. Lambda 가 while True: get_query_execution() (captured_area.py:49-52) busy-poll 도중 외부에서 invocation 이 끊긴 명백한 신호.
  • 재시도 간격 32–34s 가 매번 일관됨. 30s timeout + Cupix backoff(1s/2s/4s, http_client.rb:177 (2**(attempt-1)) + rand(0.0..0.5) jitter) 가 정확히 맞아떨어져, 매 재시도마다 동일 메커니즘으로 끊김을 추가 입증.
  • 성공 invocation 과의 시간 분포 차이가 결정적. 같은 cron 사이클의 다른 facility 들 (amrfc5, cvewtl, 2w5997, 8j7f35, 6alzdt, hi87b5, 3fq7tj, 8255p6, 12gwvg, ym21lk, 2uqu17, xf0u4z, efbnoa, zz10ne, cvxwmj, hx0c3c 등) 은 모두 진입 → 200 까지 1–2s 이내에 끝났다 (Datadog "handler_response - status_code: 200" 검색 결과). 같은 코드 경로, 같은 Lambda, 같은 시간대지만 facility 데이터량/cache 상태 차이로 일부 facility 만 30s 를 넘긴다.

결론적으로 "Lambda 가 30s 동안 한 작업"의 답은 "captured_area.py:124 의 run_athena_query(captured_area_query, …) 안 line 49-52 의 busy-poll loop 에서 Athena 응답을 기다리는 것" 이며, 그 동안 실질적으로 일을 하는 것은 Lambda 가 아니라 Athena 워커이다. Lambda 자체의 CPU 시간은 거의 boto3 get_query_execution API 호출 직렬화/역직렬화 비용뿐이고, 30s 의 거의 100% 는 Athena 의 tb_raw_voxel × tb_tesla_reality_captures join + COUNT(DISTINCT CONCAT(...)) aggregation 실행 시간이다.

Bottleneck 위치 확정: 30s 의 99% 는 captured_area.py:124run_athena_query(captured_area_query, …) 안에서, 더 정확히는 while True: athena.get_query_execution(...) (line 49-52) 가 Athena 의 SUCCEEDED 상태를 기다리는 시간이다. Lambda 자체는 거의 idle (no-sleep busy-poll = get_query_execution API call rate-limited 상태로 millisecond 단위 polling). 따라서 "Lambda 가 무엇을 하느라 오래 걸렸나" 의 답은 "Lambda 자체는 거의 아무것도 안 하고 Athena 쿼리 결과를 기다리는 중" 이며, 실제로 오래 걸린 작업은 Athena 가 수행한 다음 SQL 의 실행이다 (captured_area.py:111-123):

captured_area.py:111-123sql
WITH rc (model_id, model_type, level_id, record_id, facility_key) AS (
  SELECT * FROM tb_tesla_reality_captures
  WHERE facility_key = '<facility_key>'
    AND (model_type = 'pointcloud' OR model_type = 'capture')
)
SELECT rc.level_id AS level_id,
       COUNT(DISTINCT CONCAT(
         CAST(v.x * 10 AS varchar), CAST(v.y AS varchar),
         REVERSE(CAST(v.x * 10 AS varchar)), REVERSE(CAST(v.y AS varchar))
       )) AS "captured_area"
FROM tb_raw_voxel AS v, rc
WHERE v.model_id = rc.model_id AND v.model_type = rc.model_type
GROUP BY rc.level_id

이 쿼리가 30s 내에 끝나지 못하는 이유는 (a) tb_raw_voxel 이 facility_key 로 partition 되어 있지 않아 facility 의 모든 model 에 매칭되는 voxel row 를 broad scan 해야 하고, (b) COUNT(DISTINCT CONCAT(... 4개 CAST/REVERSE ...)) 가 row 마다 string concatenation + distinct hash 계산을 요구해 facility 의 voxel 수에 비례해 비용이 증가하기 때문이다. ResultReuseConfiguration cache miss 시 (직전 12h 내 동일 쿼리 미실행) cold path 가 30s 를 쉽게 초과한다. 영향받은 facility 들 (nr0m13, c26ton, 5sfq6v, wu5ba, 493wfh 등) 은 voxel 누적량이 커서 cold path 가 일관되게 30s 를 넘는 것으로 보인다.

성공 invocation 의 print-flush 패턴으로 부재 증거 강화 (revision 5 추가): Revision 3/4 에서 "실패 invocation 의 state: SUCCEEDED / result: 로그가 부재" 한 것을 Lambda 강제 종료 신호로 사용했다. Revision 5 에서는 같은 시각 같은 Lambda 인스턴스의 성공 invocation 을 단독 검색해 print-flush 자체는 정상 작동함을 확정했다. 3zyucx facility 의 captured_area_query (d06e578f-99e0-4eae-a995-5c1bca60d66b) 는 다음 세 줄을 동일 second(01:42:24) 에 모두 emit 했다:

Datadog logs: query d06e578f (3zyucx, SUCCESS)text
01:42:24  state: SUCCEEDED
01:42:24  result: {"UpdateCount": 0, "ResultSet": {"Rows": [["level_id","captured_area"],["73064","10788"]]}, "ResponseMetadata": {"HTTPStatusCode": 200, ...}}
01:42:24  handler_response - status_code: 200, body: {"facility_key": "3zyucx", "results": {"total": 10788, "details": [{"level_id": 73064, "captured_area": 10788}]}}

captured_area.py:53, 58, 21 의 print 출력 메커니즘은 정상 동작한다. 따라서 실패 4 invocation 에서 동일 세 줄이 한 줄도 안 남은 것은 Athena 가 결과를 주기 전 Lambda 자체가 종료 됐다는 결정적 증거이며, "로그 유실" 같은 대체 가설을 배제한다.

Lambda 종료 메커니즘 추가 확인 (revision 5 추가): Datadog 에서 "Task timed out" 키워드 (Lambda 자체 timeout 도달 시 AWS Lambda runtime 이 stderr 로 출력하는 표준 메시지) 를 같은 1시간 윈도우에서 검색 — 0 건 hit. 또한 "ThrottlingException" "captured_area"0 건 hit. 이로부터:

  • Lambda 는 자체 timeout(300s) 으로 죽지 않았다. 즉 captured_area_query 는 Lambda 가 살아 있는 동안 SUCCEEDED 도, FAILED 도, CANCELLED 도 아닌 상태로 계속 RUNNING 이었고, 외부에서 (API Gateway 가 30s integration timeout 으로) invocation 을 끊었다.
  • captured_area.py:49-52 의 busy-poll (while True: athena.get_query_execution(...)time.sleep 없음) 은 Athena GetQueryExecution API 의 기본 quota (100 TPS) 에 가깝게 호출하지만 botocore standard retry(captured_area.py:11-15 max_attempts: 3) 로 흡수되어 ThrottlingException 까지는 가지 않았다.

Lambda 가 자체로 한 "일"의 정량적 본질 (revision 5 추가): 위 두 사실을 종합하면 30s 동안 Lambda 가 실제로 수행한 작업은 다음과 같이 분해된다:

비중 누가 무엇을
~수십 ms Lambda event JSON 파싱(:95), facility_key 추출(:98-100), start_query_execution(partition_query)(:30) → 동일 second SUCCEEDED
~수십 ms Lambda start_query_execution(captured_area_query)(:124 → :30) submit
~30,000 ms Athena tb_raw_voxel × tb_tesla_reality_captures join + COUNT(DISTINCT CONCAT(...)) 실행 (RUNNING 상태)
(병행) ~30,000 ms Lambda while True: athena.get_query_execution(...) busy-poll — 초당 수십~수백 회 GetQueryExecution RPC 호출, 결과 RUNNING 상태 확인만 반복

즉 "Lambda 가 어떤 작업을 하느라 30s 가 걸렸는가" 의 정확한 답은 "Lambda 는 30s 동안 Athena GetQueryExecution 을 busy-poll 하면서 RUNNING 응답을 받는 것 외에 다른 작업을 하지 않았다. 실제 30s 짜리 작업은 Athena 가 SQL 쿼리를 실행한 것" 이다. Lambda 자체에는 비즈니스 로직 (parse_captured_area_result 등) 도, S3 IO 도, 외부 다운스트림 호출도 없다 — 단지 Athena 폴링 클라이언트 역할만 수행한다.

Technical Analysis#

Code Path#

Cron entry → service call → HTTP call:

lib/cupix/cron/facility.rb:90-106ruby
def flush_stale_captured_size
  session = Cupix::Initializer::User.tesla_internal_user.default_session
  if session.blank?
    Cupix::Logger.info('Failed to flush stale captured size: session is nil or blank', class: self.name, function: __method__, module: 'Cupix::Cron')
    return
  end

  ::Facility.stale_captured_size.limit(30).find_each do |facility|
    Cupix::Logger.info("Flushing stale captured size for Facility #{facility.id}", class: self.name, function: __method__, module: 'Cupix::Cron')

    if facility.calculate_captured_size(session: session)
      Cupix::Logger.info("Successfully flushed stale captured size for Facility #{facility.id}", class: self.name, function: __method__, module: 'Cupix::Cron')
    else
      Cupix::Logger.warn("Failed to flush stale captured size for Facility #{facility.id}", class: self.name, function: __method__, module: 'Cupix::Cron')
    end
  end
end

calculate_captured_size (rescue가 있는 버전)이 calculate_captured_size! 를 호출:

app/models/concerns/voxel_module.rb:19-56ruby
def calculate_captured_size(session: nil)
  calculate_captured_size!(session: session)
rescue => e
  Cupix::Logger.error("failed to calculate captured size for #{self.class.name} ID: #{id}, error: #{e.message}", class: self.class.name, function: __method__, model: { id: id, type: self.class.name })
  false
else
  true
end

def calculate_captured_size!(session: nil)
  if respond_to?(:captured_size)
    if self.instance_of?(::Facility)
      Cupix::VoxelService.calculate_captured_size!(facility: self, session: session)
    # ...
    end
  end
end

Failure point — captured_area! 의 RestClient::Exception rescue:

app/services/cupix/voxel_service.rb:88-99ruby
begin
  response = Cupix::HttpClient.put("#{$CUPIX_VOXEL_SERVICE_URL}/captured_area", body.to_json, headers)
  body = JSON.parse(response.body)
  Cupix::Logger.info("Completed - total: #{body.dig('results', 'total')}, ...", ...)
  body['results']
rescue RestClient::Exception => e
  Cupix::Logger.error("failed to get captured area - error: #{e.message}", class: self.name, function: __method__, facility_key: params[:facility_key], group_by_record: params[:group_by_record])
  raise Cupix::Errors::System.new(code: 'SYS20000', reason: "failed to get captured area - error: #{e.message}")

HTTP layer에 이미 503 재시도가 포함되어 있음:

lib/cupix/http_client.rb:8-67ruby
RETRIABLE_STATUS_CODES = [429, 502, 503, 504].freeze
MAX_RETRIES = 3

def self.put(url, payload, headers = {}, retries: MAX_RETRIES)
  attempt = 0
  begin
    RestClient.put(url, payload, headers)
  rescue RestClient::Exception => e
    if RETRIABLE_STATUS_CODES.include?(e.http_code) && attempt < retries
      attempt += 1
      sleep((2**(attempt - 1)) + rand(0.0..0.5))
      retry
    end
    raise
  end
end

기대 동작: voxel-service /captured_area 가 200 응답, body['results'] 반환 → captured_size 업데이트. 실제 동작: voxel-service 가 4회(1회 + 3회 재시도) 모두 503 반환 → Cupix::Errors::System raise → 상위 calculate_captured_size rescue 가 false 반환 → cron은 다음 facility 처리 계속.

Log Evidence#

Datadog query (Worker, captured_area errors only):

text
service:cupixworks-worker @class:"Cupix::VoxelService" status:error
  • 검색 시간 범위: 2026-06-15T16:00:00Z ~ 2026-06-15T17:30:00Z (UTC).
  • 결과: 11건 모두 failed to get captured area - error: 503 Service Unavailable. merge_voxel/add_partition/remove_cache 등 다른 voxel-service 호출에는 에러 없음 → endpoint 단위 부분 불가용.

같은 시간대 정상 응답 (voxel-service 가 완전 다운이 아님을 입증):

json
{
  "timestamp": "2026-06-16 01:42:25",
  "status": "info",
  "message": "Completed - total: 10788, details: [{\"level_id\"=>73064, \"captured_area\"=>10788}]",
  "class": "Cupix::VoxelService",
  "function": "captured_area!"
}
json
{
  "timestamp": "2026-06-16 01:42:23",
  "status": "info",
  "message": "Completed - total: 34759, details: [{\"level_id\"=>73398, \"captured_area\"=>5054}, {\"level_id\"=>73062, \"captured_area\"=>19829}, {\"level_id\"=>73399, \"captured_area\"=>9876}]"
}

Cron trigger 확인 (각 503 직전에 Flushing stale captured size for Facility N 로그):

text
service:cupixworks-worker "Flushing stale captured size"
text
2026-06-16 01:51:00  Flushing stale captured size for Facility 17853
2026-06-16 01:48:52  Flushing stale captured size for Facility 17842
2026-06-16 01:46:42  Flushing stale captured size for Facility 17839
2026-06-16 01:44:33  Flushing stale captured size for Facility 17821
2026-06-16 01:42:25  Flushing stale captured size for Facility 17815

각 cron 사이클(약 2분 간격)에서 첫 facility 처리 시 일관되게 503 발생. cron의 find_each는 stale facility 30개를 처리하도록 되어 있으나, 한 사이클에서 1개 facility만 에러 로그가 남는 것은 Cupix::HttpClient 재시도(최대 ~3.5초 지연 × 3회) + voxel-service 응답 대기 때문에 cron의 시간 budget 내에서 1건만 시도된 후 다음 trigger에서 다음 facility를 다시 fetch한 것으로 보인다 (uncertain — cron 스케줄/budget 동작 미확인).

Stale facility 별 에러 분포 (10 occurrences, unique facilities):

text
Facility IDs: 13933, 17291, 17815, 17821, 17839, 17842, 17853 (그 외 cluster 시작 시점 일부)

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Upstream voxel-service /captured_area endpoint 부분 불가용 (특정 인스턴스/파드 unhealthy 또는 특정 데이터 처리 실패) 동시간대 동일 endpoint 의 다른 요청은 200 성공 (Completed - total: 10788 @ 01:42:25), 503은 captured_area 만 발생, merge_voxel/add_partition 무에러 Confirmed
H2 tesla 측 retry 누락 또는 잘못된 backoff Cupix::HttpClient.put 가 503을 RETRIABLE_STATUS_CODES 에 포함, exponential backoff 3회 수행 (http_client.rb:8-67) Rejected
H3 Voxel service 전체 다운 (네트워크/DNS/인프라) 503 다수 같은 시간 voxel-service 가 200 응답한 정상 로그 존재 (captured_area! Completed @ 01:42:23, 01:42:25) Rejected
H4 잘못된 request payload (body 형식 오류) 동일 captured_area! 코드 경로의 다른 facility 요청이 정상 처리됨; 503은 보통 서버 측 unavailability 코드이지 client error 가 아님 Rejected
H5 Auth token 만료/문제 Auth 실패는 401/403 이며 503 이 아님; 동일 cron(tesla_internal_user.default_session)이 다른 호출에서는 성공 Rejected
H6 data-pipeline의 API Gateway ↔ Lambda timeout mismatch (30s vs 300s) 가 5xx 유발 data-pipeline/cupix-data/services/voxel/main.tf:336 integration timeout 30000ms vs :430 Lambda timeout 300s. 과거 PR 83809 가 동일 문제로 두 값을 맞춘 적 있음 (TSLA-11391 voxel 503 error 해결). 해당 PR 은 PR 84305(2026-01-07)에서 revert 되어 production 에 mismatch 상태가 다시 적용됨. revision 2: voxel-service 가 HTTP API (v2) 이므로 integration timeout 30s 가 hard limit — Lambda 가 30s 초과 시 항상 끊긴다. revision 3: Lambda CloudWatch print 로그 트레이스로 확정 — nr0m13 invocation 의 captured_area_query (8f21d5d1-...) 가 시작 후 SUCCEEDED 로그가 영영 남지 않았고, tesla 재시도 간격(32–34s)이 30s API GW timeout 에 정확히 일치 API Gateway HTTP API 의 30s timeout 시 응답 코드는 환경에 따라 503 으로 관찰됨 (504 가 아닐 수 있음). 본 인시던트의 503 은 이 timeout 발 5xx 와 일치 Confirmed (revision 3)

Fix Recommendation#

즉시 조치 (Critical)#

  • voxel-service 측 조사가 필요한 사안. data-pipeline-functions repo 의 /captured_area handler 와 인프라(파드 health, DB connection pool, downstream dependency) 를 점검해 503을 반환한 원인을 식별. tesla 측 코드 변경은 필요하지 않음.
  • voxel-service 측 503 발생 시점의 로그(특히 5xx 직전 stack trace, OOM/timeout)를 대조하여 root cause 가 (a) 인스턴스 하나의 unhealthy 인지 (b) 특정 facility 데이터 처리 실패인지 (c) API Gateway timeout 인지 구분.
  • data-pipeline repo 의 timeout mismatch 재검토 (revision 1 추가): cupix-data/services/voxel/main.tf:336 의 API Gateway integration timeout 이 30000ms 인 반면 Lambda(:430) 는 300s 로 설정되어 있다. 과거 동일 문제로 머지된 PR 83809(TSLA-11391) 가 PR 84305(2026-01-07) 에서 revert 되었으므로, revert 사유(commit message: "datadog 에서 lambda 실행 시간이랑 api_gateway timeout 시간이 달라서 에러 발생" — revert 메시지가 원본 PR 메시지를 그대로 포함하고 있어 의도가 모호) 를 PR 84305 작성자(Dominik Oh)에게 확인하고, 재적용 또는 별도 해결책(Step Functions / async invocation 등) 을 결정해야 함.

API Gateway 30s hard limit 우회 방안 (revision 2 추가)#

voxel-service 가 HTTP API (v2) 이므로 integration timeout 30s 는 hard limit 이다 (service quota 증액 불가). 따라서 단순 Terraform 값 조정으로는 해결 불가하며, 아래 옵션 중 하나를 선택해야 한다.

옵션 A — Lambda 실행 시간 단축 (가장 적은 변경):

  • Revision 3 의 트레이스로 확인된 바, 30s 의 거의 전부는 captured_area.py:124 의 captured_area_query Athena 실행 시간이다 (Lambda 자체는 :49-52 의 busy-poll 로 idle 상태 대기). 따라서 단축 노력은 Athena 쿼리 최적화 에 집중해야 한다:
    • tb_raw_voxelfacility_key 로 partition (현재는 tb_tesla_reality_captures 만 partition) — 가장 큰 효과 예상. 단, ETL 파이프라인 변경 필요.
    • COUNT(DISTINCT CONCAT(CAST(v.x*10 AS varchar), ..., REVERSE(...))) 의 4-way string concat → 정수 해시(xxhash64) 또는 (x, y) tuple 기반 distinct 로 교체.
    • facility 별 captured_area 를 daily/hourly precompute 한 materialized table 을 만들고 captured_area_query 가 해당 테이블만 읽도록 변경.
  • ResultReuseConfiguration 이 12시간 cache 로 이미 활성화(captured_area.py:38-43) — cron 이 같은 facility 를 12h 내 재호출하면 cache hit 으로 1s. 본 인시던트의 Stale flush cron 은 stale 조건 facility 만 처리하므로 cache hit 률 낮음.
  • 장점: 인프라 변경 최소(쿼리 변경만). 단점: 데이터 증가 시 다시 30s 초과 가능 — 근본 해결 아님.

옵션 B — 비동기 (job submit + poll/webhook) 패턴 전환 (권장):

  • captured_area endpoint 를 POST /captured_area/jobs (즉시 202 + job_id 반환) + GET /captured_area/jobs/{id} (결과 조회) 로 분리. Lambda 는 Athena start_query_execution 만 호출하고 종료, 결과 polling 은 별도 status endpoint 또는 EventBridge Athena state-change 로 처리.
  • tesla 측 Cupix::Cron::Facility.flush_stale_captured_size (이미 Sidekiq 백그라운드 cron) 이 호출자이므로 동기 응답 의존성이 약하다 (tesla/app/services/cupix/voxel_service.rb:31-37 — 결과를 바로 facility.update!(captured_size: …) 에 쓰지만, 다음 cron cycle 까지 지연 허용 가능).
  • 장점: 30s hard limit 영향 없음, 모든 long-running endpoint 에 재사용 가능. 단점: tesla cron + voxel-service 양쪽 변경 필요, job state 저장소 (DynamoDB / Athena query_execution_id) 도입.

옵션 C — API Gateway 우회 (Lambda Function URL / ALB / 직접 invoke):

  • HTTP API 대신 Lambda Function URL (timeout 15분, async invocation 시 무제한) 또는 ALB → Lambda (ALB idle timeout 4000s 까지 설정 가능) 로 교체. 인증은 IAM signing(SigV4) 또는 Lambda authorizer 재사용.
  • 또는 tesla 가 voxel-service Lambda 를 SDK 로 직접 Lambda.invoke (synchronous, 15분 timeout) 호출 — API Gateway 자체를 제거. cupixworks-worker 에 IAM role + lambda:InvokeFunction 권한 추가 필요.
  • 장점: 캐스케이드 변경 적음, captured_area 외 다른 long-running endpoint(merge_voxel 등 timeout 120s Lambda 도 동일 위험) 에도 적용 가능. 단점: API Gateway 기반 인증/throttling/CORS 손실 — 대안 인증 필요.

의사결정 가이드:

  1. CloudWatch 로 captured_area Lambda p95/p99 duration 측정 → 30s 미만이면 옵션 A 로 충분
  2. p95 가 30s 근처거나 초과하면 옵션 B 또는 C 선택. 호출 빈도가 낮은 cron-only 호출이면 옵션 C(직접 Lambda invoke) 가 변경 비용 최저
  3. voxel-service 의 다른 endpoint(merge_voxel Lambda timeout=120s, add_partition 등) 도 동일 hard-limit 영향권 — 종합 설계 시 옵션 B(공통 async 프레임워크) 가 장기적으로 유리

단기 개선 (1주 이내)#

  • Cron에서 503 같은 transient upstream 실패를 별도 카테고리로 집계해 facility 별 연속 실패 횟수가 임계치를 넘으면 alert. 현재 구현은 Cupix::Logger.warn 만 남기고 다음 cycle 에 그대로 재시도되어, 영구 503 facility 가 있을 경우 무한 재시도가 됨.
  • Cupix::VoxelService.captured_area! 의 error log 에 e.http_coderesponse.body 를 함께 남겨 향후 5xx 디버깅을 가속화.

장기 개선 (재발 방지)#

  • voxel-service 의 partial unavailability 를 감지하는 health check + circuit breaker (tesla 측). Cupix::HttpClient 에 endpoint 별 circuit breaker 도입 고려.
  • voxel-service 측 /captured_area endpoint 의 SLO 정의 + Datadog APM trace 활성화로 어떤 단계에서 503이 반환되는지 가시화.

Monitoring#

추가할 monitor (Datadog timeseries widget 호환 형식):

text
sum:trace.rack.request.errors{service:cupixworks-worker,resource_name:"captured_area"}.as_count()
text
logs("service:cupixworks-worker @class:Cupix::VoxelService @function:captured_area! status:error").index("*").rollup("count").by("@facility_key")

기존 cron 성공률 추적:

text
logs("service:cupixworks-worker @class:Cupix::Cron::Facility @function:flush_stale_captured_size status:warn").index("*").rollup("count")

Voxel-service 측에도 /captured_area 5xx rate alert 가 필요하나, voxel-service 의 monitoring stack 은 이 RCA 범위 밖.

Risk Assessment#

  • Risk level: low (현재 인시던트 자체는 백그라운드 cron 의 일시적 실패로, 다음 cycle 에서 자동 재시도됨. 단 voxel-service 부분 불가용이 장기화되면 medium 으로 격상)
  • 예상 복잡도: trivial (tesla 측 변경 불필요, 단기 개선 항목은 logging/observability)

Revision History#

Revision 1#

Feedback: "data-pipeline repo 확인해바" — voxel-service 인프라(API Gateway / Lambda) 정의가 있는 data-pipeline repo 를 직접 점검하여 503 에 기여하는 infra-side 요인이 있는지 확인 요청.

판정:

피드백 항목 판정 근거
data-pipeline repo 점검 수용 repo-mapping 결과 voxel-service infra 는 data-pipeline repo ($REPOS_DIR/data-pipeline/cupix-data/services/voxel/main.tf) 가 소유. 직접 탐색하여 다음 사실 확인: (1) captured_area Lambda timeout = 300s (main.tf:430) (2) API Gateway integration timeout = 30000ms (main.tf:336) (3) production us-west-2 deployment 이 동일 모듈 사용 (aws2/cupix/production/us/terragrunt.hcl:4,12) (4) 과거 동일 문제로 머지된 PR 83809 (TSLA-11391 voxel 503 error 해결, 2025-12-18) 가 PR 84305 (2026-01-07, commit f0033d2) 에서 revert 되어 production 이 mismatch 상태로 회귀됨. 본 인시던트(2026-06-15) 는 revert 이후 5개월간 누적된 환경에서 발생.
위 사실이 본 503 의 직접 원인인가? 부분 수용 API Gateway integration timeout 초과 시 표준 응답은 504 Gateway Timeout 이며, 본 인시던트는 503. 다만 (a) Lambda invocation 동시성 throttling 시 503 발생 가능 (b) PR 83809 가 동일 증상("voxel 503 error") 명목으로 머지됐던 점은 두 timeout mismatch 가 503 형태로 표면화된 전례를 시사. 직접 인과 확정에는 voxel-service Lambda CloudWatch duration 로그 / API Gateway access log (HTTP 503/504 분포, integration latency) 가 추가 필요. → Hypotheses 표에 H6 으로 별도 추적, Fix Recommendation 즉시 조치에 PR 84305 revert 사유 재확인 항목 추가.

변경 사항:

  • ## Root Cause Summary 에 "Infra-side 기여 요인" 단락 추가 — data-pipeline repo 의 timeout mismatch 와 PR 84305 revert 사실을 file:line 근거와 함께 기재
  • ## Hypotheses Considered 에 H6 추가 (Plausible — 추가 조사 필요)
  • ## Fix Recommendation > 즉시 조치data-pipeline repo 의 timeout mismatch 재검토 항목 추가 (PR 83809 / PR 84305 / 작성자 추적 포함)

추가 조사 내용:

  • $REPOS_DIR/data-pipeline repo 의 git log 점검 (최근 20 commit). PR 83809 → PR 84305 revert chain 식별
  • cupix-data/services/voxel/main.tf 의 API Gateway 4 endpoint(merge / captured_area / add_partition / remove_cache) timeout 모두 30000ms 로 회귀된 상태 확인 (:328, :336, :344, :352)
  • aws2/cupix/production/us/terragrunt.hcl 로 production us-west-2 가 해당 모듈을 사용함을 확인 — 본 클러스터 region(us-west-2) 과 일치
  • Lambda code 위치는 data-pipeline-functions/services/voxel/lambda/captured_area.py 로 확인했으나 본 revision 에서는 timeout/infra 측면만 조사 (handler 내부 로직은 범위 밖)

Revision 2#

Feedback: "aws api gateway timeout 시간이 hard limit 이던데 해결방안있나" — Revision 1 에서 제시한 timeout mismatch 해결안이 AWS API Gateway 의 hard limit 때문에 단순 값 조정으로는 불가능할 수 있다는 지적. 우회 방안 요청.

판정:

피드백 항목 판정 근거
API Gateway integration timeout 이 hard limit 인지 수용 voxel-service 는 terraform-aws-modules/apigateway-v2/aws 를 사용하는 HTTP API (API Gateway v2) 임을 코드로 확인 (cupix-data/services/modules/authorized_apigateway/main.tf:2-3 source 선언, variable.tf:9-12 protocol_type default "HTTP"). AWS 공식 문서 (apigateway/latest/developerguide) 에 따라 HTTP API integration timeout 은 30초 hard limit 이며 service quota 증액 불가. REST API 는 2024-06 이후 quota 요청으로 29s 이상 가능하나 throttle 감소 tradeoff 가 있고, voxel-service 는 REST API 가 아님. → Revision 1 의 timeout 값 조정안 (PR 83809120000ms) 은 hard limit 을 초과하므로 실효성 없음.
해결방안 존재 여부 수용 3개 옵션 식별 — (A) Lambda 실행 시간 단축 (captured_area.py:49-52 Athena polling + 쿼리 최적화), (B) 비동기 job-submit + poll 패턴 전환 (호출자 tesla/app/services/cupix/voxel_service.rb:31-37 이 이미 Sidekiq cron 이라 동기 응답 의존성 약함), (C) API Gateway 우회 (Lambda Function URL 15분 / ALB 4000s / 직접 Lambda.invoke 15분). 각 옵션의 장단점과 의사결정 가이드를 Fix Recommendation 새 섹션에 추가.

변경 사항:

  • ## Root Cause Summary 에 "API Gateway 종류와 hard limit 확인" 단락 추가 — HTTP API v2 임을 file:line 으로 입증, 30s hard limit 명시
  • ## Hypotheses Considered H6 의 evidence-for 컬럼에 hard limit 사실 보강
  • ## Fix Recommendation 에 "API Gateway 30s hard limit 우회 방안" 새 서브섹션 추가 — 옵션 A/B/C + 의사결정 가이드

추가 조사 내용:

  • $REPOS_DIR/data-pipeline/cupix-data/services/modules/authorized_apigateway/main.tfvariable.tf 점검 → API Gateway 종류가 v2 (HTTP API) 임을 확정
  • $REPOS_DIR/data-pipeline-functions/services/voxel/lambda/captured_area.py 점검 → Lambda 가 Athena start_query_execution 후 동기 polling (line 49-52) 으로 결과 대기. Athena 쿼리 시간이 30s 를 넘으면 API Gateway 가 끊는 구조
  • $REPOS_DIR/tesla/app/services/cupix/voxel_service.rb:31-37 호출자 점검 → Sidekiq cron 이 결과를 받아 facility.update!(captured_size: …) 동기 기록. 비동기 패턴 전환 시 cron 다음 cycle 까지 지연 허용 가능
  • AWS 공식 문서 cross-check — aws.amazon.com/about-aws/whats-new/2024/06/... 의 REST API timeout 증액 발표는 HTTP API 에 적용 안 됨 (REST API/private REST API only)

Revision 3#

Feedback: "lambda 에서 어떤 작업을 하는데 이렇게 오래 걸렸는지 확인" — 실패 invocation 동안 Lambda 가 실제로 무슨 작업을 했고 어디서 시간을 보냈는지 트레이스 요청.

판정:

피드백 항목 판정 근거
Lambda 가 30s 동안 한 작업 트레이스 수용 Datadog 로그 ("Voxel::CapturedArea" "nr0m13", 2026-06-15 16:50–16:53Z) 로 한 invocation 의 print 출력을 timestamp 순으로 정렬해 단계별로 식별: (1) 01:50:57 lambda_handler 진입 (event 출력), (2) 01:50:57 partition_query start_query_execution (captured_area.py:30, query id 2e117436-...) — ALTER TABLE ADD IF NOT EXISTS PARTITION 은 partition 이 이미 있으면 즉시 SUCCEEDED, (3) 01:50:58 captured_area_query start_query_execution (captured_area.py:124, query id 8f21d5d1-...) — tb_tesla_reality_captures × tb_raw_voxel join + COUNT(DISTINCT CONCAT(...)) aggregation, (4) captured_area.py:49-52while True: get_query_execution busy-poll 로 결과 대기. 동일 query id 로 검색 시 SUCCEEDED 또는 result 로그 부재 — Lambda 가 30s 만에 API Gateway 에 의해 강제 종료됨을 확인. tesla 재시도 4회 간격 (01:50:57 → 01:51:29 → 01:52:01 → 01:52:35) 이 30–34s 로 30s timeout + backoff(1s/2s/4s) 와 정확히 일치.
어디가 bottleneck 인가 수용 Lambda 자체는 거의 idle (busy-poll 만 수행, 자체 CPU/IO 부담 없음). 30s 의 거의 전부가 Athena captured_area_query 실행 시간 이다. 비교: 동일 시간대 성공 facility 3zyucx (level 73064, captured_area=10788) 는 진입(01:42:23) → handler_response 200(01:42:24) 으로 1초 만에 완료ResultReuseConfiguration (captured_area.py:38-43, 12h cache) cache hit 또는 데이터량 작은 케이스. 따라서 차이는 코드 경로가 아니라 facility 별 Athena 쿼리 cold-path 실행 시간 이며, 쿼리 비용은 (a) tb_raw_voxel 의 facility 미파티션으로 인한 broad scan 과 (b) COUNT(DISTINCT CONCAT(CAST(v.x*10 AS varchar), CAST(v.y AS varchar), REVERSE(...), REVERSE(...))) 의 row 단위 4-way string concat distinct 비용에서 발생.
Lambda 가 503 으로 끊긴 시점 메커니즘 수용 API Gateway HTTP API integration timeout 30s 에 도달하면 Lambda 의 while True polling 이 결과를 받기 전에 API GW 가 connection 을 끊고 5xx 반환. Lambda 입장에서는 invocation 이 그대로 종료되므로 print('... state: SUCCEEDED') / print('... handler_response') 같은 정상 종료 시 출력되는 로그가 한 줄도 남지 않는다 — 이는 본 트레이스에서 관찰된 패턴과 일치. 따라서 Revision 2 의 H6 (HTTP API 30s hard limit → 503) 가 Confirmed 로 격상 가능 (단, 503 vs 504 응답코드 차이는 API GW 내부 동작에 의존).

변경 사항:

  • ## Root Cause Summary 에 "Lambda 가 30s 동안 실제로 한 작업 (revision 3 추가)" 단락 추가 — invocation 단계별 timestamp 표 + bottleneck 위치 (captured_area.py:124 의 captured_area_query Athena 실행) + 문제의 SQL 스니펫 + 비용 분석
  • ## Hypotheses Considered H6 의 verdict 를 Plausible — 추가 조사 필요 에서 Confirmed (revision 3) 으로 변경, evidence-for 컬럼에 트레이스 증거 추가
  • ## Fix Recommendation 옵션 A (Lambda 실행 시간 단축) 를 Athena 쿼리 최적화 중심으로 재작성 — tb_raw_voxel partition 추가 / string-concat distinct 를 정수 해시로 교체 / materialized precompute table 도입

추가 조사 내용:

  • $REPOS_DIR/data-pipeline-functions/services/voxel/lambda/captured_area.py 의 print 출력 위치 매핑 (line 21, 46, 53, 58, 92, 100) — Datadog 로그의 어느 print 가 코드의 어느 line 인지 확인
  • Datadog 쿼리 "Voxel::CapturedArea" "nr0m13" (2026-06-15T16:50:00Z ~ 16:53:00Z) — 16건 hit, 4회 재시도의 4×4=16 print line 으로 구성됨을 확인
  • Datadog 쿼리 "8f21d5d1-40cc-4261-a91f-394e650c208d" (실패 invocation 의 captured_area_query id) — 1건만 hit (시작 로그). result/state 로그 부재 확인 = Lambda 가 결과 받기 전 종료
  • Datadog 쿼리 "Voxel::CapturedArea" "3zyucx" (성공 invocation, 01:42:23–01:42:24) — 동일 코드 경로지만 1초 내 200 반환 확인 = 코드 경로 동일, Athena 쿼리 시간 차이가 503 vs 200 결정자
  • captured_area.py:49-52 의 polling loop 가 time.sleep 없는 busy-loop 임을 코드에서 확인 — Lambda CPU 가 idle 이 아니라 boto3 get_query_execution 호출을 millisecond 단위로 반복해 throttling 위험도 있으나 본 인시던트와는 별개 사안

Revision 4#

Feedback: "lambda 에서 어떤작업을 하는데 이렇게 오래걸렸는지 확인" — Revision 3 에 이어 동일 질문 재요청. Revision 3 가 1회 invocation 만 트레이스했고 Phase A/B 시간 분해가 정량화되지 않았다는 점에 대한 보강 요청으로 해석.

판정:

피드백 항목 판정 근거
Lambda 가 어느 작업에서 시간을 보냈는지 (Phase A vs Phase B 분해) 수용 Datadog 로그로 nr0m13 facility 의 4회 재시도 모두에서 Phase A(partition_query) 와 Phase B(captured_area_query) 의 시작/종료 로그를 식별. Phase A 는 4회 모두 0–1s 에 SUCCEEDED (2e117436-… @01:50:57, 6e0a427b-… @01:51:29, 5256f235-… @01:52:02, f947b57e-… @01:52:36 — 각 query id 별 검색에서 모두 start/state/result 3건 hit). Phase B 는 4회 모두 start 로그만 hit, terminal 로그(state: SUCCEEDED / result: / handler_response) 부재 (8f21d5d1-…, 215cbf3a-…, 18949946-…, 8d926492-… 각각 1건씩만 hit). → "오래 걸린 작업"은 일관되게 Phase B 의 captured_area_query Athena 실행이며, Phase A(partition_query)나 Lambda 자체 처리는 수십 ms 수준.
Phase B 가 30s 를 넘긴 이유 수용 (a) 코드 경로 차이 아님 — 같은 cron 사이클에서 다른 facility 들(amrfc5, cvewtl, 2w5997, 8j7f35, 6alzdt, hi87b5, 3fq7tj, 8255p6, 12gwvg, ym21lk, 2uqu17, xf0u4z, efbnoa, zz10ne, cvxwmj, hx0c3c) 은 모두 1–2s 에 200 반환 (Datadog "handler_response - status_code: 200" 검색에서 다수 hit, 모두 동일 분 내 진입→종료). (b) facility 별 데이터량 차이 — tb_raw_voxel 이 facility_key 로 partition 되어 있지 않아(captured_area.py:104-107tb_tesla_reality_captures 에만 partition) nr0m13 처럼 voxel 누적량이 큰 facility 는 broad scan + COUNT(DISTINCT CONCAT(...)) 비용이 크게 증가. (c) ResultReuseConfiguration(captured_area.py:38-43, 12h cache) cache miss — 직전 12h 내 동일 쿼리 실행 이력 없으면 cold path.
Lambda 자체가 한 일 vs Athena 가 한 일의 분리 수용 captured_area.py:49-52while True: get_query_execution()time.sleep 없는 busy-poll 이므로 Lambda CPU 는 idle 이 아니라 boto3 호출을 millisecond 단위로 반복하지만 — 그 호출들의 실제 페이로드는 Athena 의 query 진행 상태 조회일 뿐이므로 30s 의 실질적 worker 는 Athena, Lambda 는 polling client 역할만 수행. boto3 botocore retry config(captured_area.py:11-15 max_attempts: 3, mode: standard) 도 athena API 호출에만 적용됨.

변경 사항:

  • ## Root Cause Summary 에 "4회 재시도 전체에 걸친 Phase A vs Phase B 시간 분해 (revision 4 추가)" 단락 추가 — 4회 재시도 각각의 Lambda 진입 / Phase A 종료 / Phase B 시작 / Phase B 종료 timestamp 표, 그리고 4× Phase B terminal 로그 부재의 일관성, 동일 cron 사이클의 16개+ 성공 facility 와의 시간 분포 대비
  • Revision 3 의 1회 트레이스를 4× 재시도 전반의 패턴으로 확장 — "오래 걸린 작업"의 답을 "captured_area.py:124 의 run_athena_query 안 line 49-52 의 busy-poll loop" 로 명확히 위치시키고, 실제 worker 는 Lambda 가 아니라 Athena 임을 분리

추가 조사 내용:

  • Datadog 쿼리 "Voxel::CapturedArea" (2026-06-15T16:30:00Z ~ 17:00:00Z, limit 50) — nr0m13 4회 재시도 전체 + 같은 시간대 partition_query 성공 로그 다수 회수
  • Datadog 쿼리 "215cbf3a-f6c3-4857-9257-580528b09b1b" OR "18949946-e7b6-4dd7-b619-790ce6cff43f" OR "8d926492-e33d-4759-8cf9-7814dbfe3046" (재시도 2/3/4 의 captured_area_query id) — 각각 1건씩, 모두 start 로그만 hit. terminal 로그 부재 = Revision 3 결론을 4× 일관된 패턴으로 확장
  • Datadog 쿼리 "Voxel::CapturedArea" "handler_response - status_code: 200" (2026-06-15T16:00:00Z ~ 17:30:00Z) — 같은 시간대 16개+ facility 가 1–2s 내 200 반환 확인. 코드 경로/Lambda 환경/시간대 동일 조건에서 facility 데이터량/cache 상태 차이가 503 vs 200 결정자임을 통계적으로 입증
  • Datadog metric aws.lambda.duration{functionname:*captured_area*} — production us-west-2 Lambda CloudWatch metrics 가 본 Datadog account 에서 미수집 (빈 series 반환). 따라서 Lambda CloudWatch 직접 측정은 불가하며, print 로그 timestamp 분해가 가용한 가장 정확한 트레이스 수단임을 확인

Revision 5#

Feedback: "lambda 에서 어떤작업을 하는데 이렇게 오래걸렸는지 확인" — Revision 3/4 에 이어 동일 질문 세 번째 재요청. Revision 3/4 가 "Athena query 실행" 이라고 결론냈지만 (a) "Lambda 가 자체로는 정말 아무 일도 안 했나" — busy-poll 자체가 일 아닌가, (b) "로그가 안 남은 게 Lambda 강제 종료가 맞나 아니면 단순 로그 유실인가" 의 두 잠재 의문을 해소하기 위한 보강 조사로 해석.

판정:

피드백 항목 판정 근거
실패 invocation 의 terminal 로그 부재가 "로그 유실" 이 아닌 "Lambda 강제 종료" 임을 확정 수용 같은 cron 사이클 (01:42 KST) 의 성공 invocation (3zyucx facility, query id d06e578f-99e0-4eae-a995-5c1bca60d66b) 을 단독 검색 — state: SUCCEEDED / result: {...} / handler_response - status_code: 200 세 줄이 모두 동일 second (01:42:24, UTC 16:42:24) 에 정상 emit 됨을 확인. 즉 captured_area.py:53, 58, 21 의 print-flush 메커니즘은 1초짜리 짧은 invocation 에서도 정상 작동한다. 따라서 실패 4 invocation 의 동일 세 줄 부재는 Datadog Lambda forwarder 누락이 아니라 Lambda runtime 자체가 print 라인을 실행하기 전에 종료 됐음을 의미한다. 대체 가설(로그 유실 / Datadog 인덱싱 지연 / 필터 누락)은 배제.
Lambda 종료 메커니즘이 API Gateway timeout (30s) 인지, Lambda 자체 timeout (300s) 인지 수용 "Task timed out" (AWS Lambda runtime 이 자체 timeout 도달 시 stderr 로 출력하는 표준 메시지) 키워드를 2026-06-15T16:30:00Z ~ 17:30:00Z (1h) 윈도우에서 검색 — 0건 hit. Lambda timeout(main.tf:430 300s) 은 도달하지 않았다. 따라서 Lambda 는 자체 시한 만료가 아니라 외부에서 invocation 이 끊긴 것 이며, 30s 직후에 끊긴 점 + API Gateway HTTP API 의 hard 30s integration timeout (Revision 2) + tesla 재시도 간격 32–34s (Revision 3) 와 모두 일치한다.
Lambda 자체가 busy-poll 로 일을 하긴 한 것 아닌가 (CPU 사용 측면) 부분 수용 코드상 captured_area.py:49-52while True: athena.get_query_execution(...)time.sleep 이 없어 Lambda 의 Python 인터프리터는 idle 이 아니라 boto3 GetQueryExecution RPC 를 초당 다수 회 호출한다. 다만 이 호출들은 (a) Athena 의 query 진행 상태 조회뿐이며 비즈니스 로직 / 데이터 변환 / 외부 IO 가 아니고, (b) 30s 의 "총 작업" 기준으로 보면 실질적 워커는 Athena 측 SQL 엔진이지 Lambda 가 아니다. 따라서 "Lambda 가 일을 안 했다" 는 표현은 비즈니스 작업 관점에서 옳지만 "Lambda CPU 가 idle 이었다" 는 정확하지 않음 — 두 표현을 분리해 본문에 반영. "ThrottlingException" "captured_area" 검색 결과 0건 hit 으로 botocore standard retry mode (captured_area.py:11-15) 가 Athena API quota 를 흡수했음을 확인 (Lambda 가 throttling 으로 죽은 게 아님).
30s 동안의 작업 비중을 정량적으로 분해 수용 수십 ms (Lambda: event parse, partition_query submit) + 수십 ms (Lambda: captured_area_query submit) + ~30,000 ms (Athena: RUNNING) + 병행 ~30,000 ms (Lambda: GetQueryExecution busy-poll). Lambda 측 "실제 워크" 는 polling client 역할 한정, "실제 30s 짜리 작업" 은 Athena. 이를 표로 정리해 본문에 추가.

변경 사항:

  • ## Root Cause Summary 에 "성공 invocation 의 print-flush 패턴으로 부재 증거 강화 (revision 5 추가)" 단락 추가 — 성공 invocation 의 정상 emit 로그 3줄을 인용해 "로그 유실" 대체 가설 배제
  • ## Root Cause Summary 에 "Lambda 종료 메커니즘 추가 확인 (revision 5 추가)" 단락 추가 — "Task timed out" / "ThrottlingException" 검색 결과 모두 0건임을 명시해 API Gateway 30s 단절을 단정
  • ## Root Cause Summary 에 "Lambda 가 자체로 한 '일'의 정량적 본질 (revision 5 추가)" 단락 추가 — 비중표 (Lambda 수십 ms × 2 / Athena 30,000 ms / Lambda busy-poll 30,000 ms 병행) 로 "Lambda 가 어떤 작업을 하느라 오래 걸렸는가" 의 정확한 답을 한 줄로 정리

추가 조사 내용:

  • Datadog 쿼리 "d06e578f-99e0-4eae-a995-5c1bca60d66b" (2026-06-15T16:40:00Z ~ 16:50:00Z) — 성공 invocation (3zyucx) 의 captured_area_query 단독 검색에서 start / state: SUCCEEDED / result: 세 로그가 모두 hit, terminal 로그가 정상 emit 됨을 확인. 이로써 실패 invocation 의 terminal 로그 부재가 "로그 유실 아님" 을 결정.
  • Datadog 쿼리 "Task timed out" (2026-06-15T16:30:00Z ~ 17:30:00Z, 1h 윈도우) — 0건 hit. Lambda 자체 timeout 미도달 확인.
  • Datadog 쿼리 "ThrottlingException" "captured_area" (동일 윈도우) — 0건 hit. boto3 busy-poll 이 Athena API throttling 까지 가지 않았음 확인.
  • captured_area.py 전체 재독해 — line 11-15 의 boto3 retry config (max_attempts: 3, mode: standard), line 18 의 athena client 초기화, line 28-63 의 run_athena_query 구조 (특히 line 49-52 의 time.sleep 없는 busy-loop), line 91-139 의 lambda_handler 흐름 매핑. Lambda 가 자체로 수행하는 작업은 event parse + 두 번의 query submit + 결과 dict 변환(line 65-88) 뿐, 외부 IO 나 무거운 계산이 없음을 코드로 확정.