Api::V1::CapturesController#check_resource_uploading (avg 1111ms, max 1200ms)
RCA: CapturesController#check_resource_uploading Latency
Overview#
What Happened#
2026-05-26 03:23~11:27 UTC 동안 Api::V1::CapturesController#check_resource_uploading 엔드포인트에서 평균 1,443ms, 최대 16,095ms의 비정상적 지연이 발생했다. 63건의 slow request가 ap-southeast-2, us-west-2, eu-central-1 리전에서 감지되었으며, 특히 us-west-2에서 극단적 outlier(30초+)가 집중되었다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::CapturesController#check_resource_uploading |
| top_frame | app/models/concerns/storagable/resource.rb:167 |
| env | production (ap-southeast-2, us-west-2, eu-central-1) |
| cluster_type | latency |
| avg_duration_ms | 1443ms |
| max_duration_ms | 16,095ms |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| nhc-sa (us-west-2) | ~50 | 업로드 확인 응답 1-3초 지연 |
| gad (us-west-2) | ~7 | 극단적 지연 16-32초, 클라이언트 타임아웃 가능성 |
| hassan-allam (eu-central-1) | 1 | 16초 지연 |
Timeline#
- 2026-05-26T03:00:37Z — 최초 극단적 outlier 발생 (32,731ms, gad team, us-west-2)
- 2026-05-26T03:23:13Z — 클러스터 최초 감지
- 2026-05-26T09:00-11:27Z — nhc-sa team 대량 발생 (50건, avg 1,100ms)
- 2026-05-26T11:27:48Z — 마지막 감지
Error Log#
{
"resource_name": "Api::V1::CapturesController#check_resource_uploading",
"service": "cupixworks-api",
"occurrences": 7,
"avg_ms": 1111,
"max_ms": 1200,
"sample_trace_id": "3743579325530895228"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 63
- 최초 발생: 2026-05-26T03:23:13.188Z
- 최근 발생: 2026-05-26T11:27:48.293Z
- 영향 리전: ap-southeast-2, us-west-2, eu-central-1
Root Cause Summary#
check_resource_uploading 엔드포인트는 S3 HEAD 요청(_object.exists?)을 동기적으로 호출하여 파일 업로드 완료 여부를 확인한다. DB 시간은 평균 36ms에 불과하지만, S3 API 호출이 네트워크 I/O로 인해 수백~수만 ms 블로킹된다. 특히 us-west-2 리전에서 동시간대 대량 bulk 작업(ElementTracesController#refresh, PanosController#bulk)이 동일 호스트에서 실행되면서 thread pool/connection pool 경합이 발생했고, 이로 인해 S3 응답 대기 시간이 극단적으로 증가했다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/concerns/multiple_resourcable_controller.rb:80—check_resource_uploadingaction - Resource lookup:
app/controllers/concerns/multiple_resourcable_controller.rb:148—.or()쿼리로 resource 조회 - State check & delegation: Line 81-83에서
:uploading또는:missing상태인 경우에만@resource.check_uploading호출 - Model delegation:
app/models/concerns/resourcable/capture.rb:9—resource.check_uploading호출 - S3 blocking call (Failure Point):
app/models/concerns/storagable/resource.rb:165-167— S3 object 생성 및.exists?HEAD 요청
def check_resource_uploading
case @resource.state_name
when :uploading, :missing
callback_uploaded_resource(@resource) if @resource.check_uploading
end
def check_uploading
_revision = self.revision
_object = self.object(_revision + 1)
if _object.exists? # S3 HEAD request - blocking I/O
self.etag = _object.etag.gsub('"', '') rescue nil
self.size = _object.size
self.content_type = _object.content_type rescue nil
return false if self.size.blank? || self.size.zero?
MidasOperation.record_attachment_upload(attachment: resourcable, file_size: self.size) if resourcable.is_a?(Attachment) && MidasOperation.enabled?
self.increase_revision
self.done
true
else
self.missing
false
end
end
기대 동작: S3 HEAD 요청은 일반적으로 50-200ms 내에 완료되어야 한다.
실제 동작: DB 시간 평균 36ms를 제외한 나머지(avg 1,400ms, max 32,700ms)가 S3 I/O 블로킹에 소요됨. us-west-2의 특정 호스트(ip-10-1-144-228)에서 03:00-05:41 UTC 사이 30초+ outlier가 집중 발생.
Log Evidence#
Datadog 로그 검색 쿼리:
service:cupixworks-api @duration:>1000 "check_resource_uploading"
Time: 2026-05-26T03:00:00Z to 2026-05-26T12:00:00Z
100건의 slow request에서 확인된 패턴:
Total Duration: 32748ms | DB: 20.69ms | View: 0.08ms | Unaccounted (S3): ~32,727ms
Total Duration: 16095ms | DB: 39ms | View: <1ms | Unaccounted (S3): ~16,055ms
Total Duration: 1200ms | DB: 36ms | View: <1ms | Unaccounted (S3): ~1,163ms
리전별 분포 (slow >1s requests):
us-west-2: 99/100 slow requests (avg 1707ms, max 32748ms)
ap-southeast-2: 정상 범위 (avg 208ms)
eu-central-1: 1건 outlier (16095ms)
동일 시간대 동일 호스트의 heavy 작업:
Api::V1::ElementTracesController#refresh — max 41,235ms, DB 3928ms
Api::V1::PanosController#bulk — max 12,352ms, DB 1308ms
Api::V1::ElementTracesController#bulk — max 12,005ms, DB 3617ms
resource_kind별 지연 (slow requests):
alignments_sampled: 29건, avg 1656ms
alignments_all: 28건, avg 1214ms
preview_image: 12건, avg 1124ms
processing_options: 28건, avg 1096ms
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | S3 HEAD 요청 동기 블로킹 + thread pool 경합 | DB 시간 36ms vs 전체 1443ms, 차이가 모두 unaccounted I/O. 동시간대 heavy bulk 작업이 동일 호스트에서 실행 (ElementTraces 41s, Panos 12s). 99% slow request가 us-west-2 집중. | — | Confirmed |
| H2 | DB 쿼리 병목 (N+1, slow query) | .or() 패턴이 비효율적 가능성 |
DB 시간 평균 36ms로 전체 지연의 2.5%에 불과. Datadog에서 slow query/N+1 로그 0건. | Rejected |
| H3 | S3 throttling (429) | gad team의 대량 업로드(03:00-05:41)와 극단적 outlier 시간 일치 | S3 throttling 에러 로그 없음. 단, throttle 시 SDK가 자동 retry하여 로그 없이 지연만 발생할 수 있음 — inconclusive | Inconclusive |
| H4 | 네트워크 장애 (특정 AZ) | 극단적 outlier가 동일 호스트(ip-10-1-144-228)에 집중 | 다른 리전에서도 경미한 지연 발생, 완전한 네트워크 장애는 아님 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/models/concerns/storagable/resource.rb:167— S3_object.exists?호출에 timeout 설정 추가. AWS SDK의:http_open_timeout과:http_read_timeout을 5초로 제한하여 극단적 지연(30초+)을 방지.- 또는 S3 client 초기화 시 global timeout 설정 확인 (
Cupix::StorageService설정 검토).
단기 개선 (1주 이내)#
- S3 HEAD 요청을 비동기로 전환하거나, 클라이언트가 polling하는 대신 S3 Event Notification + Lambda/SNS를 활용하여 업로드 완료를 push 방식으로 처리하는 구조 검토.
- heavy bulk 작업(
ElementTraces#refresh,Panos#bulk)과 upload check 요청을 분리된 thread pool 또는 별도 서버 그룹에서 처리하도록 라우팅 분리.
장기 개선 (재발 방지)#
- Upload 확인을 S3 Event Notification 기반으로 완전히 전환 — 클라이언트가 PUT 완료 후 서버에 알리는 것이 아니라, S3 → Lambda → API callback 방식.
- Puma thread pool 모니터링 추가 — thread 포화 시 자동 경고.
- 리전별 S3 latency 메트릭을 Datadog custom metric으로 수집.
Monitoring#
- S3 HEAD 요청 latency 모니터:
avg(trace.rack.request.duration){resource_name:api::v1::capturescontroller#check_resource_uploading} > 2000 - Thread pool 포화 경고: Puma
pool_capacitymetric이 0에 근접할 때 alert - Datadog APM query:
service:cupixworks-api resource_name:"Api::V1::CapturesController#check_resource_uploading" @duration:>5000
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — timeout 설정은 trivial하지만, 근본적 아키텍처 변경(push 방식)은 상당한 작업량 필요