ES /docs

Api::V1::FloorplanSourcesController#download_single_resource (avg 14243ms, max 14243ms)

RCA: FloorplanSourcesController#download_single_resource Latency (14.2s)

Overview#

What Happened#

2026-06-03 21:06 KST에 cupixworks-api 서비스의 Api::V1::FloorplanSourcesController#download_single_resource 엔드포인트에서 단일 요청이 14,243ms 소요되었다. 동일 리소스에 대한 정상 요청이 38-80ms에 처리되는 것과 비교하면 약 180배 느린 응답이다. 원인은 동시 floorplan 생성/업데이트 작업으로 인한 DB 락 대기(10.6초)이다.

Quick Facts#

Field Value
resource_name Api::V1::FloorplanSourcesController#download_single_resource
top_frame app/controllers/concerns/single_resourcable_controller.rb:10
env production, us-west-2
deploy production-us-west-2-20260603t0634z0-bfe9a388-cupixworks
duration 14,243ms (DB: 10,596ms)

Timeline#

  1. 2026-06-03 21:05 KST — 동일 trace에서 floorplan 생성 (POST /api/v1/floorplans) 시작, Facility 9591 관련 트랜잭션 수행
  2. 2026-06-03 21:06 KSTdownload_single_resource 요청 도달, DB 락 대기 시작
  3. 2026-06-03 21:06:57 KST — 요청 완료 (302 redirect), 총 14,144ms 소요 (DB 10,596ms)
  4. 2026-06-03 21:08 KST — 동일 trace 내 후속 floorplan 업데이트 및 tile upload 완료

Error Log#

Datadog Logs

json
{
  "resource_name": "Api::V1::FloorplanSourcesController#download_single_resource",
  "service": "cupixworks-api",
  "occurrences": 1,
  "avg_ms": 14243,
  "max_ms": 14243,
  "sample_trace_id": "1218735581250569910"
}

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-06-03 21:06 KST
  • 최근 발생: 2026-06-03 21:06 KST
  • 영향 사용자: Gregory Morel (nestle/344 team, user ID 15505)
  • 영향 리소스: FloorplanSource ID 35498, Facility 9591

Root Cause Summary#

download_single_resource 요청이 set_floorplan_source before_action에서 FloorplanSourceRepository.show()를 호출할 때, 동일 facility(9591)에 대한 동시 floorplan 생성/업데이트 트랜잭션이 DB row lock을 점유하고 있어 10.6초 동안 락 대기가 발생했다. FloorplanSourceRepository.permission_joins는 8개 LEFT JOIN과 facility_permissions, workspace_permissions, team_permissions 테이블을 조회하는 복합 쿼리로, 동시 트랜잭션이 관련 row에 exclusive lock을 보유하면 읽기 쿼리도 대기하게 된다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/floorplan_sources_controller.rb:59
  • set_floorplan_sourcerepository_instance.show(params[:id]) 호출
  • BaseRepository#showFloorplanSourceRepository.show (class method)
  • FloorplanSourceRepository.permission_joins 실행 — 8개 LEFT JOIN으로 권한 체크
  • 이 쿼리가 10,596ms 대기 (lock contention)
  • 쿼리 완료 후 set_single_resource에서 @resource = @model.resource 설정
  • download_single_resource에서 @resource.download_url 호출 → S3 presigned URL 생성 → 302 redirect
app/controllers/api/v1/floorplan_sources_controller.rb:59-61ruby
def set_floorplan_source
  @model = repository_instance.show(params[:floorplan_source_id] || params[:id])
end
app/controllers/concerns/single_resourcable_controller.rb:10-19ruby
def download_single_resource
  if @resource.revision == 0
    raise Cupix::Errors::Resource.new(code: 'ENT10011', reason: "Resource does not uploaded: #{@resource.revision}")
  else
    download_opts = {}
    download_opts[:filename] = params[:filename] if params[:filename].present?

    redirect_to @resource.download_url(download_opts), allow_other_host: true
  end
end
app/repositories/floorplan_source_repository.rb:26-28ruby
def self.default_joins(record)
  record.includes(:storage).joins(:facility, :workspace)
