Api::V1::PointcloudsController#cpc_mesh_upload_url (avg 19062ms, max 19062ms)
RCA: Api::V1::PointcloudsController#cpc_mesh_upload_url latency (19062ms)
Overview#
What Happened#
2026-07-30 21:05 KST 경 Api::V1::PointcloudsController#cpc_mesh_upload_url 리소스에서 19.0초 단일 latency trace 가 발생했다. 응답 자체는 오류가 아닌 latency 클러스터로, 자동 감지 인시던트 2026-07-30-svc-cupixworks-api--unknown-1 (2026-07-30 20:34 ~ 21:23 KST 창구, 자동 해소) 에 포함된 5개 클러스터 중 세 번째 이벤트다. 같은 창구에 SitetracksController#check_uploading (77.7s), BadgesController#index (12.4s), FieldsController#index (36.7s) 등 서로 다른 리소스의 latency 클러스터가 함께 자동 그룹화되어 있다.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::PointcloudsController#cpc_mesh_upload_url |
| service | cupixworks-api |
| cluster_type | latency |
| avg_duration_ms | 19062 |
| max_duration_ms | 19062 |
| entry point | app/controllers/concerns/cpc_mesh_controller.rb:11-16 |
| region | us-west-2 |
| tenant | cupix |
| sample_trace_id | 2760415299431655940 |
| env | production |
| deploy | production-us-west-2-20260730t0857z0-2610203d-cupixworks |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (Pointcloud CPC mesh upload flow) | 1 slow trace | Team dprconstruction (id 606) 의 자동화 봇(bot@fieldai.com, user_agent cupix-capture-3d-reconstruction-agent) 이 pointcloud 1248553 에 대해 CPC mesh 업로드 presigned URL 발급 요청을 19s 대기. 응답 자체는 200. 동일 인시던트에 5개 latency 클러스터가 묶여 있음. |
Timeline#
- 2026-07-30 20:34:05 KST — 서비스 저하 인시던트 시작 (
svc:cupixworks-api::unknown, 첫 클러스터9855766b). - 2026-07-30 20:35:27 KST — 두 번째 latency 클러스터
e758e925발생. - 2026-07-30 21:05:28 KST — 본 클러스터
dfa0ed54—cpc_mesh_upload_url19s trace 발생 (first_seen). - 2026-07-30 21:06:40 KST —
BadgesController#index12.4s trace (1feaf5c5). - 2026-07-30 21:23:30 KST —
FieldsController#index36.7s trace (da6d4c75), 인시던트 자동 해소.
Error Log#
{
"resource_name": "Api::V1::PointcloudsController#cpc_mesh_upload_url",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 19062,
"max_ms": 19062,
"sample_trace_id": "2760415299431655940"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-30 21:05 KST
- 최근 발생: 2026-07-30 21:05 KST
Root Cause Summary#
이 클러스터는 개별 코드 결함이 아니라, 자동 해소된 서비스 전반 성능 저하 인시던트(2026-07-30-svc-cupixworks-api--unknown-1, 20:34 ~ 21:23 KST) 창구 내부에서 발생한 단발성 slow trace 다. cpc_mesh_upload_url 경로는 set_pointcloud before-action 에서 PointcloudRepository#show 를 호출해 permission_joins (11개 이상의 LEFT JOIN + GROUP BY, pointcloud_repository.rb:40-265) 를 수행한 뒤, CpcMeshRepository#cpc_mesh_upload_url 에서 상태 전이(uploading_cpc_mesh_state) 를 수행하고 CpcMeshAttribute 시리얼라이저 (app/serializers/cpc_mesh_attribute.rb:1-9) 가 S3 presigned URL 을 생성하는 I/O + DB 결합 경로다. 해당 slow trace 의 request log 는 duration: 19048.21ms, db: 9715.31ms, serialization: 0 — 총 시간의 절반이 DB 시간에 소비되었다. 인시던트 창구 동안 서비스 전반 avg request duration 이 baseline (0.30.9s) 대비 35배 (2.02.6s) 상승했고 요청 rate 도 23배 (peak 101 req/s) 로 뛰어, 이 단일 요청이 (a) DB 커넥션 대기 / row lock 대기, (b) Rack request queue / Puma thread 경합, (c) permission_joins 쿼리 자체의 부하-민감 저하 중 하나 또는 조합으로 19s 까지 늘어난 것으로 판단된다. 개별 trace flame graph 로 어느 span 이 시간을 소비했는지 직접 확인하지는 못했으므로 세부 비중은 uncertain — needs verification. 근본 원인은 cpc_mesh_upload_url 코드 자체가 아니며, 상위 인시던트의 원인(현재 root_cause_types: [unknown] 으로 미분류) 이 규명되어야 이 클러스터도 재발 방지된다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/concerns/cpc_mesh_controller.rb:11-16—CpcMeshController#cpc_mesh_upload_url(mixed intoApi::V1::PointcloudsControlleratapp/controllers/api/v1/pointclouds_controller.rb:7). - Before-action:
app/controllers/api/v1/pointclouds_controller.rb:11,74-76—set_pointcloud→repository_instance.show(...). - Repository show + permission_joins:
app/repositories/base_repository.rb:121-129→app/repositories/pointcloud_repository.rb:40-265(11+ LEFT JOIN 로 구성된permission_joins). - State transition:
app/repositories/concerns/cpc_mesh_repository.rb:15-22—CpcMeshRepository#cpc_mesh_upload_url→@model.uploading_cpc_mesh_state. - State machine:
app/models/concerns/statable/pointcloud.rb:213-231—state_machine :cpc_mesh_state,event :uploading(DB row 업데이트). - Serializer / S3 presign:
app/serializers/cpc_mesh_attribute.rb:1-9→app/models/concerns/cpc_mesh.rb:35-44—cpc_mesh_upload_url→cpc_mesh_object.presigned_url(:put, ...).
def cpc_mesh_upload_url
repository_instance.cpc_mesh_upload_url
render_api Renderable.new({
contents: @model
})
end
@model 은 controller 의 before_action :set_pointcloud 에서 이미 로드된 상태로, repository 는 상태 전이만 수행한다.
before_action :set_pointcloud, except: %i[index create untrash purge mock]
# ...
def set_pointcloud
@model = repository_instance.show(params[:pointcloud_id] || params[:id])
end
PointcloudRepository#show 는 BaseRepository#show 를 거쳐 self.class.show 로 위임되며 내부에서 permission_joins (11+ LEFT JOIN + GROUP BY) 를 조립한다. 부하 상황에서 이 쿼리는 baseline 대비 크게 밀린다.
def cpc_mesh_upload_url
case @model.cpc_mesh_state_name
when :created, :none, :uploaded
@model.uploading_cpc_mesh_state
end
@model
end
uploading_cpc_mesh_state 는 아래 state_machine 정의에 의해 DB row 업데이트를 유발.
state_machine :cpc_mesh_state, initial: :created, namespace: :cpc_mesh_state do
state :created,
:none,
:uploading,
:uploaded do
end
event :reset do
transition any => :created
end
event :none do
transition any - [:none] => :none
end
event :uploading do
transition any - [:uploading] => :uploading
end
event :uploaded do
transition any - [:uploaded] => :uploaded
end
end
응답 시 PointcloudSerializer 가 CpcMeshAttribute 를 include 하여 cpc_mesh_upload_url attribute 렌더링 시 S3 presigned URL 을 생성한다.
def cpc_mesh_upload_url(force: false)
return nil unless cpc_mesh_state_uploading? || force
cpc_mesh_object.presigned_url(
:put,
bucket: self.hosting_bucket_name,
expires_in: $AWS[:s3][:put_presigned_url_expires_in].to_i,
acl: 'bucket-owner-full-control'
)
end
기대 동작: 정상 baseline 은 200~400ms (아래 Log Evidence 참조).
실제 동작: 인시던트 창구에서 단일 요청이 19s, 그 중 DB 시간이 9.7s. 같은 창구에 다른 4개 latency 클러스터가 함께 트리거되어 cpc_mesh_upload_url 만의 문제가 아니라 서비스 전반 부하 패턴임을 시사.
Log Evidence#
Slow trace request log (19s trace 상세):
Datadog query:
service:cupixworks-api "cpc_mesh_upload_url" @duration:>10000
{
"@timestamp": "2026-07-30T12:05:48.755Z",
"message": "[200] POST /api/v1/pointclouds/1248553/cpc_mesh_upload_url (Api::V1::PointcloudsController#cpc_mesh_upload_url)",
"status": "info",
"duration": 19048.21,
"db": 9715.31,
"serialization": { "duration": 0 },
"http": { "status_code": 200, "method": "POST" },
"controller": "Api::V1::PointcloudsController",
"action": "cpc_mesh_upload_url",
"params": { "id": "1248553" },
"team": { "domain": "dprconstruction", "id": 606 },
"user": { "id": 49289, "email": "bot@fieldai.com" },
"user_agent": "cupix-capture-3d-reconstruction-agent",
"region": "us-west-2",
"host": { "name": "ip-10-1-144-228.us-west-2.compute.internal" },
"request_id": "22cf1d30-f454-4122-9a50-0a2593551c71",
"version": "production-us-west-2-20260730t0857z0-2610203d-cupixworks"
}
duration=19048.21ms, db=9715.31ms — 총 시간의 약 51% 가 DB 시간. 응답 코드는 200 — 오류 없이 latency 만 튐. cluster_type=latency 와 일치.
Baseline 비교 — 같은 endpoint 정상 호출 (duration≈208ms):
Datadog query:
service:cupixworks-api "cpc_mesh_upload_url" status:info
{
"@timestamp": "2026-07-30T12:06:56Z",
"message": "[200] POST /api/v1/pointclouds/250393/cpc_mesh_upload_url",
"duration": 208.3,
"controller": "Api::V1::PointcloudsController",
"action": "cpc_mesh_upload_url",
"team": { "domain": "scs-assetfuture", "id": 165 }
}
Baseline 대비 ~91배 (208ms → 19048ms).
같은 창구의 cpc_mesh_upload_url 요청 (모두 200):
Datadog query:
service:cupixworks-api "cpc_mesh_upload_url"
인시던트 창구(20:34 ~ 21:23 KST) 안의 호출 예시 (모두 성공):
2026-07-30 21:05:28 [200] POST /api/v1/pointclouds/1248553/cpc_mesh_upload_url (본 slow trace, 19.0s)
2026-07-30 21:06:01 [200] POST /api/v1/pointclouds/250391/cpc_mesh_upload_url
2026-07-30 21:06:56 [200] POST /api/v1/pointclouds/250393/cpc_mesh_upload_url
2026-07-30 21:08:52 [200] POST /api/v1/pointclouds/137366/cpc_mesh_upload_url
2026-07-30 21:09:55 [200] POST /api/v1/pointclouds/1248555/cpc_mesh_upload_url
2026-07-30 21:10:07 [200] POST /api/v1/pointclouds/1248557/cpc_mesh_upload_url
2026-07-30 21:11:53 [200] POST /api/v1/pointclouds/137368/cpc_mesh_upload_url
2026-07-30 21:12:01 [200] POST /api/v1/pointclouds/1248559/cpc_mesh_upload_url
2026-07-30 21:12:44 [200] POST /api/v1/pointclouds/250395/cpc_mesh_upload_url
2026-07-30 21:13:00 [200] POST /api/v1/pointclouds/137370/cpc_mesh_upload_url
2026-07-30 21:14:40 [200] POST /api/v1/pointclouds/137372/cpc_mesh_upload_url
2026-07-30 21:15:13 [200] POST /api/v1/pointclouds/250397/cpc_mesh_upload_url
2026-07-30 21:15:20 [200] POST /api/v1/pointclouds/137374/cpc_mesh_upload_url
2026-07-30 21:15:29 [200] POST /api/v1/pointclouds/1248562/cpc_mesh_upload_url
첫 slow trace 후 후속 호출들은 정상 지연으로 복귀 — 이 요청 하나만 outlier.
상위 인시던트 status board:
$ bun run cli/incident-board.ts for-cluster dfa0ed54-7545-4c81-8023-e7def3db3669
{
"scope": "svc:cupixworks-api::unknown",
"active": null,
"recent": [{
"id": "2026-07-30-svc-cupixworks-api--unknown-1",
"title": "cupixworks-api service degraded",
"status": "resolved",
"started_at": "2026-07-30T11:34:05.124Z",
"resolved_at": "2026-07-30T12:23:30.350Z",
"cluster_ids": [
"9855766b-3975-47ac-9d01-4984a543fba9",
"e758e925-3b09-4acf-bb97-4468f619239e",
"dfa0ed54-7545-4c81-8023-e7def3db3669",
"1feaf5c5-af3f-4902-96a8-537a72bd4d9e",
"da6d4c75-28f5-4ac7-a8a1-721df2ead8c3"
]
}]
}
본 클러스터가 인시던트의 3번째 이벤트이며, 총 5개 latency 클러스터가 50분 창구에 몰려 발생 후 자동 해소됨. 자매 클러스터 5배, request rate 2~3배 (peak 101 req/s) 상승을 timeseries 로 확인. 9855766b (SitetracksController#check_uploading, 77.7s) 의 RCA (content/docs/incidents/9855766b-3975-47ac-9d01-4984a543fba9/rca.mdx) 는 서비스 전반 avg request duration 3avg:trace.aws.command.duration{service:cupixworks-api} 은 baseline 유지 → AWS API 자체는 병목 아님.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 서비스 전반 부하 상승으로 인한 request handling 경합 + DB 지연 (cpc_mesh_upload_url 코드 자체는 무결) |
(a) 동일 창구에 4개 다른 resource 의 latency 클러스터가 같은 svc 스코프로 자동 그룹화됨. (b) 9855766b RCA 에서 서비스 avg request duration 3db=9715.31ms (총 시간의 51%) — DB 시간이 큰 비중. (d) 응답 로그는 모두 200. (e) 인시던트 자동 해소 후 baseline 회복. |
— | Confirmed (proximate) |
| H2 | PointcloudRepository#permission_joins (11+ LEFT JOIN + GROUP BY) 가 부하 상황에서 개별적으로 느려짐 |
Slow trace db=9715.31ms. pointcloud_repository.rb:40-265 의 permission_joins 는 큰 조인 트리. cupixworks-api 메모리 노트: "permission_joins 는 팀/역할별 LEFT JOIN 조합이 매우 커지므로 부하 시 db time 8~10s 관측 가능"과 일치. |
같은 창구에 permission_joins 를 사용하지 않는 endpoint (BadgesController#index, FieldsController#index) 도 함께 지연 → permission_joins 단독 원인으로는 다른 클러스터를 설명하지 못함. permission_joins 는 부하 시 증폭되는 병목 중 하나로 봄 (proximate contributor). |
Confirmed as contributor, not primary |
| H3 | 상태 전이(uploading_cpc_mesh_state) 의 DB write / row lock 경합 |
cpc_mesh_upload_url 은 상태 전이를 포함하므로 DB write 발생. state_machine callback (before/after transition Cupix::Logger.info) 도 존재 (statable/pointcloud.rb:66-70, cpc_mesh state 정의에는 없으나 pointcloud 상위 state_machine 에는 있음). |
같은 창구에 read-only 요청 (BadgesController#index, FieldsController#index) 도 지연 → write 경합만으로는 설명 불가. cpc_mesh_state state_machine 정의(statable/pointcloud.rb:213-231) 자체에는 before/after transition callback 없음. |
Rejected as primary — 필요시 개별 trace 로 확인 |
| H4 | S3 presigned_url 생성 지연 (app/models/concerns/cpc_mesh.rb:35-44) |
presigned_url 은 네트워크 호출 없이 로컬 서명이지만, hosting_bucket_name / storage_option lazy load 가 DB 조회를 유발할 수 있음. |
로그 serialization.duration: 0 — 시리얼라이제이션 시간 0. presigned_url 생성은 시리얼라이저 attribute 렌더링 시점에 발생하므로 이 span 은 사실상 0. slow 는 controller/repository 단계에서 발생. |
Rejected |
| H5 | 특정 배포로 인한 회귀 (production-us-west-2-20260730t0857z0-2610203d-cupixworks) |
slow trace 의 version tag 확인됨. 배포 시점(2026-07-30 08:57 UTC = 17:57 KST) 이후 3~4시간 뒤 인시던트 발생. | 배포가 20:34 KST 인시던트 시작 시각에 유의미하게 근접하지 않고, 인시던트가 자동 해소된 점 (부하 완화로 설명 가능), 다른 endpoint 도 함께 저하된 점으로 배포 회귀 특징(특정 코드 경로만 저하)이 약함. | Inconclusive — needs verification |
| H6 | Cupix::PubSub::Subscribers::UserRecipeGenerator 의 private method 'service_jwt' called for class Cupix::NotificationService 예외가 latency 원인 |
같은 창구 (21:15 ~ 21:29 KST) 에 UserRecipeGenerator 실패 로그 다수 관찰. pub_sub 이 요청 흐름 중 동기적으로 호출되면 latency 유발 가능. |
(a) cpc_mesh_upload_url 경로가 UserRecipeGenerator 를 트리거하는 이벤트(facility_permission.created / full_permission_enabled) 를 발행하지 않음 — pointcloud state 변경 이벤트와 무관. (b) pub_sub 실패 로그는 21:15 이후 관측되며 본 slow trace(21:05) 시점보다 뒤. (c) Base#call 은 rescue StandardError 로 예외를 삼키므로 latency 를 크게 유발하지 않음. |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 단일 이벤트이며 이미 해소된 상위 인시던트(
2026-07-30-svc-cupixworks-api--unknown-1) 의 3번째 클러스터이므로, 이 클러스터 단독으로 즉시 코드 수정은 불필요. - 재발 감지를 위해 status board 및 아래 Monitoring 섹션 쿼리로 관찰 계속.
단기 개선 (1주 이내)#
trace_id:2760415299431655940의 APM flame graph 를 확인해 19s 중 9.7s DB 시간이 (a)PointcloudRepository#show의permission_joinsSELECT, (b)uploading_cpc_mesh_stateupdate, (c) DB 커넥션 획득 대기 중 어느 span 에 분포되어 있는지 규명. Datadog APM trace 링크가 클러스터 상단 URL 에 그대로 존재.- 같은 창구에 묶인 다른 4개 latency 클러스터(
9855766b,e758e925,1feaf5c5,da6d4c75) 의 span 분포와 교차 비교해 공통 병목(rack queue / DB / 외부 API) 을 특정. 이 클러스터는 DB 비중이 51% 로 뚜렷하므로, 다른 클러스터도 DB 지연 패턴을 공유하는지가 상위 인시던트 원인 규명의 핵심. - 메모리 노트 참고 (
memory/services/cupixworks-api.md):PointcloudRepository#show의permission_joins는 팀/역할별 LEFT JOIN 조합이 매우 커서 부하 시 db time 8~10s 로 관측됨. 부하-민감 저하가 이 요청에서 재현된 것으로 보이므로,permission_joins의 execution plan 을 부하 시나리오에서 확인.
장기 개선 (재발 방지)#
- 서비스 전반의 부하 급증 시 자동 감지·알림을 강화. 현재
svc:*::unknown스코프로만 자동 그룹핑되고 있어 root_cause 가 미분류. request rate + duration 상관 알림을 별도 스코프로 분리. - DB 지표(
postgresql.query.time,postgresql.connections.active,postgresql.locks.count) 를 Datadog 로 노출/알림해 다음 유사 인시던트에서 DB 병목을 즉시 확인 가능하도록 계측. PointcloudRepository#permission_joins의 LEFT JOIN 조합 축소 또는 materialized view / 캐시 도입 검토 (재발 시 blast radius 축소).
Monitoring#
Datadog 쿼리 예시:
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#cpc_mesh_upload_url}
avg:trace.rack.request.duration{service:cupixworks-api}
sum:trace.rack.request.hits{service:cupixworks-api}.as_rate()
avg:trace.active_record.instantiation.duration{service:cupixworks-api}
avg:postgresql.query.time{service:cupixworks-api}
각 쿼리 모두 timeseries widget 에 그대로 삽입 가능. count by(...) / | stats 같은 monitor-only 문법은 사용하지 않음.
Risk Assessment#
- Risk level: low — 단일 latency 이벤트, 이미 해소, 오류 아님 (200 응답).
- 예상 복잡도: trivial (이 클러스터 단독 관점). 상위 인시던트 원인 규명은 별도로 추적 필요.