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_coordinate 가 Aws::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#
- 2026-07-13 15:06 KST — 첫 번째
The specified bucket does not exist에러 발생 (first_seen). - 2026-07-13 16:06 KST — 정확히 1시간 뒤 동일 에러 재발생 (
last_seen). Hourly cron 재시도 패턴과 일치. - status board — 같은 서비스의 recent 인시던트
2026-07-13-svc-cupixworks-migration-worker--unknown-1(04:07–05:03 UTC) 는 별도 클러스터 5건에 대한 것으로, 이번 클러스터는 그 이후 시간대에 별도 발생.
Error Log#
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_stream 은 storage_option.s3_hosting_bucket_name 으로 지정된 S3 버킷에 JSON 을 업로드하는데, 해당 Record 의 storage_option 이 가리키는 버킷이 실제로는 S3 에 존재하지 않는다 (Aws::S3::Errors::NoSuchBucket → The specified bucket does not exist). 매 정각마다 동일 Record 가 다시 stale_over_hour 조건 (fresh_state_updated_at < 1.hour.ago) 을 만족해 재실행되므로 시간당 1건씩 반복 발생한다.
버킷이 존재하지 않는 원인은 두 가지 중 하나 (증거 부족 — needs verification):
storage_option이 과거에 삭제/이름 변경된 legacy 버킷을 여전히 참조.storage_option은 새로 생성되었으나 대응하는 S3 버킷이 프로비저닝되지 않음.
Technical Analysis#
Code Path#
- Entry point:
lib/cupix/cron/record.rb:8-14— hourly cron 이stale_over_hour/refreshing_over_hourscope 를 순회하며 각 Record 에flush_geo_coordinate호출. - Scope 정의:
app/models/concerns/fresh_state.rb:13—fresh_state: :staleANDfresh_state_updated_at < 1.hour.ago. 실패 시fresh_fresh_state!가 실행되지 않으므로 (아래 참조) 같은 Record 가 매시 재조회됨. - Failure point:
app/models/concerns/record_geo_coordinate.rb:45—geo_coordinate_s3_object.upload_stream(...)호출 시 S3 가NoSuchBucket반환. - Rescue:
app/models/concerns/record_geo_coordinate.rb:90-94—StandardError를 잡고Cupix::Logger.error("flush_geo_coordinate - error - message: #{e.message}", ...)로 기록 후false반환. 예외를 그대로 삼키고 있어 Sidekiq retry 나 상위 알림으로 전파되지 않음.
Cron 진입점:
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 (재실행 조건):
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 }
실제 실패 코드:
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
버킷 지정:
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::NoSuchBucket→rescue블록에서 로그만 남기고false반환.fresh_fresh_state!미실행 →fresh_state_updated_at그대로 → 다음 hourly cron 에서 동일 Record 를 다시 처리 → 매시 1건씩 반복 실패.
Log Evidence#
Datadog 쿼리 (클러스터 파일에서 그대로 사용):
service:cupixworks-migration-worker status:error @environment:production "flush_geo_coordinate - error - message: The specified bucket does not exist"
핵심 로그 (Datadog 응답):
{
"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"
}
{
"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 만 실패하고 있음.
주변 컨텍스트 (같은 서비스, 같은 시간대):
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 — 참고용):
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 exist 는 Aws::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_name 이 nil 또는 잘못된 값 반환 |
— | nil 이면 AWS SDK 가 ArgumentError 나 InvalidBucketName 을 던졌을 것. The specified bucket does not exist 는 서버가 실제 요청을 받고 리턴한 오류 → 값은 문자열이며 SDK 형식은 유효했다 |
Rejected |
| H4 | Cron 로직 결함으로 무한 재시도 발생 | 매 정각 1시간 간격으로 정확히 1건씩 반복. rescue 후 fresh_fresh_state! 를 건너뛰어 fresh_state_updated_at 이 그대로 남는 것과 정합 (record_geo_coordinate.rb:89-97, fresh_state.rb:13) |
이는 근본 원인이 아니라 증상 증폭 요인. 근본은 H1 | Confirmed (부수 요인) |
Fix Recommendation#
즉시 조치 (Critical)#
- 영향받은 Record 를 식별한다. Datadog 원 로그의
record.idattribute 를 조회 (Cupix::Logger 는record: { id: self.id }를 넘김) 하거나, ap-northeast-1 tenantcupix의 hourly cron 에서 매시 실패하는 Record ID 를 확인. — 파일 수정 대상 아님. 운영 확인 필요. - 해당 Record 의
storage_option및storage_option.s3_hosting_bucket_name을 확인하고, 실제 S3 에 존재하는 유효한 버킷을 가리키도록 데이터 교정 (또는 버킷을 신규 프로비저닝). 코드 변경 불필요, 운영/인프라 조치.
단기 개선 (1주 이내)#
app/models/concerns/record_geo_coordinate.rb:90-94의rescue 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 (프로비저닝 프로세스 orhead_bucket체크). Legacy/이름 변경된 버킷 참조가 남지 않도록.- Cron 에서 반복적으로 실패하는 Record 는 N회 이상 실패 시 별도 dead-letter 상태로 전환하여 정상 처리 흐름에서 제외 (현재는
fresh_state_updated_at이 갱신되지 않아 무한 재시도).
Monitoring#
Release dashboard timeseries widget 용 쿼리 (모두 timeseries-safe):
service:cupixworks-migration-worker status:error @function:flush_geo_coordinate "The specified bucket does not exist"
service:cupixworks-migration-worker status:error @function:flush_geo_coordinate
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 세분화 정도).