Database lock contention causing API latency spikes
RCA: Api::V1::Admin::TeamsController#index (avg 18908ms, max 18908ms)
Overview#
What Happened#
2026-07-08 12:55:32 KST에 Retool 클라이언트가 호출한 GET /api/v1/admin/teams?filter=id:87&fields=id,account_manager,customer_success_managers 요청 한 건이 총 18506ms(그중 DB 12047ms) 걸려 응답했다. 응답 자체는 HTTP 200이었고 latency 클러스터로만 잡혔다. 같은 시각(±2분) 다른 여러 엔드포인트(PanosController, ElementTracesController, SessionsController 등)도 15초 이상의 지연을 겪었다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::Admin::TeamsController#index |
| controller | Api::V1::Admin::TeamsController |
| action | index |
| top_frame | app/controllers/api/v1/admin/teams_controller.rb:17-27 |
| params | filter=id:87, fields=[id, account_manager, customer_success_managers] |
| user | miriam.kim@cupix.com (user.id 6942, team.id 133 admin) |
| user_agent | Retool/2.0 |
| duration | 18506.22ms |
| db | 12047.42ms (전체의 65%) |
| view | 0.1ms |
| serialization | 4ms |
| request_id | 1a4c5cbe-6f91-4587-9fa2-e491a35ac21e |
| host | ip-10-1-80-134.us-west-2.compute.internal |
| deploy | production-us-west-2-20260708t0134z0-a1160ff5-cupixworks |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| Retool 관리자 도구 (admin domain) | 1 | 팀 조회가 ~18.5초 지연 — 사용자에게는 UI 로딩 지연으로 체감 |
| 동시 요청자 (pano upload, element traces 등) | 다수 | 같은 창(12:53-12:56 KST)에 여러 API가 15초+ 지연 (연쇄 영향) |
Timeline#
- 2026-07-08 12:53:00 KST 부근 —
PanosController#bulk,ElementTracesController#refresh등 다수의 API에서 15초+ 지연이 시작 (Datadog@duration:>15000검색) - 2026-07-08 12:55:14 KST — 같은 사용자가
filter=id:1022로 조회한 첫 요청은 정상(728ms, DB 268ms) - 2026-07-08 12:55:12 KST — 문제의
filter=id:87요청이 접수 (Datadog span first_seen) - 2026-07-08 12:55:32 KST — 문제의 요청이 200 OK로 완료(18506ms, DB 12047ms)
- 2026-07-08 12:56:14 KST — 광범위한 지연 완화 (마지막 slow log)
Error Log#
{
"resource_name": "Api::V1::Admin::TeamsController#index",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 18908,
"max_ms": 18908,
"sample_trace_id": "388353435410329205"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-08 12:55:12 KST
- 최근 발생: 2026-07-08 12:55:12 KST
- 엔드포인트:
GET /api/v1/admin/teams - 사용자 영향: 단일 요청 지연(~18.5s). 같은 시간대 다른 endpoint에서도 15초+ 응답이 다수 관측되어 사용자 체감 응답성 저하가 넓게 있었을 것으로 추정.
Root Cause Summary#
이 클러스터는 단발성 latency spike이며, 원인은 요청 본체 로직 자체가 느렸다기보다 동일 시간대의 서비스 전반적인 DB 대기 시간 상승으로 판단된다. 총 18.5초 중 12초가 db 시간(65%)이었고, 같은 창에 여러 다른 컨트롤러(PanosController#bulk, ElementTracesController#refresh, SessionsController#show 등)도 15초 이상 지연되었다. Admin::TeamRepository#_search → BaseRepository#search → default_joins(:primary_csm, :secondary_csm, :account_manager, :customer_success_managers, :storage eager load) → SalesTeamAttribute 직렬화(UserSerializer.new(team.customer_success_managers, ...).serializable_hash) 경로가 정상 컨디션에서는 700ms 안에 끝나기 때문에, 이번 spike는 코드 결함이 아닌 인프라/부하 요인의 노출로 본다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/admin/teams_controller.rb:17-27—TeamsController#index - Search:
app/repositories/admin/team_repository.rb:39-109— Elasticsearch로filter=id:87을 조회 후 결과를 hydrate - Eager load:
app/repositories/admin/team_repository.rb:345-354—default_joins가 다섯 개 관계를 preload - Serialization:
app/serializers/admin/team_serializer.rb+app/serializers/concerns/sales_team_attribute.rb:28-36—customer_success_managers직렬화가 별도의UserSerializer를 매 팀마다 생성
def index
teams = repository_instance.search(
Cupix::QueryOption::Team.new(get_query_option(enable_current_team: false), params)
)
render_api Renderable.new(
search_result: teams,
is_collection: true,
serializer_option: @serializer_option.merge(params: { current_user: current_user })
)
end
def _search(query_option = nil)
set_query_option(query_option)
# ...Elasticsearch bool 쿼리 구성...
response = self.class.current_class.search(
self.query_option.serializable_hash
).paginate(
per_page: self.query_option.per_page,
page: self.query_option.page
)
set_response(response)
response
end
class << self
def default_joins(record)
record.includes(
:user,
:primary_csm,
:secondary_csm,
:account_manager,
:customer_success_managers,
:storage
)
end
end
attribute :customer_success_managers do |team, params|
UserSerializer.new(team.customer_success_managers, {
fields: {
user: User.cached_fields
}
}).serializable_hash[:data].map do |data|
data[:attributes]
end
end
- 기대 동작:
filter=id:87은 단일 팀 조회이므로 Elasticsearch 검색 + 5개 associationincludes+UserSerializer한 번 호출로 수백 ms 안에 완료되어야 한다. 실제로 같은 사용자가 같은 fields 조합으로filter=id:1022을 20초 앞서 호출했을 때는 728ms, DB 268ms 로 종료되었다. - 실제 동작: 동일한 코드 경로에서 DB 12047ms, 총 18506ms가 소요됨.
view=0.1ms,serialization=4ms이므로 병목은 순전히 ActiveRecord/DB 왕복 구간이었다.
Log Evidence#
Datadog 쿼리(재현용):
service:cupixworks-api "GET /api/v1/admin/teams "
2026-07-08T03:55:30Z ~ 2026-07-08T03:55:35Z
문제의 slow request (18506ms):
{
"@timestamp": "2026-07-08T03:55:32.355Z",
"message": "[200] GET /api/v1/admin/teams (Api::V1::Admin::TeamsController#index)",
"status": "info",
"controller": "Api::V1::Admin::TeamsController",
"action": "index",
"duration": 18506.22,
"db": 12047.42,
"view": 0.1,
"serialization": { "duration": 4 },
"params": { "filter": "id:87", "fields": ["id", "account_manager", "customer_success_managers"] },
"user": { "id": 6942, "email": "miriam.kim@cupix.com" },
"team": { "domain": "admin", "id": 133 },
"user_agent": "Retool/2.0 (+https://docs.tryretool.com/docs/apis)",
"http": { "status_code": 200, "method": "GET" },
"request_id": "1a4c5cbe-6f91-4587-9fa2-e491a35ac21e",
"host": { "name": "ip-10-1-80-134.us-west-2.compute.internal" },
"region": "us-west-2"
}
같은 사용자의 정상 요청 (직전, 728ms):
{
"@timestamp": "2026-07-08T03:55:14.450Z",
"duration": 727.68,
"db": 268.19,
"params": { "filter": "id:1022", "fields": ["id", "account_manager", "customer_success_managers"] },
"user": { "id": 6942, "email": "miriam.kim@cupix.com" },
"host": { "name": "ip-10-1-144-228.us-west-2.compute.internal" }
}
동시간대 다른 엔드포인트에서도 15초+ 지연이 다수 관측됨. Datadog 쿼리:
service:cupixworks-api @duration:>15000
2026-07-08T03:53:00Z ~ 2026-07-08T03:57:00Z
관측된 슬로우 요청 예시 (일부):
12:55:56 KST POST /api/v1/panos (PanosController#create)
12:55:52 KST POST /api/v1/panos/92132608/mask_upload_url
12:55:44 KST PUT /api/v1/element_traces (ElementTracesController#bulk)
12:55:44 KST POST /api/v1/panos/92132467/mask_upload_url
12:55:32 KST GET /api/v1/sessions (SessionsController#show)
12:55:32 KST GET /api/v1/admin/teams ← 본 클러스터 요청
12:55:24 KST PUT /api/v1/elements (ElementsController#bulk)
12:55:08 KST PUT /api/v1/element_traces/refresh
12:54:44 KST PUT /api/v1/element_traces/refresh
여러 호스트(ip-10-1-80-134, ip-10-1-144-228 등)에 걸쳐 발생. 7일 창에서 Admin::TeamsController#index @db:>5000은 이 한 건뿐 — 즉 이 코드 경로 자체의 만성 hot-spot 은 아니다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 팀 87의 customer_success_managers association이 매우 커서 SalesTeamAttribute 직렬화가 지연시켰다 |
filter=id:87인 요청이 늦다 |
serialization.duration=4ms, view=0.1ms. 병목은 DB 구간. 21:55:41 UTC의 다른 id:87 요청은 duration=3880ms인 반면 db=260ms로 DB 시간은 정상 — 데이터 크기 문제라면 매번 DB가 커야 함 |
Rejected |
| H2 | Elasticsearch가 느려서 _search 단계가 오래 걸렸다 |
ES는 search 파이프라인의 첫 단계 |
duration - db - view - serialization ≈ 6450ms가 ES/기타에 남지만, ES 문제라면 대부분 요청이 실패하거나 대량 slow가 보여야 함. 실제로는 다른 컨트롤러 다수도 지연 — ES 단독 원인으로 국한하기 어려움 |
Inconclusive |
| H3 | RDS/Aurora 순간 부하로 DB round-trip 이 지연됨 | db=12047ms이 전체의 65%, 같은 시각 다른 엔드포인트도 15초+ 지연이 동시 발생, 여러 호스트에 걸침, 24시간 창에서 같은 컨트롤러의 유사 spike는 없음 — 시스템 전반 이벤트로 관측 |
직접적인 slow-query 로그나 RDS 메트릭은 이 조사에서 확인하지 않음 — needs verification (RDS Performance Insights, CloudWatch) | Confirmed (primary) |
| H4 | Sidekiq/워커 대량 쓰기(pano ingestion)로 인한 write lock 경합 | 같은 시각 POST/PUT /api/v1/panos* 요청이 다수 동시 발생하며 모두 15초+ 지연 |
Sidekiq worker 자체 로그는 이 조사에서 확인하지 않음 — needs verification | Inconclusive (기여 요인 가능) |
Fix Recommendation#
즉시 조치 (Critical)#
- 즉시 코드 변경이 필요 없다고 판단. 본 클러스터는 단발성이며 관찰 시점(2026-07-08 12:55 KST) 이후 재발이 없다.
- 대신 이 시각의 RDS/Aurora Performance Insights 와 Elasticsearch cluster health를 대조해 원인 인프라를 확정할 것.
cupix-infrastructure에서 해당 RDS 인스턴스와 ES 도메인의 이 시간대 CPU/read latency/wait event를 확인.
단기 개선 (1주 이내)#
SalesTeamAttribute#customer_success_managers(app/serializers/concerns/sales_team_attribute.rb:28-36)는 팀마다UserSerializer.new(...).serializable_hash를 새로 생성한다. 목록 API에서는 성능 영향이 있으므로, 팀 컬렉션 조회 시에는has_attribute?(:customer_success_manager_email)같은 flat attribute 경로(다른 attribute들이 하는 방식)로 전환하는 것을 검토. 단, 이번 사건과의 인과는 낮으므로 우선순위는 medium.- Retool 통합처럼 자동화된 클라이언트가 반복 조회하는 admin API에 클라이언트 측 timeout/재시도 정책을 확인해, 20초 근처에서 재시도 폭발이 유발되지 않도록 한다.
장기 개선 (재발 방지)#
- p95/p99 latency SLO 를 컨트롤러/액션 단위로 정의하고, 30초 이상 응답이 다수 컨트롤러에 걸쳐 동시에 관측되면 Sidekiq/DB 부하 이벤트로 인지해 알림. 현재는 개별 컨트롤러의 latency 클러스터로만 잡혀 큰 그림이 늦게 보임.
- Pano ingestion burst 처럼 짧은 순간에 write가 몰리는 워크로드에 대해 rate-limit 또는 lane 분리(별도 read replica 라우팅 등) 검토.
Monitoring#
- 이 컨트롤러/액션의 latency 추이:
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:Api::V1::Admin::TeamsController#index} by {env}
- 서비스 전반의 slow request 카운트(같은 시각 상관 사건을 초기 감지):
sum:trace.rack.request.hits{service:cupixworks-api,env:production,duration:>15s} by {resource_name}.as_count()
- DB 병목 상관관계:
avg:trace.rack.request.duration.by.resource_name{service:cupixworks-api} by {resource_name}
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (단발성, 코드 결함 아님)
- 재발 시 액션: 인프라 원인 확정 후 필요시 short-term 개선(SalesTeamAttribute flat 경로) 시행