ES /docs

Api::V1::CapturesController#update (avg 22908ms, max 52518ms)

RCA: Api::V1::CapturesController#update Latency (avg 22908ms, max 52518ms)

Overview#

What Happened#

2026-05-26 03:21~06:09 UTC 사이에 cupixworks-api 서비스의 CapturesController#update 엔드포인트에서 평균 22.9초, 최대 52.5초의 극심한 지연이 발생했다. 총 54건이 us-west-2, ap-southeast-2, eu-central-1, ap-southeast-1 리전에서 감지되었으며, 모두 HTTP 200으로 정상 응답하지만 응답 시간이 비정상적으로 길었다.

Quick Facts#

Field Value
resource_name Api::V1::CapturesController#update
top_frame app/models/concerns/thumbnailable.rb:39
env production (us-west-2, ap-southeast-2, eu-central-1, ap-southeast-1)

Affected Teams#

Team / Domain Error Count Impact
gad (us-west-2) 18 Capture 702355 — 각 요청 ~51초, 폴링 클라이언트 블로킹
pclconstruction (us-west-2) 12 Capture 702430, 702445, 702440 — 12~40초 지연
smdmhc-hawkins (ap-southeast-2) 6 Capture 73099 — ~15초 지연
hassan-allam (eu-central-1) 3 Capture 37116 — ~7초 지연

Timeline#

  1. 2026-05-26 03:21 UTC — 최초 slow trace 감지 (capture 702355, us-west-2)
  2. 2026-05-26 05:57 UTC — Capture 702355의 reconstruction_stateprocessingdone 전환, 해당 캡처의 slow 요청 종료
  3. 2026-05-26 06:09 UTC — 마지막 slow trace 기록
  4. 2026-05-26T12:00 UTC — RCA 분석 완료

Error Log#

Datadog Logs

json
{
  "resource_name": "Api::V1::CapturesController#update",
  "service": "cupixworks-api",
  "occurrences": 54,
  "avg_ms": 22908,
  "max_ms": 52518,
  "sample_trace_id": "1597344888784382339"
}

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 54
  • 최초 발생: 2026-05-26T03:21:21.409Z
  • 최근 발생: 2026-05-26T06:09:59.042Z

Root Cause Summary#

CapturesController#update 응답의 JSON 직렬화 과정에서 thumbnails_urls 필드가 요청될 때, Thumbnailable concern의 thumbnails_urls 메서드가 CarrierWave fog storage를 통해 각 thumbnail 파일의 존재 여부를 S3 HEAD 요청으로 순차 확인한다. Capture가 reconstruction_state: processing 상태일 때 thumbnail 파일이 아직 S3에 업로드되지 않은 상태이므로, 각 HEAD 요청이 S3 timeout까지 대기한 후 "not found"를 반환한다. Thumbnail 개수에 비례하여 지연이 누적되며, pano 수가 많은 캡처일수록 지연이 심해진다.

Technical Analysis#

Code Path#

  1. Entry point: app/controllers/api/v1/captures_controller.rb:46-50update 액션이 repository_instance.update(params) 호출 후 super로 직렬화/렌더링 수행
app/controllers/api/v1/captures_controller.rb:46-50ruby
def update
  @model = repository_instance.update(params)

  super
end
  1. Serializer invocation: app/serializers/thumbnail_attribute.rb:13-18 — 응답 JSON 생성 시 thumbnails_urls attribute가 model.thumbnails_urls 호출
app/serializers/thumbnail_attribute.rb:13-18ruby
attribute :thumbnails_urls do |model, params|
  if model.respond_to?(:thumbnails)
    model.thumbnails_urls
  else
    nil
  end
end
  1. Failure point: app/models/concerns/thumbnailable.rb:38-51thumbnails_urls 메서드에서 thumbnails.all? { |thumbnail| thumbnail.file.present? } 호출. 이 코드가 각 thumbnail에 대해 S3 HEAD 요청을 순차적으로 실행한다.
app/models/concerns/thumbnailable.rb:38-51ruby
def thumbnails_urls
  has_thumbnails = thumbnails.any? && thumbnails.all? { |thumbnail| thumbnail.file.present? }

  if !has_thumbnails
    nil
  else
    thumbnails.map do |thumbnail|
      {
        thumb: "#{thumbnail.url(:thumb)}?#{updated_at.to_i}",
        original: "#{thumbnail.url}?#{updated_at.to_i}"
      }
    end
  end
end
  1. Storage layer: app/uploaders/thumbnail_uploader.rb:7storage :fog 설정으로 인해 CarrierWave의 file.present? 호출이 로컬 파일시스템이 아닌 S3 API (HEAD object)로 전달됨
app/uploaders/thumbnail_uploader.rb:1-7ruby
class ThumbnailUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  include SignedContentUploader

  DEFAULT_THUMB_SIZE = [300, 200].freeze

  storage :fog

기대 동작: thumbnails_urls 직렬화가 밀리초 단위로 완료되어야 함.

