ES /docs

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#

  1. 2024-05-29 09:05 KST — 최초 발생 (first_seen)
  2. 2026-07-13 12:07 KST — 최근 발생 (last_seen)
  3. 2026-08-04 — RCA 수행. last_seen 이 14일 retention 밖이라 Datadog 로그 0건, Error Tracking 집계만 존재

Error Log#

Datadog Logs

text
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 이다. 10001msconfig/initializers/elasticsearch.rb:25timeout: 10 설정에서 결정되는 deterministic 값이고, 0 bytes received 는 ES 가 응답을 시작조차 못했음을 뜻한다 — 즉 ES 클러스터의 일시적 부하/지연이 원인이지 tesla 애플리케이션 코드의 결함이 아니다. 게다가 tesla 는 이 예외를 write 경로(searchable.rb:112 → error 로그 + BulkIndexWorker async 재색인)와 read 경로(base_repository.rb:94 rescue StandardErrorBG10002 → 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
config/initializers/elasticsearch.rb:17-28ruby
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
app/models/concerns/searchable.rb:112-120ruby
    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
app/repositories/base_repository.rb:94-97ruby
    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 내):

text
service:cupixworks-api "Operation timed out after"
text
"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::TimeoutErrorrescue StandardError 앞에 명시적으로 rescue 하여 warn 레벨로 분류할 수 있다. write 경로(searchable.rb:112)가 이미 Faraday::TimeoutError 를 별도로 잡는 것과 분류를 일치시켜, 일반적인 ES 오류와 transient timeout 을 알람에서 구분하는 목적이다. 이는 alarm-noise 감소용 개선이지 버그 수정이 아니다.

장기 개선 (재발 방지)#

  • ES 클러스터 응답시간(p95/p99) 및 노드 부하를 모니터링하여 지연이 지속·증가하는 추세인지 관찰. 지속적 지연이 확인되면 ES 클러스터 스케일업/샤드 리밸런싱 등 인프라 관점 조치를 검토한다. tesla 애플리케이션 레벨 변경은 불필요.

Monitoring#

ES timeout 발생 추이 (write 경로 error 로그 기준):

text
service:cupixworks-api "TimeoutError" "Operation timed out after"

ES 관련 502(BG10002) 응답 추이 (read 경로):

text
service:cupixworks-api "BG10002"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (권장 개선은 optional 한 alarm-noise 분류 조정)

Noise Verdict#

noise — ES 클러스터의 일시적 응답 지연으로 인한 transport timeout 이며 tesla 코드가 write·read 경로 모두에서 이미 정상 처리하므로 코드 수정이 필요 없다.