ES /docs

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#

  1. 2026-07-30 20:34:05 KST — 서비스 저하 인시던트 시작 (svc:cupixworks-api::unknown, 첫 클러스터 9855766b).
  2. 2026-07-30 20:35:27 KST — 두 번째 latency 클러스터 e758e925 발생.
  3. 2026-07-30 21:05:28 KST — 본 클러스터 dfa0ed54cpc_mesh_upload_url 19s trace 발생 (first_seen).
  4. 2026-07-30 21:06:40 KSTBadgesController#index 12.4s trace (1feaf5c5).
  5. 2026-07-30 21:23:30 KSTFieldsController#index 36.7s trace (da6d4c75), 인시던트 자동 해소.

Error Log#

Datadog Logs

text
{
  "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-16CpcMeshController#cpc_mesh_upload_url (mixed into Api::V1::PointcloudsController at app/controllers/api/v1/pointclouds_controller.rb:7).
  • Before-action: app/controllers/api/v1/pointclouds_controller.rb:11,74-76set_pointcloudrepository_instance.show(...).
  • Repository show + permission_joins: app/repositories/base_repository.rb:121-129app/repositories/pointcloud_repository.rb:40-265 (11+ LEFT JOIN 로 구성된 permission_joins).
  • State transition: app/repositories/concerns/cpc_mesh_repository.rb:15-22CpcMeshRepository#cpc_mesh_upload_url@model.uploading_cpc_mesh_state.
  • State machine: app/models/concerns/statable/pointcloud.rb:213-231state_machine :cpc_mesh_state, event :uploading (DB row 업데이트).
  • Serializer / S3 presign: app/serializers/cpc_mesh_attribute.rb:1-9app/models/concerns/cpc_mesh.rb:35-44cpc_mesh_upload_urlcpc_mesh_object.presigned_url(:put, ...).
app/controllers/concerns/cpc_mesh_controller.rb:11-16ruby
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 는 상태 전이만 수행한다.

app/controllers/api/v1/pointclouds_controller.rb:11,74-76ruby
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#showBaseRepository#show 를 거쳐 self.class.show 로 위임되며 내부에서 permission_joins (11+ LEFT JOIN + GROUP BY) 를 조립한다. 부하 상황에서 이 쿼리는 baseline 대비 크게 밀린다.

app/repositories/concerns/cpc_mesh_repository.rb:15-22ruby
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 업데이트를 유발.

app/models/concerns/statable/pointcloud.rb:213-231ruby
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

응답 시 PointcloudSerializerCpcMeshAttribute 를 include 하여 cpc_mesh_upload_url attribute 렌더링 시 S3 presigned URL 을 생성한다.

app/models/concerns/cpc_mesh.rb:35-44ruby
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:

text
service:cupixworks-api "cpc_mesh_upload_url" @duration:>10000
json
{
  "@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:

text
service:cupixworks-api "cpc_mesh_upload_url" status:info
json
{
  "@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:

text
service:cupixworks-api "cpc_mesh_upload_url"

인시던트 창구(20:34 ~ 21:23 KST) 안의 호출 예시 (모두 성공):

text
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:

text
$ bun run cli/incident-board.ts for-cluster dfa0ed54-7545-4c81-8023-e7def3db3669
json
{
  "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분 창구에 몰려 발생 후 자동 해소됨. 자매 클러스터 9855766b (SitetracksController#check_uploading, 77.7s) 의 RCA (content/docs/incidents/9855766b-3975-47ac-9d01-4984a543fba9/rca.mdx) 는 서비스 전반 avg request duration 35배, request rate 2~3배 (peak 101 req/s) 상승을 timeseries 로 확인. avg: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 35x, request rate 23x 상승 확인. (c) slow trace 의 db=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::UserRecipeGeneratorprivate 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#showpermission_joins SELECT, (b) uploading_cpc_mesh_state update, (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#showpermission_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 쿼리 예시:

text
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#cpc_mesh_upload_url}
text
avg:trace.rack.request.duration{service:cupixworks-api}
text
sum:trace.rack.request.hits{service:cupixworks-api}.as_rate()
text
avg:trace.active_record.instantiation.duration{service:cupixworks-api}
text
avg:postgresql.query.time{service:cupixworks-api}

각 쿼리 모두 timeseries widget 에 그대로 삽입 가능. count by(...) / | stats 같은 monitor-only 문법은 사용하지 않음.

Risk Assessment#

  • Risk level: low — 단일 latency 이벤트, 이미 해소, 오류 아님 (200 응답).
  • 예상 복잡도: trivial (이 클러스터 단독 관점). 상위 인시던트 원인 규명은 별도로 추적 필요.