ES /docs

S3 bucket not found — nonexistent hosting bucket reference

RCA: flush_geo_coordinate - The specified bucket does not exist

Error Log#

Datadog Logs

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

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 30
  • 최초 발생: 2026-04-06T08:06:29.694Z
  • 최근 발생: 2026-04-07T07:06:28.984Z

Root Cause Summary#

ap-northeast-1 리전에 배포된 cupixworks-worker의 cron job이 매 시간 Record ID 1에 대해 flush_geo_coordinate를 실행하고 있으나, 해당 Record의 storage_option에 저장된 S3 hosting bucket이 실제로 존재하지 않는 bucket을 가리키고 있습니다. Record ID 1은 시스템 초기에 생성된 레코드로, fresh_statestale 또는 refreshing 상태에 머물러 있어 매 시간 flush_refreshing_records/flush_stale_records cron에 의해 반복적으로 flush 대상에 포함됩니다. flush 시 geo_coordinate_s3_objectstorage_option.s3_hosting_bucket_name으로 S3 upload을 시도하지만, 해당 bucket이 AWS에 존재하지 않아 Aws::S3::Errors::NoSuchBucket 예외가 발생하고, rescue 블록에서 에러 로그를 남기며 실패합니다. flush가 실패하면 fresh_fresh_state! (line 89)에 도달하지 못해 fresh_statestale/refreshing에서 벗어나지 못하고, 다음 cron 주기에 다시 대상이 되는 무한 반복 구조입니다.

Technical Analysis#

Code Path#

  • Entry point: config/schedule.rb:82 - 매 시간 :06Cupix::Cron::Record.flush_refreshing_records 실행
  • Cron handler: lib/cupix/cron/record.rb:12-14 - flush_refreshing_recordsrefreshing_over_hour scope의 모든 Record에 대해 flush_geo_coordinate 호출
ruby
# lib/cupix/cron/record.rb:12-14
def flush_refreshing_records
  ::Record.refreshing_over_hour.each(&:flush_geo_coordinate)
end
  • Scope 정의: app/models/concerns/fresh_state.rb:14 - fresh_staterefreshing이고 fresh_state_updated_at이 1시간 이전인 Record를 선택
ruby
# app/models/concerns/fresh_state.rb:14
scope :refreshing_over_hour, -> { where(fresh_state: :refreshing).where('fresh_state_updated_at < ?', 1.hour.ago).or(where(fresh_state_updated_at: nil)).untrashed }
  • Flush 실행: app/models/concerns/record_geo_coordinate.rb:37-97 - flush_geo_coordinate 메서드가 S3에 JSON을 upload
ruby
# app/models/concerns/record_geo_coordinate.rb:45-48
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|
  • S3 Object 생성: app/models/concerns/record_geo_coordinate.rb:103-108 - storage_option.s3_hosting_bucket_name을 bucket으로 사용
ruby
# app/models/concerns/record_geo_coordinate.rb:103-108
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
  • Failure point: app/models/concerns/record_geo_coordinate.rb:45 - upload_stream 호출 시 AWS S3 SDK가 NoSuchBucket 에러를 raise
ruby
# app/models/concerns/record_geo_coordinate.rb:90-94
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
  • 기대 동작: flush_geo_coordinate가 성공하면 line 89에서 fresh_fresh_state!를 호출하여 fresh_statefresh로 전환, 이후 cron 대상에서 제외됨
  • 실제 동작: S3 bucket이 존재하지 않아 line 45에서 예외 발생 → rescue 블록(line 90)으로 이동 → fresh_state가 변경되지 않음 → 다음 cron 주기에 다시 대상이 됨

Log Evidence#

사용한 Datadog 쿼리:

text
service:cupixworks-worker status:error "flush_geo_coordinate" "The specified bucket does not exist"

모든 에러 로그가 동일한 패턴을 보입니다. 매 시간 :06 분에 발생하며, 일부 시간대에는 :07 분에 추가 에러가 발생합니다 (:07 분 에러는 4시간 주기의 flush_stale_records cron에 해당):

