Api::V1::SketchesController#check_resource_uploading (avg 1164ms, max 1164ms)
RCA: Api::V1::SketchesController#check_resource_uploading Latency (1164ms)
Overview#
What Happened#
2026-05-27 03:48:26 UTC에 ap-southeast-2 리전의 cupixworks-api 서비스에서 Api::V1::SketchesController#check_resource_uploading 엔드포인트가 1164ms의 응답 시간을 기록했다. 이 엔드포인트는 S3에 업로드된 리소스의 존재 여부를 확인하는 동기식 S3 HEAD 요청을 수행하며, 단일 요청에서 네트워크 지연으로 인해 임계값(500ms)을 초과했다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::SketchesController#check_resource_uploading |
| top_frame | app/models/concerns/storagable/resource.rb:167 |
| env | production, ap-southeast-2 |
| avg_duration | 1164ms |
| sample_trace_id | 3491682449394385184 |
Timeline#
- 2026-05-27T03:48:26Z — sketch 12491에 대한
check_resource_uploading요청이 1164ms 소요 - 2026-05-27T03:48:26Z — APM latency cluster로 감지됨
- 2026-05-27T05:28:45Z — APM 메트릭 확인: 해당 시간대 평균 응답시간 500-636ms로 전반적 지연 존재
Error Log#
{
"resource_name": "Api::V1::SketchesController#check_resource_uploading",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 1164,
"max_ms": 1164,
"sample_trace_id": "3491682449394385184"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-05-27T03:48:26.593Z
- 최근 발생: 2026-05-27T03:48:26.593Z
사용자 영향은 제한적이다. 단일 sketch 업로드 확인 요청에서 ~1.2초 지연이 발생했으나, HTTP 200으로 정상 응답하여 기능적 오류는 없었다. 다만 APM 메트릭에 따르면 동일 시간대(03:48 UTC 전후)에 이 엔드포인트의 평균 응답시간이 500-636ms로 평소(180-320ms)보다 높았다.
Root Cause Summary#
check_resource_uploading 액션은 Storagable::Resource#check_uploading 메서드를 통해 S3 HEAD 요청(Aws::S3::Object#exists?)을 동기적으로 수행한다. ap-southeast-2 리전에서 S3 버킷으로의 네트워크 라운드트립이 일시적으로 증가하여 단일 요청에서 1164ms가 소요되었다. 이 엔드포인트는 S3 응답 시간에 직접적으로 의존하는 구조이므로, S3 지연이 그대로 API 응답 지연으로 전파된다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/concerns/multiple_resourcable_controller.rb:80 - before_action
set_multiple_resource:app/controllers/concerns/multiple_resourcable_controller.rb:145-151 - S3 object 생성:
app/models/concerns/storagable/resource.rb:85-98 - S3 existence check (주요 지연 지점):
app/models/concerns/storagable/resource.rb:163-183 - Failure point:
app/models/concerns/storagable/resource.rb:167—_object.exists?호출
실행 흐름:
set_multiple_resource에서 DB 쿼리로 resource를 조회한다:
def set_multiple_resource
kind_or_key = ApplicationRecord.sanitize_sql(params[:kind_or_key])
@resource = @model.resources.where(kind: kind_or_key).or(@model.resources.where(key: kind_or_key)).first
raise Cupix::Errors::NotFound.new(code: 'ARG10002', reason: 'Resource not found') and return if @resource.nil?
end
check_resource_uploading에서 resource 상태를 확인하고check_uploading을 호출한다:
def check_resource_uploading
case @resource.state_name
when :uploading, :missing
callback_uploaded_resource(@resource) if @resource.check_uploading
end
end
check_uploading에서 S3 HEAD 요청이 발생한다 — 이것이 주요 지연 지점이다:
def check_uploading
_revision = self.revision
_object = self.object(_revision + 1)
if _object.exists? # ← S3 HEAD request (주요 지연)
self.etag = _object.etag.gsub('"', '') rescue nil # ← 추가 S3 메타데이터 조회
self.size = _object.size # ← 추가 S3 메타데이터 조회
self.content_type = _object.content_type rescue nil # ← 추가 S3 메타데이터 조회
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
object메서드는Aws::S3::Object를 생성한다:
def object(ver = nil)
_ver = ver.nil? ? self.revision : ver
raise Cupix::Errors::System.new(code: 'SYS10000', reason: "Invalid revision #{ver}") if !_ver.is_a?(Integer) || !(_ver > -1)
Cupix::StorageService.object(
storage_option: storage_option,
bucket_name: storage_option.s3_source_bucket_name,
key: self.object_key(_ver)
)
end
StorageService.object는Aws::S3::Object.new를 반환한다 (네트워크 요청 없음 — lazy):
def object(storage_option: nil, **kwargs)
opts = parse_storage_option(storage_option).merge(kwargs)
opts[:force_path_style] = true
check_required_params(opts, %i[region bucket_name key])
Aws::S3::Object.new(opts)
end
기대 동작: exists?가 S3 HEAD 요청으로 50-200ms 이내에 응답하고, 전체 엔드포인트가 300ms 이내에 완료.
실제 동작: S3 HEAD 요청이 일시적 네트워크 지연으로 인해 ~800-1000ms가 소요되었고, etag/size/content_type 메타데이터 조회와 DB 저장을 합산하여 총 1164ms가 소요.
Log Evidence#
Datadog 로그 검색으로 해당 시점의 요청을 확인:
service:cupixworks-api "SketchesController#check_resource_uploading"
Time: 2026-05-27T03:40:00Z to 2026-05-27T03:55:00Z
해당 시간대 로그 (정상 200 응답):
2026-05-27 12:48:29 KST [200] PUT /api/v1/sketches/12491/resources/sketch/check_uploading (Api::V1::SketchesController#check_resource_uploading)
APM 메트릭 쿼리:
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::sketchescontroller_check_resource_uploading,env:production}
APM 메트릭 결과 (2시간 구간, 단위: 초):
시간대 03:12 UTC (최대 스파이크): 1.318s
시간대 03:14 UTC: 0.750s
시간대 03:36-03:50 UTC: 0.500-0.636s (지속적 고지연)
평상시 (03:00 UTC 이전): 0.180-0.320s
동일 시간대 storage 관련 로그:
service:cupixworks-api status:warn "S3" OR "storage" OR "timeout"
Time: 2026-05-27T03:30:00Z to 2026-05-27T04:10:00Z
결과: S3/storage 관련 경고 로그는 없었으나, set_storage 호출이 다수 확인됨:
{
"timestamp": "2026-05-27 13:09:38 KST",
"status": "info",
"message": "Set a storage on Mask using default storage on apse2",
"class": "Mask",
"function": "set_storage"
}
이는 ap-southeast-2 리전 storage가 활발히 사용되고 있음을 나타내며, 동시 S3 요청 부하가 지연에 기여했을 가능성이 있다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | S3 HEAD 요청의 일시적 네트워크 지연 (ap-southeast-2 → S3) | APM 메트릭에서 03:12-03:50 UTC 구간 전체적으로 지연 증가 (0.5-1.3s). check_uploading의 핵심 로직이 _object.exists? S3 호출임 (resource.rb:167). 평소 180-320ms vs 이벤트 시점 1164ms. |
S3 서비스 장애 공지 없음 (확인 불가) | Confirmed |
| H2 | DB 쿼리 지연 (set_multiple_resource의 OR 쿼리) |
.where(kind:).or(.where(key:)) 패턴은 인덱스 활용이 비효율적일 수 있음 |
DB 쿼리 단독으로 1164ms를 유발하기 어려움. 동일 시간대 다른 컨트롤러 액션은 정상 응답 | Rejected |
| H3 | Storage option cache miss로 인한 추가 DB 조회 | set_storage_option이 Rails.cache miss 시 DB 조회. 동시간대 set_storage 로그 다수 확인 |
set_storage 로그는 Mask 모델에 대한 것이며, cache miss는 추가 10-50ms 수준으로 전체 지연(1164ms)의 주요 원인은 아님 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
이 이벤트는 단발성(1회)이며 S3 네트워크 지연에 의한 일시적 현상으로, 즉각적인 코드 수정은 불필요하다. 다만 500ms 임계값을 지속적으로 초과하는 패턴이 반복되면 아래 조치를 적용한다.
단기 개선 (1주 이내)#
app/models/concerns/storagable/resource.rb:167-170에서exists?호출 후etag,size,content_type을 개별적으로 접근하는 대신, 단일head_object호출로 모든 메타데이터를 한 번에 가져오는 방식으로 변경. AWS SDK의Aws::S3::Object#exists?는 내부적으로head_object를 호출하고 응답을 버리므로, 직접head_object를 호출하여 결과를 캐싱하면 추가 네트워크 라운드트립을 제거할 수 있다.- S3 요청에 대한 타임아웃 설정 추가 (3초 이내)를 검토.
장기 개선 (재발 방지)#
- polling 방식(
check_uploading반복 호출)을 S3 Event Notification (S3:ObjectCreated) 기반의 이벤트 드리븐 방식으로 전환 검토. 이렇게 하면 동기적 S3 HEAD 요청 자체가 불필요해진다. - ap-southeast-2 리전에서 S3 접근 지연이 반복되는 경우, S3 Transfer Acceleration 또는 리전 내 S3 엔드포인트 최적화를 검토.
Monitoring#
check_resource_uploading엔드포인트의 p95 latency 모니터 설정 (임계값: 1000ms)- S3 HEAD 요청 latency 추적을 위한 커스텀 메트릭 추가 검토
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::sketchescontroller_check_resource_uploading,env:production} > 1.0
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
단발성 이벤트이며 기능적 오류 없이 정상 응답(200)을 반환했다. ap-southeast-2 리전에서의 일시적 S3 네트워크 지연으로 판단되며, 구조적 결함보다는 인프라 수준의 일시적 현상이다.