ES /docs

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_url200
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#

  1. 2026-07-30 22:10:53 KST — 동일 세션 1248658/octree_upload_url 정상 응답 (~duration 대략 400ms 수준).
  2. ~2026-07-30 22:11:29 KST — 문제의 요청 시작 (request_id 2332acbd-…, host ip-10-1-80-134).
  3. 2026-07-30 22:11:28.976 KST (first_seen, cluster 감지 시각) — collector 가 APM span 을 latency cluster 로 기록.
  4. 2026-07-30 22:11:40.228 KST — 응답 로그 기록 (duration=10512.14ms, db=2004.33ms, status 200).
  5. 2026-07-30 22:12:10 / 22:12:19 KST — 같은 세션 후속 요청 (1248662, 1248664) 정상 응답 → 지연은 이 한 요청에 국한.

Error Log#

Datadog Logs

text
{
  "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.

app/controllers/concerns/octree_controller.rb:11-16ruby
def octree_upload_url
  repository_instance.octree_upload_url
  render_api Renderable.new({
    contents: @model
  })
end

Before-action 이 매 요청마다 pointcloud 를 다시 로드한다.

app/controllers/api/v1/pointclouds_controller.rb:11ruby
before_action :set_pointcloud, except: %i[index create untrash purge mock]
app/controllers/api/v1/pointclouds_controller.rb:72-76ruby
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).

app/repositories/concerns/octree_repository.rb:15-22ruby
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).

app/models/concerns/statable/pointcloud.rb:193-211ruby
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 생성.

app/serializers/octree_attribute.rb:1-9ruby
module OctreeAttribute
  extend ActiveSupport::Concern

  included do
    attribute :octree_state
    attribute :octree_download_url
    attribute :octree_upload_url
  end
end
app/models/concerns/octree.rb:35-44ruby
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):

text
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_urlssys[:voxels_objects] 순회 후 URL 문자열 조립, potree_urlCupix::StorageService.object_url 로 URL 조립), 개수와 매핑이 많아 정상 상태에서 이미 400~1000ms 를 소비한다 (아래 Log Evidence 참조).

Failure point: 결정적 코드 결함 없음. 지연은 puma 워커 단의 일시적 프로세스-레벨 stall.

기대 동작: 정상 세션에서 관측된 400ms 안팎. 실제 동작: 같은 세션 · 같은 사용자 · 같은 파라미터인데 이 요청만 10512ms.

Log Evidence#

Datadog 쿼리 (재현):

text
service:cupixworks-api "octree_upload_url"

시간 범위: 2026-07-30T13:10:00Z ~ 2026-07-30T13:12:30Z.

같은 세션 · 동일 팀 · 동일 user-agent 의 인접 요청 duration 비교:

text
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

문제 요청 원본 (핵심 필드 발췌):

json
{
  "@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"
}

동일 창의 인접 요청 (정상):

json
{ "@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" }
json
{ "@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" }
json
{ "@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분 창):

text
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 인 요청 (샘플):

text
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.internalsystem.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 (초 단위):

text
p95:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#octree_upload_url,env:production}

P99 latency:

text
p99:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller#octree_upload_url,env:production}

3초 이상 소요된 요청 수 (rate, log-based):

text
sum:logs.hits{service:cupixworks-api,@controller:Api\:\:V1\:\:PointcloudsController,@action:octree_upload_url,@duration:>3000}.as_count()

해당 puma 호스트의 CPU steal:

text
avg:system.cpu.steal{service:cupixworks-api,host:ip-10-1-80-134.us-west-2.compute.internal}

Ruby GC major gc (available 시):

text
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 작업.