ES /docs

flush_geo_coordinate - error - message: The specified bucket does not exist

RCA: flush_geo_coordinate - The specified bucket does not exist

Overview#

What Happened#

2026-07-13 KST 오후 시간대에 cupixworks-migration-worker 서비스 (ap-northeast-1) 에서 Record#flush_geo_coordinateAws::S3::Errors::NoSuchBucket (The specified bucket does not exist) 로 실패했다. 매시 정각(KST 기준)에 한 번씩 총 2건 발생했으며, 실행 주체는 hourly cron (Cupix::Cron::Record.flush_stale_records / flush_refreshing_records) 으로 추정된다. 특정 Record 의 storage_option.s3_hosting_bucket_name 이 실제 S3 에 존재하지 않는 버킷을 가리키고 있어, 해당 Record 의 geo-coordinate JSON 을 S3 에 업로드하지 못했다.

Quick Facts#

Field Value
exception.class Aws::S3::Errors::NoSuchBucket (from message)
exception.message The specified bucket does not exist
top_frame app/models/concerns/record_geo_coordinate.rb:93 (rescue), 실제 실패 지점 record_geo_coordinate.rb:45-49 (geo_coordinate_s3_object.upload_stream)
env production, region ap-northeast-1, tenant cupix

Affected Teams#

로그에서 record id / team domain 을 특정할 수 있는 필드가 남지 않아 영향받은 team 을 확정할 수 없다 (uncertain — needs verification). Cupix::Logger.error 호출은 record: { id: self.id } 를 함께 남기도록 되어 있으나(record_geo_coordinate.rb:93), Datadog 검색 결과에는 message 만 노출되어 있어 record id 확인을 위해 원 로그 attribute 조회가 필요하다.

Team / Domain Error Count Impact
ap-northeast-1 (tenant: cupix), 특정 Record 2 해당 Record 의 geo-coordinate JSON 이 S3 에 갱신되지 못함 → geo_coordinate_url 로 조회되는 데이터 stale

Timeline#

  1. 2026-07-13 15:06 KST — 첫 번째 The specified bucket does not exist 에러 발생 (first_seen).
  2. 2026-07-13 16:06 KST — 정확히 1시간 뒤 동일 에러 재발생 (last_seen). Hourly cron 재시도 패턴과 일치.
  3. status board — 같은 서비스의 recent 인시던트 2026-07-13-svc-cupixworks-migration-worker--unknown-1 (04:07–05:03 UTC) 는 별도 클러스터 5건에 대한 것으로, 이번 클러스터는 그 이후 시간대에 별도 발생.

Error Log#

Datadog Logs

text
flush_geo_coordinate - error - message: The specified bucket does not exist

Impact#

  • Service: cupixworks-migration-worker
  • 발생 횟수: 2
  • 최초 발생: 2026-07-13 15:06 KST
  • 최근 발생: 2026-07-13 16:06 KST
  • 범위: ap-northeast-1 리전, tenant cupix, 특정 Record 1건 (uncertain — 로그에서 record id 미확인, 반복 주기 및 발생 횟수로 볼 때 동일 Record 가 매시 재시도되는 것으로 추정)

Root Cause Summary#

Hourly cron (Cupix::Cron::Record.flush_stale_records / flush_refreshing_records) 이 Record.stale_over_hour scope 에 걸린 Record 를 순회하며 flush_geo_coordinate 를 호출한다. 이 메서드 안에서 geo_coordinate_s3_object.upload_streamstorage_option.s3_hosting_bucket_name 으로 지정된 S3 버킷에 JSON 을 업로드하는데, 해당 Record 의 storage_option 이 가리키는 버킷이 실제로는 S3 에 존재하지 않는다 (Aws::S3::Errors::NoSuchBucketThe specified bucket does not exist). 매 정각마다 동일 Record 가 다시 stale_over_hour 조건 (fresh_state_updated_at < 1.hour.ago) 을 만족해 재실행되므로 시간당 1건씩 반복 발생한다.

버킷이 존재하지 않는 원인은 두 가지 중 하나 (증거 부족 — needs verification):

  1. storage_option 이 과거에 삭제/이름 변경된 legacy 버킷을 여전히 참조.
  2. storage_option 은 새로 생성되었으나 대응하는 S3 버킷이 프로비저닝되지 않음.

Technical Analysis#

Code Path#

  • Entry point: lib/cupix/cron/record.rb:8-14 — hourly cron 이 stale_over_hour / refreshing_over_hour scope 를 순회하며 각 Record 에 flush_geo_coordinate 호출.
  • Scope 정의: app/models/concerns/fresh_state.rb:13fresh_state: :stale AND fresh_state_updated_at < 1.hour.ago. 실패 시 fresh_fresh_state! 가 실행되지 않으므로 (아래 참조) 같은 Record 가 매시 재조회됨.
  • Failure point: app/models/concerns/record_geo_coordinate.rb:45geo_coordinate_s3_object.upload_stream(...) 호출 시 S3 가 NoSuchBucket 반환.
  • Rescue: app/models/concerns/record_geo_coordinate.rb:90-94StandardError 를 잡고 Cupix::Logger.error("flush_geo_coordinate - error - message: #{e.message}", ...) 로 기록 후 false 반환. 예외를 그대로 삼키고 있어 Sidekiq retry 나 상위 알림으로 전파되지 않음.

