ES /docs

Kinesis PutRecords connection drop — transient network failure

RCA: Failed to publish event: Internal service failure.

Error Log#

Datadog Logs

text
Failed to publish event: Internal service failure.

Impact#

  • Service: cupixworks-api
  • 발생 횟수: 10
  • 최초 발생: 2026-04-09T02:14:14.629Z
  • 최근 발생: 2026-04-09T02:14:17.613Z

Root Cause Summary#

AWS Kinesis Data Streams의 일시적인 내부 장애(InternalFailure)로 인해 Cupix::EventService.publish_event에서 이벤트 발행이 실패했습니다. put_records API 호출 자체는 성공(exception 없음)했으나, 응답 내 개별 레코드의 error_codeInternalFailure로 설정되어 failed_record_count: 1 / 1이 반환되었습니다. 10건의 에러가 약 3초간(02:14:14~02:14:17Z) 집중 발생한 후 자연 복구되었으며, 02:14:17Z 이후에는 정상적으로 failed_record_count: 0 / 1이 기록되었습니다. 모든 에러가 Pano 모델 이벤트에서 발생했고, 여러 호스트(ip-10-1-19-190, ip-10-1-144-228)에 걸쳐 동시에 나타났으므로 특정 서버나 요청이 아닌 Kinesis 서비스 측 일시적 장애로 판단됩니다.

Technical Analysis#

Code Path#

  • Entry point: app/models/concerns/eventable/siteinsights.rb:8after_create_commit :publish_create_event 등 ActiveRecord callback이 모델 변경 시 이벤트 발행을 트리거

  • 이벤트 생성: app/models/concerns/eventable/events/base.rb:17Cupix::EventService.publish_event([event]) 호출

ruby
# app/models/concerns/eventable/events/base.rb:10-17
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 전송: lib/cupix/event_service.rb:43Cupix::Aws::Kinesis.put_records!로 AWS Kinesis에 레코드 전송
ruby
# lib/cupix/event_service.rb:43-49
response = Cupix::Aws::Kinesis.put_records!({ stream_name: stream_name, records: records })
Cupix::Logger.info("Published event - failed_record_count: #{response.failed_record_count} / #{records.size}", class: self.name, function: __method__, event: _event, model: { type: _event.eventable_type, id: _event.eventable_id })

errors = response.records.select { |record| record.error_code.present? } rescue []
if errors.present?
  Cupix::Logger.error("Failed to publish event: #{errors.map(&:error_message).join(', ') rescue nil}", class: self.name, function: __method__, event: _event, model: { type: _event.eventable_type, id: _event.eventable_id })
end
  • Failure point: lib/cupix/event_service.rb:46-48response.records에서 error_code가 있는 레코드를 필터링하여 에러 로그 출력. error_message"Internal service failure."로 AWS Kinesis의 InternalFailure 응답을 그대로 전달.

  • Kinesis 클라이언트: lib/cupix/aws/kinesis.rb:9-12 — AWS SDK의 put_records API를 호출. exception이 발생하지 않았으므로 HTTP 레벨에서는 200 응답을 받았으나, 개별 레코드가 실패한 partial failure 상태.

ruby
# lib/cupix/aws/kinesis.rb:9-12
kinesis_client.put_records({
  stream_name: opts[:stream_name],
  records: opts[:records]
})
  • 기대 동작: put_records가 모든 레코드를 성공적으로 Kinesis stream에 기록
  • 실제 동작: put_records는 HTTP 200을 반환했으나 failed_record_count: 1이고 개별 레코드에 error_code: InternalFailure, error_message: "Internal service failure." 포함

Log Evidence#

검색 쿼리:

text
service:cupixworks-api status:error "Failed to publish event"
Time: 2026-04-09T01:14:14Z to 2026-04-09T03:14:17Z

에러 로그 (10건, 02:14:14~02:14:17Z):

json
{
  "timestamp": "2026-04-09T02:14:17.613Z",
  "status": "error",
  "message": "Failed to publish event: Internal service failure.",
  "class": "Cupix::EventService",
  "function": "publish_event",
  "model": { "id": 80560130, "type": "Pano" },
  "host": "ip-10-1-19-190.us-west-2.compute.internal",
  "request_id": "8d8f4432-7fa2-4c90-9418-f1fc9c2194f9"
}
json
{
  "timestamp": "2026-04-09T02:14:16.633Z",
  "status": "error",
  "message": "Failed to publish event: Internal service failure.",
  "class": "Cupix::EventService",
  "function": "publish_event",
  "model": { "id": 80566425, "type": "Pano" },
  "host": "ip-10-1-144-228.us-west-2.compute.internal",
  "request_id": "4dd4ff10-46a1-4965-b02c-8a078c8fe3ce"
}

