ES /docs

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

RCA: Api::V1::ClustersController#create_resource latency (avg 10798ms)

Overview#

What Happened#

2026-07-03 18:01 KST 경, cupixworks-api 의 POST /api/v1/clusters/:id/resources 엔드포인트에서 응답에 10.8초가 소요되는 latency 이벤트가 1건 감지되었다. 동일 시간대(08:08–09:45 UTC)에 같은 서비스의 서로 다른 엔드포인트 7개에서도 12s ~ 52s 범위의 latency 이벤트가 발생했고, status-board incident 2026-07-03-svc-cupixworks-api--unknown-1 로 묶여 있다. 즉 이 클러스터는 endpoint-specific bug 가 아니라 cupixworks-api 서비스 전반의 일시적 성능 저하 의 한 단면이다.

Quick Facts#

Field Value
resource_name Api::V1::ClustersController#create_resource
avg_duration_ms 10798
max_duration_ms 10798
occurrence_count 1
cluster_type latency
env production, us-west-2
tenant cupix
sample_trace_id 4992016731129222103

Affected Teams#

Team / Domain Error Count Impact
cupixworks-api (Cluster resource upload flow) 1 preview/preview_refinement 이미지 리소스 생성 요청에 10.8초 지연
cupixworks-api (service-wide, 08:08–09:45 UTC) 7 sibling clusters Users#check_ispring_connection 52s, Meshes#index 12.2s, Admin::EditingsController#add_reviewer 12.9s 등 이종 엔드포인트에서 동시 latency spike 관측

