ES /docs

check_voxels_uploading S3 ListObjectsV2 high latency

RCA: Api::V1::PointcloudsController#check_voxels_uploading Latency (avg 2292ms, max 5467ms)

Overview#

What Happened#

ap-southeast-2 리전에서 Api::V1::PointcloudsController#check_voxels_uploading 엔드포인트가 평균 2292ms, 최대 5467ms의 응답 시간을 보임. 이 엔드포인트는 S3 ListObjectsV2 API 호출을 동기적으로 수행하여 voxel 파일 업로드 완료 여부를 확인하는데, ap-southeast-2 리전에서의 S3 API 호출 지연이 주요 원인.

Quick Facts#

Field Value
resource_name Api::V1::PointcloudsController#check_voxels_uploading
avg_duration 2292ms
max_duration 5467ms
env production, ap-southeast-2

Timeline#

  1. 2026-05-26T04:04:13Z — 최초 고지연 요청 감지
  2. 2026-05-26T05:57:12Z — 마지막 고지연 요청 감지 (총 4건)
  3. 2026-05-26 — RCA 분석 완료

Error Log#

Datadog Logs

json
{
  "resource_name": "Api::V1::PointcloudsController#check_voxels_uploading",
  "service": "cupixworks-api",
  "occurrences": 4,
  "avg_ms": 2292,
  "max_ms": 5467,
  "sample_trace_id": "2129367162740673873"
}

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 4
  • 최초 발생: 2026-05-26T04:04:13.979Z
  • 최근 발생: 2026-05-26T05:57:12.032Z
  • 영향: ap-southeast-2 리전 사용자의 voxel 업로드 확인 요청이 수 초간 블로킹됨. 기능 장애는 아니나 UX 저하.

Root Cause Summary#

check_voxels_uploading 엔드포인트는 요청 처리 중 S3 ListObjectsV2 API를 동기적으로 호출하여 voxel 파일 존재 여부를 확인한다. ap-southeast-2 리전의 API 서버에서 S3 버킷으로의 네트워크 왕복 시간(RTT)이 높아 17초의 지연이 발생한다. APM 메트릭에서 동일 리전의 동일 엔드포인트에 대해 1.1s6.99s의 응답 시간이 관측됨. 또한 voxel 파일이 존재하는 경우 done_voxel_state 전환 시 Cupix::VoxelService.add_partition HTTP 호출과 remove_cache HTTP 호출이 추가로 동기 실행되어 지연을 가중시킨다.

Technical Analysis#

Code Path#

  • Entry point: app/controllers/concerns/voxels_controller.rb:33
  • Repository layer: app/repositories/concerns/voxels_repository.rb:33
  • Model method: app/models/concerns/voxel_module/s3.rb:39
  • S3 call: app/services/cupix/storage_service.rb:64 (object_listlist_objects_v2)
  • State transition: app/models/concerns/voxel_module/reality_capture.rb:55 (before_transition to: :done)
  • External HTTP call: app/services/cupix/voxel_service.rb:115 (add_partition)

1. Controller → Repository → Model 위임:

app/controllers/concerns/voxels_controller.rb:33-45ruby
def check_voxels_uploading
  @model = repository_instance.check_voxels_uploading

  render_api Renderable.new({
    contents: @model,
    serializer: VoxelsSerializer,
    serializer_option: {
      fields: {
        voxels: @fields
      }
    }
  })
end

2. S3 ListObjectsV2 호출 (주요 병목):

app/models/concerns/voxel_module/s3.rb:39-48ruby
def check_voxels_uploading
  self.voxels_objects = voxels_object_key_list  # S3 API 호출

  if self.voxels_objects.present?
    Cupix::Logger.info('Voxels uploading is done', class: self.class.name, function: __method__, id: id)
    self.done_voxel_state  # 상태 전환 → 추가 HTTP 호출 발생
  else
    self.missing_voxel_state
  end
end

voxels_object_key_listCupix::StorageService.object_key_list를 호출하며, 내부적으로 S3 list_objects_v2를 실행:

app/services/cupix/storage_service.rb:64-79ruby
def object_list(storage_option: nil, **kwargs)
  opts = parse_storage_option(storage_option).merge(kwargs)

  check_required_params(opts, %i[region bucket_name prefix])

  client(storage_option: storage_option)
    .list_objects_v2(
      bucket: storage_option.s3_hosting_bucket_name,
      prefix: opts[:prefix],
      delimiter: 'delimiter'
    )
    .contents
    .reject do |object|
      object.key.end_with?('/')
    end
end

3. done 상태 전환 시 동기 HTTP 호출:

app/models/concerns/voxel_module/reality_capture.rb:55-66ruby
before_transition to: :done do |model, transition|
  if model.respond_to?(:increase_voxel_revision)
    model.increase_voxel_revision
    Cupix::VoxelService.add_partition(model: model)  # 동기 HTTP PUT

    if model.has_attribute?(:level_id)
      Cupix::VoxelService.remove_cache(model: model.level)  # 동기 HTTP 호출
    end

    true
  end
