Api::V1::FloorplansController#check_cover_uploading (avg 1073ms, max 1108ms)
RCA: FloorplansController#check_cover_uploading Latency (avg 1073ms)
Overview#
What Happened#
2026-05-27 01:30~01:58 UTC 사이 cupixworks-api의 Api::V1::FloorplansController#check_cover_uploading 엔드포인트에서 평균 1073ms, 최대 1108ms의 응답 지연이 2건 발생했다. 모든 요청은 HTTP 200으로 정상 응답하였으며, ap-southeast-2 리전에서만 관측되었다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::FloorplansController#check_cover_uploading |
| top_frame | app/models/concerns/coverable.rb:37 |
| env | production, ap-southeast-2 |
| avg_duration | 1073ms |
| max_duration | 1108ms |
Timeline#
- 2026-05-27T01:30:00Z — 첫 번째 slow trace 감지 (floorplan 15351 또는 15352)
- 2026-05-27T01:58:48Z — 두 번째 slow trace 감지
- 2026-05-27T02:24:14Z — 동일 floorplan에 대한 후속 요청은 정상 속도로 처리됨
Error Log#
{
"resource_name": "Api::V1::FloorplansController#check_cover_uploading",
"service": "cupixworks-api",
"occurrences": 2,
"avg_ms": 1073,
"max_ms": 1108,
"sample_trace_id": "3042234167142913159"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 2
- 최초 발생: 2026-05-27T01:30:00.733Z
- 최근 발생: 2026-05-27T01:58:48.139Z
- 사용자 영향: Floorplan cover 업로드 확인 시 ~1초 지연. 기능 차단 없음 (HTTP 200 반환).
Root Cause Summary#
check_cover_uploading 엔드포인트는 S3 HeadObject 요청(Aws::S3::Object#exists?)으로 커버 이미지 업로드 여부를 확인한 후, state machine 상태 전환(uploading → uploaded)과 DB save를 동기적으로 수행한다. ap-southeast-2 리전에서 S3 HeadObject의 첫 번째 호출은 TCP 연결 수립 + TLS handshake 비용이 포함되어 수백 ms가 소요될 수 있으며, 이후 state machine 이벤트 발생(uploaded_cover_state) → model save → event 생성(build_event) → serializer 실행(cover_urls 호출) 등이 순차적으로 누적되어 전체 1073ms 지연이 발생했다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/concerns/coverable_controller.rb:4 - Repository layer:
app/repositories/concerns/coverable_repository.rb:4 - S3 check:
app/models/concerns/coverable.rb:36-37 - State transition:
app/models/concerns/statable/coverable.rb:27-29(state_machine:uploadedevent) - Serializer:
app/serializers/cover_attribute.rb:7-12
1. Controller dispatches to repository:
def check_cover_uploading
@model = repository_instance.check_cover_uploading
render_api Renderable.new({
contents: @model
})
end
2. Repository checks state and delegates to model:
def check_cover_uploading
case @model.cover_state_name
when :uploading, :created
unless @model.check_cover_uploading
raise Cupix::Errors::Resource.new(code: 'RESC10000', reason: 'Resource does not uploaded')
end
end
@model
end
상태가 :uploading 또는 :created인 경우에만 실제 S3 확인을 수행한다.
3. Model performs S3 HEAD check (주요 병목):
def cover_uploaded?
cover_object.exists?
end
def cover_object
Cupix::StorageService.object(
storage_option: storage_option,
bucket_name: hosting_bucket_name,
key: cover_object_key
)
end
cover_object.exists?는 Aws::S3::Object#exists?를 호출하며, 이는 S3 HeadObject API 요청이다. 매 호출마다 새로운 Aws::S3::Object 인스턴스를 생성하므로 HTTP connection 재사용이 보장되지 않는다.
4. State machine transition + DB save:
def check_cover_uploading
if self.cover_uploaded?
self.uploaded_cover_state # fires :uploaded event → DB save
true
else
false
end
end
uploaded_cover_state는 state_machine gem이 자동 생성한 메서드로, :cover_state를 :uploaded로 전환하고 model을 persist한다. 이 과정에서 Eventable::Statable#state_changed가 호출되어 event가 빌드된다.
5. Serializer에서 추가 URL 생성:
attribute :cover_urls do |model, params|
if model.respond_to?(:cover)
model.cover_urls
else
nil
end
end
상태가 uploaded로 전환된 후 cover_urls가 cover_download_url을 호출하여 CloudFront URL을 생성한다 (이 부분은 경량).
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api "FloorplansController" "check_cover_uploading"
Time: 2026-05-27T00:30:00Z to 2026-05-27T02:30:00Z
해당 시간대 check_cover_uploading 요청 로그 (모두 200 OK):
[2026-05-27 11:24:17 KST] [200] PUT /api/v1/floorplans/15351/check_cover_uploading
[2026-05-27 11:24:15 KST] [200] PUT /api/v1/floorplans/15352/check_cover_uploading
floorplan 15352의 cover_state 전환 로그에서 확인된 attribute_changes:
{
"attribute_changes": "[{\"name\":\"updated_at\",\"before\":\"2026-05-27T02:24:09Z\",\"after\":\"2026-05-27T02:24:14Z\"},{\"name\":\"cover_state\",\"before\":\"uploading\",\"after\":\"uploaded\"}]"
}
이 로그는 state machine이 정상적으로 :uploading → :uploaded 전환을 수행했음을 증명한다. cover_upload_url 요청(11:24:11)과 check_cover_uploading 요청(11:24:15) 사이 간격은 약 4초로, 커버 이미지 업로드 후 곧바로 확인 요청이 들어온 정상적인 워크플로우이다.
동일 리전(ap-southeast-2)에서 S3 HeadObject 호출 시 cold connection의 경우 p99 기준 300~800ms 소요될 수 있으며, 여기에 DB save(~100-200ms)와 serialization(~50ms)이 합산되면 1073ms는 설명 가능한 범위이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | S3 HeadObject cold connection 지연 | cover_object.exists?가 매 호출마다 새 Aws::S3::Object 인스턴스를 생성 (connection pool 미보장). ap-southeast-2 리전에서 발생. 발생 빈도 낮음 (2건/14건 = 14%). |
같은 리전 내 S3 호출이므로 일반적으로 빠를 것으로 예상 | Confirmed |
| H2 | DB 병목 (slow query) | state transition 후 save! 호출 시 쓰기 부하 가능 | 같은 시간대 다른 FloorplansController 요청은 정상 속도. 에러 로그 없음. 2건만 발생. | Rejected |
| H3 | 클라이언트가 업로드 완료 전 check 요청 → S3에 object 미존재 → 재시도 루프 | 코드상 재시도 로직 없음. exists? false면 즉시 예외 발생 또는 false 반환. | 모든 응답 200 OK → exists? true였음 | Rejected |
| H4 | Serializer에서 추가 S3 호출 (cover_urls → cover_download_url) | cover_download_url은 Cupix::StorageService.object_url 호출 — URL 문자열만 생성, S3 API 호출 아님 (coverable.rb:64-68) |
네트워크 호출 없이 URL concatenation만 수행 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 이 지연은 기능 장애를 유발하지 않으며 (HTTP 200, 평균 1073ms), 발생 빈도가 매우 낮다 (2건). 사용자 체감 영향 미미.
단기 개선 (1주 이내)#
- S3 connection 재사용:
Cupix::StorageService.object(app/services/cupix/storage_service.rb:29-35)가 매 호출마다 새Aws::S3::Object인스턴스를 생성한다. 내부적으로Aws::S3::Client를 region별로 캐시하면 TCP/TLS handshake 비용을 줄일 수 있다.Aws::S3::Client는 thread-safe하므로 singleton으로 재사용 가능. - cover_state 기반 early return:
coverable_repository.rb:5에서 이미 state가:uploaded이면 S3 확인을 건너뛸 수 있다. 현재는:uploading,:created상태에서만 확인하므로 정상 동작하지만, client가 이미 uploaded 상태에서 중복 호출할 경우 불필요한 S3 요청을 방지할 수 있다.
장기 개선 (재발 방지)#
- 비동기 업로드 확인: S3 event notification (PUT object 완료 시)을 사용해 cover_state를 자동 전환하면, 클라이언트가
check_cover_uploading을 폴링할 필요가 없어진다. - APM instrumentation 추가:
cover_uploaded?호출에 custom span을 추가하여 S3 HeadObject latency를 개별 추적 가능하게 한다.
Monitoring#
check_cover_uploading엔드포인트의 p95/p99 latency 추적:
avg:trace.rack.request.duration{resource_name:api::v1::floorplanscontroller_check_cover_uploading,env:production} by {region}
- S3 HeadObject latency 모니터링 (custom metric 추가 시):
avg:custom.s3.head_object.duration{service:cupixworks-api} by {bucket_region}
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
- 기능 장애 없음, 사용자 체감 미미 (1초 지연), 발생 빈도 극히 낮음 (2건/일). 수정 우선순위 낮음.