ES /docs

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#

  1. 2026-05-27T03:48:26Z — sketch 12491에 대한 check_resource_uploading 요청이 1164ms 소요
  2. 2026-05-27T03:48:26Z — APM latency cluster로 감지됨
  3. 2026-05-27T05:28:45Z — APM 메트릭 확인: 해당 시간대 평균 응답시간 500-636ms로 전반적 지연 존재

Error Log#

Datadog Logs

json
{
  "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? 호출

실행 흐름:

  1. set_multiple_resource에서 DB 쿼리로 resource를 조회한다:
app/controllers/concerns/multiple_resourcable_controller.rb:145-151ruby
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
  1. check_resource_uploading에서 resource 상태를 확인하고 check_uploading을 호출한다:
app/controllers/concerns/multiple_resourcable_controller.rb:80-84ruby
def check_resource_uploading
  case @resource.state_name
  when :uploading, :missing
    callback_uploaded_resource(@resource) if @resource.check_uploading
  end
end
  1. check_uploading에서 S3 HEAD 요청이 발생한다 — 이것이 주요 지연 지점이다:
app/models/concerns/storagable/resource.rb:163-183ruby
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
  1. object 메서드는 Aws::S3::Object를 생성한다:
app/models/concerns/storagable/resource.rb:85-98ruby
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
  1. StorageService.objectAws::S3::Object.new를 반환한다 (네트워크 요청 없음 — lazy):
app/services/cupix/storage_service.rb:29-36ruby
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 로그 검색으로 해당 시점의 요청을 확인:

text
service:cupixworks-api "SketchesController#check_resource_uploading"
Time: 2026-05-27T03:40:00Z to 2026-05-27T03:55:00Z

해당 시간대 로그 (정상 200 응답):

text
2026-05-27 12:48:29 KST [200] PUT /api/v1/sketches/12491/resources/sketch/check_uploading (Api::V1::SketchesController#check_resource_uploading)

APM 메트릭 쿼리:

text
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::sketchescontroller_check_resource_uploading,env:production}

APM 메트릭 결과 (2시간 구간, 단위: 초):

text
시간대 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 관련 로그:

text
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 호출이 다수 확인됨:

json
{
  "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 추적을 위한 커스텀 메트릭 추가 검토
text
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 네트워크 지연으로 판단되며, 구조적 결함보다는 인프라 수준의 일시적 현상이다.