Cron 진입점:

lib/cupix/cron/record.rb:8-14ruby
def flush_stale_records
  ::Record.stale_over_hour.each(&:flush_geo_coordinate)
end

def flush_refreshing_records
  ::Record.refreshing_over_hour.each(&:flush_geo_coordinate)
end

Scope (재실행 조건):

app/models/concerns/fresh_state.rb:13-14ruby
scope :stale_over_hour, -> { where(fresh_state: :stale).where('fresh_state_updated_at < ?', 1.hour.ago).or(where(fresh_state_updated_at: nil)).untrashed }
scope :refreshing_over_hour, -> { where(fresh_state: :refreshing).where('fresh_state_updated_at < ?', 1.hour.ago).or(where(fresh_state_updated_at: nil)).untrashed }

실제 실패 코드:

app/models/concerns/record_geo_coordinate.rb:37-97ruby
def flush_geo_coordinate(accept_delay: false)
  lock_flushing_geo_coordinate

  _delayed_flush_geo_coordinate and return if accept_delay

  Cupix::Logger.info('flush_geo_coordinate - begin', class: self.class.name, function: __method__, module: 'RecordGeoCoordinate', record: { id: self.id })
  start_time = DateTime.now.to_i

  geo_coordinate_s3_object.upload_stream(
    content_type: 'application/json',
    cache_control: "max-age=#{1.year.to_i}",
    acl: 'bucket-owner-full-control'
  ) do |write_stream|
    # ... (배치 순회하며 pano JSON 스트리밍) ...
  end

  update(geo_coordinate_url_updated_at: DateTime.now)
  # ...
  fresh_fresh_state!
rescue StandardError => e
  unlock_flushing_geo_coordinate

  Cupix::Logger.error("flush_geo_coordinate - error - message: #{e.message}", class: self.class.name, function: __method__, module: 'RecordGeoCoordinate', record: { id: self.id })
  false
else
  unlock_flushing_geo_coordinate
end

버킷 지정:

app/models/concerns/record_geo_coordinate.rb:103-109ruby
def geo_coordinate_s3_object
  Cupix::StorageService.object(
    storage_option: storage_option,
    bucket_name: storage_option.s3_hosting_bucket_name,
    key: s3_object_key
  )
end

기대 vs 실제:

  • 기대: storage_option.s3_hosting_bucket_name 이 실제 존재하는 S3 버킷을 가리키고 JSON 이 업로드된 뒤 fresh_fresh_state!fresh_state 가 갱신되어 다음 시간에는 scope 에 걸리지 않는다.
  • 실제: 버킷이 존재하지 않아 Aws::S3::Errors::NoSuchBucketrescue 블록에서 로그만 남기고 false 반환. fresh_fresh_state! 미실행 → fresh_state_updated_at 그대로 → 다음 hourly cron 에서 동일 Record 를 다시 처리 → 매시 1건씩 반복 실패.

Log Evidence#

Datadog 쿼리 (클러스터 파일에서 그대로 사용):

text
service:cupixworks-migration-worker status:error @environment:production "flush_geo_coordinate - error - message: The specified bucket does not exist"

핵심 로그 (Datadog 응답):

json
{
  "timestamp": "2026-07-13 16:06:25",
  "status": "error",
  "message": "flush_geo_coordinate - error - message: The specified bucket does not exist",
  "class": "Record",
  "function": "flush_geo_coordinate"
}
json
{
  "timestamp": "2026-07-13 15:06:27",
  "status": "error",
  "message": "flush_geo_coordinate - error - message: The specified bucket does not exist",
  "class": "Record",
  "function": "flush_geo_coordinate"
}

시간 간격: 15:06 → 16:06 (정확히 60분). 두 발생 사이에 flush_geo_coordinate - begin 로그가 다수 존재 (아래 참조) → hourly cron 이 정상적으로 도는데 이 Record 만 실패하고 있음.

주변 컨텍스트 (같은 서비스, 같은 시간대):

text
2026-07-13 16:06:29 info  flush_geo_coordinate - begin           (성공 Record 다수)
2026-07-13 16:06:29 info  flush_geo_coordinate - finished        (다른 Record 는 0-1초 내 완료)
...
2026-07-13 16:06:25 error flush_geo_coordinate - error - message: The specified bucket does not exist

같은 서비스에서 병렬로 발생 중인 다른 에러 (별도 클러스터, 다른 fingerprint — 참고용):

text
flush_geo_coordinate - error - message: Failed to open TCP connection to s3.me-south-1.amazonaws.com:443 (execution expired)

