Api::V1::PanosController#check_tile_uploading (avg 16622ms, max 16622ms)
RCA: Api::V1::PanosController#check_tile_uploading slow request (16.6 s)
Overview#
What Happened#
2026-06-24 20:35 KST에 cupixworks-api의 PUT /api/v1/panos/:id/check_tile_uploading 요청 한 건이 16,622 ms 동안 실행되어 latency 임계치를 초과했다. 같은 시간대에 동일 엔드포인트 호출이 분당 수십 건씩 정상 응답(200)으로 처리되고 있었으므로 systemic outage가 아니라 개별 trace의 외부 의존성 지연으로 보인다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::PanosController#check_tile_uploading |
| service | cupixworks-api |
| cluster_type | latency |
| avg_duration_ms | 16622 |
| max_duration_ms | 16622 |
| sample_trace_id | 4385947387163049037 |
| env | production, us-west-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (Pano tile upload finalize) | 1 | 단일 클라이언트가 타일 업로드 검증 단계에서 약 16 초 대기. 200 응답으로 종료되어 데이터 손상은 없음 |
Timeline#
- 2026-06-24 20:35:40 KST — trace
4385947387163049037시작. 동일 컨트롤러로의 다른 200 응답이 같은 초에 다수 기록됨 - 2026-06-24 20:35:56 KST — 16,622 ms 경과 후 응답 완료 (Datadog latency cluster 트리거)
- 2026-06-24 21:35 KST — error-sweeper collector가 cluster 생성
active service-level incident 2026-06-24-svc-cupixworks-api--unknown-1 (started 2026-06-24 14:02 KST)이 같은 서비스에 열려 있어 이 클러스터도 동일 incident에 묶였다. 해당 incident는 root_cause_types: ["unknown"]으로 분류되어 있어 강제 연관은 아니다.
Error Log#
{
"resource_name": "Api::V1::PanosController#check_tile_uploading",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 16622,
"max_ms": 16622,
"sample_trace_id": "4385947387163049037"
}
이 cluster는 exception이 아니라 latency trace 한 건이다. Datadog Logs Search에서 동일 trace id로 조회한 결과 매칭 로그가 없었다 (service:cupixworks-api "4385947387163049037" → 0건). 즉 application-level error log 없이 200으로 완료된 느린 요청이다.
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-06-24 20:35 KST
- 최근 발생: 2026-06-24 20:35 KST
Root Cause Summary#
PanosController#check_tile_uploading은 호출될 때마다 S3 list_objects_v2 API를 동기적으로 호출하여 업로드된 타일 객체 개수를 센다. 이 요청 한 건에서 S3 list 호출이 약 16 초 동안 블로킹된 것이 직접적 latency 원인이다. 동시간대에 같은 엔드포인트의 다른 호출은 정상 응답되었으므로 코드/데이터 버그가 아니라 S3 측 transient slowness 또는 특정 prefix의 비정상적으로 큰 키 개수(첫 page를 채우기 위한 S3 내부 스캔 시간 증가)가 유력하다. exception이 발생하지 않은 점, 후속 동일 trace가 재발하지 않은 점, error/warn 로그가 비어있는 점이 이를 뒷받침한다.
Technical Analysis#
Code Path#
Entry point: app/controllers/concerns/tilable_controller.rb:4
def check_tile_uploading
@model = repository_instance.check_tile_uploading(params.permit(:revision_type).to_h)
render_api Renderable.new({
contents: @model,
serializer_option: @serializer_option
})
end
Repository delegation: app/repositories/concerns/tilable_repository.rb:4
def check_tile_uploading(params)
@model.check_tile_uploading!
@model.done_state if @model.respond_to?(:state_cloning?) && @model.state_cloning?
@model
end
Model logic — 여기서 S3를 호출한다: app/models/concerns/tile/s3.rb:9-15
def check_tile_uploading!
objects_count = tile_uploading_objects.size
self.tile_size = objects_count if self.has_attribute?(:tile_size)
raise Cupix::Errors::InvalidState.new(code: 'STAT10000', reason: 'No tile objects found in S3') if objects_count.zero?
self.uploaded_tile_state!
end
def tile_uploading_objects
Cupix::StorageService.object_list(
storage_option: storage_option,
bucket_name: storage_option.s3_hosting_bucket_name,
prefix: tile_object_key_base(ver: tile_upload_revision)
)
end
Failure point — S3 호출 자체: app/services/cupix/storage_service.rb:64-79
def object_list(storage_option: nil, **kwargs)
opts = parse_storage_option(storage_option).merge(kwargs)
check_required_params(opts, %i[region bucket_name prefix])
client(storage_option: storage_option)
.list_objects_v2(
bucket: storage_option.s3_hosting_bucket_name,
prefix: opts[:prefix],
delimiter: 'delimiter'
)
.contents
.reject do |object|
object.key.end_with?('/')
end
end
기대 동작: S3 list_objects_v2가 prefix 아래 키 수백~수천 개에 대해 100 ms 이내에 첫 page를 반환한다. PUT 요청은 1 초 안에 200을 응답해야 한다.
실제 동작: 동일 호출이 16,622 ms 동안 블로킹되었다. 코드 경로상 추가 외부 호출은 없고 (DB write는 uploaded_tile_state! 한 번뿐), 90 % 이상의 시간이 S3 round-trip에서 소요된 것으로 추정된다. Ruby AWS SDK는 기본 retry 정책이 있어 503/timeout 시 backoff 재시도를 수행하므로, 한 번의 transient slowness가 두세 차례 재시도로 누적되면 십수 초로 늘어날 수 있다.
Log Evidence#
쿼리 1 — trace id로 application log 조회 (0건):
service:cupixworks-api "4385947387163049037"
time: 2026-06-24T11:00:00Z .. 2026-06-24T12:30:00Z
쿼리 2 — 같은 endpoint의 동시간대 호출 빈도 (정상 200 다수):
service:cupixworks-api "check_tile_uploading"
time: 2026-06-24T11:00:00Z .. 2026-06-24T12:30:00Z
샘플 응답 (incident 분 ±30 초):
2026-06-24 20:35:59 KST info [200] PUT /api/v1/panos/89880130/check_tile_uploading (Api::V1::PanosController#check_tile_uploading)
2026-06-24 20:35:59 KST info [200] PUT /api/v1/panos/89879205/check_tile_uploading (Api::V1::PanosController#check_tile_uploading)
2026-06-24 20:35:59 KST info [200] PUT /api/v1/panos/89879702/check_tile_uploading (Api::V1::PanosController#check_tile_uploading)
2026-06-24 20:35:59 KST info [200] PUT /api/v1/panos/89880200/check_tile_uploading (Api::V1::PanosController#check_tile_uploading)
2026-06-24 20:35:59 KST info [200] PUT /api/v1/panos/553795/check_tile_uploading (Api::V1::PanosController#check_tile_uploading)
쿼리 3 — cupixworks-api warn/error 동시간대 (0건):
service:cupixworks-api status:error
time: 2026-06-24T11:30:00Z .. 2026-06-24T11:45:00Z
쿼리 4 — tile/S3 관련 warn (0건):
service:cupixworks-api "S3" "tile" status:warn
time: 2026-06-24T10:00:00Z .. 2026-06-24T13:00:00Z
종합: 같은 분에 다수의 정상 200 응답이 기록되어 서비스 전체 가용성은 정상이었다. 한 trace만 16 초를 기록했고 관련 exception/warn 로그가 없다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | S3 list_objects_v2 transient slowness (network jitter, AWS-side throttling, 또는 retry backoff 누적) |
Code path에서 외부 호출은 S3 한 곳뿐 (app/models/concerns/tile/s3.rb:18-22). 동시간대 다른 호출은 정상. error/warn 로그 없음. AWS SDK 기본 retry는 ms→초 단위로 누적 가능 |
직접적 S3 metric은 cluster 파일에 없음 — APM trace span 확인 필요 (uncertain — needs verification) | Confirmed (most likely) |
| H2 | 대상 pano의 prefix에 비정상적으로 많은 타일 키가 존재해 첫 page scan 시간이 폭증 | tile_object_key_base 는 prefix 하나이며 페이지네이션 없이 .contents.reject로 첫 page만 사용. 단일 prefix scan 시간은 키 분포에 따라 길어질 수 있음 |
trace id가 로그에 남지 않아 어느 pano id인지 식별 불가. 16 초는 단일 S3 page 응답으로는 매우 길어 단독 원인 가능성은 낮음 | Inconclusive |
| H3 | DB transaction lock 또는 uploaded_tile_state! write 지연 |
동시간대 동일 endpoint 호출 다수 정상 | code path상 write는 단일 row update. PostgreSQL slow query 흔적 없음 (logs 0건). 코드상 lock 획득 구간 없음 | Rejected |
| H4 | 부모 incident 2026-06-24-svc-cupixworks-api--unknown-1의 공통 원인 (예: 호스트 GC/CPU spike) |
같은 서비스에서 동시간대 다수 cluster가 묶임 | 부모 incident는 root_cause_types: ["unknown"]이며 다른 클러스터는 별개 endpoint. 공통 호스트 지표 미확인 (uncertain) |
Inconclusive |
| H5 | Application-level bug (무한 루프, 잘못된 query) | 없음 | code path가 단순 (controller → repo → model.check_tile_uploading! → S3 list → state update). exception 없음 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 직접 조치는 불필요. 단일 trace, 200 응답, 데이터 손상 없음
- APM에서 trace
4385947387163049037의 span breakdown을 확인하여 16 초가 S3 호출에 집중되었는지 검증 - 같은 cluster가 24 시간 내 재발하면
aws.s3.request.duration{bucket:<s3_hosting_bucket_name>}p95를 함께 검토
단기 개선 (1주 이내)#
Cupix::StorageService.object_list호출에 명시적 timeout/circuit breaker 적용 검토. 현재 코드(app/services/cupix/storage_service.rb:64-79)는 SDK 기본값에 의존하므로 retry 누적 시 십수 초까지 늘어날 수 있음check_tile_uploading!에서objects_count.zero?판정만 필요하다면list_objects_v2(max_keys: 1)한 번 호출로 단축 가능 (app/models/concerns/tile/s3.rb:10). 단,tile_size = objects_count갱신 로직과 양립하지 않으므로tile_size의존 모델을 식별한 뒤 적용 — needs verification
장기 개선 (재발 방지)#
- 타일 검증을 동기 API에서 분리해 background worker (Sidekiq)로 이전하고, 클라이언트는 polling 또는 webhook으로 결과 수신
- S3 list 호출량을 줄이기 위해 client-side에서 업로드 완료 manifest를 전송하도록 protocol 개편 검토
Monitoring#
다음 쿼리를 Datadog dashboard timeseries widget에 등록한다.
S3 list 호출 p95 (이 endpoint가 동기 호출하는 의존):
avg:aws.s3.request.duration{servicename:cupixworks-api}.rollup(avg, 60)
check_tile_uploading p95 duration:
p95:trace.rack.request{service:cupixworks-api,resource_name:api::v1::panoscontroller#check_tile_uploading}
check_tile_uploading 호출 처리량:
sum:trace.rack.request.hits{service:cupixworks-api,resource_name:api::v1::panoscontroller#check_tile_uploading}.as_count()
10 초 이상 걸린 trace 개수 (alert candidate):
sum:trace.rack.request.duration.by.resource_service.95p{service:cupixworks-api,resource_name:api::v1::panoscontroller#check_tile_uploading}
(주의: cluster 생성 시점 기준으로 위 trace.* metric 쿼리는 빈 결과를 반환했다. APM metric tag 명을 환경에서 재확인 후 dashboard 등록 — needs verification.)
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (코드 변경 없이 monitoring 보강 권장. 재발 시 단기 개선 항목 검토)