end
app/repositories/floorplan_source_repository.rb:57-161ruby
record.joins("
  LEFT JOIN (
    SELECT facility_id, permission
    FROM facility_permissions
    WHERE facility_permissions.accessor_id = #{sanitized_user_id}
      AND facility_permissions.accessor_type = 'User'
    ) AS facility_user_permissions
      ON facility_user_permissions.facility_id = floorplan_sources.facility_id
  -- ... 7 more LEFT JOINs on facility_permissions, workspace_permissions, team_permissions ...
").group('id').select(_select).where("
  (GREATEST(...) = 1 AND GREATEST(...) > 0) OR GREATEST(...) > 1
")

DB 쿼리가 floorplan_sources 테이블을 joins(:facility, :workspace)로 조회하면서, 동시 트랜잭션이 해당 facility/workspace row에 exclusive lock을 보유 중이면 InnoDB에서 읽기 대기(lock wait)가 발생한다. 특히 MySQL의 기본 innodb_lock_wait_timeout은 50초이므로, 10초 대기 후 동시 트랜잭션이 커밋되면서 lock이 해제된 것으로 보인다.

Log Evidence#

Datadog에서 동일 trace (ID: 1218735581250569910)의 로그를 조회한 결과, 해당 trace는 floorplan 생성부터 download까지 전체 워크플로우를 포함:

text
Query: trace_id:1218735581250569910
Time window: 2026-06-03T12:05:22Z ~ 2026-06-03T12:09:12Z

핵심 로그:

text
2026-06-03T12:05:22.940Z - POST /api/v1/floorplans (Floorplan 생성, Facility 9591)
2026-06-03T12:06:42.975Z - GET download_single_resource (FloorplanSource 35497) → 38.22ms, DB 6.92ms ✓ 정상
2026-06-03T12:06:45.063Z - GET download_single_resource (FloorplanSource 35497) → 79.34ms, DB 32.8ms ✓ 정상
2026-06-03T12:06:46.976Z - GET download_single_resource (FloorplanSource 35498) → 39.44ms, DB 6.15ms ✓ 정상 (다른 호스트)
2026-06-03T12:06:57.073Z - GET download_single_resource (FloorplanSource 35498) → 14144.3ms, DB 10596.13ms ✗ 문제 발생
2026-06-03T12:08:28.113Z - GET download_single_resource (FloorplanSource 35498) → 64.89ms, DB 9.16ms ✓ 정상 (이후 해소)

문제 요청의 상세 정보:

json
{
  "host": "ip-10-1-80-134.us-west-2.compute.internal",
  "duration_ms": 14144.3,
  "db_ms": 10596.13,
  "http_status": 302,
  "user": "gregory.morel1@fr.nestle.com",
  "user_id": 15505,
  "team": "nestle/344",
  "resource_id": 35498,
  "redirect_location": "https://s3.eu-west-3.amazonaws.com/cupixworks-source-0c24fc2dfb77-euwe3/resources/w3hvo2/euwe3/v1"
}

동일 시간대 error-level 로그는 0건. 문제 해소 후 동일 리소스에 대한 요청은 즉시 정상 복귀 (64.89ms).

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 DB lock contention으로 인한 쿼리 대기 — 동시 floorplan 생성 트랜잭션이 facility/workspace row에 exclusive lock을 보유 DB time 10,596ms (전체 14,144ms 중 75%), 동일 trace에서 floorplan 생성(12:05:22Z) 진행 중, 이후 요청(12:08:28Z)은 즉시 정상 Confirmed
H2 S3 presigned URL 생성 지연 (AWS API latency) 총 시간 14,144ms 중 DB 외 시간 약 3,548ms DB time이 대부분(75%)을 차지, S3는 redirect URL만 생성하므로 네트워크 호출 최소, 다른 정상 요청에서도 동일 S3 버킷 사용 Rejected
H3 permission_joins 쿼리 자체의 비효율 (8개 LEFT JOIN) 복잡한 쿼리 구조, 정상 시에도 6-33ms 소요 정상 요청은 6-33ms로 충분히 빠름, 쿼리 구조가 변경된 적 없음 Rejected
H4 Host-specific 이슈 (ip-10-1-80-134의 리소스 문제) 문제 요청과 두 번째로 느린 요청(79ms) 모두 동일 호스트 79ms도 정상 범위, 이후 다른 호스트에서도 정상 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

이 이슈는 일회성 lock contention으로, 현재 시스템에 지속적 영향 없음. 즉각적인 코드 수정은 불필요하다.

단기 개선 (1주 이내)#

  • set_floorplan_source에서 show() 호출 시 download_single_resource 액션의 경우 권한 체크를 단순화하거나, read-only 쿼리에 statement timeout을 설정
    • 파일: app/controllers/api/v1/floorplan_sources_controller.rb:59-61
    • 방향: download 액션은 별도의 lightweight 쿼리(permission join 생략, find_by(id:) + 별도 권한 체크)를 사용하여 lock 대기 가능성 감소

장기 개선 (재발 방지)#

  • Floorplan 생성/업데이트 시 facility/workspace 테이블에 대한 lock 범위를 최소화 (advisory lock 또는 optimistic locking 도입)
  • download_single_resource 같은 읽기 전용 엔드포인트에 read replica 사용 검토
  • MySQL statement timeout (SET SESSION MAX_EXECUTION_TIME = 5000) 적용하여 10초 이상 대기 방지

Monitoring#

  • download_single_resource 엔드포인트 p99 latency 모니터링 추가
  • DB query time > 5000ms인 요청에 대한 alert 설정
text
service:cupixworks-api resource_name:"Api::V1::FloorplanSourcesController#download_single_resource" @duration:>5000ms
  • InnoDB lock wait 모니터링: innodb_row_lock_waits 메트릭

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial
  • 단일 발생 이벤트로, 동시 작업 패턴이 반복되지 않는 한 재발 가능성 낮음. 동일 facility에 대한 대량 batch 작업 시 유사 패턴 발생 가능.