Api::V1::CapturesController#entity_parameters (avg 12693ms, max 12693ms)
RCA: Api::V1::CapturesController#entity_parameters slow request (avg 12693ms)
Overview#
What Happened#
2026-06-26 11:03 KST에 cupixworks-api(production, ap-southeast-2)에서 GET /api/v1/captures/:id/entity_parameters 요청 1건이 약 12.7초가 소요된 latency 이상으로 감지되었다. 응답은 정상(2xx)이지만 같은 endpoint의 다른 호출이 100ms 미만으로 처리되는 것과 비교해 평소 대비 100배 이상 느린 단일 outlier이며, 같은 시점 다른 클러스터들과 함께 status board에 cupixworks-api service degraded 인시던트로 그룹핑되었다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::CapturesController#entity_parameters |
| cluster_type | latency |
| avg_duration_ms | 12693 |
| max_duration_ms | 12693 |
| sample_trace_id | 4653776727289616755 |
| env | production, region ap-southeast-2 |
| tenant | cupix |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (capture 도메인) | 1 | 단일 사용자 요청 1건 ~12.7s 지연. 클라이언트 timeout 초과 시 UI 로딩 실패 가능 |
Timeline#
- 2026-06-26 11:03:40 KST —
Api::V1::CapturesController#entity_parameters단일 trace(4653776727289616755)가 12693ms 지속, latency cluster 생성 (first_seen = last_seen) - 2026-06-26 10:25:34 KST — 같은 service의 다른 latency/error 클러스터들이 status-board
2026-06-26-svc-cupixworks-api--unknown-1인시던트에 묶이기 시작 (cluster943fdcb4...) - 2026-06-26 11:03:40 KST — 본 클러스터(
d2d9b1f5-...) 등록, 동일 incident 묶음에 합류 - 이후 동일 endpoint 호출 — 11:09 ~ 11:14 KST 사이 약 30건 동일 resource 호출 모두
[200] OK, 12s급 지연 재발 없음
Error Log#
{
"resource_name": "Api::V1::CapturesController#entity_parameters",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 12693,
"max_ms": 12693,
"sample_trace_id": "4653776727289616755"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-06-26 11:03:40 KST
- 최근 발생: 2026-06-26 11:03:40 KST
Root Cause Summary#
이 클러스터는 Api::V1::CapturesController#entity_parameters 요청 한 건이 평소(<100ms) 대비 12.7초로 지연된 latency outlier다. 코드 경로상 Capture#entity_parameters는 (1) entity_parameter 스코프, (2) 부모 모델 체인을 따라가는 재귀 루프, (3) system_entity_parameter, (4) default_entity_parameter 4개의 분리된 쿼리를 순차 실행하며 각각 entity_parameter_groups에 eager_load JOIN을 한다. 부모 체인 길이만큼 쿼리가 추가되는 N+1 성격의 구조이지만, 같은 시점 동일 endpoint의 다른 호출은 빠르게 처리되었으므로 코드 자체가 항상 느린 것은 아니다. 단일 outlier로서 가장 가능성 높은 직접 원인은 (a) 해당 capture가 평소보다 깊은 부모 체인 또는 매우 많은 entity_parameter 행을 가진 outlier 레코드이거나, (b) status-board에 함께 묶인 동일 시간대 cupixworks-api service degraded 인시던트의 일부로서 발생한 일시적 DB/connection pool 지연이다. 단일 샘플(occurrence_count=1)과 error/warn 로그 부재로 둘 사이를 단정하기에는 증거가 부족하다 — 추가 검증 필요.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/captures_controller.rb:1(Api::V1::CapturesController < Api::V1::ApiController) - 액션은
EntityParameterableControllerconcern에서 정의됨
def entity_parameters
_results = repository_instance.entity_parameters(params)
render_api Renderable.new({
contents: _results,
is_collection: true,
serializer: EntityParameterSerializer,
serializer_option: {
fields: {
entity_parameter: @fields
}
}
})
end
- Repository 단계에서
Capture인스턴스를 조회한 뒤 모델의entity_parameters를 호출:
def entity_parameters(params = {})
_id_or_key = params[:id] || params[:key]
if _id_or_key.present?
if self.class.current_class == ::Review
review = ::Review.find_by(key: _id_or_key) || (raise Cupix::Errors::Parameter.new(...))
_group_entity = review.facility
else
_group_entity = self.class.current_class.repository_class.new(current_user: current_user, current_team: current_team).show(_id_or_key)
end
elsif self.class.current_class == ::Team
_group_entity = current_team
...
_group_entity.entity_parameters
end
- 핵심 잠재 hot spot —
Capture#entity_parameters는 부모 체인을 재귀적으로 순회하며 각 단계마다 별도 쿼리를 실행하고where.not(name: array)필터를 매번 적용한다:
def entity_parameters
_entity_params = entity_parameter
parent = self.class.parent_model if self.class.respond_to?(:parent_model)
entity_parameter_models(parent).each do |model_class|
model_name = model_class.name.downcase
_entity_params += self.send(model_name).entity_parameter.where.not(name: _entity_params.map(&:name))
end
_system_params = system_entity_parameter.where.not(
name: _entity_params.map(&:name)
)
_default_params = default_entity_parameter.where.not(
name: _entity_params.map(&:name) + _system_params.map(&:name)
)
_entity_params + _system_params + _default_params
end
- Failure point (perf):
app/models/concerns/entity_parameterable.rb:15-33— 부모 체인 길이 ×eager_load(:entity_parameter_group)JOIN, 매 단계NOT IN (...)배열 비교. 부모 체인이 깊거나(예: capture → facility → workspace → team) 한 단계라도entity_parameter가 많으면 누적 시간이 급격히 증가한다. - 또한 capture 자체 조회(
Capture.repository_class.show)는Capture모델에 70+ concern이 include되어 있어(app/models/capture.rb:1-75)after_find/serializer 단계에서 추가 cost가 발생할 여지가 있다.
기대 동작 vs 실제 동작:
- 기대:
entity_parameters는 endpoint 다른 호출들과 마찬가지로 100ms 미만에 응답한다 (같은 시간대 30+건 모두 200 OK & 빠름). - 실제: 단일 trace
4653776727289616755만 12693ms 소요. 같은 클러스터 안에 추가 샘플이 없어 패턴이 아닌 outlier로 분류됨.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api "entity_parameters"
service:cupixworks-api resource_name:"Api::V1::CapturesController#entity_parameters"
service:cupixworks-api status:warn (시간 범위 2026-06-26T01:30:00Z ~ 02:30:00Z)
service:cupixworks-api status:error (시간 범위 2026-06-26T01:30:00Z ~ 02:30:00Z)
인시던트 시각(02:03:40 UTC = 11:03:40 KST) 주변 ±10분 범위에서 동일 endpoint 호출은 모두 정상 응답:
2026-06-26 11:14:01 info [200] GET /api/v1/captures/721849/entity_parameters (Api::V1::CapturesController#entity_parameters)
2026-06-26 11:12:15 info [200] GET /api/v1/captures/78631/entity_parameters (Api::V1::CapturesController#entity_parameters)
2026-06-26 11:11:59 info [200] GET /api/v1/captures/46478/entity_parameters (Api::V1::CapturesController#entity_parameters)
2026-06-26 11:11:54 info [200] GET /api/v1/captures/722317/entity_parameters (Api::V1::CapturesController#entity_parameters)
2026-06-26 11:11:00 info [200] GET /api/v1/captures/721836/entity_parameters (Api::V1::CapturesController#entity_parameters)
@duration:>5000 필터로 좁힌 동일 endpoint 로그는 같은 2시간 윈도우에 2건만 잡힘:
2026-06-26 11:05:48 info [200] GET /api/v1/captures/78612/entity_parameters
2026-06-26 10:02:10 info [200] GET /api/v1/captures/78584/entity_parameters
— 모두 200 OK. 본 트레이스(4653776727289616755) 자체는 Datadog Logs index에서 @trace_id 태그로 검색해도 항목이 잡히지 않았다(Found 0 logs). APM trace는 클러스터 파일의 Datadog URL을 통해서만 조회 가능하다 (uncertain — APM 상의 span breakdown 확인 필요).
동일 시간대 status:warn/status:error 로그는 모두 entity_parameters 경로와 무관:
Record/Pointcloud/ElementTrace: NotFound - attributes_in_database(_update_document) — Elasticsearch 인덱싱 경로, 본 endpoint와 무관OpcOperation: 409 Conflict(외부 ICS-11388 서비스 stopped),IntegrationRepository#opc_access_token— integration(1849), capture entity_parameters와 무관
즉 본 latency outlier 시점에 entity_parameters 경로에서 발생한 ERROR/WARN 레벨 신호는 없다.
Status-board 컨텍스트(같은 svc 인시던트에 포함된 클러스터 목록):
incident 2026-06-26-svc-cupixworks-api--unknown-1 (open, started 2026-06-26 10:25:34 KST)
cluster_ids: 943fdcb4..., 66924c5b..., 2e4071f3..., 48253066..., d22ba254..., b61f8f39..., fa2be8d1...
root_cause_types: ["unknown"]
본 클러스터(d2d9b1f5...)는 collector 그룹핑상 같은 svc 인시던트에 합류(cluster_ids에 추가됨).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 특정 capture가 매우 깊은 parent_model 체인 또는 큰 entity_parameter/entity_parameter_group 데이터를 가진 outlier여서 Capture#entity_parameters의 누적 쿼리 시간이 폭증 |
코드상 entity_parameterable.rb:19-22 루프는 부모 모델 수만큼 추가 쿼리·JOIN·NOT IN(array) 필터 실행; 같은 endpoint의 다른 captures는 빠름(11:11~11:14 다수 200 OK <1s 추정) |
해당 trace의 정확한 capture id가 sample_trace_id만 가지고 있고 cluster 파일에 없음. APM span breakdown으로 직접 확인 필요 | Inconclusive — needs APM trace inspection |
| H2 | 같은 시간대 cupixworks-api service degraded 인시던트로 인한 DB connection pool 포화/일시적 latency 스파이크가 이 요청에 우연히 걸림 |
status-board가 2026-06-26-svc-cupixworks-api--unknown-1 open 인시던트(7개 cluster) 보고; 본 cluster도 그 묶음에 들어감 |
같은 시간대 다른 entity_parameters 호출은 빠르게 200 OK 처리됨 — 광범위한 풀 포화로는 설명이 어려움. 단일 connection이 long-running query에 갇혔다면 다른 요청은 영향 적을 수 있음 | Inconclusive — needs APM/DB metrics correlation |
| H3 | Endpoint 자체의 예외/timeout (5xx) 발생 | — | 같은 endpoint의 ±10분 모든 로그가 [200]; 동일 시간대 error/warn 로그는 OPC 409, ElementTrace NotFound 등 무관 메시지뿐 |
Rejected |
| H4 | 외부 의존성(예: Elasticsearch) 호출이 entity_parameters 응답 경로 안에 포함되어 외부 지연 유발 |
— | EntityParameterableController#entity_parameters와 EntityParameterableRepository#entity_parameters는 ActiveRecord 조회만 수행, ES 호출 없음 (render_api Renderable.new(...) → DB only). ES 인덱싱 warn은 동일 시간대지만 다른 모델(Record/ElementTrace)의 비동기 _update_document 경로 |
Rejected |
| H5 | 외부 서비스 의존성 outage (dep:* incident) | — | status-board scope = svc:cupixworks-api::unknown (svc, dep 아님). 외부 dependency 인시던트로 분류되지 않음 |
Rejected |
확정된 H1, H2 중 하나로 좁히려면 APM trace 4653776727289616755의 span breakdown(DB span 누적 vs 대기 시간)이 필요하다.
Fix Recommendation#
즉시 조치 (Critical)#
- 즉시 코드 수정 권장 없음. occurrence_count=1, 같은 endpoint의 다른 호출 모두 정상. 인시던트의 root cause type이
unknown인 상태에서 단일 outlier에 코드 변경을 적용하는 것은 over-fitting 위험. - 클러스터 파일의 Datadog APM URL로 trace
4653776727289616755를 열어 span breakdown을 확인:- DB span 누적이 절대치(>10s)인지, 또는 단일 span의 wall-clock wait이 큰지 → H1 vs H2 판별
- capture id, parent_model 체인 길이,
entity_parameters행 수 식별
단기 개선 (1주 이내)#
- APM/메트릭으로 재발 모니터링: 동일 resource에서 12s급 outlier가 추가로 잡히는지 7일 윈도우로 추적. 추가 샘플 없이는 잠재적 N+1을 단정하지 않는다.
Capture#entity_parameters쿼리 최소화 가능성 검토 (app/models/concerns/entity_parameterable.rb:15-33):- 부모 체인 순회를 한 번의
eager_load로 합칠 수 있는지 검토 (예: 단일 SQL로 capture + parent 체인의 모든entity_parameter를 GROUP BY name 우선순위로 가져오기) where.not(name: array)의 array 크기가 커질 경우 PostgreSQL의NOT IN성능이 급격히 떨어질 수 있어 LEFT JOIN +IS NULL패턴 검토- 단, 본 클러스터만으로는 변경 정당성 부족 — 후속 outlier 누적 후 결정 권장
- 부모 체인 순회를 한 번의
장기 개선 (재발 방지)#
entity_parameters응답 latency용 RUM/APM SLO 정의 (예: p95 < 500ms, p99 < 1s). 임계값 초과 시 알람.Capture#entity_parameters결과를updated_at기반 캐싱(이미EntityParameterable::Record#element_filtering_margin에서 Rails.cache 1h 패턴 사용 — 동일 패턴 확장 가능). 캐시 적용 전 데이터 신선도 요구사항 확인 필요.
Monitoring#
추가/활용 권장 Datadog 쿼리:
엔드포인트 평균 응답 시간 (단일 outlier 재발 탐지):
avg:trace.rails.request{service:cupixworks-api,resource_name:api::v1::capturescontroller#entity_parameters} by {env}
엔드포인트 p95 latency:
p95:trace.rails.request{service:cupixworks-api,resource_name:api::v1::capturescontroller#entity_parameters} by {env}
전체 cupixworks-api p95 (광범위 dB/풀 이슈 감지):
p95:trace.rails.request{service:cupixworks-api} by {env}
PostgreSQL 쿼리 평균 시간 (DB 측 지연 상관관계):
avg:postgresql.query.time{service:cupixworks-api}
알람 예시 (별도 monitor에서 작성 — 본 섹션 쿼리 블록은 dashboard timeseries용):
Api::V1::CapturesController#entity_parametersp95 > 1s 가 10분 지속 시 warn- 같은 resource의 max duration > 10s 발생 시 info (현재 본 cluster 케이스)
Risk Assessment#
- Risk level: low (단일 occurrence, 사용자 에러 0, 다른 호출 정상)
- 예상 복잡도: standard (즉시 코드 변경 없음. APM trace 분석 + 모니터링 강화)