실제 동작: reconstruction_state: processing 상태의 캡처에서 thumbnail 파일이 S3에 존재하지 않아, 각 thumbnail마다 S3 HEAD 요청이 timeout 또는 404를 기다린 후 반환. N개의 thumbnail × S3 응답 대기 시간 = 수십 초의 누적 지연.

Log Evidence#

Datadog APM trace 분석에서 확인된 패턴:

text
service:cupixworks-api resource_name:"Api::V1::CapturesController#update" env:production @duration:>5000000000

모든 slow request의 공통 특징:

text
- DB time: 15-38ms (전체의 0.03%)
- View/serialization time: 0ms (reported)
- 99.9%+ unaccounted "other" time
- 요청 params에 항상 "thumbnails_urls" 포함: ["id", "name", "thumbnails_urls"]
- 모든 요청이 HTTP 200 정상 응답

최악 사례 (Capture 702355, team: gad, us-west-2):

text
- 18 consecutive PUT requests
- 각 요청 50-56초 소요
- 요청 간격 ~50-55초 (응답 대기 후 다음 폴링)
- reconstruction_state: processing → done 전환 시점(05:57:05)에 slow 요청 종료
- Remote IP: 44.228.8.68 (AWS 내부 IP, 백엔드 폴러로 추정)
- user_agent 미설정

지연 시간과 캡처 크기의 상관관계:

text
| Capture | Avg Duration | reconstruction_state |
|---------|-------------|---------------------|
| 702355  | 51,671ms    | processing          |
| 702430  | 40,263ms    | processing          |
| 702445  | 20,631ms    | processing          |
| 73099   | 15,568ms    | processing          |
| 702440  | 11,778ms    | processing          |
| 37116   | 7,445ms     | processing          |

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 thumbnails_urls 직렬화 시 S3 HEAD 요청이 순차적으로 실행되어 지연 발생 thumbnailable.rb:39에서 thumbnail.file.present? 호출 → fog storage HEAD 요청; DB time 0.03%로 DB 아님; reconstruction_state=processing 시에만 발생; thumbnail 수와 duration 비례 Confirmed
H2 Elasticsearch review query가 지연 원인 stale_review/capture.rb에 around_save callback 존재 DB time이 15-38ms로 매우 낮음; ES 쿼리는 DB time에 포함됨; 모든 요청에서 동일 패턴이면 ES가 원인일 수 없음 Rejected
H3 AWS Lambda zip invocation이 synchronous하게 블로킹 zippable/capture.rb에서 Lambda invoke 코드 존재 invocation_type: 'Event'는 비동기; update 시마다 실행되지 않음 (publish 시에만); 상태 변경 조건 미충족 Rejected
H4 State machine transition callback이 지연 원인 statable/capture.rb에 다수의 after_transition callback 존재 update 시 항상 state 변경이 발생하지 않음; DB time 15-38ms로 callback 실행 시간이 미미 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 파일: app/models/concerns/thumbnailable.rb:38-51
  • 방향: thumbnails_urls 메서드에서 thumbnail.file.present? 호출을 제거하고, thumbnail JSON 배열의 존재 여부만으로 URL을 생성하도록 변경. S3 존재 확인이 필요한 경우 캐시된 메타데이터(DB 컬럼 또는 Redis)를 활용.
  • reconstruction_state: processing 상태의 캡처에 대해 thumbnails_urlsnil로 즉시 반환하는 early return 추가도 효과적.

단기 개선 (1주 이내)#

  • ThumbnailAttribute serializer에서 thumbnails_urls 필드가 클라이언트에 의해 명시적으로 요청된 경우에만 계산하도록 lazy evaluation 적용.
  • 폴링 클라이언트(44.228.8.68)가 thumbnails_urls 필드를 요청하지 않도록 API 호출 파라미터 수정. 불필요한 필드 요청 제거.

장기 개선 (재발 방지)#

  • CarrierWave fog storage의 file.present? 호출을 API 요청 경로에서 완전히 제거. Thumbnail 존재 여부는 비동기 워커에서 확인 후 DB 플래그로 관리.
  • 직렬화 단계에서 외부 I/O (S3, HTTP) 호출을 금지하는 아키텍처 규칙 수립 및 lint/CI 검증 추가.

Monitoring#

  • APM trace에서 CapturesController#update p95 duration 모니터링:
text
service:cupixworks-api resource_name:"Api::V1::CapturesController#update" env:production
Monitor: p95 duration > 5000ms for 5 minutes → alert
  • thumbnails_urls 직렬화 시간 별도 custom metric 추가:
text
statsd.distribution('captures.thumbnails_urls.duration_ms', elapsed_ms, tags: ['service:cupixworks-api'])

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard — thumbnails_urls 메서드 로직 수정은 단순하나, 폴링 클라이언트의 동작 변경 및 CarrierWave 의존성 제거는 테스트 범위가 넓음.