ES /docs

Api::V1::ClustersController#create_resource (avg 5057ms, max 5057ms)

RCA: ClustersController#create_resource Latency (5057ms)

Overview#

What Happened#

2026-06-03 08:07 KST에 cupixworks-api 서비스의 Api::V1::ClustersController#create_resource 엔드포인트에서 단일 요청이 5057ms의 응답 시간을 기록했다. 해당 엔드포인트의 정상 응답 시간(평균 50-120ms)에 비해 약 42-100배 느린 outlier이다.

Quick Facts#

Field Value
resource_name Api::V1::ClustersController#create_resource
top_frame app/controllers/concerns/multiple_resourcable_controller.rb:52
env production, us-west-2
avg_duration 5057ms
sample_trace_id 7160848916220558224

Timeline#

  1. 2026-06-03 08:07 KSTClustersController#create_resource 요청이 5057ms 소요되어 latency threshold 초과
  2. 2026-06-03 08:07 KST — 동일 trace에서 capture postprocessor 완료(JobsController#complete_action), 상태 변경, EditingEntity 생성 등 다수의 DB 작업 동시 발생
  3. 2026-06-03 17:07 KST — Error Sweeper에 의해 latency cluster로 수집

Error Log#

Datadog Logs

text
{
  "resource_name": "Api::V1::ClustersController#create_resource",
  "service": "cupixworks-api",
  "occurrences": 1,
  "avg_ms": 5057,
  "max_ms": 5057,
  "sample_trace_id": "7160848916220558224"
}

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-06-03 08:07 KST
  • 최근 발생: 2026-06-03 08:07 KST

Root Cause Summary#

ClustersController#create_resource 요청이 set_cluster before_action에서 ClusterRepository.permission_joins를 통해 12개의 LEFT JOIN과 다중 permission 테이블을 결합하는 무거운 SQL 쿼리를 실행한다. 동일 trace(7160848916220558224)에서 capture postprocessor 완료 콜백이 동시에 실행되면서 reset_parent_cached_entity_updates, EditingEntity 생성, capture 상태 변경 등 다수의 DB 쓰기 작업이 발생했다. 이로 인해 DB 커넥션 경합 또는 row-level lock 대기가 발생하여 permission 조회 쿼리가 평소 50-120ms에서 5초 이상으로 지연되었다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/api/v1/clusters_controller.rb:1ClustersController 요청 수신
  • Before action: app/controllers/api/v1/clusters_controller.rb:6set_clusterrepository_instance.show(params[:id]) 호출
  • Repository show: app/repositories/base_repository.rb:337permission_joins(default_joins(current_class), current_user).where(attrs) 실행
  • Permission query: app/repositories/cluster_repository.rb:115-298 — 12개 LEFT JOIN으로 permission 체크
  • Resource creation: app/controllers/concerns/multiple_resourcable_controller.rb:52-78create_resource 실행
app/controllers/api/v1/clusters_controller.rb:6ruby
before_action :set_cluster, except: %i[index group create untrash purge mock]
app/controllers/api/v1/clusters_controller.rb:49-51ruby
def set_cluster
  @model = repository_instance.show(params[:id])
end
app/repositories/base_repository.rb:331-343ruby
query =
  if skip_permission || current_user == ::User.unauthorized_user
    where(attrs)
  elsif current_class == ::Review || (review_id || capture_id).present?
    permission_joins(default_joins(current_class), current_user, review_id: review_id || -1, capture_id: capture_id || -1).where(attrs)
  elsif current_user.present?
    permission_joins(default_joins(current_class), current_user).where(attrs)
  else
    raise Cupix::Errors::System.new(code: 'SYS30000', reason: 'current_user or review is required on Repository')
  end

scope = current_class.visibility_scope(visibility)
model = query.merge(scope).first

ClusterRepository.permission_joins는 12개의 LEFT JOIN 서브쿼리를 생성하여 capture_permissions, record_permissions, facility_permissions, workspace_permissions, team_permissions 테이블에서 사용자의 접근 권한을 확인한다:

app/repositories/cluster_repository.rb:105-113ruby
def self.default_joins(record)
  record.includes(:storage).joins(:capture, :facility, :workspace).select('
    clusters.*,
    facilities.name AS facility_name,
    facilities.key AS facility_key,
    workspaces.name AS workspace_name,
    captures.name AS capture_name
  ')
end

기대 동작: set_cluster의 permission 쿼리는 인덱스를 활용하여 50-120ms 내에 완료. 실제 동작: 동시에 실행된 postprocessor 콜백의 다수 DB 쓰기로 인해 lock 경합이 발생하여 5057ms 소요.

Log Evidence#

동일 trace ID에서 발생한 동시 작업들:

text
Datadog query: service:cupixworks-api trace_id:7160848916220558224
Time range: 2026-06-02T22:00:00Z to 2026-06-03T00:30:00Z
json
{"timestamp": "2026-06-03 08:07:43", "status": "info", "message": "[200] PUT /api/v1/jobs/1100217/actions/postprocessor/complete (Api::V1::JobsController#complete_action)"}
{"timestamp": "2026-06-03 08:07:43", "status": "info", "message": "[Capture] state changed from editing_initial to editing_ready"}
{"timestamp": "2026-06-03 08:07:41", "status": "info", "message": "[Capture] processing_postprocessor_agent_finished"}
{"timestamp": "2026-06-03 08:07:41", "status": "info", "message": "job 1100217 job_stopping_callback done", "class": "JobCallbackWorker"}
{"timestamp": "2026-06-03 08:07:41", "status": "info", "message": "reset Facility (ID: 17133) cached entity updates"}
{"timestamp": "2026-06-03 08:07:39", "status": "info", "message": "[Capture] state changed from finalizing to done"}
{"timestamp": "2026-06-03 08:07:37", "status": "info", "message": "create editing entity on Capture 706786"}
{"timestamp": "2026-06-03 08:07:35", "status": "info", "message": "candidate_editings type: Capture, count: 1", "class": "EditingEntity"}
{"timestamp": "2026-06-03 08:07:33", "status": "info", "message": "create editing on Capture 706786", "class": "EditingEntity"}
{"timestamp": "2026-06-03 08:07:31", "status": "info", "message": "[Capture] state changed from editing_waiting to editing_ready"}

APM 메트릭 분석 (최근 2시간 avg duration):

text
Metric query: avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::clusterscontroller_create_resource}

정상 시: 50-120ms avg, spike 구간: 300-870ms avg, outlier: 5057ms (단일 요청).

24시간 max duration 메트릭에서도 대부분 50-200ms이나, 특정 시간대에 1.08s, 2.6s까지 spike가 관찰됨. 5057ms는 그 중 최대 outlier.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 set_cluster의 permission_joins 쿼리가 동시 DB 쓰기(postprocessor 콜백)로 인한 lock 경합으로 지연 동일 trace에서 8:07:31~8:07:43 사이 capture 상태 변경, EditingEntity 생성, Facility 캐시 리셋 등 다수 쓰기 작업 발생. permission_joins가 facility, capture, workspace 테이블 JOIN 필요. avg 메트릭에서 동일 시간대 spike(870ms avg) 확인. 명시적 deadlock/lock 로그는 미발견 (warn 레벨 검색 결과 0건) Confirmed
H2 Resource save 시 S3 presigned URL 생성 또는 외부 호출로 지연 create_resourceresource.save만 호출하며 S3 URL 생성은 별도 resource_upload_url 액션에서 수행 create_resource (line 52-78) 코드는 DB save만 수행, S3 호출 없음. Storagable::Resourceset_storagebefore_create에서 storage_id 할당만 수행 (DB lookup, S3 아님) Rejected
H3 counter_culture 콜백이 capture 테이블의 clusters_count 업데이트 시 lock 경합 유발 Cluster 모델에 counter_culture :capture 존재 (cluster.rb:41-43). create_resource는 Resource를 생성하므로 Cluster의 counter_culture는 미실행 create_resource는 Resource를 생성하지 Cluster를 생성하지 않으므로 counter_culture 트리거 안 됨 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 조치 불필요. 단일 발생(1회)이며 기능적 오류 없이 성공적으로 응답(200)을 반환했다. DB 부하에 의한 일시적 지연이며 사용자 영향이 미미하다.

단기 개선 (1주 이내)#

  • ClusterRepository.permission_joins (cluster_repository.rb:115-298)의 12개 LEFT JOIN 쿼리에 대한 실행 계획(EXPLAIN) 분석. 인덱스 누락 여부 확인.
  • set_cluster before_action에서 create_resource 액션이 호출될 때, 이미 check_kind before_action이 실행되므로 permission 체크 순서를 최적화하거나, create_resource에 한하여 경량 permission 체크(owner check 등)를 적용할 수 있는지 검토.

장기 개선 (재발 방지)#

  • Permission 체크를 materialized view 또는 캐시 레이어로 분리하여 실시간 12-JOIN 쿼리의 DB 부하를 줄이는 방안 검토.
  • Postprocessor 완료 콜백의 DB 쓰기를 비동기(Sidekiq)로 분리하여 API 요청과 동시 실행되는 DB 경합을 최소화.

Monitoring#

  • create_resource 엔드포인트에 P95/P99 latency alert 추가:
text
avg(last_5m):p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::clusterscontroller_create_resource} > 1
  • Permission JOIN 쿼리 실행 시간 추적을 위한 커스텀 span 추가 검토.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (monitoring 추가) / standard (쿼리 최적화)