이 me-south-1 timeout 은 이번 클러스터와 fingerprint 가 다르며 (동일 함수, 다른 원인/리전), 이번 RCA 대상이 아니다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 특정 Record 의 storage_option.s3_hosting_bucket_name 이 존재하지 않는 S3 버킷을 참조 S3 표준 오류 문자열 The specified bucket does not existAws::S3::Errors::NoSuchBucket 시그니처. 같은 시각 다른 Record 의 flush_geo_coordinate - finished 로그 다수 → 서비스/자격증명 자체는 정상. record_geo_coordinate.rb:106 에서 버킷명은 storage_option 에서 온다 Confirmed
H2 일시적 S3 outage / regional 장애 동일 리전 (ap-northeast-1) 의 다른 Record 는 정상 처리됨 (Datadog flush_geo_coordinate - finished 로그 다수). NoSuchBucket 은 4xx 성격의 명시적 오류로, 장애 시 나오는 5xx/timeout 과 다름 같은 시간대 me-south-1 timeout 은 있으나 별개 리전/별개 fingerprint Rejected
H3 코드 결함으로 s3_hosting_bucket_namenil 또는 잘못된 값 반환 nil 이면 AWS SDK 가 ArgumentErrorInvalidBucketName 을 던졌을 것. The specified bucket does not exist 는 서버가 실제 요청을 받고 리턴한 오류 → 값은 문자열이며 SDK 형식은 유효했다 Rejected
H4 Cron 로직 결함으로 무한 재시도 발생 매 정각 1시간 간격으로 정확히 1건씩 반복. rescuefresh_fresh_state! 를 건너뛰어 fresh_state_updated_at 이 그대로 남는 것과 정합 (record_geo_coordinate.rb:89-97, fresh_state.rb:13) 이는 근본 원인이 아니라 증상 증폭 요인. 근본은 H1 Confirmed (부수 요인)

Fix Recommendation#

즉시 조치 (Critical)#

  • 영향받은 Record 를 식별한다. Datadog 원 로그의 record.id attribute 를 조회 (Cupix::Logger 는 record: { id: self.id } 를 넘김) 하거나, ap-northeast-1 tenant cupix 의 hourly cron 에서 매시 실패하는 Record ID 를 확인. — 파일 수정 대상 아님. 운영 확인 필요.
  • 해당 Record 의 storage_optionstorage_option.s3_hosting_bucket_name 을 확인하고, 실제 S3 에 존재하는 유효한 버킷을 가리키도록 데이터 교정 (또는 버킷을 신규 프로비저닝). 코드 변경 불필요, 운영/인프라 조치.

단기 개선 (1주 이내)#

  • app/models/concerns/record_geo_coordinate.rb:90-94rescue StandardError 에서 Aws::S3::Errors::NoSuchBucket 을 별도 분기 처리하여, 재시도로 해결되지 않는 데이터 이상은 warn 레벨로 로그를 남기거나 (또는 별도 알림 채널) 상위에서 감지 가능하도록 한다. 지금은 매시 반복되는 영구 실패가 일반 error 노이즈에 묻혀 있다.
  • 로그에 bucket_name 을 함께 남기도록 Cupix::Logger.error(...) 컨텍스트에 bucket: storage_option.s3_hosting_bucket_name 추가. 다음번 같은 이슈에서 대상 storage_option 을 즉시 식별할 수 있게 한다.

장기 개선 (재발 방지)#

  • StorageOption 저장 시 대응하는 S3 버킷이 실제 존재하는지 검증하는 lifecycle hook (프로비저닝 프로세스 or head_bucket 체크). Legacy/이름 변경된 버킷 참조가 남지 않도록.
  • Cron 에서 반복적으로 실패하는 Record 는 N회 이상 실패 시 별도 dead-letter 상태로 전환하여 정상 처리 흐름에서 제외 (현재는 fresh_state_updated_at 이 갱신되지 않아 무한 재시도).

Monitoring#

Release dashboard timeseries widget 용 쿼리 (모두 timeseries-safe):

text
service:cupixworks-migration-worker status:error @function:flush_geo_coordinate "The specified bucket does not exist"
text
service:cupixworks-migration-worker status:error @function:flush_geo_coordinate
text
service:cupixworks-migration-worker @function:flush_geo_coordinate status:info "flush_geo_coordinate - finished"
  • Alert 제안: The specified bucket does not exist 발생이 2시간 연속 카운트 ≥ 1 → 경고 (반복 실패 = 데이터 교정 필요 신호).
  • Success rate: 위 3번째 쿼리 (finished info) 로 정상 처리량을 병행 관측하여 서비스 자체의 광범위 장애와 개별 Record 데이터 이상을 구분.

Risk Assessment#

  • Risk level: low (단일 Record 로 추정, 다른 Record 처리는 정상, 서비스 전반 영향 없음).
  • 예상 복잡도: standard (근본 원인은 데이터/인프라 정합성 이슈; 코드 개선은 로깅/rescue 세분화 정도).