job 10455 job_stopped_callback error - /var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/l
RCA: job_stopped_callback error — Cupix::Event logger open failure
Overview#
What Happened#
2026-07-06 09:35 KST에 production cupixworks-worker (ap-southeast-1)에서 Cupix::Cron::Job.clean_running_jobs cron 실행 중 3건의 에러가 발생했다. Job 10455/10456/10457을 stopped 상태로 전이하는 과정에서 Cupix::Event singleton logger가 #{Rails.root}/log/tesla_production-event.log 파일을 열려다 실패했다. 동일 패턴이 지난 14일간 4개 프로덕션 리전 전체에서 총 17회 관측된다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Errno::* (uncertain — needs verification, e.message 필드가 로그에 비어 있음) |
| top_frame | logger-1.6.6/lib/logger/log_device.rb:114:in initialize (실제 File.open C-frame) |
| triggered_from | lib/cupix/event.rb:8 (Cupix::Event Singleton initialize) |
| runtime | Ruby 3.3.0, logger 1.6.6, activesupport 7.2.2 |
| deploy | production-ap-southeast-1-20260706T0035Z0-13e7c827-cupixworks |
| env | production, region ap-southeast-1 |
| cron | every '5,15,25,35,45,55 * * * *' (config/schedule.rb:67) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-worker (Job / Capture state machine) | 17건 / 14일 (본 클러스터 3건) | Capture job_stopped_callback 이 예외로 종료 → event_reason_processing_finalized 이후 로직(예: Cupix::EventService.publish_event, event_created!, Analytics.track)이 트랜잭션 내에서 실행되지 못하고 rescue 되어 Cupix::Logger.error 만 남는다. state machine 트랜지션(:stopped)은 after_transition 콜백에서 예외가 삼켜지므로 job 상태 자체는 stopped로 커밋된다. |
이벤트 publish 실패로 Segment/Analytics 이벤트 유실 및 이벤트 스토어의 Event 레코드 미생성 가능성이 있다 — uncertain, needs verification via Cupix::EventService.publish_event 성공 여부 로그.
Timeline#
- 2026-07-06 09:35:34 KST — ap-southeast-1의
rails runner Cupix::Cron::Job.clean_running_jobs실행 (cron*/10@ :35) - 2026-07-06 09:35:34 KST — Job 10455/10456/10457 (state=running,
state_updated_at < 3.days.ago)가stopped_state_with_error('TES10003', ...)로 전이됨 - 2026-07-06 09:35:34 KST — 각 Job의
after_transition to: :stopped→JobCallbackWorker.perform_inline→job.jobable.job_stopped_callback→event_reason_processing_finalized→Cupix::Event.publish→ Singletoninitialize진입 - 2026-07-06 09:35:34 KST —
Logger::LogDevice#open_logfile의File.open이Errno::*raise,JobCallbackWorker#perform(rescue StandardError) 가 캐치하여 Datadog에 error 로그로 기록 (log_device.rb:114)
Error Log#
job 10455 job_stopped_callback error - /var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:114:in `initialize'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:114:in `open'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:114:in `open_logfile'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:90:in `set_dev'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:19:in `initialize'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger.rb:593:in `new'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger.rb:593:in `initialize'
/var/app/current/vendor/bundle/ruby/3.3.0/gems/activesupport-7.2.2/lib/active_support/logger.rb:34:in `initialize'
/var/app/current/lib/cupix/event.rb:8:in `initialize'
/usr/lib64/ruby/3.3.0/singleton.rb:124:in `instance'
/var/app/current/lib/cupix/event.rb:18:in `publish'
/var/app/current/app/models/concerns/eventable/events/base.rb:18:in `create_event'
/var/app/current/app/models/concerns/eventable/custom_reason_callbacks.rb:21:in `event_reason_processing_finalized'
/var/app/current/app/models/concerns/statable/capture.rb:153:in `block (3 levels) in <module:Capture>'
...
/var/app/current/app/workers/job_callback_worker.rb:10:in `perform'
...
/var/app/current/lib/cupix/cron/job.rb:28:in `block in clean_running_jobs'
/var/app/current/lib/cupix/cron/job.rb:22:in `clean_running_jobs'
로그 필드 관찰: Cupix::Logger.error 포맷은 "error - #{e.class}: #{e.message}\n#{backtrace}" (참조: app/workers/job_callback_worker.rb:14) 인데 실제 로그에서는 error - 뒤에 곧바로 backtrace 가 시작한다. 즉 e.class 와 e.message 가 렌더링에서 비어 있다 — 정상적인 Ruby Errno::* 예외라면 Errno::EACCES: Permission denied @ rb_sysopen - /path 같이 채워지므로, 이는 (a) e.message 가 실제로 빈 문자열이거나 (b) #{e.class}: #{e.message}\n 부분이 로그 라인에서 잘려나갔을 가능성을 시사. needs verification — 서버에서 실제 tesla_production.log (표준 rails 로그) 나 tesla_production-json.log 원본을 확인하여 예외 클래스와 메시지 확정 필요.
Impact#
- Service:
cupixworks-worker - 발생 횟수: 3 (본 클러스터), 17 (지난 14일 전체 리전)
- 최초 발생: 2026-07-06 09:35 KST
- 최근 발생: 2026-07-06 09:35 KST
- 리전: ap-southeast-1 (본 클러스터), 확대 시 us-west-2 · ap-southeast-2 · eu-central-1 · ap-southeast-1 4개 전체
Root Cause Summary#
Cupix::Event Singleton logger 가 #{Rails.root}/log/tesla_#{Rails.env}-event.log 를 'daily' shift_age 로 열 때 Errno::* 를 raise 한다. 실패 지점은 Logger::LogDevice#open_logfile 의 File.open(filename, MODE_TO_OPEN) 호출 (logger-1.6.6/lib/logger/log_device.rb:114). Errno::ENOENT 는 코드가 rescue 하여 create_logfile 로 fallback 하므로 (line 115-116), 실제로 새어나온 예외는 그 외 Errno (예: EACCES, EMFILE, EROFS, EISDIR) 다. 발생 패턴이 (1) 모든 리전에서 관측되고 (2) cron 스케줄 5,15,25,35,45,55 * * * * 시각과 정확히 일치하며 (3) 동일 프로세스의 Cupix::Logger (tesla_#{env}-json.log) 쓰기는 성공한다는 점을 종합하면, 특정 파일(tesla_*-event.log) 에 대한 국소적 실패이며 daily rotation 경계에서의 파일 상태 불일치 (예: 다른 프로세스가 rotate 중이어서 rename 완료 전 상태) 또는 file-descriptor 고갈이 가장 유력한 후보다. 정확한 errno 확정은 서버 파일 로그 확인 필요.
Technical Analysis#
Code Path#
Entry point: lib/cupix/cron/job.rb:22 (clean_running_jobs)
::Job.running.where('state_updated_at < ?', 3.days.ago).each do |job|
if job.aws_tasks.running.exists?
running_jobs_with_running_tasks_over_3days << job.id
job.aws_tasks.running.each(&:stop!)
else
Cupix::Logger.info("[TES10003] Stopped job found: #{job.id}", ...)
job.stopped_state_with_error('TES10003', 'Job has been stopped by system')
end
end
stopped_state_with_error 는 state machine 이벤트 :stopped_state 를 fire 하며, Statable::Job 의 after_transition any => :stopped 콜백이 JobCallbackWorker.perform_inline(job.id, 'job_stopped_callback') 를 동기 호출한다:
after_transition any => :stopped do |job, transition|
job.aws_tasks.each do |task|
PullTaskWorker.perform_in(20.second, task.id)
end
jid = JobCallbackWorker.perform_inline(job.id, 'job_stopped_callback')
Cupix::Logger.info("invoke job_stopped_callback with jid: #{jid} for job #{job.id}")
end
JobCallbackWorker#perform 이 job.jobable.job_stopped_callback(job) 를 호출:
def perform(id, callback_name)
Cupix::Logger.info("job #{id}, run #{callback_name}", ...)
job = ::Job.find_by_id(id)
return if job.nil?
job.jobable.send(callback_name, job)
Cupix::Logger.info("job #{id} #{callback_name} done", ...)
rescue StandardError => e
Cupix::Logger.error("job #{id} #{callback_name} error - #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}", class: self.class.name, function: __method__, job: { id: id, callback: callback_name })
end
Jobable::Capture#job_stopped_callback 이 done_state 로 전이시키고, before_transition ... to: :done 콜백에서 event_reason_processing_finalized 를 호출한다:
before_transition from: any - :moving, to: :done do |capture, transition|
capture.done_at ||= DateTime.now
capture.event_reason_processing_finalized(capture) if capture.respond_to?(:event_reason_processing_finalized)
def event_reason_processing_finalized(model)
model.set_current_user(model.user)
model.build_event(
action: 'update',
reason: 'processing_finalized'
)
::Eventable::Events::Update.create_event(model)
end
def create_event(model)
return nil if invalid_event?(model)
begin
event = _create_event(model)
reason = extract_reason(event, model)
properties = build_properties(model)
track_event(model, reason, properties)
Cupix::EventService.publish_event([event])
Cupix::Event.publish(event.serializable_hash(stringify_nested_fields: false)) # ← Singleton .instance 진입점
rescue StandardError => e
Cupix::Logger.error("Failed to create event: #{e.message}", ...)
raise e
else
...
end
end
Failure point: lib/cupix/event.rb:8 — Singleton lazy initialization 에서 Ruby stdlib logger 의 File.open 실패:
require 'singleton'
module Cupix
class Event < ActiveSupport::Logger
include Singleton
def initialize
super("#{Rails.root}/log/tesla_#{Rails.env}-event.log", 'daily') # ← 여기서 File.open 실패
self.formatter = Cupix::EventFormatter.new
self.level = $CUPIX_LOGGER_LEVEL || Logger::INFO
end
Ruby stdlib 의 실제 실패 라인:
def open_logfile(filename)
begin
dev = File.open(filename, MODE_TO_OPEN) # line 114 — Errno::* raise
rescue Errno::ENOENT
create_logfile(filename) # ENOENT 는 rescue 되어 create 로 fallback
else
dev = fixup_mode(dev, filename)
dev.sync = true
dev.binmode if @binmode
dev
end
end
기대 동작: File.open 이 성공하거나 Errno::ENOENT → create_logfile 로 recover. 실제 동작: Errno::ENOENT 이외의 Errno::* 가 raise 되어 rescue 되지 않고 상위 JobCallbackWorker#perform rescue 로 전파됨. 결과적으로 (a) Event 스토어에 이벤트 미생성 (line 24 model.event_created! 미실행), (b) 이벤트 publish 실패, (c) before_transition ... to: :done 콜백이 예외로 종료되어 capture 는 :done 으로 전이하지 못하고 원 상태 유지 (state machine 트랜잭션 rollback).
Log Evidence#
Datadog query 사용:
service:cupixworks-worker "log_device.rb" @environment:production
지난 14일 발생 분포 (region 및 발생 시각):
2026-07-06T00:35:34.142Z region:ap-southeast-1 (job 10457)
2026-07-06T00:35:34.142Z region:ap-southeast-1 (job 10456)
2026-07-06T00:35:34.141Z region:ap-southeast-1 (job 10455)
2026-07-04T20:15:28.768Z region:eu-central-1 (job 117790)
2026-07-04T05:45:29.807Z region:ap-southeast-2 (job 218234)
2026-07-04T04:25:33.146Z region:ap-southeast-2 (job 218150)
2026-07-03T23:05:28.904Z region:ap-southeast-2 (job 217942)
2026-07-03T21:45:29.677Z region:eu-central-1 (job 117156)
2026-07-03T15:45:32.855Z region:eu-central-1 (job 117006)
2026-07-03T06:35:30.761Z region:ap-southeast-2 (job 217093)
2026-07-03T06:25:36.611Z region:ap-southeast-2 (job 217060)
2026-07-03T05:35:32.027Z region:ap-southeast-2 (job 216996)
2026-06-22T18:55:24.357Z region:us-west-2 (job 1144039)
2026-06-22T13:25:33.798Z region:us-west-2 (job 1143780)
2026-06-22T12:45:28.974Z region:us-west-2 (job 1143913)
2026-06-22T12:05:29.262Z region:us-west-2 (job 1143916)
2026-06-22T09:50:18.554Z region:us-west-2 (Sidekiq job died after all retries)
모든 발생 시각의 분(minute)이 {05,15,25,35,45,55} 에 속함 (config/schedule.rb:67 cron 스케줄과 일치). 4개 리전 전부에서 관측 → 특정 호스트/디스크 문제 아님.
에러 로그 원문 (job 10455):
job 10455 job_stopped_callback error - /var/app/current/vendor/bundle/ruby/3.3.0/gems/logger-1.6.6/lib/logger/log_device.rb:114:in `initialize'
...
여기서 error - 직후에 파일 경로 (backtrace 첫 줄) 가 오는데, 로거 포맷 ("error - #{e.class}: #{e.message}\n#{backtrace}") 상 Errno::EACCES: Permission denied @ rb_sysopen - /var/app/current/log/tesla_production-event.log 같은 prefix 가 있어야 정상. 비어 있는 이유는 확정되지 않음 — needs verification (서버 원본 로그 확인).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | #{Rails.root}/log/ 디렉토리가 없거나 권한 부족 |
File.open 실패 지점 |
동일 프로세스의 Cupix::Logger 가 tesla_production-json.log 는 정상 쓰기 (Datadog 로그 자체가 이 파일 경유). 발생률 17/2000+ 로 sparse |
Rejected |
| H2 | 'daily' shift_age 회전 경계에서 동시 rename/open race → transient Errno::EACCES 또는 ENOTDIR |
발생 시각이 cron minute 경계에 정확히 정렬. 여러 cron runner 가 동일 minute (5/15/…) 에 병렬 실행되어 동일 로그파일을 동시 open. Ruby Logger daily 모드는 write 시 shift 를 체크하며 rename 을 수행하는데 여러 프로세스가 경합 가능 |
발생률이 매 cron 마다는 아님 (17/2000+). 정확한 errno 미확인 | Likely — needs errno verification |
| H3 | Errno::EMFILE (파일 디스크립터 고갈) |
Rails runner 프로세스는 각 .each 에서 다수의 파일/소켓 오픈 (AWS SDK, DB, Sidekiq, S3). 사용량 급증 시 fd 고갈 가능 |
Cupix::Logger (-json.log) 는 성공 → fd 완전 고갈은 아님. 그러나 Cupix::Logger 는 이미 initialize 되어 fd 를 hold 하고, Cupix::Event 는 매 process 첫 진입 시에만 initialize |
Possible — needs verification |
| H4 | 배포/deploy 순간 /var/app/current symlink 스왑으로 파일 경로 순간 불일치 |
Elastic Beanstalk 는 배포 시 current symlink 를 새 릴리스로 rename. 그 순간 open 진행 중이면 stale 경로 참조 가능 | 발생이 배포 타이밍과 상관 없음 — 배포는 상시 rolling 이지만 발생은 5-분 cron minute 만 정렬 | Rejected |
| H5 | 로그 로테이션 스크립트 (logrotate/cron) 가 tesla_*-event.log 를 rename/rm 하는 순간 File.open 이 stale inode 로 실패 | event.log 만 국소 실패, 다른 로그는 정상 |
logrotate 설정을 확인 못함 — needs verification (EC2 /etc/logrotate.d/) |
Inconclusive |
Fix Recommendation#
권장 조치 — event.log 폐지 (사용자 승인 방향)#
Revision 1 피드백에 따라, 재발 방지의 핵심 조치로 Cupix::Event 로컬 파일 로거 폐지를 선택한다. 코드 조사 결과 다음이 확인되었다:
- 이중 발행 구조:
app/models/concerns/eventable/events/base.rb:17-18및app/factories/concerns/bulkable_factory/annotation.rb:225-228모두에서Cupix::EventService.publish_event(...)(Kinesis) 와Cupix::Event.publish(...)(로컬 파일) 이 연달아 호출된다. - 후속 시스템의 존재:
lib/cupix/event_service.rb:8-19의release_date_by는 production 2024-11-06 이후 활성. Kinesis stream ({tenant}-cupixworks-{env}-EventStream) 을 통해cupixworks/applications/event-service(README: "'24, Jun. Begin Project to migrate main storage for event models from RDS to S3 ... S3, Athena, Lambda, Kinesis and API Gateway") 로 저장된다. 즉, 이벤트 스토어의 primary 는 이미 EventService 쪽. - 로컬 파일 소비자:
.ebextensions/003-filebeat.config:99-116에서 filebeat 가/var/app/current/log/*-event.log를 tail 하여cls-tesla.cupix.internal:5044(logstash) 로 전송한다. 즉 이 파일은 감사/디버그가 아니라 레거시 이벤트 shipping 파이프라인의 소스다. 단순히 로거를IO::NULL로 바꾸면 이 파이프라인이 조용히 끊긴다. - 호출 지점 범위:
Cupix::Event.publish를 참조하는 애플리케이션 코드는 위 두 지점(events/base.rb, bulkable_factory/annotation.rb)이 유일하다 (grep Cupix::Event\. app/ lib/).
이 사실을 종합하면 "폐지" 는 단순 삭제가 아니라 레거시 filebeat 파이프라인 종료와 짝지어야 하는 조정 작업이다. 아래 순서로 진행한다.
Phase 1 — 즉시 방어 (state machine 트랜잭션 보호)#
app/models/concerns/eventable/events/base.rb:18및app/factories/concerns/bulkable_factory/annotation.rb:228의Cupix::Event.publish(...)호출을 개별rescue StandardError로 감싼다. 실패 시Cupix::Logger.warn으로 강등하고 이벤트 스토어(EventService/Kinesis) 커밋과 state machine 전이는 계속 진행한다.lib/cupix/event.rb:7의initialize를 open 실패 시IO::NULLfallback 하도록 방어한다 — Singleton 이 한 번이라도 실패하면 다음 호출까지 계속 실패로 남으므로 초기화 자체가 예외를 삼켜야 한다.
이 두 조치만으로 본 클러스터의 job callback 예외 전파와 capture :done 전이 실패는 즉시 차단된다.
Phase 2 — 이중 발행 제거 + 파일 로거 폐지 (사전 검증 후 단일 PR)#
Revision 2 피드백("Phase 2/3 한꺼번에 작업")에 따라, 원래 나눠져 있던 "검증" 과 "삭제" 를 하나의 PR 로 묶어 진행한다. 단, PR 을 열기 전 아래 두 사전 검증을 완료하고 그 결과를 PR description 에 근거로 첨부한다 — 검증을 건너뛰고 삭제만 진행하면 filebeat → logstash → ES/S3 파이프라인이 조용히 끊길 수 있기 때문이다 (cupix-infrastructure/cupix-service/logstash-eb/main.tf:143-158 의 ES_HOST, CAPTURE_TRACE_S3_BUCKET_NAME 는 실사용 sink).
사전 검증 (PR 여는 시점에 근거를 확보)
- V1. Kinesis 결번 확인 — production 4개 리전 (us-west-2, ap-southeast-1, ap-southeast-2, eu-central-1) 에서 최근 7일간
Cupix::EventService.publish_event성공 로그 (lib/cupix/event_service.rb:44"Published event - failed_record_count") 카운트와Cupix::Event.publish호출 카운트가 일치하는지 Datadog 로 비교. 결번이 있으면 EventService 쪽 결함을 먼저 잡고 삭제를 보류. - V2. 다운스트림 소비자 인벤토리 —
cupix-infrastructure/cupix-service/logstash-eb/main.tf:143-158의 logstash 가event.log로부터 유래한 이벤트를 어느 ES 인덱스 / S3 prefix 로 라우팅하는지 logstash pipeline 정의(logstash 레포/config) 확인. 해당 인덱스/prefix 를 참조하는 대시보드·리포트·ETL 이 남아 있으면 폐지 대신 Kinesis 를 소스로 하는 shipper 로 교체 후 진행 (아래 delete 스텝을 건너뛴다).
삭제 스텝 (V1·V2 모두 clear 인 경우, 위 두 검증 근거를 첨부한 단일 PR 로 실행)
app/models/concerns/eventable/events/base.rb:18및app/factories/concerns/bulkable_factory/annotation.rb:228의Cupix::Event.publish(...)라인 삭제.lib/cupix/event.rb및Cupix::EventFormatter제거..ebextensions/003-filebeat.config:99-116의*-event.logfilestream 블록 및output.logstash(hostcls-tesla.cupix.internal:5044) 제거..platform/hooks/postdeploy/70_restart_sidekiq.sh:14,71_restart_transfer_sidekiq.sh:14,72_restart_migration_sidekiq.sh:14의APP_EVENT_LOG_FILEPATH변수 및 line 16 의 touch 대상에서 해당 파일 제거.
Phase 1 이 이미 예외 전파를 차단한 뒤에만 이 Phase 를 진행한다. Phase 1 배포 없이 검증+삭제를 한꺼번에 합치면, 검증 관측 창 동안 여전히 본 클러스터 에러가 재발하기 때문이다.
병합하지 않는 경우 (거부 경로)
- V1 이 결번을 드러내면: EventService 결함 조사가 우선. 삭제 PR 은 열지 않고 원래대로 두 단계(검증 → 결함 수정 → 재검증 → 삭제) 로 분리.
- V2 가 활성 소비자를 확인하면:
event.log삭제 대신 Kinesis→ES/S3 shipper 로 교체하는 인프라 작업이 선행되어야 하므로 병합 불가.
부수 개선 (부분 수용 항목)#
- 에러 메시지 포맷 검증: 폐지가 완료되면 자연 소멸하지만, 남아 있는 다른
Cupix::Logger.error("... #{e.class}: #{e.message}\n#{backtrace}", ...)호출들이 동일한 prefix 소실을 재현할 수 있으므로Cupix::Logger포맷터 검증은 별건 이슈로 유지. (app/workers/job_callback_worker.rb:14,lib/cupix/logger.rb) - cron staggering:
config/schedule.rb:67의5,15,25,35,45,55 * * * *는 여러 cron 이 동일 분에 몰리는 잠재적 경합 요인이지만, event.log 폐지가 완료되면 파일 open race 는 발생하지 않으므로 우선순위 하향.
Monitoring#
기존 클러스터를 모니터링할 Datadog 쿼리 (release dashboard timeseries widget 에 그대로 붙일 수 있는 raw log query):
service:cupixworks-worker status:error @environment:production "log_device.rb"
job_callback_worker 전체 실패 추이:
service:cupixworks-worker status:error @class:JobCallbackWorker
Cupix::Event 관련 fallback 도입 후 성공/실패 counter 를 새로 emit 하고 별도 모니터로 관측 권장 (metric cupix.event.publish.failure 등, 구현 후 대시보드에 추가).
Risk Assessment#
- Risk level: medium — 발생률은 낮으나 (14일에 17회) capture state 전이가 예외로 종료되어
:done커밋에 실패할 가능성이 있어 사용자 관점에서 "processing 완료 지연" 이 관측될 수 있음. 이벤트 유실 규모는 미확인. - 예상 복잡도: standard —
Cupix::Event초기화 방어 +create_event의publishrescue 분리 수준의 변경.
Revision History#
Revision 1#
Feedback: "event.log 폐지 검토 적용" — 장기 개선 항목으로 나열되어 있던 Cupix::Event 로컬 파일 로거 폐지 방향을 재발 방지의 주된 조치로 채택할 것.
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
| event.log (Cupix::Event) 폐지 검토 적용 | 수용 | lib/cupix/event.rb:8 의 파일 로거가 실패 원점이고, app/models/concerns/eventable/events/base.rb:17 에서 Cupix::EventService.publish_event (Kinesis, lib/cupix/event_service.rb:21-49) 가 동일 이벤트를 이미 발행 중임. cupixworks applications/event-service/README.md:2-4 는 이벤트 스토어가 RDS→S3 로 Kinesis 경유 이관 중임을 명시 — 즉 이벤트 스토어의 primary path 는 이미 EventService. 로컬 파일 로거는 삭제 가능한 이중 발행. |
| 단순 삭제/no-op 로 진행 | 부분 수용 | .ebextensions/003-filebeat.config:99-113 에서 filebeat 가 /var/app/current/log/*-event.log 를 cls-tesla.cupix.internal:5044 (logstash) 로 tail 하고 있음. 이 파이프라인의 다운스트림 소비자가 남아 있으면 삭제 즉시 이벤트 shipping 이 조용히 끊긴다. 따라서 삭제는 수용하되 단계별(Phase 1 방어 → Phase 2 이중 발행 검증 → Phase 3 폐지) 로 재구성. |
| 기타 "부수 개선" (에러 메시지 포맷, cron staggering) 유지 여부 | 부분 수용 | 폐지 완료 시 본 클러스터의 file open race 는 자연 소멸하므로 cron staggering 우선순위 하향. 그러나 Cupix::Logger 포맷터의 e.class/e.message prefix 소실 문제는 다른 rescue 지점에도 잠재하므로 별건 이슈로 유지. |
변경 사항:
## Fix Recommendation을 전면 재작성. 기존 "즉시 조치 / 단기 / 장기" 3단 구조 대신, 폐지 조치를 중심으로 Phase 1 (state machine 트랜잭션 보호) → Phase 2 (이중 발행 검증) → Phase 3 (파일 로거 폐지) 순서의 실행 계획으로 재구성.- Phase 3 에 삭제 대상 파일/라인을 명시:
app/models/concerns/eventable/events/base.rb:18,app/factories/concerns/bulkable_factory/annotation.rb:228,lib/cupix/event.rb,.ebextensions/003-filebeat.config:99-113,.platform/hooks/postdeploy/70|71|72_restart_*_sidekiq.sh:14(APP_EVENT_LOG_FILEPATH). - 기존 "즉시 조치 rescue + retry" 는 Phase 1 로, "단기 초기화 실패 방어" 는 Phase 1 두 번째 항목으로, "장기 daily rotation 제거" 는 폐지에 흡수되어 삭제.
추가 조사 내용:
tesla레포에서Cupix::Event.참조 지점 전수 조사 → 애플리케이션 코드는app/models/concerns/eventable/events/base.rb:18과app/factories/concerns/bulkable_factory/annotation.rb:228두 곳만 존재.Cupix::EventService.publish_event구현(lib/cupix/event_service.rb) 확인 → Kinesis{tenant}-cupixworks-{env}-EventStream로 발행하며release_date_by로 production 2024-11-06 이후 활성.cupixworks레포의applications/event-service/README.md확인 → RDS→S3 이관 프로젝트 (Kinesis + Lambda + Athena + API Gateway) 명시..ebextensions/003-filebeat.config및.platform/hooks/postdeploy/70|71|72_restart_*_sidekiq.sh확인 → filebeat 가*-event.log를 여전히 shipping 중, 소비자 확인이 폐지의 전제 조건.
Revision 2#
Feedback: "phase 2,3 까지 한꺼번에 작업해버리는거 검토" — Revision 1 에서 나눠 놓은 Phase 2(이중 발행 검증) 와 Phase 3(파일 로거 폐지) 를 하나의 작업으로 합칠 수 있는지 검토.
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
| Phase 2 + Phase 3 을 하나의 PR 로 합치기 | 부분 수용 | Phase 3 의 삭제 스텝(app/models/concerns/eventable/events/base.rb:18, app/factories/concerns/bulkable_factory/annotation.rb:228, lib/cupix/event.rb, .ebextensions/003-filebeat.config:99-116, .platform/hooks/postdeploy/70|71|72_restart_*_sidekiq.sh:14) 자체는 서로 원자적으로 커밋되어야 안전(어느 하나만 남으면 dangling 참조 발생). 반면 Phase 2 의 V1(Kinesis 결번 확인) 은 Datadog 관측 창을 필요로 하는 런타임 검증이라 코드 PR 안으로 접을 수 없음. V2(logstash 다운스트림 소비자 확인) 는 cupix-infrastructure/cupix-service/logstash-eb/main.tf:143-158 에서 ES_HOST·CAPTURE_TRACE_S3_BUCKET_NAME sink 가 실제로 프로비저닝되어 있음을 확인 — 해당 sink 를 참조하는 대시보드/ETL 인벤토리는 코드만으로 결론이 안 나므로 인프라팀 확인이 필요. 따라서 "PR 은 하나로 합치되, 사전 검증 결과를 PR description 에 근거로 첨부" 형태로 수용. |
| 사전 검증 자체를 건너뛰고 병합 | 거부 | .ebextensions/003-filebeat.config:113-116 에서 filebeat 가 event_data message key 로 cls-tesla.cupix.internal:5044 (logstash) 로 이벤트를 지속 전송 중. cupix-infrastructure/cupix-service/logstash-eb/main.tf:147-158 의 logstash-eb 는 프로덕션 리전(aws2, aws2-au, aws2-ca, aws2-eu, aws2-jp, aws2-sg, aws5) 모두에서 활성. 검증 없이 삭제하면 downstream ES/S3 인덱싱이 조용히 끊긴다. |
| Phase 1 과도 병합 | 거부 | Phase 1 은 예외 전파를 즉시 차단하는 방어 조치라 별도 hotfix 로 먼저 배포되어야 한다. Phase 1 없이 검증 관측 창(V1 최소 7일) 을 여는 동안 본 클러스터의 job callback 예외/state machine 롤백은 계속 재발한다 — app/models/concerns/statable/capture.rb:151-153 의 before_transition ... to: :done 콜백이 예외로 종료되면 capture 는 :done 커밋에 실패한 채로 남는다. |
변경 사항:
## Fix Recommendation의 Phase 2 와 Phase 3 을 하나의 "Phase 2 — 이중 발행 제거 + 파일 로거 폐지 (사전 검증 후 단일 PR)" 로 병합. 스텝 번호는 Phase 1 (1,2) 을 이어 3,4,5,6 으로 재정렬.- 검증 V1(Kinesis 결번) / V2(logstash 다운스트림 소비자) 을 "사전 검증" 소섹션으로 명시하고, 이 두 검증이 PR description 에 근거로 첨부되어야 함을 규정.
- "병합하지 않는 경우" 소섹션을 추가해서 V1/V2 실패 시 원래의 2단계 분리로 되돌아가는 경로를 명시.
- 삭제 대상에
.ebextensions/003-filebeat.config의output.logstash블록(line 114-116) 을 명시적으로 포함(Revision 1 에서는 filestream 만 언급). - Phase 1 은 그대로 유지 (병합 대상 아님).
추가 조사 내용:
cupix-infrastructure/cupix-service/logstash-eb/main.tf전체 확인 → logstash EB 환경이ES_HOST(Elasticsearch) 와CAPTURE_TRACE_S3_BUCKET_NAME(S3) 두 sink 를 갖고 실사용 중임을 확인.es_passwordrandom resource 도 매 apply 마다 갱신 관리되는 활성 리소스.cupix-infrastructure전 리전 terragrunt.hcl 확인 → logstash-eb 모듈이nswgov-aws2-au,cupix-aws2-au/production,cupix-aws2-ca/production,cupix-aws2-eu/production,cupix-aws2-jp/production,cupix-aws2-sg/production,cupix-aws2/{dev,qa,stage,production},cupix-aws5/production,cupix-aws6/{dev,qa,stage}에 프로비저닝됨 — 전 프로덕션 리전에서 활성..ebextensions/003-filebeat.config:113-116재확인 →output.logstashblock 이cls-tesla.cupix.internal:5044로 여전히 라우팅 중.