Api::V1::PanosController#create_tile_upload_credentials (avg 15839ms, max 15839ms)
RCA: Api::V1::PanosController#create_tile_upload_credentials latency (15.8s)
Overview#
What Happened#
2026-06-24 14:50 KST (eu-central-1) 에 Api::V1::PanosController#create_tile_upload_credentials 요청 1건이 15.8초 만에 응답했다. 이 엔드포인트는 pano tile 업로드를 위한 임시 AWS 자격증명을 발급하는 short-lived API 로, 동일 시간대 트래픽의 max p100 latency 가 0.3-6초 범위인 점을 고려하면 명백한 outlier 다. 에러 응답이나 예외는 발생하지 않았다 (status 200).
Quick Facts#
| Field | Value |
|---|---|
| exception.class | N/A — latency 클러스터, 예외 없음 |
| top_frame | app/services/cupix/storage_service.rb:181 (Cupix::StorageService.upload_credentials) |
| resource_name | Api::V1::PanosController#create_tile_upload_credentials |
| avg_duration_ms | 15839 |
| max_duration_ms | 15839 |
| occurrence_count | 1 |
| env | production, eu-central-1 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (pano tile upload) | 1 | 단일 사용자의 pano tile 업로드 시작이 약 15초 지연 (응답은 정상). 동일 분 트래픽 대비 1건만 영향. |
Timeline#
- 2026-06-24 14:50 KST — eu-central-1 에서
create_tile_upload_credentials요청 1건이 15.8s 만에 200 응답 (clusterfirst_seen/last_seen동일) - 2026-06-24 14:50 KST 전후 — 동일 엔드포인트의 다른 요청들은 sub-second ~ 수 초 범위로 정상 처리 (Datadog log 다수 건 확인)
- 2026-06-24 (RCA 시각) — error-sweeper 가 latency cluster 로 감지하여 본 분석 수행
Error Log#
resource_name: Api::V1::PanosController#create_tile_upload_credentials
service: cupixworks-api
occurrences: 1
avg_ms: 15839
max_ms: 15839
sample_trace_id: 2659508146527786825
region: eu-central-1
tenant: cupix
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-06-24 14:50 KST
- 최근 발생: 2026-06-24 14:50 KST
- Status Board: active incident
2026-06-24-svc-cupixworks-api--unknown-1(svc:cupixworks-api::unknown) 가 같은 날 14:02 KST 부터 열려 있으나, 본 latency 클러스터는 해당 incident 의cluster_ids에 포함되어 있지 않다. 별개 사건으로 취급한다.
Root Cause Summary#
create_tile_upload_credentials 코드 경로는 매우 짧으며, 외부 호출은 단 하나 — AWS STS get_federation_token — 뿐이다. 컨트롤러는 권한 체크 후 Cupix::StorageService.upload_credentials 를 호출하고, 그 내부에서 get_credential_token → Aws::STS::Client#get_federation_token 이 동기로 실행된다. 동일 분 내 다른 요청들이 모두 sub-second ~ 수 초로 응답한 점, 그리고 본 요청만 15.8s 가 걸린 점을 종합하면, AWS STS GetFederationToken API 의 일시적 지연(또는 SDK 의 client-side retry/backoff) 이 가장 유력한 root cause 다. 코드 자체에는 명시적인 timeout 설정도, retry 제어도 없어 외부 의존성의 tail latency 가 그대로 응답 시간으로 노출되는 구조다. 단발성 1건이므로 코드 버그가 아닌 외부 의존성 tail latency 로 분류한다.
Technical Analysis#
Code Path#
Entry point: app/controllers/concerns/tilable_controller.rb:13 (POST /api/v1/panos/:id/tile_upload_credentials)
concern :tile do
put 'check_tile_uploading'
post 'tile_upload_credentials', action: :create_tile_upload_credentials
end
def create_tile_upload_credentials
credentials = repository_instance.create_tile_upload_credentials
render_json 200, credentials
end
Repository (pano variant): permission check 후 모델로 위임.
def create_tile_upload_credentials
raise Cupix::Errors::PermissionDenied.new(code: 'PERM10000', reason: 'Permission denied') unless @model.updatable_by?(self.current_user)
@model.tile_upload_credentials
end
Model concern: 외부 호출(StorageService.upload_credentials) 한 번.
def tile_upload_credentials
Cupix::StorageService.upload_credentials(
storage_option: storage_option,
id: id,
bucket_name: storage_option.s3_hosting_bucket_name,
key: tile_object_key_base(ver: tile_upload_revision)
)
end
Failure point (latency 소스): app/services/cupix/storage_service.rb:181 — 내부에서 STS get_federation_token 동기 호출.
def upload_credentials(storage_option: nil, **kwargs)
opts = parse_storage_option(storage_option).merge(kwargs)
check_required_params(opts, %i[id region bucket_name key])
expiration_in = opts[:expiration_in] || 4.hour
expires_at = expiration_in.from_now
token = get_credential_token(
storage_option: storage_option,
name: "#{Rails.env}-#{opts[:id]}-#{Time.now.to_i}",
policy: upload_credential_policy(opts[:bucket_name], opts[:key]),
duration_seconds: expiration_in.to_i
)
# ... build response hash
end
def get_credential_token(storage_option: nil, **kwargs)
client = sts_client(storage_option: storage_option)
opts = kwargs
opts[:duration_seconds] ||= 7200
check_required_params(opts, %i[policy name duration_seconds])
begin
# NOTE: MinIO doesn't support get_federation_token
if storage_option.bucket_type == 'minio'
opts[:role_arn] = 'arn:xxx:xxx:xxx:xxxx'
opts[:role_session_name] = opts[:name]
opts.delete(:name)
resp = client.assume_role(opts)
else
resp = client.get_federation_token(opts) # ← 동기 외부 호출, timeout/retry 옵션 미지정
end
rescue StandardError => e
raise Cupix::Errors::System.new(code: 'SYS20000', reason: "Can't create temporary token", message: e.message)
else
resp.credentials
end
end
sts_client 는 SDK 기본값으로 Aws::STS::Client 를 생성한다 — 명시적 http_open_timeout / http_read_timeout / retry_limit 옵션이 없다.
def sts_client(storage_option: nil, **kwargs)
opts = parse_storage_option(storage_option).merge(kwargs)
Aws::STS::Client.new(opts)
end
기대 동작 vs 실제 동작:
- 기대: STS
GetFederationToken은 보통 100ms 이내, 외부 호출이 짧으므로 전체 응답 < 1s. - 실제: 1건이 15.8s 소요. SDK 기본 retry (3회) + 기본 read_timeout (60s) 환경에서 STS 호출이 한 번 실패하고 retry 가 성공했거나, 단일 호출이 그 자체로 느렸을 가능성이 있다 — 단발성이라 trace 단의 span 분해 없이는 정확한 비율 단정 불가 (
uncertain -- needs verification).
Log Evidence#
Datadog 쿼리 (재현)#
service:cupixworks-api "Api::V1::PanosController#create_tile_upload_credentials"
from: 2026-06-24T05:48:00Z
to: 2026-06-24T05:53:00Z
해당 시간 window 에서 같은 엔드포인트로 처리된 다수 건 — 모두 200, 별도의 에러 / warn 없음. 일부 sample:
2026-06-24 14:48:00 info [200] POST /api/v1/panos/9634888/tile_upload_credentials
2026-06-24 14:48:00 info [200] POST /api/v1/panos/14071548/tile_upload_credentials
... (분당 수십 건의 정상 200 처리)
동일 시간대 API error 검색 결과#
service:cupixworks-api status:error env:production
from: 2026-06-24T05:30:00Z
to: 2026-06-24T06:10:00Z
→ Found 0 logs
이 cluster 와 직접 연결된 error log 는 없다. 본 cluster 는 APM trace-side latency span 으로부터 수집된 것이며, application-level 예외가 동반되지 않았다.
Endpoint-level p100 latency 추세 (max:trace.rack.request.duration)#
metric: max:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::panoscontroller_create_tile_upload_credentials}
window: last 12h ending 2026-06-24 20:22 KST
대표 값 (초): 3.03, 2.87, 1.48, 0.67, ..., 6.09, 4.81, 2.92, ..., 4.43, 3.31, 4.21, 3.21, 2.99, 2.91, ...
전체 시계열의 대다수가 0.3-6s 사이. cluster 가 보고한 15.8s 는 12 시간 timeseries 전체에서도 매우 드문 outlier (메트릭 aggregation 윈도우 평균에는 묻혔으나 raw trace 에서 포착됨).
Status Board#
$ bun run cli/incident-board.ts for-cluster 8d269564-bc1a-4e96-9479-c1a601f57cd6
scope: svc:cupixworks-api::unknown
active: { id: "2026-06-24-svc-cupixworks-api--unknown-1", started_at: "2026-06-24T05:02:30Z", ... }
active incident 가 같은 서비스에 열려 있으나, 본 cluster ID 는 해당 incident 의 cluster_ids 목록에 없다 (별도 latency 사건).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | AWS STS GetFederationToken tail latency / 일시적 지연 (SDK default retry + read_timeout 으로 인해 client 측에서 흡수) |
코드 경로상 외부 호출은 STS 하나뿐 (app/models/concerns/tile/s3.rb:55-62, app/services/cupix/storage_service.rb:122-145); 동일 분 다른 요청은 sub-second; 15.8s 는 SDK 기본 read_timeout(60s) 안에서 retry 1-2 회가 발생할 수 있는 구간 |
trace span 레벨에서 STS sub-span 의 정확한 소요 시간은 본 RCA 에서 확인 못 함 (uncertain -- needs verification) |
Confirmed (most likely) |
| H2 | Rails 앱 측 lock contention / GC pause / Sidekiq 간섭 | 동일 host 가 같은 분 대량 요청을 잘 처리한 점, error log 0 건 | host-level metric (CPU, GC) 미확인 — 가능성 낮음 | Rejected |
| H3 | updatable_by? 권한 검사에서 N+1 또는 DB slow query |
권한 검사 단계가 코드 경로에 있음 | 동일 사용자 대상으로 분당 수십 건이 sub-second 로 처리됨; DB slow query 였다면 다른 요청도 영향 | Rejected |
| H4 | 진행 중인 active incident (svc:cupixworks-api::unknown) 와 동일 root cause |
같은 서비스, 같은 날 발생 | Status board 의 cluster_ids 목록에 본 cluster 가 없음 — detector 가 별개 사건으로 분류 |
Rejected |
| H5 | 코드 버그 / 무한루프 / 동기 polling 누락 | — | 동일 코드 경로로 분당 수십 건이 정상 처리 (200); occurrence_count=1 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 단발성 1건, 사용자 에러 없음 (200 응답), 서비스 영향 미미. 코드 변경 불필요.
단기 개선 (1주 이내)#
Aws::STS::Client에 명시적 timeout / retry 옵션 부여 —app/services/cupix/storage_service.rb:14-18(sts_client) 에서http_open_timeout,http_read_timeout,retry_limit,retry_max_delay를 짧게 설정하여 외부 의존성 tail latency 가 사용자 응답에 그대로 노출되지 않도록 한다. 예: open 2s / read 5s / retry 2. 같은 패턴이client,bucket,object,signer(app/services/cupix/storage_service.rb:5-43) 에도 적용 가능.- 재발 모니터링 — 본 endpoint 의 p99 latency 가 5s 를 초과하면 알림. 추세적이라면 STS 호출 빈도 자체를 줄이는 방향 (token caching) 으로 단기 추가 개선.
장기 개선 (재발 방지)#
- STS token caching —
create_tile_upload_credentials는 매 요청마다 4시간짜리 STS federation token 을 새로 발급한다 (app/services/cupix/storage_service.rb:185-192). 동일 (storage_option, bucket, key prefix) 에 대해 단기 cache (예: Redis, expiry < token expiration) 를 두면 STS 호출 횟수와 tail latency 노출 자체가 감소. 단, policy 가 per-pano key 기반이라 cache key 설계 필요. - SDK-level instrumentation — STS / S3 SDK 호출에 별도 span tag 를 붙여, 향후 latency 분석 시 외부 호출 시간과 application 시간을 즉시 분리할 수 있게 한다.
Monitoring#
추가할 알림 / dashboard 패널 (release dashboard timeseries widget 용 text 블록):
max:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::panoscontroller_create_tile_upload_credentials}
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::panoscontroller_create_tile_upload_credentials}
sum:trace.rack.request.hits{service:cupixworks-api,resource_name:api::v1::panoscontroller_create_tile_upload_credentials}.as_rate()
알림 임계값 제안 (모니터링용 monitor 식은 별도): p95 > 5s 가 5분 이상 지속 시 warning, p95 > 10s 면 alert.
Risk Assessment#
- Risk level: low (단발성, 1건, 200 응답, 사용자 데이터 손실 없음)
- 예상 복잡도: trivial (즉시 조치 불필요. 단기 개선만 standard 수준의 SDK timeout/retry 설정 작업)