Api::V1::VideosController#create (avg 1563ms, max 4144ms)
RCA: Api::V1::VideosController#create Latency (avg 1268ms, max 4144ms)
Overview#
What Happened#
2026-05-26 03:34~13:17 UTC 사이 cupixworks-api 서비스의 Api::V1::VideosController#create 엔드포인트에서 평균 1268ms, 최대 4144ms의 응답 지연이 52건 발생했다. 전체 리전(ap-southeast-2, us-west-2, eu-central-1, ap-southeast-1)에서 관측되었으며, HTTP 응답은 모두 200 OK로 기능 장애는 아니지만 사용자 체감 성능이 저하되었다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::VideosController#create |
| avg_duration | 1268ms |
| max_duration | 4144ms |
| env | production (ap-southeast-2, us-west-2, eu-central-1, ap-southeast-1) |
| user_agent | Dart/3.11 (dart:io) (모바일 앱) |
Timeline#
- 2026-05-26T03:34:37Z — 최초 고지연 요청 감지
- 2026-05-26T04:17:36Z — 최대 지연 4144ms 발생 (ap-southeast-2)
- 2026-05-26T13:17:09Z — 마지막 고지연 요청 관측
- 2026-05-27 — RCA 완료
Error Log#
{
"resource_name": "Api::V1::VideosController#create",
"service": "cupixworks-api",
"occurrences": 21,
"avg_ms": 1563,
"max_ms": 4144,
"sample_trace_id": "914670550937679908"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 52
- 최초 발생: 2026-05-26T03:34:37.268Z
- 최근 발생: 2026-05-26T13:17:09.869Z
- 영향: 모바일 앱(Dart 클라이언트)에서 비디오 생성 시 1~4초 대기 발생. 기능 장애는 없으나 사용자 경험 저하.
Root Cause Summary#
VideosController#create의 응답 지연은 DB 시간(평균 48ms)이 아닌 애플리케이션 레벨 처리(평균 1200ms+)에서 발생한다. 코드 분석 결과, 비디오 생성 후 serializer가 upload_url을 생성할 때 Cupix::StorageService.client를 호출하여 매 요청마다 새로운 Aws::S3::Client 인스턴스를 생성하고, Aws::S3::Presigner로 presigned URL을 발급하는 과정이 주요 병목이다. AWS SDK 클라이언트 초기화에는 credential resolution, region endpoint lookup, HTTP connection setup이 포함되어 수백ms가 소요될 수 있으며, 원격 리전(ap-southeast-2)에서 특히 지연이 심하다. 또한 around_create_pano 콜백에서 Resource 레코드 생성, set_storage 콜백에서의 storage 조회, serializer에서의 다수 연관 객체 lazy loading이 복합적으로 지연을 가중시킨다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/videos_controller.rb:35-38 - Factory:
app/factories/video_factory.rb:6-26 - Base factory save:
app/factories/base_factory.rb:124(model.save!) - Around create callback (Resource 생성):
app/models/concerns/resourcable/video.rb:10-18 - Storage 설정:
app/models/concerns/storagable.rb:32-52(before_create :set_storage) - Serializer upload_url:
app/serializers/video_serializer.rb:19-25 - Presigned URL 생성:
app/models/concerns/storagable/resource.rb:111-131 - S3 Client 생성:
app/services/cupix/storage_service.rb:5-12
1. Controller → Factory → Save
def create
@model = factory_instance.create!(params)
super
end
factory_instance.create!가 Video 레코드를 생성하고, super가 serializer를 통해 응답을 렌더링한다.
2. around_create 콜백 — Resource 레코드 생성
around_create :around_create_pano
def around_create_pano
yield
begin
self.create_video_resource
rescue => e
raise e
end
end
Video INSERT 이후 즉시 Resource 레코드를 추가 INSERT한다. 이 Resource가 이후 presigned URL 생성의 대상이 된다.
3. Serializer — upload_url 생성 (핵심 병목)
attribute :upload_url do |video, params|
if %i[created resource_uploading resource_missing].include?(video.state_name)
video.resource_upload_url
else
nil
end
end
새로 생성된 비디오는 created 상태이므로 항상 resource_upload_url을 호출한다.
4. Presigned URL 생성 — 매 요청마다 S3 Client 신규 생성
def presigned_upload_url(revision, force: false)
revision ||= self.revision + 1
if !force && revision < self.revision
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid revision: #{revision}")
end
client = Cupix::StorageService.client(storage_option: storage_option)
signer = Aws::S3::Presigner.new(client: client)
bucket_name = storage_option.s3_source_bucket_name
expires_in = 2.hour.to_i
signer.presigned_url(
:put_object,
bucket: bucket_name,
key: object(revision).key,
storage_class: 'ONEZONE_IA',
expires_in: expires_in,
acl: 'bucket-owner-full-control'
)
end
매 호출마다 Aws::S3::Client.new를 실행한다 — 클라이언트 캐싱/풀링 없음.
def client(storage_option: nil, **kwargs)
opts = parse_storage_option(storage_option).merge(kwargs)
opts[:force_path_style] = true
check_required_params(opts, %i[region])
Aws::S3::Client.new(opts)
end
AWS SDK 클라이언트 초기화는 credential resolution (IMDSv2 metadata fetch 또는 credential chain), endpoint resolution, HTTP transport setup을 포함한다. 특히 EC2 instance metadata 기반 credential의 경우 IMDS 호출이 추가 네트워크 왕복을 발생시킨다.
5. Serializer의 추가 lazy loading
attribute :capture do |video|
{
id: video.capture_id,
name: video.has_attribute?(:capture_name) ? video.capture_name : video.capture.name,
measure_ready_at: video.has_attribute?(:capture_measure_ready_at) ? ...
method: video.has_attribute?(:capture_method) ? ... : video.capture.try(:capture_type).try(:method),
...
}
end
create 액션에서는 preloaded 속성이 없으므로 capture, camera, record, level 등을 개별 쿼리로 lazy load한다. DB 시간 자체는 48ms로 작지만 추가 round-trip이 발생한다.
Log Evidence#
Datadog APM 트레이스에서 확인된 지연 패턴:
service:cupixworks-api resource_name:"Api::V1::VideosController#create" env:production @duration:>500ms
리전별 지연 분포:
| Region | Count | Avg Duration | Max Duration | Avg DB Time | Non-DB Time |
| ap-southeast-2 | 8 | 1620ms | 3142ms | 48ms | 1572ms |
| us-west-2 | 33 | 1242ms | 2122ms | 49ms | 1194ms |
| eu-central-1 | 7 | 1094ms | 1270ms | 57ms | 1036ms |
| ap-southeast-1 | 2 | 1225ms | 1287ms | 41ms | 1184ms |
최대 지연 요청의 상세:
{
"timestamp": "2026-05-26T04:17:36Z",
"region": "ap-southeast-2",
"host": "ip-10-1-145-251.ap-southeast-2.compute.internal",
"team_id": 128,
"capture_id": 73277,
"total_duration_ms": 4142.52,
"db_time_ms": 51,
"non_db_time_ms": 4091,
"file_type": ".insv"
}
핵심 패턴: DB 시간은 모든 요청에서 26-73ms로 정상이며, 총 지연의 95-99%가 non-DB 처리(S3 클라이언트 초기화 + presigned URL 생성)에서 소비된다. ap-southeast-2 리전이 가장 느린 것은 해당 리전에서의 AWS SDK 초기화 및 credential resolution 지연이 더 큰 것으로 추정된다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | S3 presigned URL 생성 시 매번 새 Aws::S3::Client 인스턴스 생성으로 인한 지연 |
storage_service.rb:11에서 매 호출마다 Aws::S3::Client.new 실행 확인. Non-DB 시간이 1000-4000ms로 SDK 초기화 비용과 일치. ap-southeast-2가 가장 느린 것은 원격 IMDS/credential 지연과 부합 |
— | Confirmed |
| H2 | N+1 쿼리로 인한 DB 지연 | Serializer에서 capture, camera, record, level 등을 lazy load하는 코드 확인 (video_serializer.rb:30-60) |
DB 시간이 평균 48ms로 정상 범위. 총 지연의 5% 미만 | Rejected |
| H3 | around_create_pano 콜백의 Resource INSERT가 병목 |
콜백에서 추가 INSERT 확인 (resourcable/video.rb:20-26) |
DB 시간에 이미 포함되어 있으며 48ms 범위 내. 단독으로 1000ms+ 지연을 설명하지 못함 | Rejected |
| H4 | Storage 선택 로직(set_storage)에서의 외부 호출 지연 |
storagable.rb:32-52에서 facility/team/default storage 조회 |
DB 쿼리만 수행하며 외부 호출 없음. DB 시간에 포함됨 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 파일:
app/services/cupix/storage_service.rb:5-12 - 방향:
Aws::S3::Client인스턴스를 리전별로 캐싱한다.Thread.current또는RequestStore를 활용하여 동일 요청 내에서는 물론, 프로세스 수명 동안 리전+credential 조합별로 클라이언트를 재사용한다. - 근거: AWS SDK 클라이언트는 thread-safe하며 재사용이 권장된다. 매 요청마다 새로 생성할 필요가 없다.
단기 개선 (1주 이내)#
- Serializer에서 upload_url 생성을 비동기/lazy로 분리:
create응답에서upload_url을 즉시 반환하지 않고, 클라이언트가 별도POST /videos/:id/upload_url엔드포인트를 호출하도록 변경하거나, presigned URL 생성을 백그라운드로 이동하여 응답 지연을 줄인다. - Serializer preloading:
create액션에서 생성된 Video에 대해 capture, camera, record, level 연관 객체를 eager load하여 N+1 쿼리를 제거한다.
장기 개선 (재발 방지)#
Cupix::StorageService에 connection pooling 레이어를 도입하여 전체 서비스에서 S3 클라이언트 재사용을 보장한다.- APM에서 presigned URL 생성 시간을 별도 span으로 계측하여 지속적 모니터링이 가능하도록 한다.
Monitoring#
avg(trace.rack.request.duration){service:cupixworks-api, resource_name:Api::V1::VideosController#create}— p95/p99 지연 트래킹- 알림 조건: p95 > 2000ms 또는 p99 > 3000ms
service:cupixworks-api resource_name:"Api::V1::VideosController#create" @duration:>2000ms
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard — S3 클라이언트 캐싱은 기존
StorageService클래스 내 변경으로 완료 가능하며, 다른 resourcable 엔드포인트(panos, pointclouds 등)에도 동일한 개선 효과가 적용된다.