ES /docs

Failed to save Partial JSON Capture 701882 to file /var/data_changes/captures/partial/2026-05-22/usw

RCA: Failed to save Partial JSON Capture to file (No space left on device)

Overview#

What Happened#

2026-05-22 20:20 UTC부터 2026-05-23 00:38 UTC까지 약 4시간 동안, cupixworks-worker 서비스의 SavePartialJsonToFileWorker/var/data_changes/ 볼륨의 디스크 용량 부족(Errno::ENOSPC)으로 인해 partial JSON 파일 저장에 실패했다. Capture, ElementTrace, Pointcloud, Element 등 다수의 엔티티에서 총 8,272건의 에러가 발생했다.

Quick Facts#

Field Value
exception.class Errno::ENOSPC
exception.message No space left on device @ rb_sysopen - /var/data_changes/captures/partial/2026-05-22/uswe2/...json.gz
top_frame app/models/concerns/data_ware_house/partial_json.rb:144
runtime Ruby 3.3.0, Sidekiq 7.3.9
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
Data Warehouse (data_changes pipeline) 8,272 Partial JSON export 실패 — downstream 데이터 동기화 지연

Timeline#

  1. 2026-05-22T20:20:48Z — 최초 에러 발생. rb_sysopen syscall에서 ENOSPC 에러 시작
  2. 2026-05-22T20:20~00:36 — 4시간 동안 지속적 실패. Sidekiq가 새 job을 계속 받으며 에러 누적
  3. 2026-05-23T00:36+ — 날짜 변경으로 새 디렉토리(/2026-05-23/) 생성 시도 시 dir_s_mkdir에서도 ENOSPC 발생
  4. 2026-05-23T00:38:47Z — 관측 윈도우 내 마지막 에러

Error Log#

Datadog Logs

text
Failed to save Partial JSON Capture 701882 to file /var/data_changes/captures/partial/2026-05-22/uswe2/1779480277939315-captures-701882.json.gz

Impact#

  • Service: cupixworks-worker
  • 발생 횟수: 8,272
  • 최초 발생: 2026-05-22T20:20:48.465Z
  • 최근 발생: 2026-05-23T00:38:47.671Z

영향받은 엔티티: Capture (701289, 701550, 701882 등 다수), ElementTrace (102272260, 115321486 등 다수), Pointcloud (1103877~1103882), Element (11612623, 14936857 등). Partial JSON export 실패로 인해 downstream data lake 동기화가 4시간 이상 중단되었으며, worker의 retry: false 설정으로 인해 실패한 job은 자동 재처리되지 않아 데이터 유실 가능성이 있다.

Root Cause Summary#

/var/data_changes/ 파티션의 디스크 공간이 완전히 소진되어 File.open(filepath, 'wb') 호출 시 Errno::ENOSPC (No space left on device) 예외가 발생했다. 이 볼륨은 모든 partial JSON export 파일이 날짜별로 파티셔닝되어 저장되는 곳으로, 오래된 파일에 대한 자동 정리(cleanup/rotation) 메커니즘이 코드에 존재하지 않아 시간이 지남에 따라 용량이 고갈된 것으로 판단된다.

Technical Analysis#

Code Path#

  • Entry point: SavePartialJsonToFileWorker#perform (app/workers/save_partial_json_to_file_worker.rb:63)
  • Model method: DataWareHouse::PartialJson#save_partial_json_to_filegenerate_and_save_partial_json (partial_json.rb:115-118)
  • Failure point: save_partial_json_fileFile.open(filepath, 'wb') (partial_json.rb:144)

Worker가 Sidekiq data_changes 큐에서 job을 받아 모델의 partial JSON을 gzip 압축 후 /var/data_changes/ 경로에 저장한다:

app/workers/save_partial_json_to_file_worker.rb:62-66ruby
begin
  model.save_partial_json_to_file(all_data: all_data, operation: operation, changes: changes, timestamp: timestamp)
rescue NoMethodError, Errno::ENOSPC, Errno::EACCES => e
  Cupix::Logger.error(e.message, class: self.class, function: 'perform', error_class: e.class.name)
end

실제 파일 저장 로직에서 디렉토리 생성 후 파일을 쓸 때 ENOSPC가 발생한다:

app/models/concerns/data_ware_house/partial_json.rb:121-149ruby
def save_partial_json_file(content, path)
  filepath = Rails.env.development? ? "./tmp/storage/#{path}" : "/var/data_changes/#{path}"
  create_directory_if_not_exists(filepath)

  # Verify directory is writable before attempting file write
  dirname = File.dirname(filepath)
  unless File.writable?(dirname)
    # ... permission check ...
    raise Errno::EACCES, "Directory #{dirname} is not writable ..."
  end

  File.open(filepath, 'wb') { |file| file.write(content) }  # ← ENOSPC 발생 지점
rescue Errno::EACCES, Errno::ENOSPC => e
  Cupix::Logger.error("Failed to save Partial JSON #{self.class.name} #{self.id} to file #{filepath}", ...)
end

파일 경로 구성:

app/models/concerns/data_ware_house/partial_json.rb:81-87ruby
def partial_json_object_key(datetime: nil, timestamp: nil)
  pluralized_model_name = self.class.name.underscore.pluralize
  partitioning_datetime = resolve_partitioning_datetime(datetime)
  _timestamp = timestamp || current_timestamp
  "#{pluralized_model_name}/partial/#{partitioning_datetime.strftime('%Y-%m-%d')}/#{region_code}/#{_timestamp}-#{pluralized_model_name}-#{self.id}.json.gz"
end