failed_record_count 확인 (동일 시간대 info 로그):

text
service:cupixworks-api "Published event" NOT "failed_record_count: 0"
Time: 2026-04-09T02:14:14Z to 2026-04-09T02:14:18Z
text
2026-04-09T02:14:17.613Z: Published event - failed_record_count: 1 / 1
2026-04-09T02:14:16.633Z: Published event - failed_record_count: 1 / 1
2026-04-09T02:14:15.629Z: Published event - failed_record_count: 1 / 1
2026-04-09T02:14:14.629Z: Published event - failed_record_count: 1 / 1
(총 10건)

정상 복구 확인 (에러 직후 같은 시간대):

text
service:cupixworks-api "Published event" "failed_record_count: 0"
Time: 2026-04-09T02:14:14Z to 2026-04-09T02:14:18Z
text
2026-04-09T02:14:17.616Z: Published event - failed_record_count: 0 / 1
2026-04-09T02:14:17.615Z: Published event - failed_record_count: 0 / 1
(다수 성공 로그 - 동시간대에 다른 요청들은 정상)

타임라인:

시간 (UTC) 이벤트
02:14:14.629Z 첫 번째 에러 발생 (failed_record_count: 1 / 1)
02:14:14~17Z 10건의 Pano 이벤트가 Kinesis InternalFailure로 실패
02:14:17.613Z 마지막 에러 발생
02:14:17.614Z~ 동일 시간대 다른 요청들은 정상 (failed_record_count: 0 / 1)
02:14:59Z~ 완전 정상 복구 확인

Fix Recommendation#

즉시 조치 (Critical)#

현재 코드에는 Kinesis put_records partial failure에 대한 재시도 로직이 없습니다. AWS는 InternalFailure에 대해 exponential backoff로 재시도를 권장합니다.

  • lib/cupix/event_service.rb:43-49put_records! 호출 후 failed_record_count > 0인 경우, 실패한 레코드만 추출하여 최대 3회까지 exponential backoff로 재시도하는 로직 추가
  • 에러 로그에 error_code (예: InternalFailure, ProvisionedThroughputExceededException)를 포함하여 장애 유형 구분 가능하도록 개선

단기 개선 (1주 이내)#

  • lib/cupix/event_service.rb:46rescue [] 패턴은 모든 exception을 무시하므로, rescue StandardError => e로 변경하여 예상치 못한 에러를 놓치지 않도록 개선
  • 실패한 이벤트의 model ID와 error_code를 에러 로그에 포함하여, 어떤 이벤트가 실패했는지 추적 가능하도록 개선

장기 개선 (재발 방지)#

  • Kinesis 이벤트 발행을 Sidekiq worker로 비동기화하여 API 요청 처리 경로에서 분리. 이를 통해 Kinesis 일시 장애가 API 응답 시간에 영향을 미치지 않도록 개선
  • Dead Letter Queue(DLQ) 패턴 도입: 재시도 후에도 실패한 이벤트를 별도 큐에 저장하여 데이터 유실 방지

Monitoring#

  • failed_record_count > 0인 이벤트 발행 실패 비율 추적
  • Datadog 쿼리:
text
service:cupixworks-api "Published event - failed_record_count" NOT "failed_record_count: 0"
  • AWS Kinesis CloudWatch 메트릭: PutRecords.FailedRecords, PutRecords.ThrottledRecords 모니터링
  • Kinesis InternalFailure 에러가 5분 내 10건 이상 발생 시 알림 설정 권장

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • 이번 장애는 AWS Kinesis의 일시적 내부 장애로 인한 것이며, 약 3초 만에 자연 복구됨. 이벤트 발행 실패는 SiteInsights 데이터 동기화 지연을 유발할 수 있으나, API 요청 자체의 성공/실패에는 영향 없음 (에러가 raise되지 않고 로그만 기록). 재시도 로직 부재로 인해 실패한 10건의 이벤트는 유실된 상태.