Kinesis PutRecords connection drop — transient network failure
RCA: Failed to publish event: Internal service failure.
Error Log#
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_code가 InternalFailure로 설정되어 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:8—after_create_commit :publish_create_event등 ActiveRecord callback이 모델 변경 시 이벤트 발행을 트리거 -
이벤트 생성:
app/models/concerns/eventable/events/base.rb:17—Cupix::EventService.publish_event([event])호출
# 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:43—Cupix::Aws::Kinesis.put_records!로 AWS Kinesis에 레코드 전송
# 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-48—response.records에서error_code가 있는 레코드를 필터링하여 에러 로그 출력.error_message가"Internal service failure."로 AWS Kinesis의InternalFailure응답을 그대로 전달. -
Kinesis 클라이언트:
lib/cupix/aws/kinesis.rb:9-12— AWS SDK의put_recordsAPI를 호출. exception이 발생하지 않았으므로 HTTP 레벨에서는 200 응답을 받았으나, 개별 레코드가 실패한 partial failure 상태.
# 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#
검색 쿼리:
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):
{
"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"
}
{
"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 로그):
service:cupixworks-api "Published event" NOT "failed_record_count: 0"
Time: 2026-04-09T02:14:14Z to 2026-04-09T02:14:18Z
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건)
정상 복구 확인 (에러 직후 같은 시간대):
service:cupixworks-api "Published event" "failed_record_count: 0"
Time: 2026-04-09T02:14:14Z to 2026-04-09T02:14:18Z
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-49—put_records!호출 후failed_record_count > 0인 경우, 실패한 레코드만 추출하여 최대 3회까지 exponential backoff로 재시도하는 로직 추가- 에러 로그에
error_code(예:InternalFailure,ProvisionedThroughputExceededException)를 포함하여 장애 유형 구분 가능하도록 개선
단기 개선 (1주 이내)#
lib/cupix/event_service.rb:46—rescue []패턴은 모든 exception을 무시하므로,rescue StandardError => e로 변경하여 예상치 못한 에러를 놓치지 않도록 개선- 실패한 이벤트의 model ID와 error_code를 에러 로그에 포함하여, 어떤 이벤트가 실패했는지 추적 가능하도록 개선
장기 개선 (재발 방지)#
- Kinesis 이벤트 발행을 Sidekiq worker로 비동기화하여 API 요청 처리 경로에서 분리. 이를 통해 Kinesis 일시 장애가 API 응답 시간에 영향을 미치지 않도록 개선
- Dead Letter Queue(DLQ) 패턴 도입: 재시도 후에도 실패한 이벤트를 별도 큐에 저장하여 데이터 유실 방지
Monitoring#
failed_record_count > 0인 이벤트 발행 실패 비율 추적- Datadog 쿼리:
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건의 이벤트는 유실된 상태.