ES /docs

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 호출이 InternalFailureHttp503Error로 실패했다. 모든 모델 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#

  1. 2026-04-27T15:10:57Z -- 최초 에러 발생. Cupix::Aws::Kinesis#put_records! 에서 Aws::Kinesis::Errors::InternalFailure 발생
  2. 2026-04-27T15:27:24Z~15:27:47Z -- Aws::Kinesis::Errors::Http503Error 간헐적으로 혼재 (8건)
  3. 2026-04-27T15:27:58Z -- 마지막 에러 로그. 이후 Kinesis 서비스 복구로 에러 중단
  4. 2026-04-27T16:20Z -- RCA 분석 시작

Error Log#

Datadog Logs

text
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_eventCupix::Aws::Kinesis.put_records!를 동기적으로 호출하며, retry/backoff 로직 없이 즉시 예외를 상위로 전파한다. Eventable::Events::Base#create_eventrescue 블록에서 에러를 로깅 후 re-raise하므로, ActiveRecord 콜백 체인 내에서 모든 이벤트 발행 시도가 실패로 이어졌다. 이는 외부 AWS 서비스 장애에 대한 애플리케이션 측 방어 로직 부재가 직접적 원인이다.

Technical Analysis#

Code Path#

Entry point: 모델 ActiveRecord 콜백 (after_create, after_update, after_destroy)

app/models/concerns/eventable/callbacks.rb:30-40ruby
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

app/models/concerns/eventable/events/base.rb:7-26ruby
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

lib/cupix/event_service.rb:21-54ruby
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!

lib/cupix/aws/kinesis.rb:5-17ruby
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:13event_service.rbbase.rb:19 → ActiveRecord 콜백으로 전파됨.

Log Evidence#

Datadog에서 사용한 쿼리:

text
service:cupixworks-api "Failed to create event" "Aws::Kinesis::Errors::InternalFailure"
text
service:cupixworks-api "Kinesis" status:error
text
service:cupixworks-api "Aws::Kinesis::Errors::Http503Error" status:error

대표 에러 로그 (Eventable::Events::Update):

json
{
  "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):

json
{
  "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건):

text
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

InternalFailureHttp503Error가 혼재하는 것은 AWS Kinesis 서비스가 500/503을 번갈아 반환하며 장애 상태였음을 확인한다.

cupixworks-worker 영향 확인:

text
service:cupixworks-worker status:error ("Kinesis" OR "InternalFailure" OR "create event")

결과: 0건. Worker 서비스에는 영향 없음.

warn 레벨 로그 확인:

text
service:cupixworks-api status:warn ("Kinesis" OR "create event" OR "event creation")

결과: 0건. Kinesis 관련 warn 로그는 존재하지 않음.

Stack trace (JobCallbackWorker 경유):

text
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 리전 서비스 장애 InternalFailureHttp503Error 혼재, 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의 InternalFailureHttp503Error는 일시적 서비스 장애이므로 재시도로 복구 가능하다. AWS SDK Ruby의 built-in retry 설정(retry_limit, retry_base_delay)을 활용하거나, Cupix::Aws::Kinesis.kinesis_client 초기화 시 retry 옵션을 명시적으로 설정한다.

단기 개선 (1주 이내)#

  1. 이벤트 발행 실패를 비차단(non-blocking)으로 전환: Eventable::Events::Base#create_event (base.rb:19-21)에서 Kinesis 예외를 re-raise하지 않고 로깅만 수행하는 옵션 검토. 현재는 이벤트 발행 실패가 모델 콜백 체인 전체를 중단시키므로, 핵심 비즈니스 로직(모델 저장)이 부수 효과(이벤트 발행)의 실패에 의존하는 구조이다.

  2. Cupix::EventService.publish_event의 rescue 절 보완: 현재 RestClient::Exception만 처리하고 Aws::Kinesis::Errors::ServiceError는 처리하지 않음 (event_service.rb:50). AWS SDK 예외에 대한 명시적 에러 핸들링 추가.

장기 개선 (재발 방지)#

  1. 비동기 이벤트 발행 아키텍처: 현재 동기식 after_create/after_update 콜백 내에서 Kinesis를 직접 호출하는 구조를 비동기(Sidekiq worker) 기반으로 전환. SiteInsights 경로(KinesisPutRecordsWorker)는 이미 비동기+retry(3회) 구조를 사용하고 있으므로, 표준 이벤트 발행 경로도 동일 패턴 적용 검토.

  2. Circuit breaker 패턴 도입: Kinesis 연속 실패 시 일정 시간 호출을 중단하고 이벤트를 로컬 큐에 버퍼링하는 circuit breaker 도입으로, 장애 시 불필요한 에러 로그 폭발 방지.

Monitoring#

추가 모니터링 권장:

text
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