Elasticsearch transient communication failure — transport error
RCA: Faraday::TimeoutError: Operation timed out after 10001 milliseconds with 0 bytes received
Overview#
What Happened#
tesla (cupixworks-api)의 Elasticsearch 클라이언트가 요청을 보낸 뒤 응답을 받지 못하고 Faraday::TimeoutError 로 종료된 이벤트다. 10001 milliseconds 는 ES 클라이언트에 설정된 request timeout: 10 초 상한값이며, 0 bytes received 는 ES 가 응답을 시작조차 하지 못했음을 의미한다. 즉 ES 클러스터의 일시적 지연/응답 지연이 원인이며, tesla 코드는 이 예외를 이미 write 경로와 read 경로 모두에서 처리하고 있다. 16개월간 71건(하루 1건 미만)으로 매우 낮은 빈도다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Faraday::TimeoutError |
| exception.message | Operation timed out after 10001 milliseconds with 0 bytes received |
| top_frame | app/models/concerns/searchable.rb:112 (write) / app/repositories/base_repository.rb:94 (read) |
| runtime | Ruby (Rails, elasticsearch-transport 7.5.0 + faraday 0.17.5 + patron 0.13.3) |
| env | production (cupixworks) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
tesla (cupixworks-api) |
71 (16개월 누적) | ES 색인/검색 요청 일시 실패. write 경로는 async 재색인으로 자동 복구, read 경로는 해당 요청만 502 반환 |
Timeline#
- 2024-05-29 09:05 KST — 최초 발생 (first_seen)
- 2026-07-13 12:07 KST — 최근 발생 (last_seen)
- 2026-08-04 — RCA 수행. last_seen 이 14일 retention 밖이라 Datadog 로그 0건, Error Tracking 집계만 존재
Error Log#
Operation timed out after 10001 milliseconds with 0 bytes received
Impact#
- Service:
cupixworks-api(실제 repo: tesla) - 발생 횟수: 71 (16개월 누적, 하루 1건 미만)
- 최초 발생: 2024-05-29 09:05 KST
- 최근 발생: 2026-07-13 12:07 KST
Root Cause Summary#
Faraday::TimeoutError: Operation timed out after 10001 milliseconds with 0 bytes received 는 tesla 의 Elasticsearch 클라이언트가 요청 후 10초(설정된 request timeout: 10 초) 동안 ES 로부터 단 1바이트도 응답받지 못해 발생한 transport 계층 timeout 이다. 10001ms 는 config/initializers/elasticsearch.rb:25 의 timeout: 10 설정에서 결정되는 deterministic 값이고, 0 bytes received 는 ES 가 응답을 시작조차 못했음을 뜻한다 — 즉 ES 클러스터의 일시적 부하/지연이 원인이지 tesla 애플리케이션 코드의 결함이 아니다. 게다가 tesla 는 이 예외를 write 경로(searchable.rb:112 → error 로그 + BulkIndexWorker async 재색인)와 read 경로(base_repository.rb:94 rescue StandardError → BG10002 → 502) 양쪽에서 이미 처리하고 있어 unhandled crash 로 이어지지 않는다. 따라서 이 이슈는 코드 수정이 필요 없는 transient 외부 의존성 이벤트, 즉 noise 다.
service: cupixworks-api 로 태깅되어 있으나 이는 Datadog 의 기본 c.service 값(config/initializers/datadog.rb:32)일 뿐이고, ES 인스트루먼트 span 은 별도로 cupixworks-elasticsearch(datadog.rb:44)로 잡힌다. 이번 이벤트가 app-service span 아래로 surface 된 것이며, 근본 원인은 동일한 ES transport timeout 이다.
Technical Analysis#
Code Path#
- Timeout 상한 설정:
config/initializers/elasticsearch.rb:23-27
Elasticsearch::Model.client = ConnectionPool::Wrapper.new(size: 10, timeout: 7) {
Elasticsearch::Client.new(
host: ENV.fetch('RAILS_ES_HOST') { 'localhost' },
port: ENV.fetch('RAILS_ES_PORT') { DEFAULT_RAILS_ES_PORT },
user: ENV['RAILS_ES_USER'],
password: ENV['RAILS_ES_PASSWORD'],
transport_options: {
request: {
timeout: 10 # ← "10001 milliseconds" 상한의 출처
}
}
) do |faraday|
- Write/index 경로 failure handling:
app/models/concerns/searchable.rb:112-114
rescue Faraday::TimeoutError => e
Cupix::Logger.error("TimeoutError - #{e.message}", class: self.class.name, function: __method__)
BulkIndexWorker.perform_async(self.class.name, [id], 'index')
rescue Elasticsearch::Transport::Transport::Error => e
Cupix::Logger.error("ElasticsearchError - #{e.message}", class: self.class.name, function: __method__)
BulkIndexWorker.perform_async(self.class.name, [id], 'index')
rescue StandardError => e
Cupix::Logger.error("StandardError - #{e.message}", class: self.class.name, function: __method__)
BulkIndexWorker.perform_async(self.class.name, [id], 'index')
end
색인 경로에서는 timeout 발생 시 error 로그를 남기고 BulkIndexWorker 로 비동기 재색인을 예약한다 — 데이터 유실 없이 자동 복구된다.
- Read/search 경로 failure handling:
app/repositories/base_repository.rb:94-97
rescue StandardError => e
Cupix::Logger.error(e.message.to_s, class: self.class.name, method: __method__)
raise Cupix::Errors::BadGateway.new(code: 'BG10002', reason: 'Bad Gateway error on Elasticsearch')
Faraday::TimeoutError < Faraday::ClientError < Faraday::Error < StandardError 이므로 read 경로에서는 rescue StandardError 에 잡혀 BG10002 → HTTP 502 로 매핑된다. 해당 요청 하나만 실패하며 프로세스 crash 는 없다.
- 기대 동작 vs 실제 동작: 기대 동작은 ES 가 10초 내 응답하는 것. 실제 동작은 ES 클러스터의 일시적 지연으로 응답이 시작조차 되지 않아(
0 bytes received) faraday 가 timeout 을 raise. tesla 코드는 이를 정상적으로 rescue 하므로 코드 결함이 아니다.
Log Evidence#
Datadog 로그 검색 (14일 retention 내):
service:cupixworks-api "Operation timed out after"
"Operation timed out after 10001 milliseconds"
두 쿼리 모두 Found 0 logs 를 반환했다. last_seen(2026-07-13 12:07 KST)이 조회 시점(2026-08-04)에서 14일 retention window 밖이며, 이 클러스터는 et: (Error Tracking) 이슈로 Datadog Error Tracking 이 로그 retention 을 넘겨 occurrence 를 집계·보존한 것이다. Representative 메시지의 10001 은 config 의 timeout: 10 에서 결정되는 deterministic 상수이므로 representative sample 은 신뢰할 수 있으며 stale variant 가 아니다.
Incident board 조회 결과 이 cluster scope(svc:cupixworks-api::unknown)에 active incident 없음. recent 항목(2026-07-29, 2026-07-30)은 last_seen 과 무관한 별도 시점이다.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | ES 클러스터의 일시적 지연으로 인한 transport-level timeout (transient, code 결함 아님) | 10001ms = elasticsearch.rb:25 timeout: 10 설정에서 결정된 deterministic 값; 0 bytes received = ES 응답 미시작; write/read 경로 모두 rescue 처리(searchable.rb:112, base_repository.rb:94); 16개월 71건 저빈도 |
— | Confirmed |
| H2 | tesla 애플리케이션 코드의 unhandled 예외 (code 버그) | — | Faraday::TimeoutError 는 write 경로(searchable.rb:112)에서 명시적으로, read 경로(base_repository.rb:94)에서 rescue StandardError 로 처리됨. crash 없음 |
Rejected |
| H3 | Representative 메시지가 stale (다른 variant 를 old sample 로 pin) | — | 메시지 10001 은 config 상수에서 유도되는 fixed 값이라 variant 가 아님; Datadog 로그 0건이라 반증 불가하나 deterministic 특성상 trustworthy |
Rejected |
Fix Recommendation#
즉시 조치 (Critical)#
없음. 코드 결함이 아니라 ES 클러스터의 transient 지연이므로 즉시 코드 수정 대상이 아니다.
단기 개선 (1주 이내)#
- read 경로(
app/repositories/base_repository.rb:94)에서Faraday::TimeoutError를rescue StandardError앞에 명시적으로 rescue 하여warn레벨로 분류할 수 있다. write 경로(searchable.rb:112)가 이미Faraday::TimeoutError를 별도로 잡는 것과 분류를 일치시켜, 일반적인 ES 오류와 transient timeout 을 알람에서 구분하는 목적이다. 이는 alarm-noise 감소용 개선이지 버그 수정이 아니다.
장기 개선 (재발 방지)#
- ES 클러스터 응답시간(p95/p99) 및 노드 부하를 모니터링하여 지연이 지속·증가하는 추세인지 관찰. 지속적 지연이 확인되면 ES 클러스터 스케일업/샤드 리밸런싱 등 인프라 관점 조치를 검토한다. tesla 애플리케이션 레벨 변경은 불필요.
Monitoring#
ES timeout 발생 추이 (write 경로 error 로그 기준):
service:cupixworks-api "TimeoutError" "Operation timed out after"
ES 관련 502(BG10002) 응답 추이 (read 경로):
service:cupixworks-api "BG10002"
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (권장 개선은 optional 한 alarm-noise 분류 조정)
Noise Verdict#
noise — ES 클러스터의 일시적 응답 지연으로 인한 transport timeout 이며 tesla 코드가 write·read 경로 모두에서 이미 정상 처리하므로 코드 수정이 필요 없다.