Failed to save partial JSON Element 15382518
RCA: Failed to save partial JSON Element 15382518
Overview#
What Happened#
2026-05-22 20:20 UTC부터 23:57 UTC까지 약 3.5시간 동안 cupixworks-worker 서비스의 data_changes Sidekiq queue에서 partial JSON 파일 저장이 대량 실패했다. 단일 호스트(ip-10-1-18-233.us-west-2.compute.internal)의 /var/data_changes/ 파티션 디스크 용량 부족(No space left on device)이 원인이며, 264,144건의 에러가 발생했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Errno::ENOSPC |
| exception.message | No space left on device @ rb_sysopen - /var/data_changes/elements/partial/2026-05-22/uswe2/1779481236606406-elements-15382518.json.gz |
| top_frame | app/models/concerns/data_ware_house/partial_json.rb:144 |
| runtime | Ruby 3.3.0, Sidekiq 7.3.9, Rails 7.2.2 |
| deploy | production-us-west-2-20260519T0920Z0-3e770a15-cupixworks |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| Data Warehouse (partial JSON pipeline) | 264,144 | 모든 모델(Element, ElementTrace, Record, Capture)의 partial JSON export 실패 — 데이터 웨어하우스 동기화 지연 |
| Elasticsearch indexing | ~수십건 | BulkPartialIndexDeadWorker 후속 실패 (document_missing_exception) |
Timeline#
- 2026-05-22T20:20:42Z — 최초 에러 발생.
Element 15382518partial JSON 저장 실패 (Errno::ENOSPC) - 2026-05-22T20:20~23:57Z — 동일 호스트에서 모든 partial JSON 저장 작업 연쇄 실패 (264,144건)
- 2026-05-22T23:41~23:42Z — 후속 영향:
BulkPartialIndexDeadWorker에서 Elasticsearchdocument_missing_exception발생 - 2026-05-22T23:57:59Z — 마지막 에러 로그 기록
Error Log#
Failed to save partial JSON Element 15382518
Impact#
- Service:
cupixworks-worker - 발생 횟수: 264,144
- 최초 발생: 2026-05-22T20:20:42.030Z
- 최근 발생: 2026-05-22T23:57:59.483Z
- 영향 범위: 단일 호스트(
ip-10-1-18-233)에서 처리되는 모든 data warehouse partial JSON export 작업. Element, ElementTrace, Record, Capture 모델 전체에 영향. 데이터 웨어하우스로의 변경사항 전파가 3.5시간 동안 중단됨.
Root Cause Summary#
호스트 ip-10-1-18-233.us-west-2.compute.internal의 /var/data_changes/ 파티션 디스크 공간이 고갈되어 Errno::ENOSPC (No space left on device) 예외가 발생했다. DataWareHouse::PartialJson concern의 save_partial_json_file 메서드에서 gzip 압축된 JSON 파일을 로컬 디스크에 쓰려 할 때 File.open 호출이 실패했다. 이 에러는 rescue 절에서 로깅만 수행하고 예외를 re-raise하지 않으므로, 각 실패가 조용히 무시되면서 264,144건의 에러 로그만 생성되었다.
Technical Analysis#
Code Path#
- Entry point:
BulkSavePartialJsonToFileWorker#perform(app/workers/bulk_save_partial_json_to_file_worker.rb:71-74) 또는SavePartialJsonToFileWorker#perform(app/workers/save_partial_json_to_file_worker.rb:63) - 두 worker 모두 각 model 인스턴스에 대해
save_partial_json_to_file을 호출
def save_partial_json_to_file(all_data: false, operation: '(updated)', changes: nil, timestamp: nil)
if $FORWARD_DATA_CHANGES != true
Cupix::Logger.debug('Data changes forwarding is disabled', class: self.class, module: 'DataWareHouse', function: 'save_partial_json_to_file')
return nil
end
generate_and_save_partial_json(operation, all_data: all_data, changes: changes || saved_changes, timestamp: timestamp)
end
generate_and_save_partial_json은 JSON 데이터를 준비하고 gzip 압축한 뒤save_partial_json_file을 호출
def generate_and_save_partial_json(operation, all_data: false, changes: nil, timestamp: nil)
json_data = prepare_partial_json_data(operation, all_data: all_data, changes: changes)
json_content = ActiveSupport::Gzip.compress(json_data.to_json)
save_partial_json_file(json_content, partial_json_object_key(timestamp: timestamp))
end
- Failure point:
save_partial_json_file메서드의File.open호출 (line 144)
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)
dir_stat = File.stat(dirname)
Cupix::Logger.error(
'Directory not writable before file write attempt',
class: self.class.name, module: 'DataWareHouse', function: 'save_partial_json_file',
directory: dirname, permissions: format('%o', dir_stat.mode & 0o777),
uid: dir_stat.uid, gid: dir_stat.gid, process_uid: Process.uid, process_euid: Process.euid
)
raise Errno::EACCES, "Directory #{dirname} is not writable"
end
File.open(filepath, 'wb') { |file| file.write(content) }
rescue Errno::EACCES, Errno::ENOSPC => e
Cupix::Logger.error("Failed to save Partial JSON #{self.class.name} #{self.id} to file #{filepath}",
class: self.class.name, module: 'DataWareHouse', function: 'save_partial_json_file',
error: e.message, backtrace: e.backtrace)
else
Cupix::Logger.debug("Successfully saved Partial JSON #{self.class.name} #{self.id} to file #{filepath}",
class: self.class.name, module: 'DataWareHouse', function: 'save_partial_json_file')
end
기대 동작: File.open(filepath, 'wb')가 gzip 압축된 JSON을 /var/data_changes/elements/partial/2026-05-22/uswe2/{timestamp}-elements-{id}.json.gz에 기록.
실제 동작: 디스크 용량 부족으로 Errno::ENOSPC 예외 발생 → rescue 절에서 에러 로깅 후 nil 반환 (silent failure). Worker는 retry: false로 설정되어 있어 재시도 없이 종료.
- Bulk worker의 에러 전파 부재 (
app/workers/bulk_save_partial_json_to_file_worker.rb:71-78):
models.find_in_batches(batch_size: 50) do |groups|
groups.each do |model|
model.save_partial_json_to_file(all_data: true, operation: operation, timestamp: timestamp)
end
end
rescue NoMethodError => e
Cupix::Logger.error(e.message, class: self.class, function: 'perform')
end
Bulk worker는 Errno::ENOSPC를 catch하지 않지만, 해당 예외는 이미 save_partial_json_file 내부에서 rescue되므로 worker까지 전파되지 않는다. 결과적으로 디스크가 가득 찬 상태에서도 worker는 계속 다음 모델을 처리하며 264,144건의 에러 로그를 생성했다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-worker status:error @environment:production "Failed to save partial JSON"
service:cupixworks-worker status:error @environment:production "Failed to save Partial JSON" @function:save_partial_json_file
핵심 로그 항목 (Path A - Bulk save, 초기 에러):
{
"timestamp": "2026-05-22T20:20:42.030Z",
"message": "Failed to save partial JSON Element 15382518",
"host": "ip-10-1-18-233.us-west-2.compute.internal",
"class": "Element",
"module": "DataWareHouse",
"function": "bulk_partial_save_to_file",
"error": "No space left on device @ rb_sysopen - /var/data_changes/elements/partial/2026-05-22/uswe2/1779481236606406-elements-15382518.json.gz"
}
핵심 로그 항목 (Path B - Individual save, 후기 에러):
{
"timestamp": "2026-05-22T23:50:00Z",
"message": "Failed to save Partial JSON Record 129985 to file /var/data_changes/records/partial/2026-05-22/uswe2/1779494399564283-records-129985.json.gz",
"host": "ip-10-1-18-233.us-west-2.compute.internal",
"class": "Record",
"module": "DataWareHouse",
"function": "save_partial_json_file",
"error": "No space left on device @ rb_sysopen"
}
후속 영향 (Elasticsearch indexing 실패):
{
"timestamp": "2026-05-22T23:41:00Z",
"message": "ES bulk partial index dead",
"class": "BulkPartialIndexDeadWorker",
"function": "perform",
"reason": "doc_missing_after_max_retries",
"model": "ElementTrace"
}
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 디스크 용량 부족 (Errno::ENOSPC) |
에러 메시지에 "No space left on device @ rb_sysopen" 명시. 단일 호스트 ip-10-1-18-233에서만 발생. 모든 모델(Element, ElementTrace, Record, Capture) 동시 실패. |
— | Confirmed |
| H2 | 파일 퍼미션 문제 (Errno::EACCES) |
코드가 Errno::EACCES도 같은 rescue 절에서 처리 |
에러 메시지가 "No space left on device"이지 "Permission denied"가 아님. File.writable? 사전 검사 통과 (별도 에러 로그 없음) |
Rejected |
| H3 | Worker가 존재하지 않는 모델을 조회하여 실패 | Bulk worker에 retry 로직 존재 (models.count != ids.count 체크) |
에러가 save_partial_json_file 단계에서 발생 — 모델 조회는 성공한 뒤 파일 쓰기에서 실패 |
Rejected |
| H4 | 특정 Element만의 데이터 이상 (큰 JSON) | 클러스터 제목이 특정 Element ID를 참조 | 동일 시간대에 Element, ElementTrace, Record, Capture 전부 실패 — 특정 모델/레코드 문제가 아닌 인프라 문제 | Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
- 해당 호스트의
/var/data_changes/파티션 디스크 공간 확보 (오래된 partial JSON 파일 정리 또는 볼륨 확장) - 현재 디스크 사용량 모니터링하여 다른 호스트도 동일 상태인지 확인
단기 개선 (1주 이내)#
save_partial_json_file(partial_json.rb:145-146)에서Errno::ENOSPC발생 시 디스크 용량 정보를 로그에 포함하고, 연속 실패 시 circuit breaker 패턴을 적용하여 불필요한 반복 시도 방지/var/data_changes/디렉토리의 자동 정리 cron job 추가 — 예: 7일 이상 된 파일 삭제 또는 S3 업로드 완료된 파일 정리BulkSavePartialJsonToFileWorker에서 첫 번째Errno::ENOSPC감지 시 배치 처리를 즉시 중단하도록 early exit 로직 추가 (현재는 모든 레코드에 대해 실패를 반복)
장기 개선 (재발 방지)#
/var/data_changes/파티션에 대한 디스크 사용량 임계값 알림 설정 (80% 도달 시 경고)- Partial JSON 파일을 로컬 디스크 대신 직접 S3에 streaming upload하는 방식으로 아키텍처 변경 검토 — 로컬 디스크 의존성 제거
- Worker 호스트의 EBS 볼륨 자동 확장 또는 ephemeral storage 정리 자동화
Monitoring#
추가할 메트릭/알림:
service:cupixworks-worker status:error "No space left on device"
service:cupixworks-worker status:error @function:save_partial_json_file
- CloudWatch 또는 Datadog Agent를 통해
/var/data_changes/마운트 포인트의 disk usage percentage 모니터링 - 임계값: 80% warning, 90% critical
BulkPartialIndexDeadWorker에러 카운트 모니터링 — partial JSON 실패의 하류 지표
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard
- 데이터 유실 가능성: partial JSON이 기록되지 않은 3.5시간 동안의 변경사항은 데이터 웨어하우스에 반영되지 않았음. 해당 모델들의 변경 이력이 data warehouse에서 누락될 수 있으며, full sync로 복구 필요 여부 확인 필요.