end

4. add_partition HTTP 호출:

app/services/cupix/voxel_service.rb:102-129ruby
def add_partition(model: nil, session: nil)
  # ...
  response = Cupix::HttpClient.put("#{$CUPIX_VOXEL_SERVICE_URL}/add_partition", body.to_json, headers)
  body = JSON.parse(response.body)
  # ...
end

기대 동작: API 요청은 빠르게 S3 상태를 확인하고 응답해야 함 (목표: <500ms). 실제 동작: S3 list_objects_v2 호출이 ap-southeast-2에서 15초 소요. voxel이 존재하는 경우 add_partition + remove_cache 추가 HTTP 호출로 27초 총 지연.

Log Evidence#

APM 메트릭 조회 (ap-southeast-2 리전):

text
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller_check_voxels_uploading,region:ap-southeast-2}

결과 (24시간, 초 단위):

json
{
  "times": [1779759300000, 1779763500000, 1779768000000, 1779771900000, 1779772500000, 1779774900000, 1779780600000, 1779781500000],
  "values": [1.115319, 6.556662, 3.255954, 1.347751, 6.986963, 6.782132, 6.783877, 5.589174]
}

"Voxels uploading is done" 로그 (인시던트 시간대):

json
{
  "timestamp": "2026-05-26T06:48:01.912Z",
  "message": "Voxels uploading is done",
  "class": "Capture",
  "function": "check_voxels_uploading",
  "region": "us-west-2"
}

참고: us-west-2 리전에서도 동일 엔드포인트가 호출되지만, 해당 리전은 지연 문제 없음. ap-southeast-2에서만 고지연 발생.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 S3 list_objects_v2 크로스리전 호출 지연 APM 메트릭: ap-southeast-2에서 1.1~6.99s 응답. StorageService.object_list는 pagination 없이 단일 API 호출. us-west-2는 정상. Confirmed
H2 voxel-service add_partition HTTP 호출 지연 done_voxel_state 전환 시 동기 HTTP PUT 호출 (voxel_service.rb:115). 외부 서비스 RTT가 총 지연에 합산됨. 4건 모두 동일 패턴이 아닐 수 있음 (voxel 미존재 시 호출 안 됨) Contributing
H3 S3 버킷에 대량 오브젝트로 listing 느림 list_objects_v2는 delimiter 사용하여 prefix 범위 제한됨. 1000개 제한 내 결과. delimiter 사용으로 범위가 좁혀져 대량 오브젝트 문제는 아닌 것으로 판단 Rejected
H4 DB 쿼리 또는 모델 로딩 지연 APM 메트릭이 전체 request duration만 보여주나, 코드 경로상 DB 쿼리는 모델 로딩(before_action)에서만 발생하며 핵심 로직은 S3/HTTP 호출 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • app/models/concerns/voxel_module/s3.rb:39-48: check_voxels_uploading 메서드에서 done_voxel_state 전환을 비동기(Sidekiq worker)로 분리하여 S3 확인 후 즉시 응답 반환. add_partitionremove_cache 호출을 별도 worker에서 처리.

단기 개선 (1주 이내)#

  • app/services/cupix/storage_service.rb:64-79: object_listmax_keys: 1 옵션 추가 — check_voxels_uploading은 파일 존재 여부만 확인하면 되므로 전체 목록을 가져올 필요 없음. max_keys: 1로 S3 응답 시간 단축.
  • ap-southeast-2 리전의 API 서버가 같은 리전의 S3 버킷에 접근하도록 storage_option 구성 검증. 크로스리전 호출이면 같은 리전 버킷 사용 권장.

장기 개선 (재발 방지)#

  • Voxel 업로드 완료 확인을 polling 방식에서 S3 Event Notification (또는 EventBridge) 기반 push 방식으로 전환. 클라이언트가 업로드 완료 시 콜백을 보내거나, S3 이벤트가 상태 전환을 트리거하도록 아키텍처 변경.
  • 모든 동기 외부 서비스 호출(add_partition, remove_cache)을 비동기 worker로 이관하여 API 응답 시간에서 제거.

Monitoring#

  • APM 메트릭 알림 추가:
text
avg:trace.rack.request.duration{service:cupixworks-api,resource_name:api::v1::pointcloudscontroller_check_voxels_uploading,region:ap-southeast-2} > 2
  • S3 API 호출 duration 별도 custom metric 추가 (StorageService 레벨)
  • ap-southeast-2 리전의 p95/p99 latency 모니터 설정

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 기능 장애 없음 (모든 요청 200 OK), 응답 지연만 발생. 사용자 체감 품질 저하이나 데이터 손실이나 에러는 없음.