EventService lacks retry logic for Kinesis failures
RCA: Failed to create event: Aws::Kinesis::Errors::InternalFailure
Overview#
What Happened#
2026-04-27 15:10~15:27 UTC (약 17분) 동안 eu-central-1 리전의 cupixworks-api에서 AWS Kinesis put_records 호출이 InternalFailure 및 Http503Error로 실패했다. 모든 모델 create/update/destroy 이벤트 발행이 차단되어 약 5,694건의 에러가 발생했다. AWS Kinesis 서비스 측 장애로 인한 것이며, 애플리케이션의 동기식 이벤트 발행 경로에 retry 로직이 없어 모든 실패가 즉시 에러로 전파되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Aws::Kinesis::Errors::InternalFailure |
| exception.message | Failed to create event: Aws::Kinesis::Errors::InternalFailure |
| top_frame | lib/cupix/aws/kinesis.rb:9 |
| env | production, eu-central-1 |
| deploy | production-eu-central-1-20260426T2216Z0-a4578cc0-cupixworks |
| host | ip-10-1-16-213.eu-central-1.compute.internal |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cupixworks-api (eu-central-1) | 5,694 | 모델 이벤트(Pano 등)의 Kinesis 스트림 발행 실패. 이벤트 DB 레코드는 생성되지만 downstream 소비자(EventStream)에 전달되지 않음 |
Timeline#
- 2026-04-27T15:10:57Z -- 최초 에러 발생.
Cupix::Aws::Kinesis#put_records!에서Aws::Kinesis::Errors::InternalFailure발생 - 2026-04-27T15:27:24Z~15:27:47Z --
Aws::Kinesis::Errors::Http503Error간헐적으로 혼재 (8건) - 2026-04-27T15:27:58Z -- 마지막 에러 로그. 이후 Kinesis 서비스 복구로 에러 중단
- 2026-04-27T16:20Z -- RCA 분석 시작
Error Log#
Failed to create event: Aws::Kinesis::Errors::InternalFailure
Impact#
- Service:
cupixworks-api - 발생 횟수: 5,694
- 최초 발생: 2026-04-27T15:10:57.644Z
- 최근 발생: 2026-04-27T15:27:58.223Z
17분간 eu-central-1 리전의 모든 모델 이벤트(create/update/delete) Kinesis 발행이 실패했다. Eventable::Events::Base#create_event에서 예외가 re-raise되므로, 이벤트 발행 실패 시 해당 모델 콜백 체인이 중단된다. 이로 인해 model.event_created! 호출이 스킵되고, downstream EventStream 소비자에게 이벤트가 전달되지 않았다. cupixworks-worker에는 영향이 없었다.
Root Cause Summary#
AWS Kinesis eu-central-1 리전에서 약 17분간 서비스 장애가 발생하여 put_records API 호출이 InternalFailure(주) 및 Http503Error(부) 응답을 반환했다. Cupix::EventService.publish_event는 Cupix::Aws::Kinesis.put_records!를 동기적으로 호출하며, retry/backoff 로직 없이 즉시 예외를 상위로 전파한다. Eventable::Events::Base#create_event의 rescue 블록에서 에러를 로깅 후 re-raise하므로, ActiveRecord 콜백 체인 내에서 모든 이벤트 발행 시도가 실패로 이어졌다. 이는 외부 AWS 서비스 장애에 대한 애플리케이션 측 방어 로직 부재가 직접적 원인이다.
Technical Analysis#
Code Path#
Entry point: 모델 ActiveRecord 콜백 (after_create, after_update, after_destroy)
after_create do |model|
Eventable::Events::Create.create_event(model) if model.event_creation_on_create?
end
after_update do |model|
Eventable::Events::Update.create_event(model) if model.event_creation_on_update?
end
after_destroy do |model|
Eventable::Events::Delete.create_event(model) if model.event_creation_on_delete?
end
모델의 create/update/destroy 시 Eventable::Events::{Create|Update|Delete}.create_event이 동기적으로 호출된다.
Event creation orchestrator: Eventable::Events::Base.create_event
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]) # Kinesis 호출 지점
Cupix::Event.publish(event.serializable_hash(stringify_nested_fields: false))
rescue StandardError => e
Cupix::Logger.error("Failed to create event: #{e.message}", function: __method__, class: self.name, model: model.class.to_s, id: model.id, event_params: model.event_params, error: e)
raise e # 예외를 re-raise하여 콜백 체인 중단
else
model.event_created!
event
end
end
Line 17에서 Cupix::EventService.publish_event를 호출하고, 예외 발생 시 line 20에서 로깅 후 line 21에서 re-raise한다. else 블록의 model.event_created!는 실행되지 않는다.
Kinesis publish: Cupix::EventService.publish_event
def self.publish_event(events = [])
return if events.blank?
return if %w[development test].include?(Rails.env)
return unless release_date_by(Rails.env)
_event = events.first
records = events.map do |event|
{
data: event.serializable_hash.to_json,
partition_key: Current.request_id || SecureRandom.uuid
}
end
case ::Cupix::Tesla.launch_mode
when 'CUPIXWORKS'
stream_name = "#{::Cupix::Tesla.tenant}-cupixworks-#{Rails.env}-EventStream"
when 'CUPIXVISTA'
stream_name = "#{::Cupix::Tesla.tenant}-cupixvista-#{Rails.env}-EventStream"
else
Cupix::Logger.error('Failed to publish event: stream is not defined', ...)
end
response = Cupix::Aws::Kinesis.put_records!({ stream_name: stream_name, records: records })
# ...
rescue RestClient::Exception => e
# RestClient 예외만 별도 처리. AWS SDK 예외는 catch하지 않음
raise Cupix::Errors::System.new(code: 'SYS20000', reason: "Failed to publish event: #{e.message}")
end
Line 43에서 Cupix::Aws::Kinesis.put_records!를 호출한다. rescue 절이 RestClient::Exception만 처리하므로 Aws::Kinesis::Errors::InternalFailure는 처리되지 않고 상위로 전파된다.
Failure point: Cupix::Aws::Kinesis.put_records!
def put_records!(opts = {})
raise Cupix::Errors::Argument.new(code: 'ARG10001', reason: 'stream_name is blank') if opts[:stream_name].blank?
raise Cupix::Errors::Argument.new(code: 'ARG10001', reason: 'records is blank') if opts[:records].blank?
kinesis_client.put_records({
stream_name: opts[:stream_name],
records: opts[:records]
})
rescue => e
Cupix::Logger.error("Failed to put records: #{e.message}", class: self.name, function: __method__, stream_name: opts[:stream_name], record_count: opts[:records]&.size, record_sizes: opts[:records]&.map { |r| r[:data]&.bytesize })
raise e
end
Line 9에서 AWS SDK put_records를 호출하고, AWS 서비스가 InternalFailure를 반환하면 line 13-16에서 에러를 로깅 후 re-raise한다. retry 로직이 전혀 없다.
기대 동작: put_records 호출이 성공하여 이벤트가 Kinesis 스트림에 발행되고, model.event_created!가 호출됨.
실제 동작: AWS Kinesis가 InternalFailure를 반환하여 예외가 kinesis.rb:13 → event_service.rb → base.rb:19 → ActiveRecord 콜백으로 전파됨.
Log Evidence#
Datadog에서 사용한 쿼리:
service:cupixworks-api "Failed to create event" "Aws::Kinesis::Errors::InternalFailure"
service:cupixworks-api "Kinesis" status:error
service:cupixworks-api "Aws::Kinesis::Errors::Http503Error" status:error
대표 에러 로그 (Eventable::Events::Update):
{
"message": "Failed to create event: Aws::Kinesis::Errors::InternalFailure",
"status": "error",
"class": "Eventable::Events::Update",
"function": "create_event",
"error.msg": "Aws::Kinesis::Errors::InternalFailure",
"model": "Pano",
"host": "ip-10-1-16-213.eu-central-1.compute.internal",
"environment": "production",
"tenant": "cupix",
"version": "production-eu-central-1-20260426T2216Z0-a4578cc0-cupixworks"
}
대표 에러 로그 (Cupix::Aws::Kinesis):
{
"message": "Failed to put records: Aws::Kinesis::Errors::InternalFailure",
"status": "error",
"class": "Cupix::Aws::Kinesis",
"function": "put_records!"
}
Http503Error 혼재 (15:27:24~15:27:47Z, 총 8건):
2026-04-27T15:27:47.670Z Eventable::Events::Update - Failed to create event: Aws::Kinesis::Errors::Http503Error
2026-04-27T15:27:47.669Z Cupix::Aws::Kinesis - Failed to put records: Aws::Kinesis::Errors::Http503Error
2026-04-27T15:27:32.176Z Eventable::Events::Update - Failed to create event: Aws::Kinesis::Errors::Http503Error
2026-04-27T15:27:24.162Z Eventable::Events::Update - Failed to create event: Aws::Kinesis::Errors::Http503Error
InternalFailure와 Http503Error가 혼재하는 것은 AWS Kinesis 서비스가 500/503을 번갈아 반환하며 장애 상태였음을 확인한다.
cupixworks-worker 영향 확인:
service:cupixworks-worker status:error ("Kinesis" OR "InternalFailure" OR "create event")
결과: 0건. Worker 서비스에는 영향 없음.
warn 레벨 로그 확인:
service:cupixworks-api status:warn ("Kinesis" OR "create event" OR "event creation")
결과: 0건. Kinesis 관련 warn 로그는 존재하지 않음.
Stack trace (JobCallbackWorker 경유):
api/v1/jobs_controller.rb:13 (update)
-> concerns/parameter/job.rb:55 (set_parameters)
-> concerns/statable/job.rb:91 (state machine callback)
-> workers/job_callback_worker.rb:10 (perform)
-> concerns/jobable/capture.rb:32 (job_stopped_callback)
-> models/concerns/eventable/events/base.rb:17 (create_event)
-> lib/cupix/event_service.rb:43 (publish_event)
-> lib/cupix/aws/kinesis.rb:9 (put_records!)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | AWS Kinesis eu-central-1 리전 서비스 장애 | InternalFailure와 Http503Error 혼재, 17분간 집중 발생 후 자연 복구, 단일 리전(eu-central-1)에만 영향, 애플리케이션 배포 변경 없음 (20260426T2216Z0) |
-- | Confirmed |
| H2 | 잘못된 stream name 또는 Kinesis 설정 오류 | -- | 에러가 InternalFailure(서버 측)이지 ResourceNotFoundException이 아님. 장애 전후로 동일 설정으로 정상 동작. stream name은 코드에서 동적 생성 ({tenant}-cupixworks-{env}-EventStream) |
Rejected |
| H3 | Kinesis throughput limit 초과 (ProvisionedThroughputExceededException) | -- | 에러 타입이 InternalFailure/Http503Error이지 throughput 관련 예외가 아님. AWS SDK는 throughput 초과 시 별도 예외 클래스 사용 |
Rejected |
| H4 | 네트워크 문제 (VPC, endpoint 연결 실패) | -- | 네트워크 문제 시 Seahorse::Client::NetworkingError 또는 timeout 예외 발생. InternalFailure는 AWS 서비스가 요청을 수신 후 반환한 응답 |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
Cupix::Aws::Kinesis.put_records! (lib/cupix/aws/kinesis.rb:5-17)에 exponential backoff retry 로직 추가. AWS SDK의 InternalFailure 및 Http503Error는 일시적 서비스 장애이므로 재시도로 복구 가능하다. AWS SDK Ruby의 built-in retry 설정(retry_limit, retry_base_delay)을 활용하거나, Cupix::Aws::Kinesis.kinesis_client 초기화 시 retry 옵션을 명시적으로 설정한다.
단기 개선 (1주 이내)#
-
이벤트 발행 실패를 비차단(non-blocking)으로 전환:
Eventable::Events::Base#create_event(base.rb:19-21)에서 Kinesis 예외를 re-raise하지 않고 로깅만 수행하는 옵션 검토. 현재는 이벤트 발행 실패가 모델 콜백 체인 전체를 중단시키므로, 핵심 비즈니스 로직(모델 저장)이 부수 효과(이벤트 발행)의 실패에 의존하는 구조이다. -
Cupix::EventService.publish_event의 rescue 절 보완: 현재RestClient::Exception만 처리하고Aws::Kinesis::Errors::ServiceError는 처리하지 않음 (event_service.rb:50). AWS SDK 예외에 대한 명시적 에러 핸들링 추가.
장기 개선 (재발 방지)#
-
비동기 이벤트 발행 아키텍처: 현재 동기식
after_create/after_update콜백 내에서 Kinesis를 직접 호출하는 구조를 비동기(Sidekiq worker) 기반으로 전환. SiteInsights 경로(KinesisPutRecordsWorker)는 이미 비동기+retry(3회) 구조를 사용하고 있으므로, 표준 이벤트 발행 경로도 동일 패턴 적용 검토. -
Circuit breaker 패턴 도입: Kinesis 연속 실패 시 일정 시간 호출을 중단하고 이벤트를 로컬 큐에 버퍼링하는 circuit breaker 도입으로, 장애 시 불필요한 에러 로그 폭발 방지.
Monitoring#
추가 모니터링 권장:
service:cupixworks-api "Failed to put records" status:error
- 알림 조건: 5분 내 50건 이상 발생 시 경고. Kinesis 서비스 장애의 조기 감지.
- 대시보드:
Cupix::Aws::Kinesis#put_records!호출 성공/실패 비율 추적. - AWS Health Dashboard eu-central-1 Kinesis 서비스 상태 모니터링 연동.
Risk Assessment#
- Risk level: medium
- 예상 복잡도: standard