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#
- 2026-07-03 17:08 KST — 첫 sibling latency 클러스터 발생 (status-board incident
2026-07-03-svc-cupixworks-api--unknown-1시작) - 2026-07-03 18:01 KST — 본 클러스터 최초/최종 발생 (
Api::V1::ClustersController#create_resource, 10798ms) - 2026-07-03 18:02–18:45 KST — 추가 sibling 클러스터 3건 발생, incident 는 여전히 open 상태
- 2026-07-03 이후 — 로그상 관련 리소스 요청은 정상 200 응답으로 복귀 (같은 09:00–09:04 UTC 구간 내 다른
ClustersController#create_resource호출은 정상 응답)
Error Log#
{
"resource_name": "Api::V1::ClustersController#create_resource",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 10798,
"max_ms": 10798,
"sample_trace_id": "4992016731129222103"
}
동일 엔드포인트의 정상 응답 (같은 시간대):
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):
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 흐름:
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_resource 는 MultipleResourcableController concern 에서 정의되며 컨트롤러 자체에는 없다. 실제 실행 순서:
before_action :set_cluster(index/group/create/untrash/purge/mock 외 모든 액션) →set_cluster는repository_instance.show(params[:id])호출before_action :check_kind(create_resource 전용) →allowed_resource_kinds = %w[preview_image preview_refinement_image]검증before_action :set_resource_serializer_option→ serializer option 셋업create_resourceaction 본체
def set_cluster
@model = repository_instance.show(params[:id])
end
...
def allowed_resource_kinds
%w[preview_image preview_refinement_image]
end
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) 을 수행한다:
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 로 재현:
service:cupixworks-api "ClustersController#create_resource"
from: 2026-07-03T08:55:00Z to: 2026-07-03T09:05:00Z
같은 시간대 요청은 모두 200 정상:
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 구간, 이벤트 발생 무렵):
... 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 창에 몇 개의 극단값이 관측됨:
... 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 로그 (같은 창):
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}::unknownincident 스코프를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 시계열 (서비스 전반의 병목 조기 감지):
p95:trace.rack.request.duration{service:cupixworks-api}
p99:trace.rack.request.duration{service:cupixworks-api}
- 이 엔드포인트의 p95 latency (같은 spike 반복 여부 확인):
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::clusterscontroller#create_resource}
- 서비스 전반 5초 이상 요청 hit count:
sum:trace.rack.request.hits{service:cupixworks-api,duration:>5s}.as_count()
- Puma 큐잉 시간 (병목이 웹 큐잉인지 확인용, 태그명은 배포 환경 기준으로 조정):
avg:puma.request.queue_time{service:cupixworks-api}
- 외부 ispring 관련 호출 (해당 endpoint 재현 시):
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 는 표준 성능 튜닝 작업 수준.