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-worker의 Cupix::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#
- 2026-06-16 01:33 KST — 첫 503 발생 (
captured_area!, occurrence 시작) - 2026-06-16 01:42 KST — 일부 요청은 정상 응답 (
Completed - total: 10788/34759); voxel service는 완전 다운 아님, 부분 불가용 - 2026-06-16 01:33–01:53 KST — 약 2분 간격으로 cron이 facility 1건씩 처리, 매번
503발생 (총 10건) - 2026-06-16 01:53 KST — 마지막 503 (
Facility 17853)
Error Log#
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_query의state: 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:124 의 run_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):
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 했다:
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-15max_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:
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! 를 호출:
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:
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 재시도가 포함되어 있음:
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):
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 가 완전 다운이 아님을 입증):
{
"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!"
}
{
"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 로그):
service:cupixworks-worker "Flushing stale captured size"
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):
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-functionsrepo 의/captured_areahandler 와 인프라(파드 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-pipelinerepo 의 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_voxel을facility_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 는 Athenastart_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 손실 — 대안 인증 필요.
의사결정 가이드:
- CloudWatch 로 captured_area Lambda p95/p99 duration 측정 → 30s 미만이면 옵션 A 로 충분
- p95 가 30s 근처거나 초과하면 옵션 B 또는 C 선택. 호출 빈도가 낮은 cron-only 호출이면 옵션 C(직접 Lambda invoke) 가 변경 비용 최저
- voxel-service 의 다른 endpoint(
merge_voxelLambda 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_code와response.body를 함께 남겨 향후 5xx 디버깅을 가속화.
장기 개선 (재발 방지)#
- voxel-service 의 partial unavailability 를 감지하는 health check + circuit breaker (tesla 측).
Cupix::HttpClient에 endpoint 별 circuit breaker 도입 고려. - voxel-service 측
/captured_areaendpoint 의 SLO 정의 + Datadog APM trace 활성화로 어떤 단계에서 503이 반환되는지 가시화.
Monitoring#
추가할 monitor (Datadog timeseries widget 호환 형식):
sum:trace.rack.request.errors{service:cupixworks-worker,resource_name:"captured_area"}.as_count()
logs("service:cupixworks-worker @class:Cupix::VoxelService @function:captured_area! status:error").index("*").rollup("count").by("@facility_key")
기존 cron 성공률 추적:
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-pipelinerepo 의 timeout mismatch 와 PR 84305 revert 사실을 file:line 근거와 함께 기재## Hypotheses Considered에 H6 추가 (Plausible — 추가 조사 필요)## Fix Recommendation > 즉시 조치에data-pipelinerepo 의 timeout mismatch 재검토 항목 추가 (PR 83809 / PR 84305 / 작성자 추적 포함)
추가 조사 내용:
$REPOS_DIR/data-pipelinerepo 의 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 83809 의 120000ms) 은 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 ConsideredH6 의 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.tf와variable.tf점검 → API Gateway 종류가 v2 (HTTP API) 임을 확정$REPOS_DIR/data-pipeline-functions/services/voxel/lambda/captured_area.py점검 → Lambda 가 Athenastart_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-52 의 while 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 ConsideredH6 의 verdict 를 Plausible — 추가 조사 필요 에서 Confirmed (revision 3) 으로 변경, evidence-for 컬럼에 트레이스 증거 추가## Fix Recommendation옵션 A (Lambda 실행 시간 단축) 를 Athena 쿼리 최적화 중심으로 재작성 —tb_raw_voxelpartition 추가 / 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 이 아니라 boto3get_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-107 은 tb_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-52 의 while 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) —nr0m134회 재시도 전체 + 같은 시간대 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-52 의 while 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 나 무거운 계산이 없음을 코드로 확정.