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#
- 2026-05-26 03:21 UTC — 최초 slow trace 감지 (capture 702355, us-west-2)
- 2026-05-26 05:57 UTC — Capture 702355의
reconstruction_state가processing→done전환, 해당 캡처의 slow 요청 종료 - 2026-05-26 06:09 UTC — 마지막 slow trace 기록
- 2026-05-26T12:00 UTC — RCA 분석 완료
Error Log#
{
"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#
- Entry point:
app/controllers/api/v1/captures_controller.rb:46-50—update액션이repository_instance.update(params)호출 후super로 직렬화/렌더링 수행
def update
@model = repository_instance.update(params)
super
end
- Serializer invocation:
app/serializers/thumbnail_attribute.rb:13-18— 응답 JSON 생성 시thumbnails_urlsattribute가model.thumbnails_urls호출
attribute :thumbnails_urls do |model, params|
if model.respond_to?(:thumbnails)
model.thumbnails_urls
else
nil
end
end
- Failure point:
app/models/concerns/thumbnailable.rb:38-51—thumbnails_urls메서드에서thumbnails.all? { |thumbnail| thumbnail.file.present? }호출. 이 코드가 각 thumbnail에 대해 S3 HEAD 요청을 순차적으로 실행한다.
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
- Storage layer:
app/uploaders/thumbnail_uploader.rb:7—storage :fog설정으로 인해 CarrierWave의file.present?호출이 로컬 파일시스템이 아닌 S3 API (HEAD object)로 전달됨
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 분석에서 확인된 패턴:
service:cupixworks-api resource_name:"Api::V1::CapturesController#update" env:production @duration:>5000000000
모든 slow request의 공통 특징:
- 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):
- 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 미설정
지연 시간과 캡처 크기의 상관관계:
| 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_urls를nil로 즉시 반환하는 early return 추가도 효과적.
단기 개선 (1주 이내)#
ThumbnailAttributeserializer에서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#updatep95 duration 모니터링:
service:cupixworks-api resource_name:"Api::V1::CapturesController#update" env:production
Monitor: p95 duration > 5000ms for 5 minutes → alert
thumbnails_urls직렬화 시간 별도 custom metric 추가:
statsd.distribution('captures.thumbnails_urls.duration_ms', elapsed_ms, tags: ['service:cupixworks-api'])
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard —
thumbnails_urls메서드 로직 수정은 단순하나, 폴링 클라이언트의 동작 변경 및 CarrierWave 의존성 제거는 테스트 범위가 넓음.