text
2026-04-07 16:06:28 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-07 15:06:24 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-07 14:06:20 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-07 13:07:16 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-07 13:06:20 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-07 12:06:28 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
...
2026-04-06 17:07:23 KST - flush_geo_coordinate - error - message: The specified bucket does not exist
2026-04-06 17:06:29 KST - flush_geo_coordinate - error - message: The specified bucket does not exist

Raw JSON 로그에서 확인한 핵심 정보:

json
{
  "message": "flush_geo_coordinate - error - message: The specified bucket does not exist",
  "class": "Record",
  "function": "flush_geo_coordinate",
  "module": "RecordGeoCoordinate",
  "record": { "id": 1 },
  "host": { "name": "ip-10-1-80-188.ap-northeast-1.compute.internal" },
  "environment": "production",
  "service_role": "worker"
}

핵심 관찰:

  1. Record ID가 항상 1 - 모든 에러가 Record ID 1에서 발생
  2. 호스트가 ap-northeast-1 리전 - 일본 리전 worker에서만 발생
  3. 매 시간 반복 - cron schedule '6 * * * *' (flush_refreshing_records)와 '7 */4 * * *' (flush_stale_records)에 의해 반복 실행
  4. ap-northeast-1 리전의 storage config (config/storage-ap-northeast-1.yaml)에는 production 환경에 cupixworks-hosting-* 패턴의 bucket만 정의되어 있으나, Record ID 1은 시스템 초기 생성 레코드로 구(legacy) 패턴의 s3_hosting_bucket_namesys[:storage_option]에 가지고 있을 가능성이 높음 - 해당 bucket은 ap-northeast-1 리전에 프로비저닝되지 않았거나 이미 삭제됨

Fix Recommendation#

즉시 조치 (Critical)#

  • Record ID 1의 sys 컬럼에서 storage_options3_hosting_bucket_name 값을 확인하고, 실제 존재하는 bucket으로 수정하거나, 해당 Record가 더 이상 사용되지 않는 경우 fresh_statefresh로 전환하여 cron 대상에서 제외
  • 확인 방법: Rails console에서 Record.find(1).storage_option.s3_hosting_bucket_name 실행하여 현재 bucket 이름 확인 후 AWS console에서 해당 bucket 존재 여부 검증

단기 개선 (1주 이내)#

  • app/models/concerns/record_geo_coordinate.rb:90-94의 rescue 블록에서 NoSuchBucket 에러를 별도 처리하여, bucket 미존재 시 무한 재시도를 방지하는 로직 추가 (예: 특정 횟수 이상 실패 시 fresh_statefresh로 전환하거나 별도 알림 발송)
  • lib/cupix/cron/record.rbflush_stale_records/flush_refreshing_records에서 실패한 Record를 추적하는 로깅 추가

장기 개선 (재발 방지)#

  • Storage migration 시 모든 Record의 sys[:storage_option]이 유효한 bucket을 가리키는지 검증하는 스크립트 추가
  • flush_geo_coordinate 실패 시 일정 횟수 이상 반복 실패하면 자동으로 cron 대상에서 제외하는 circuit breaker 패턴 도입
  • ap-northeast-1 리전 배포 시 legacy Record의 storage option 호환성 검증 프로세스 정립

Monitoring#

  • 추가할 메트릭: flush_geo_coordinate 실패율을 Record ID별로 추적
  • Datadog 쿼리 예시:
text
service:cupixworks-worker "flush_geo_coordinate - error" | stats count by @record.id
  • NoSuchBucket 에러가 동일 Record에 대해 24시간 이상 반복될 경우 알림 설정

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial
  • 영향 범위: Record ID 1만 영향받으며, 실제 사용자 데이터에 대한 영향은 없음 (시스템 초기 생성 레코드). 다만, 매 시간 불필요한 에러 로그가 발생하여 노이즈를 유발하고, cron job의 에러 집계를 왜곡할 수 있음