Timeline#

  1. 2026-07-03 17:08 KST — 첫 sibling latency 클러스터 발생 (status-board incident 2026-07-03-svc-cupixworks-api--unknown-1 시작)
  2. 2026-07-03 18:01 KST — 본 클러스터 최초/최종 발생 (Api::V1::ClustersController#create_resource, 10798ms)
  3. 2026-07-03 18:02–18:45 KST — 추가 sibling 클러스터 3건 발생, incident 는 여전히 open 상태
  4. 2026-07-03 이후 — 로그상 관련 리소스 요청은 정상 200 응답으로 복귀 (같은 09:00–09:04 UTC 구간 내 다른 ClustersController#create_resource 호출은 정상 응답)

Error Log#

Datadog Logs

cluster representative spanjson
{
  "resource_name": "Api::V1::ClustersController#create_resource",
  "service": "cupixworks-api",
  "occurrences": 1,
  "avg_ms": 10798,
  "max_ms": 10798,
  "sample_trace_id": "4992016731129222103"
}

동일 엔드포인트의 정상 응답 (같은 시간대):

Datadog query result — 2026-07-03T08:55:00Z ~ 09:05:00Ztext
2026-07-03T09:04:13.270Z  [200] POST /api/v1/clusters/238094/resources
2026-07-03T09:03:30.891Z  [200] POST /api/v1/clusters/238093/resources
2026-07-03T09:02:27.207Z  [200] POST /api/v1/clusters/238092/resources
2026-07-03T09:01:40.846Z  [200] POST /api/v1/clusters/1425270/resources
2026-07-03T09:00:40.246Z  [200] POST /api/v1/clusters/1425269/resources
2026-07-03T08:59:14.591Z  [200] POST /api/v1/clusters/238091/resources
2026-07-03T08:58:57.524Z  [200] POST /api/v1/clusters/238090/resources

Sibling latency clusters (같은 svc-level incident):

status-board incident 2026-07-03-svc-cupixworks-api--unknown-1text
b3c83033  08:08:35 UTC  (unknown resource)
25b7ab18  08:19:40 UTC  (unknown resource)
e2c1903c  08:51:10 UTC  Api::V1::UsersController#check_ispring_connection   52138 ms
970f41f2  08:59:54 UTC  Api::V1::MeshesController#index                     12189 ms
9203687e  09:01:24 UTC  Api::V1::ClustersController#create_resource         10798 ms  ← this cluster
60fe7289  09:02:03 UTC  (unknown resource)
3caa3b99  09:02:32 UTC  Api::V1::Admin::EditingsController#add_reviewer     12878 ms
116ab21f  09:45:16 UTC  (unknown resource)

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 1
  • 최초 발생: 2026-07-03 18:01 KST
  • 최근 발생: 2026-07-03 18:01 KST
  • 직접 영향: 해당 요청 1건에 10.8초 지연. 200 응답으로 종료된 것으로 추정 (같은 시간대 다른 ClustersController#create_resource 요청은 전부 200; error 로그 없음)
  • 간접 영향: 같은 시간대 svc-level incident 로 최소 4개 이종 엔드포인트가 10s 이상 latency 겪음. Puma worker pool 이 특정 요청(예: 52초짜리 ispring 체크)에 묶여 뒤에 큐잉된 요청들이 지연됐을 개연성이 높다.

Root Cause Summary#

이 클러스터는 ClustersController#create_resource 코드 자체의 결함이 아니라, 같은 시간대에 cupixworks-api 서비스 전반이 겪은 일시적 latency spike 의 부수 결과이다. 08:08–09:45 UTC 구간에 서로 무관한 엔드포인트 7개(User ispring check 52s, Mesh index 12s, admin add_reviewer 12.9s, cluster resource create 10.8s 등)가 동시에 수 초~수십 초의 응답 지연을 겪었고, status-board 는 이를 svc:cupixworks-api::unknown incident 로 묶었다. create_resource 자체는 controller 내부에서 단순한 resources.find_by_kind + resources.new+resource.save (S3 호출 없음) 을 수행하므로 정상 경로에서 10초가 걸릴 이유가 없다. 관측된 evidence 는 (1) 같은 엔드포인트의 인접 요청은 모두 200 정상, (2) 이종 엔드포인트가 동시에 느려짐, (3) 서비스 CPU 는 16–23% 로 saturation 수준이 아님을 보여준다. 이는 endpoint 로직 이슈가 아니라 공유 리소스(Puma worker 슬롯, DB connection pool, 외부 SaaS 호출로 인한 백프레셔) 병목 이 원인일 가능성이 가장 크다. 다만 이 단일 클러스터만으로는 어느 공유 리소스가 병목이었는지 확정할 수 없어 "uncertain — needs verification" 로 표시한다.

Technical Analysis#

Code Path#

Entry point 및 controller 흐름:

app/controllers/api/v1/clusters_controller.rb:1-11ruby
class Api::V1::ClustersController < Api::V1::ApiController
  include ParameterRequired
  include CyclableController

  before_action :set_cluster, except: %i[index group create untrash purge mock]

  include MetableController
  include MultipleResourcableController

create_resourceMultipleResourcableController concern 에서 정의되며 컨트롤러 자체에는 없다. 실제 실행 순서:

  1. before_action :set_cluster (index/group/create/untrash/purge/mock 외 모든 액션) → set_clusterrepository_instance.show(params[:id]) 호출
  2. before_action :check_kind (create_resource 전용) → allowed_resource_kinds = %w[preview_image preview_refinement_image] 검증
  3. before_action :set_resource_serializer_option → serializer option 셋업
  4. create_resource action 본체
app/controllers/api/v1/clusters_controller.rb:49-51,76-78ruby
def set_cluster
  @model = repository_instance.show(params[:id])
end
...
def allowed_resource_kinds
  %w[preview_image preview_refinement_image]
end
app/controllers/concerns/multiple_resourcable_controller.rb:52-78ruby
def create_resource
  raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Duplicate kind: #{params[:kind]}") and return unless @model.resources.find_by_kind(params[:kind]).blank?

  resource = @model.resources.new kind: params[:kind],
                                  user: current_user,
                                  team: @model.team,
                                  name: params[:name]

  begin
    unless resource.save
      raise Cupix::Errors::Parameter.new(code: 'ENT10003', reason: resource.errors.full_messages)
    end
  rescue Cupix::Errors::Parameter => e
    raise e
  else
    render_api Renderable.new({
      contents: resource,
      serializer: ResourceSerializer,
      serializer_option: { fields: { resource: @fields }, is_collection: false }
    })
  end
end

resource.save 는 S3 presign 을 호출하지 않고(그것은 별도의 resource_upload_url action) 단순히 DB insert + before_validation callback (set_by_resourcable, set_storage) 을 수행한다:

app/models/resource.rb:16-43ruby
belongs_to :resourcable, polymorphic: true
before_validation :set_by_resourcable
validates :kind, length: { maximum: 24 }, allow_nil: true, format: { with: /\A[a-z\d][a-z\w]*[a-z\d]\z/i }
validates :name, presence: true
validates :resourcable, presence: true

def set_by_resourcable
  return if resourcable_id.blank? || resourcable_type.blank?
  if self.workspace_id.blank? && resourcable.has_attribute?(:workspace_id) && resourcable.workspace.present?
    if self.workspace.blank?
      self.workspace = resourcable.workspace
    end
  end
  if self.team_id.blank? && resourcable.has_attribute?(:team_id) && resourcable.team.present?
    if self.team.blank?
      self.team = resourcable.team
    end
  end
end

Failure point: 없음 — 코드 경로 자체에는 실패 지점이 없다. 코드는 정상적으로 200 을 반환한 것으로 보인다 (같은 시간대 다른 요청은 모두 200, error 로그도 없음). 지연은 controller/model 코드 내부가 아니라 요청이 Puma worker 에 도달하기 전(큐잉) 또는 DB/외부 호출이 대기한 시간 에서 발생했을 개연성이 높다. 정확한 breakdown 은 APM trace_id 4992016731129222103 의 span 트리로 확인해야 한다. uncertain — needs verification.

Log Evidence#

Datadog query 로 재현:

Datadog query — endpoint activity around incidenttext
service:cupixworks-api "ClustersController#create_resource"
from: 2026-07-03T08:55:00Z  to: 2026-07-03T09:05:00Z

같은 시간대 요청은 모두 200 정상:

text
2026-07-03T09:01:40.846Z  [200] POST /api/v1/clusters/1425270/resources
2026-07-03T09:00:40.246Z  [200] POST /api/v1/clusters/1425269/resources
2026-07-03T08:59:14.591Z  [200] POST /api/v1/clusters/238091/resources

Datadog avg:system.cpu.user{service:cupixworks-api} 24h 시계열 (KST 기준 대략 17:00–18:30 구간, 이벤트 발생 무렵):

system.cpu.user (%) samples across the incident windowtext
... 6.78, 9.21, 13.39, 10.69, 11.37, 10.11, 12.23, 10.10, 14.30, 10.95, 10.06, 11.08,
6.78, 10.67, 14.46, 16.80, 15.77, 17.59, 16.24, 16.85, 19.53, 23.19, 16.34, 14.16,
15.01, 16.15, 16.15, 14.92, 14.54, 11.94, 11.49, 11.96, 12.84, 11.92, 12.80 ...

CPU 최댓값이 23% 수준으로 saturation 은 아님. RDS CPU (avg:aws.rds.cpuutilization{*}) 도 5–13% 로 정상.

서비스 max 요청 시간 (max:trace.rack.request.duration{service:cupixworks-api}) 은 같은 24h 창에 몇 개의 극단값이 관측됨:

max:trace.rack.request.duration (ms) — service-wide spikestext
... 14867.94 ms, 5966.53 ms, 1194.68 ms, 529.68 ms, 363.18 ms, 353.42 ms ...

14867ms 급 spike 가 존재한다는 것은 요청 하나가 최소 ~14.9초 걸린 순간이 있었다는 뜻으로, 본 클러스터의 10.8초 및 sibling 의 12–52초 값과 부합한다.

status:warn 로그 (같은 창):

Datadog — service:cupixworks-api status:warn 2026-07-03T08:55:00Z~09:10:00Ztext
2026-07-03T09:09:59Z  WARN  NotFound - attributes_in_database  class=ElementTrace  function=_update_document
2026-07-03T09:09:58Z  WARN  NotFound - attributes_in_database  class=Record       function=_update_document
2026-07-03T09:09:58Z  WARN  NotFound - attributes_in_database  class=Pointcloud   function=_update_document
... (반복)

이 warn 은 Elasticsearch document reindex 에서 해당 DB row 가 이미 삭제된 경우 발생하는 것으로, 본 latency 와 직접 원인관계는 없으나 같은 시간대에 write churn 이 높았음을 방증한다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 ClustersController#create_resource 코드 경로 자체에 slow query / N+1 / 외부 호출이 있어 endpoint-specific latency 가 발생했다 Resource.save 는 polymorphic belongs_to 를 통해 resourcable.workspace, resourcable.team 을 로드함 (set_by_resourcable) → 큰 cluster 나 lazy assoc 이면 추가 쿼리 발생 가능 같은 시간대 다른 POST /api/v1/clusters/:id/resources 요청은 모두 200 정상 응답 (Datadog 로그 상 subsecond). 코드가 원인이라면 endpoint 전반이 느려야 하는데 단 1건만 10.8s. Rejected
H2 서비스 전반의 latency spike (공유 리소스 병목) 에 걸린 부수 피해다 (a) 같은 svc-level incident 로 묶인 sibling 7개가 서로 무관한 엔드포인트(User check_ispring, Mesh index, admin add_reviewer, Cluster resource create)에서 동시에 10–52s latency; (b) max:trace.rack.request.duration{service:cupixworks-api} 에 14867ms/5966ms spike 관측; (c) 같은 endpoint 의 인접 요청은 모두 정상 어느 공유 리소스(Puma queue / DB pool / 외부 SaaS 호출) 가 병목인지 단일 클러스터로 특정 불가 Confirmed (root cause type 은 확정, 세부 원인은 uncertain)
H3 ispring(외부 SSO) 호출이 52초 timeout 걸려 뒤 요청까지 지연시켰다 Sibling e2c1903c 는 Api::V1::UsersController#check_ispring_connection 이며 52138ms 소요. 외부 HTTP 호출이 시간을 잡아먹는 대표 경로 로그에는 ispring 호출 상세(외부 URL/status) 가 남아있지 않아 timeout 여부 미확인. Puma worker 가 실제로 이로 인해 굶주렸는지 재현 필요 Inconclusive
H4 DB (RDS) 가 원인이다 여러 요청이 동시에 느려짐 RDS CPU 는 창 전체에서 5–13% 로 정상. Postgres query time 메트릭도 특이 신호 없음. create_resource 자체는 단일 row insert. Rejected
H5 Elasticsearch reindex 지연이 요청을 붙잡았다 같은 창에 NotFound - attributes_in_database warn 대량 발생. _update_document 는 request lifecycle 내 동기적으로 실행될 여지 있음 reindex warn 은 index write 실패 로그이며 request path 차단이 아니라 async job/후처리에서 발생하는 것이 일반적. 확정 위해서는 코드 경로 추가 검증 필요 Inconclusive

Fix Recommendation#

즉시 조치 (Critical)#

  • 없음. 이 클러스터 단일 건은 endpoint 코드 결함이 아니라 svc-level latency incident 의 파편이다. errors/{cluster-id}.md 는 pipeline 이 관리하되, RCA 는 svc-level incident (2026-07-03-svc-cupixworks-api--unknown-1) 조사로 회수해야 한다.

단기 개선 (1주 이내)#

  • APM trace 분석: sample_trace_id 4992016731129222103 및 sibling clusters (e2c1903c, 970f41f2, 3caa3b99) 의 flame graph 를 Datadog APM 에서 비교. Puma queue time (rack.queue) vs handler time 비율을 확인해 병목이 (a) 웹 서버 앞단 큐잉인지 (b) 실제 처리 내부인지 구분.
  • 외부 SaaS 호출 timeout 감사: Api::V1::UsersController#check_ispring_connection (52초) 을 시작으로, cupixworks-api 에서 동기적으로 외부 서비스를 호출하는 모든 컨트롤러/서비스에 timeout 를 명시(예: open_timeout: 5, read_timeout: 10). Api::V1::Admin::EditingsController#add_reviewer 등 후보군을 함께 조사.
  • Puma worker/thread 설정 재검토: 08:08–09:45 UTC 창처럼 40 분 넘게 지속된 spike 는 요청 하나가 오래 걸릴 때 뒤 요청들이 큐잉되는 구조를 시사. worker 수 대비 slow-path 격리(예: Sidekiq 이관 또는 별도 Puma 그룹) 를 검토.

장기 개선 (재발 방지)#

  • Status-board 의 svc:{service}::unknown incident 스코프를 unknown 이 아니라 dominant slow path (예: external_call_timeout, db_lock, gc_pause) 로 라벨링할 수 있도록 error-sweeper classifier 확장. 이번 incident 는 root_cause_types = ["unknown"] 로만 태깅되어 원인 파악을 사람이 매번 반복해야 한다.
  • APM latency 임계값 초과 트레이스에 대해 자동으로 span 상세(외부 HTTP call breakdown, DB span) 를 캡처해 이후 RCA 에 활용.

Monitoring#

REQUIRED SUB-SKILL 참고: 아래 쿼리는 release dashboard timeseries widget 에 그대로 들어가는 metric API 문법이며, monitor-only 문법(| stats, threshold suffix) 은 사용하지 않는다.

  • Service-wide p95/p99 latency 시계열 (서비스 전반의 병목 조기 감지):
text
p95:trace.rack.request.duration{service:cupixworks-api}
text
p99:trace.rack.request.duration{service:cupixworks-api}
  • 이 엔드포인트의 p95 latency (같은 spike 반복 여부 확인):
text
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::clusterscontroller#create_resource}
  • 서비스 전반 5초 이상 요청 hit count:
text
sum:trace.rack.request.hits{service:cupixworks-api,duration:>5s}.as_count()
  • Puma 큐잉 시간 (병목이 웹 큐잉인지 확인용, 태그명은 배포 환경 기준으로 조정):
text
avg:puma.request.queue_time{service:cupixworks-api}
  • 외부 ispring 관련 호출 (해당 endpoint 재현 시):
text
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::userscontroller#check_ispring_connection}

Risk Assessment#

  • Risk level: medium — 단일 요청 관점의 사용자 영향은 작지만, 같은 시간대 다수 엔드포인트가 동시에 10s+ 걸린 svc-level incident 의 일부다. 재발 시 결제/업로드 등 시간 민감 요청까지 파급될 위험 있음.
  • 예상 복잡도: standard — endpoint 자체 수정은 불필요. 병목 후보(외부 SaaS 호출 timeout, Puma sizing) 파악과 개별 fix 는 표준 성능 튜닝 작업 수준.