디렉토리 생성 시에도 동일한 ENOSPC가 발생 (자정 이후):

app/models/concerns/data_ware_house/base.rb:26-29ruby
def create_directory_if_not_exists(filepath)
  dirname = File.dirname(filepath)
  FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
end

기대 동작: gzip 압축된 JSON을 /var/data_changes/{model}/partial/{date}/{region}/ 경로에 저장하고 success 로그를 남긴다.

실제 동작: 디스크 용량 부족으로 File.open 또는 FileUtils.mkdir_p에서 Errno::ENOSPC가 발생하고, rescue 블록에서 에러 로그를 남긴 후 조용히 실패한다. retry: false 설정으로 job이 재시도되지 않아 해당 변경 데이터가 영구 유실된다.

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-worker status:error "Failed to save Partial JSON" @environment:production

대표적인 에러 로그 (rb_sysopen 실패):

text
Failed to save Partial JSON Capture 701882 to file /var/data_changes/captures/partial/2026-05-22/uswe2/1779486119759576-captures-701882.json.gz
Error: No space left on device @ rb_sysopen - /var/data_changes/captures/partial/2026-05-22/uswe2/1779486119759576-captures-701882.json.gz

스택 트레이스:

text
/var/app/current/app/models/concerns/data_ware_house/partial_json.rb:197:in `initialize'
/var/app/current/app/models/concerns/data_ware_house/partial_json.rb:197:in `open'
/var/app/current/app/models/concerns/data_ware_house/partial_json.rb:197:in `save_partial_json_file'
/var/app/current/app/models/concerns/data_ware_house/partial_json.rb:171:in `generate_and_save_partial_json'
/var/app/current/app/models/concerns/data_ware_house/partial_json.rb:71:in `save_partial_json_to_file'
/var/app/current/app/workers/save_partial_json_to_file_worker.rb:63:in `perform'

자정 이후 mkdir 실패:

text
Error: No space left on device @ dir_s_mkdir - /var/data_changes/pointclouds/partial/2026-05-23
/usr/lib64/ruby/3.3.0/fileutils.rb:402:in `mkdir'
/var/app/current/app/models/concerns/data_ware_house/base.rb:28:in `create_directory_if_not_exists'

영향받은 엔티티 유형과 에러 진행 패턴:

시간대 실패 syscall 의미
2026-05-22T20:20+ rb_sysopen 파일 쓰기 불가 (데이터 블록 소진)
2026-05-23T00:36+ dir_s_mkdir 디렉토리 생성도 불가 (inode 또는 전체 용량 소진)

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 /var/data_changes/ 디스크 용량 소진 (ENOSPC) 모든 에러 메시지에 "No space left on device" 명시. 4시간 동안 모든 엔티티 유형에서 동일 에러. 자정 이후 mkdir도 실패. Confirmed
H2 파일 권한 문제 (EACCES) 코드에 EACCES 처리 존재 (partial_json.rb:127-141) 에러 메시지가 ENOSPC이지 EACCES가 아님. writable check를 통과한 후 write에서 실패. Rejected
H3 특정 capture 701882의 데이터 이상 (거대 JSON) 동일 시간대에 Capture, ElementTrace, Pointcloud, Element 등 다수 엔티티에서 동일 에러 발생. 단일 엔티티 문제가 아닌 시스템 전체 이슈. Rejected
H4 Sidekiq 과부하로 인한 일시적 write 실패 같은 시간대에 대량 job 실행 ENOSPC는 부하와 무관한 OS-level 에러. 재시도해도 동일 결과. Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  1. 디스크 용량 확보: /var/data_changes/ 볼륨에서 오래된 파일 정리 (예: 7일 이상 된 partial JSON 파일 삭제). 이미 S3로 sync된 파일은 로컬에서 제거 가능.
  2. 유실 데이터 복구: retry: false 설정으로 실패한 8,272건의 job은 자동 재처리되지 않으므로, 영향받은 레코드들의 partial JSON을 수동으로 재생성해야 한다.

단기 개선 (1주 이내)#

  1. 자동 cleanup 스크립트 추가: cron job 또는 systemd timer로 /var/data_changes/ 내 N일 이상 된 파일을 자동 삭제. S3 sync 완료 확인 후 삭제하는 로직이 이상적.
  2. 디스크 사용량 모니터링 알림: /var/data_changes/ 파티션 사용률이 80%를 초과하면 알림을 발생시켜 사전 대응.
  3. 실패 시 재시도 전략 검토: retry: false 대신 제한적 retry (예: 3회)와 disk space 부족 시 별도 dead letter queue로 이동하는 방안 고려.

장기 개선 (재발 방지)#

  1. EBS 볼륨 자동 확장 (Auto Scaling) 또는 충분한 고정 용량 할당으로 디스크 고갈 자체를 방지.
  2. Streaming upload: 로컬 디스크에 임시 저장하지 않고 S3에 직접 streaming upload하여 로컬 디스크 의존도 제거.
  3. Circuit breaker 패턴: ENOSPC 발생 시 일정 기간 새 write 시도를 중단하고, 디스크 복구 후 backlog을 처리하는 방식으로 불필요한 에러 로그 폭증 방지.

Monitoring#

  • /var/data_changes/ 파티션 디스크 사용량 메트릭 (80% threshold alert)
  • Partial JSON save 실패율 모니터
text
service:cupixworks-worker status:error "Failed to save Partial JSON"
text
system.disk.in_use{device:/var/data_changes} > 0.8

Risk Assessment#

  • Risk level: medium
  • 예상 복잡도: standard (디스크 정리는 trivial이나, 유실 데이터 복구 및 자동 cleanup 구축이 필요)