Api::V1::PointcloudsController#octree_upload_url (avg 10617ms, max 10617ms)
RCA: Api::V1::PointcloudsController#octree_upload_url (avg 10617ms, max 10617ms)
Overview#
What Happened#
2026-07-30 22:11 KST 경 cupixworks-api (us-west-2) 에서 POST /api/v1/pointclouds/1248660/octree_upload_url 요청 1건이 10.6초에 응답했다. 같은 세션의 인접 요청은 0.37~1.03초에 처리되었으며, 동일 시간대 다른 요청/에러 로그에도 이상 징후가 없었다. 단발성 slow trace 이며 사용자에게 반환된 HTTP 상태는 200.
Quick Facts#
| Field | Value |
|---|---|
| resource_name | Api::V1::PointcloudsController#octree_upload_url |
| http | POST /api/v1/pointclouds/1248660/octree_upload_url → 200 |
| duration | 10512.14 ms (log field), 10617 ms (cluster avg/max) |
| db | 2004.33 ms |
| host | ip-10-1-80-134.us-west-2.compute.internal |
| request_id | 2332acbd-1792-4ead-b072-6f0ad88d07fd |
| deploy | production-us-west-2-20260730t0904z0-2610203d-cupixworks |
| env | production, us-west-2 |
| user_agent | cupix-capture-3d-reconstruction-agent |
| user / team | joey.papangellin@cupix.com / team.domain accoes (816) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
accoes (team.id 816) |
1 slow request | 3D reconstruction agent 업로드 파이프라인이 10초 지연 (기능 실패 없음, 200 응답) |
Timeline#
- 2026-07-30 22:10:53 KST — 동일 세션
1248658/octree_upload_url정상 응답 (~duration 대략 400ms 수준). - ~2026-07-30 22:11:29 KST — 문제의 요청 시작 (request_id
2332acbd-…, hostip-10-1-80-134). - 2026-07-30 22:11:28.976 KST (
first_seen, cluster 감지 시각) — collector 가 APM span 을 latency cluster 로 기록. - 2026-07-30 22:11:40.228 KST — 응답 로그 기록 (
duration=10512.14ms,db=2004.33ms, status 200). - 2026-07-30 22:12:10 / 22:12:19 KST — 같은 세션 후속 요청 (
1248662,1248664) 정상 응답 → 지연은 이 한 요청에 국한.
Error Log#
{
"resource_name": "Api::V1::PointcloudsController#octree_upload_url",
"service": "cupixworks-api",
"occurrences": 1,
"avg_ms": 10617,
"max_ms": 10617,
"sample_trace_id": "628090013459636768"
}
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-30 22:11 KST
- 최근 발생: 2026-07-30 22:11 KST
- 사용자 영향: 없음 (요청은 성공, 200). Client 인
cupix-capture-3d-reconstruction-agent업로드 파이프라인이 이 요청에서 10초 지연을 겪음. - 호스트/영역 확산: 단일 puma 호스트(
ip-10-1-80-134)에서 단일 요청. 인접 요청은 다른 호스트(ip-10-1-19-190,ip-10-1-144-228)에서 정상 응답.
Root Cause Summary#
octree_upload_url 엔드포인트 자체의 코드는 짧고 외부 blocking I/O 가 없다 — set_pointcloud 로 pointcloud 를 조회하고, octree_state state machine 을 :uploading 으로 전이하며, serializer 가 로컬 계산으로 presigned S3 URL 을 만들어 응답한다. 그러나 이 요청 하나만 DB 시간(db=2004ms)과 총 소요 시간(duration=10512ms)이 정상 대비 약 10배로 균일하게 증가했고, 인접한 같은 사용자의 요청은 정상이며 같은 시간대 다른 서비스 에러 로그도 없다. 이는 특정 라우팅/코드 경로의 결함이 아니라, 해당 puma 워커 프로세스에서의 일시적 stop-the-world 급 지연(예: GC pause, 소켓/DB 커넥션 재수립, EC2 노이지 네이버) 이 발생한 결과로 판단된다. 다만 이 엔드포인트가 매번 write 트랜잭션(state transition)을 수반하고, 응답 serializer 가 potree/voxels/thumbnail/cpc 등 다수 URL 필드를 계산하도록 요청되고 있어 tail latency 에 취약한 구조라는 부수 요인이 존재한다.
Technical Analysis#
Code Path#
Entry: 컨트롤러 concern.
def octree_upload_url
repository_instance.octree_upload_url
render_api Renderable.new({
contents: @model
})
end
Before-action 이 매 요청마다 pointcloud 를 다시 로드한다.
before_action :set_pointcloud, except: %i[index create untrash purge mock]
protected
def set_pointcloud
@model = repository_instance.show(params[:pointcloud_id] || params[:id])
end
Repository 는 state 를 확인한 뒤 :created / :none / :uploaded 인 경우 uploading_octree_state 이벤트를 발화한다 (state_machines gem → DB write).
def octree_upload_url
case @model.octree_state_name
when :created, :none, :uploaded
@model.uploading_octree_state
end
@model
end
State machine 정의 (namespace octree_state).
state_machine :octree_state, initial: :created, namespace: :octree_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
응답 serializer 가 octree_upload_url 을 계산 → 모델에서 로컬 signer 로 presigned PUT URL 생성.
module OctreeAttribute
extend ActiveSupport::Concern
included do
attribute :octree_state
attribute :octree_download_url
attribute :octree_upload_url
end
end
def octree_upload_url(force: false)
return nil unless octree_state_uploading? || force
octree_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
정상 케이스: presigned_url(:put, …) 은 AWS SDK 의 로컬 서명 (네트워크 왕복 없음). octree_download_url 은 state 가 :uploading 이므로 nil 반환. 실제 무거운 부분은 컨트롤러의 set_pointcloud (Elasticsearch/DB 조회), state transition write, 그리고 응답에 요청된 다른 필드들(potree_url, voxels_result_urls, thumbnail_urls, cpc_download_url 등)이다.
Client 가 요청한 필드 목록 (로그의 params.fields):
id, name, kind, pointcloud_type, state, potree_state, potree_paths,
resource_state, parent, record, level, capture, bim_icp_tm, use_bim_icp_tm,
potree_url, entry_filename, voxel_state, voxels_result_urls, meta,
thumbnail_urls, potree_upload_urls, created_at, cpc_download_url
이들 필드도 대부분 로컬 계산이지만 (예: voxels_result_urls 는 sys[:voxels_objects] 순회 후 URL 문자열 조립, potree_url 은 Cupix::StorageService.object_url 로 URL 조립), 개수와 매핑이 많아 정상 상태에서 이미 400~1000ms 를 소비한다 (아래 Log Evidence 참조).
Failure point: 결정적 코드 결함 없음. 지연은 puma 워커 단의 일시적 프로세스-레벨 stall.
기대 동작: 정상 세션에서 관측된 400ms 안팎. 실제 동작: 같은 세션 · 같은 사용자 · 같은 파라미터인데 이 요청만 10512ms.
Log Evidence#
Datadog 쿼리 (재현):
service:cupixworks-api "octree_upload_url"
시간 범위: 2026-07-30T13:10:00Z ~ 2026-07-30T13:12:30Z.
같은 세션 · 동일 팀 · 동일 user-agent 의 인접 요청 duration 비교:
2026-07-30 22:10:53 KST POST /api/v1/pointclouds/1248658/octree_upload_url 200
2026-07-30 22:11:40 KST POST /api/v1/pointclouds/1248660/octree_upload_url 200 ← 이 요청 (10512.14ms)
2026-07-30 22:12:10 KST POST /api/v1/pointclouds/1248662/octree_upload_url 200
2026-07-30 22:12:19 KST POST /api/v1/pointclouds/1248664/octree_upload_url 200
문제 요청 원본 (핵심 필드 발췌):
{
"@timestamp": "2026-07-30T13:11:40.228Z",
"controller": "Api::V1::PointcloudsController",
"action": "octree_upload_url",
"http": {
"method": "POST",
"status_code": 200,
"url_details": { "path": "/api/v1/pointclouds/1248660/octree_upload_url" }
},
"duration": 10512.14,
"db": 2004.33,
"view": 0.1,
"serialization": { "duration": 0 },
"host": { "name": "ip-10-1-80-134.us-west-2.compute.internal" },
"request_id": "2332acbd-1792-4ead-b072-6f0ad88d07fd",
"user": { "id": 23533, "email": "joey.papangellin@cupix.com" },
"team": { "id": 816, "domain": "accoes" },
"user_agent": "cupix-capture-3d-reconstruction-agent"
}
동일 창의 인접 요청 (정상):
{ "@timestamp": "2026-07-30T13:14:38.276Z", "path": "/api/v1/pointclouds/1248672/octree_upload_url",
"duration": 471.25, "db": 169.07, "host": "ip-10-1-19-190.us-west-2.compute.internal" }
{ "@timestamp": "2026-07-30T13:14:02.219Z", "path": "/api/v1/pointclouds/1248670/octree_upload_url",
"duration": 369.22, "db": 162.88, "host": "ip-10-1-19-190.us-west-2.compute.internal" }
{ "@timestamp": "2026-07-30T13:13:35.701Z", "path": "/api/v1/pointclouds/1248668/octree_upload_url",
"duration": 1032.81, "db": 42.31, "host": "ip-10-1-144-228.us-west-2.compute.internal" }
duration 은 10배, db 도 정확히 10배 → I/O 와 CPU 시간이 균일하게 부풀려짐 (특정 단계만 튄 게 아님). 이는 흔히 워커 프로세스 전역 stall (GC major, 스와핑, EC2 CPU steal, DB 세션 재수립 등) 에서 나타나는 패턴이다.
같은 창의 에러 로그 (service:cupixworks-api status:error, 15분 창):
Found 1 logs:
2026-07-30 22:11:49 KST error "Exception occurred at set_upload_state. from: upload_done, to: upload_done, state_updated_at: 2026-07-30 12:57:16 UTC"
이 에러는 set_upload_state (capture/resource 계열 state machine, app/concerns/parameter/capture.rb, resource.rb) 로 pointcloud 의 octree_state 와 무관하다. 시간이 근접하지만 다른 endpoint 이며 원인 관계로 볼 근거는 없다.
지난 7일간 octree_upload_url 에서 @duration:>3000ms 인 요청 (샘플):
2026-07-30 22:41:13 KST 1248712 | 2026-07-30 22:11:40 KST 1248660 ← 이 클러스터
2026-07-30 21:53:42 KST 1248654 | 2026-07-30 10:14:54 KST 1247186
2026-07-30 10:11:51 KST 1247176 | 2026-07-30 10:06:52 KST 1247170
2026-07-30 10:05:32 KST 1247166 | 2026-07-30 09:58:14 KST 1247162
2026-07-30 09:18:21 KST 1247112 | 2026-07-30 09:18:08 KST 1247110
...(총 30건 이상, 대부분 같은 자동화 agent 발신)
>3s 지연이 산발적으로 발생하고 있으나 대부분은 이 클러스터(>500ms 컷) 아래에 흡수되어 있고, >5s 는 오늘 4건에 불과 → 상시 문제라기보다 tail latency 이슈.
Status board 결과 (참고): svc:cupixworks-api::unknown scope 로 오늘 오전(2026-07-30 20:34–21:23 KST) 에 다른 클러스터들로 인한 "service degraded" 인시던트가 resolved 됨. 이 slow trace 는 그 인시던트가 종료된 뒤(약 48분 후) 발생했고 별도 클러스터로 관측됨 → 해당 인시던트와 직접 연결짓기에는 증거 부족.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 문제의 puma 워커/호스트에서 일시적 프로세스-레벨 stall (GC major, CPU steal, DB 커넥션 재수립 등) 로 db 와 non-DB 시간이 함께 ~10× 증가 |
같은 세션 인접 요청은 정상 (369–1032ms), 문제 요청만 duration=10512ms · db=2004ms; DB 와 non-DB 가 같은 비율로 증가; 문제 요청만 다른 호스트(ip-10-1-80-134) 에서 처리됨 |
프로세스 stall 을 직접 증명하는 ruby.gc.*, puma.busy_threads, system.cpu.steal 지표가 확인되지 않음 (본 조사에서 metrics 미조회) — "uncertain, needs metrics verification" |
Confirmed (best fit; metrics 로 최종 확증 필요) |
| H2 | Endpoint 코드 자체의 결함 (예: N+1, 무거운 serializer 필드) 이 원인 | 요청 응답에 potree_url, voxels_result_urls, thumbnail_urls, potree_upload_urls, cpc_download_url 등 다수 계산 필드 포함 |
코드 상 이들은 로컬 문자열/서명 계산이며 네트워크 왕복 없음. 동일 파라미터 · 동일 세션의 인접 요청은 400–1000ms 에 처리 → 코드 자체는 문제 원인이 아님 | Rejected (structural amplifier 로만 남아 있음, root cause 아님) |
| H3 | State transition (uploading_octree_state) 이 락 경합/느린 write 로 병목 |
이 요청만 db=2004ms 로 정상 대비 12–48× |
같은 endpoint 의 다른 pointcloud 는 같은 시간대에 42–169ms 로 write 성공. 특정 row 락이라면 재시도 요청도 지연되어야 하는데 22:12:10 (1248662), 22:12:19 (1248664) 요청은 정상. 특정 row/사용자에 국한된 락 근거 없음 | Rejected |
| H4 | S3 presigned URL 생성 (presigned_url(:put, …)) 이 자격 증명 갱신/네트워크 지연 |
요청은 반드시 이 경로를 밟음 | AWS SDK 의 presigned_url 은 로컬 HMAC 서명 (네트워크 없음). serialization.duration=0 로그로 view 렌더 자체는 짧음 (0.1ms). |
Rejected |
| H5 | 오전(20:34–21:23 KST) 에 발생한 cupixworks-api service degraded 인시던트의 여파 |
같은 서비스의 인접 시간대 이슈 | 인시던트는 21:23 KST 에 resolved. 문제 요청은 22:11 KST — 약 48분 뒤에 발생. 인시던트 종료 후 관측된 다른 클러스터도 없음. | Rejected (관련성 낮음) |
| H6 | 외부 의존성 (S3, RDS) 광역 장애 | — | Status board 에 dep:* active 없음; 같은 시간대 cupixworks-api status:error 는 1건이고 무관한 endpoint; 인접 요청은 다른 호스트에서 정상 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
특정 코드 결함이 확인되지 않았고 단발성 slow trace 이므로 즉시 코드 변경이 필요한 항목 없음. 단, 재발/집중 여부를 판단할 관측 데이터가 부족하므로 아래 "Monitoring" 을 우선 활성화하여 24~72시간 관찰.
증거 보강을 위해 다음을 확인:
- 문제 시각(2026-07-30 13:11 UTC ±5분) 에 host
ip-10-1-80-134.us-west-2.compute.internal의system.cpu.steal,system.load.1,ruby.gc.major_gc.count,puma.busy_threads메트릭 (Datadog metrics). - RDS/Aurora 의 동일 시각
Deadlocks,CPUUtilization,DatabaseConnections,ReadLatency/WriteLatency. - 최근 배포 (
production-us-west-2-20260730t0904z0-2610203d) 와 이전 배포 사이에 puma 튜닝/Ruby 버전/gem 변경이 있었는지 diff 확인.
단기 개선 (1주 이내)#
Api::V1::PointcloudsController#octree_upload_url응답에 대해 client (cupix-capture-3d-reconstruction-agent) 가 요청하는params.fields를 검토 (potree_url,voxels_result_urls,thumbnail_urls,potree_upload_urls,cpc_download_url등). Upload URL 발급만 필요한 호출이라면 이 요청은fields=[id, octree_state, octree_upload_url]정도로 축소 가능 → tail latency 노출면을 줄임. 이는 순수 client 변경이므로 서버 코드는 유지.- APM 에 P95/P99 SLO 알림 (아래 참조) 을 추가하여 tail latency 재현 시 즉시 캡처.
장기 개선 (재발 방지)#
- Read-only 조회에 write 트랜잭션이 붙는 구조 (
octree_upload_url마다 state transition) 는 tail latency 에 취약. State 가 이미:uploading인 경우에는 no-op 이라는 사실을 활용해, client 가 URL 재발급만 원할 때는 별도 read-only 경로 (예:check_octree_uploading처럼 상태를 바꾸지 않는 endpoint) 로 유도하는 것을 검토 (아키텍처 논의 필요, 자동 반영 대상 아님). - puma 워커 GC/힙 리포팅 상시화 (
GC.stat을 주기적으로 Datadog metric 으로 push).
Monitoring#
writing-datadog-monitoring-queries 가이드에 따라 dashboard timeseries widget 에 바로 붙일 수 있는 형태로 작성.
P95 latency (초 단위):
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#octree_upload_url,env:production}
P99 latency:
p99:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#octree_upload_url,env:production}
3초 이상 소요된 요청 수 (rate, log-based):
sum:logs.hits{service:cupixworks-api,@controller:Api\:\:V1\:\:PointcloudsController,@action:octree_upload_url,@duration:>3000}.as_count()
해당 puma 호스트의 CPU steal:
avg:system.cpu.steal{service:cupixworks-api,host:ip-10-1-80-134.us-west-2.compute.internal}
Ruby GC major gc (available 시):
sum:ruby.gc.major_gc.count{service:cupixworks-api}.as_count()
알림 기준 제안: p95 > 1.5s 로 5분 이상 지속, p99 > 5s spike, @duration:>5000 카운트가 10분에 3건 이상.
Risk Assessment#
- Risk level: low — 단발성 slow trace, 사용자 반환은 200, 데이터 정합성 이슈 없음, 동일 시간대 광역 영향 없음.
- 예상 복잡도: trivial (즉각적 코드 변경 없음; 관측만 추가하면 됨). 다만 client field 축소 (단기) 는 3D reconstruction agent 팀과의 조율이 필요한 standard 작업.