Api::V1::ElementsController#index (avg 1082ms, max 1082ms)
RCA: Api::V1::ElementsController#index Latency (1082ms)
Overview#
What Happened#
2026-05-27 00:16:25Z에 cupixworks-api의 Api::V1::ElementsController#index 엔드포인트에서 1082ms 지연이 감지되었다. 두 개의 대규모 facility(9,894개, 11,244개 element)를 동시에 페이지네이션하는 자동화 클라이언트(cupix-agent)가 직렬화 병목과 첫 페이지 DB 쿼리 오버헤드를 유발했다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::ElementsController#index |
| top_frame | app/repositories/element_repository.rb:41 (permission_joins) |
| runtime | Ruby 3.3.7 / Rails |
| env | production, us-west-2 |
| avg_duration | 1082ms |
| max_duration | 1082ms |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| pclconstruction (team 739) | 다수 요청 >500ms | Element 목록 로딩 지연 |
| gilbaneco (team 780) | 다수 요청 >500ms | Element 목록 로딩 지연 |
Timeline#
- 2026-05-26T23:09:28Z — facility
pwwaqh(gilbaneco)에서 첫 slow 요청 감지 (741ms) - 2026-05-27T00:14:09Z — facility
umphr6(pclconstruction)에서 slow 요청 시작 (697ms) - 2026-05-27T00:16:25Z — 두 사용자 동시 페이지네이션으로 최고 1,077ms 도달, 클러스터 감지
- 2026-05-27T00:16:42Z — siteinsights-service Lambda 호출 4건 확인 (downstream 추가 지연)
Error Log#
{
"resource_name": "Api::V1::ElementsController#index",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 1082,
"max_ms": 1082,
"sample_trace_id": "1571525165356644939"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1 (>1s threshold), 9건 이상 >500ms
- 최초 발생: 2026-05-27T00:16:25.321Z
- 최근 발생: 2026-05-27T00:16:25.321Z
- 영향 범위: 대규모 facility(10,000+ elements)를 페이지네이션하는 사용자에게 응답 지연 발생
Root Cause Summary#
직렬화(serialization) 비용과 첫 페이지 COUNT 쿼리 오버헤드가 주요 원인이다. 가장 느린 요청(1,077ms)은 전체 시간의 94%를 직렬화에 소비했다(1,010ms serialization vs 80ms DB). 300개 element를 22개 이상의 필드(bim_bounds, custom_properties 등 포함)로 직렬화할 때 JSON 변환 비용이 급증한다. 첫 페이지 요청 시에는 대규모 테이블의 COUNT(*) 쿼리로 DB 시간이 434-607ms까지 증가한다. 추가로 11개의 LEFT JOIN을 사용하는 permission_joins가 DB 쿼리 복잡도를 높이고, downstream siteinsights-service Lambda 호출이 네트워크 지연을 추가한다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/elements_controller.rb:13(index action) - Query option 생성:
Cupix::QueryOption::Element.new(get_query_option, params) - Repository 검색:
element_repository.rb:258(search 메서드 — ES vs DB 분기) - Elasticsearch 검색:
element_repository.rb:278(search_by_elasticsearch) - Permission 필터링:
element_repository.rb:41(permission_joins — 11개 LEFT JOIN) - 직렬화:
ElementSerializer— 22+ 필드를 JSON으로 변환 - Failure point: 직렬화 단계에서 300개 레코드 × 22+ 필드의 JSON 변환 비용 폭발
def index
element_query_option = Cupix::QueryOption::Element.new(get_query_option, params)
elements = repository_instance.search(element_query_option)
render_api Renderable.new({
search_result: elements,
is_collection: true,
serializer_option: @serializer_option
})
end
repository_instance.search는 Elasticsearch 결과를 가져온 후 permission_joins를 적용한다:
def self.permission_joins(response, query_option)
# 11개의 LEFT JOIN subquery를 적용
# review_public_permissions, review_user_permissions,
# review_group_permissions, facility_user_permissions,
# facility_group_permissions, facility_system_group_permissions,
# workspace_user_permissions, workspace_group_permissions,
# team_user_permissions, team_group_permissions,
# team_system_group_permissions
# ...
end
Permission 필터링 후 GROUP BY + MAX() 집계:
.group('id').select(_select)
# _select에는 11개의 MAX() aggregate 함수 포함
기대 동작: 페이지당 300개 element를 <500ms 내에 응답 실제 동작: 직렬화에 최대 1,010ms 소요, 첫 페이지 DB 쿼리에 607ms 소요
Log Evidence#
Datadog에서 고지연 요청을 검색한 쿼리:
service:cupixworks-api @http.url_details.path:/api/v1/elements @duration:>500ms
Time range: 2026-05-26T23:00:00Z to 2026-05-27T01:00:00Z
가장 느린 요청 (1,077ms):
{
"duration_ms": 1077,
"db_duration_ms": 80,
"serialization_ms": 1010,
"page": "14/33",
"total_entries": 9894,
"facility_key": "pwwaqh",
"user": "nbelmontelledo@gilbaneco.com",
"team": "gilbaneco",
"per_page": 300,
"user_agent": "cupix-agent"
}
첫 페이지 요청 (843ms, DB 지배적):
{
"duration_ms": 843,
"db_duration_ms": 607,
"serialization_ms": 245,
"page": "1/38",
"total_entries": 11244,
"facility_key": "umphr6",
"user": "mhughes@pcl.com",
"team": "pclconstruction"
}
Downstream siteinsights-service Lambda 호출 (trace ID 연결):
service:siteinsights-service @http.url_details.path:/element_records
X-Datadog-Trace-Id: 1571525165356644939
{
"timestamps": ["00:16:40.738Z", "00:16:41.094Z", "00:16:41.539Z", "00:16:42.398Z"],
"facility_key": "pwwaqh",
"level_ids": "68722",
"per_page": 300,
"user_agent": "rest-client/2.1.0 (linux x86_64) ruby/3.3.7p123"
}
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 직렬화 병목 — 300개 레코드의 22+ 필드 JSON 변환 비용 | 1,077ms 요청 중 1,010ms(94%)가 serialization; 대형 필드(bim_bounds, custom_properties) 포함 |
— | Confirmed |
| H2 | 첫 페이지 COUNT(*) 쿼리 오버헤드 | page 1 요청의 DB 시간 434-607ms vs 후속 페이지 30-80ms | COUNT 캐싱이 4시간 TTL로 존재(_pagination_total 메서드) |
Confirmed (캐시 미스 시) |
| H3 | N+1 쿼리 문제 | permission_joins의 11개 LEFT JOIN은 단일 쿼리로 실행됨 | Datadog에 N+1 경고 없음; 직렬화 시간이 DB 시간보다 지배적 | Rejected |
| H4 | Downstream Lambda(siteinsights-service) 지연 | 동일 trace에서 Lambda 4회 호출 확인 | Lambda 호출은 별도 비동기 경로로 추정; 메인 응답 시간에 직접 기여하는지 불확실 | Inconclusive |
| H5 | 동시 사용자 부하로 인한 리소스 경합 | 두 사용자가 동시에 대규모 페이지네이션 수행; 3개 인스턴스 분산 | 인스턴스가 3개로 분산되어 있어 CPU 경합 가능성 낮음 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
app/serializers/element_serializer.rb— 기본 응답에서 무거운 필드(bim_bounds,custom_properties) 제외,fields[]파라미터로 명시 요청 시에만 포함하도록 조건부 직렬화 적용app/repositories/element_repository.rb:474-485—_pagination_total의 캐시 키를 facility_id + query_hash 기반으로 단순화하여 캐시 적중률 향상
단기 개선 (1주 이내)#
- per_page 최대값을 300에서 100으로 축소하거나, 대규모 facility에 대해 cursor-based pagination 도입
- ElementSerializer에
fields파라미터 기반의 sparse fieldset 지원 추가 — 클라이언트가 필요한 필드만 요청하도록 유도
장기 개선 (재발 방지)#
- Permission 필터링을 Elasticsearch 인덱스에 사전 반영하여 11개 LEFT JOIN을 제거
- Materialized view 또는 denormalized permission 테이블로 permission_joins 쿼리 복잡도를 O(1)로 축소
cupix-agent자동화 클라이언트에 rate limiting 적용하여 동시 대량 페이지네이션 방지
Monitoring#
- 추가할 메트릭:
elements_controller.serialization_duration_ms(serialization 시간 별도 추적) - Datadog APM에
@duration:>500ms임계값 알림 설정
service:cupixworks-api resource_name:"Api::V1::ElementsController#index" @duration:>1000ms
- Facility별 element 수 대시보드 — 10,000+ element facility 모니터링
Risk Assessment#
- Risk level: low (기능 장애 아닌 성능 저하, 대규모 facility 한정)
- 예상 복잡도: standard (직렬화 최적화는 비교적 안전한 변경, permission 리팩터링은 장기 과제)