ES /docs

Elasticsearch::Transport::Transport::Errors::NotFound: [404] {"error":{"root_cause":[{"type":"document_missing_exception

RCA: Elasticsearch::Transport::Transport::Errors::NotFound [404] document_missing_exception

Overview#

What Happened#

tesla (cupixworks-api / cupixworks-worker)의 Searchable concern이 레코드 update 시점에 Elasticsearch _update 요청을 보내는데, 대상 문서가 아직 색인되지 않은 상태라 ES가 [404] document_missing_exception을 반환한다. 이 실패한 client.update HTTP 요청이 cupixworks-elasticsearch APM instrumentation span에 error로 태깅되어 Error Tracking 이슈로 집계된다. 다만 애플리케이션 코드는 이 예외를 명시적으로 rescue 하여 warn 로그를 남기고 _index_document로 문서를 재색인하므로, 사용자에게 노출되는 실패나 데이터 유실은 없다.

Quick Facts#

Field Value
exception.class Elasticsearch::Transport::Transport::Errors::NotFound
exception.message [404] {"error":{"root_cause":[{"type":"document_missing_exception", ...}]}}
top_frame app/models/concerns/searchable.rb:94 (client.update)
runtime Ruby / Rails (elasticsearch-transport 7.5.0, faraday 0.17.5 + patron)
env production

Affected Teams#

Team / Domain Error Count Impact
tesla (search indexing) 18281 (16개월 누적) 없음 — rescue 후 자동 재색인, 사용자 무영향

Timeline#

  1. 2023-09-12 18:27 KST — 최초 발생 (Representative sample: editing_entities index, _doc][23575]).
  2. 2026-08-03 21:07 KST — 최근 발생 (Cupix::Cron::Integration, index cupix-cron-integrations).
  3. 2026-08-04 14:08 KST — last_seen (Workarea, index workareas, _doc][139962]).
  4. 2026-08-04 — RCA 수행. Representative가 STALE임을 확인 (현재는 editing_entities가 아닌 elements/workareas/cupix-cron-integrations 등 다양한 index에서 발생).

Error Log#

Datadog Logs

text
[404] {"error":{"root_cause":[{"type":"document_missing_exception","reason":"[_doc][23575]: document missing","index_uuid":"OrsiP2DkQzWgpo7vU2vU5g","shard":"4","index":"editing_entities"}],"type":"document_missing_exception","reason":"[_doc][23575]: docum

Impact#

  • Service: cupixworks-elasticsearch (APM instrumentation span — 실제 앱은 tesla)
  • 발생 횟수: 18281 (first_seen 2023-09-12 이후 약 16개월 누적)
  • 최초 발생: 2023-09-12 18:27 KST
  • 최근 발생: 2026-08-04 14:08 KST

Root Cause Summary#

Searchable concern은 레코드 생성 시 after_commit on: [:create] → _index_document로 ES에 색인하고, 갱신 시 after_commit on: [:update] → _update_document로 부분 갱신(client.update)을 수행한다. 색인이 아직 완료되지 않았거나(색인 실패 후 BulkIndexWorker 재색인 대기 중, dual-write tmp_index 미완, ES refresh 지연 등) 문서가 존재하지 않는 상태에서 update가 먼저 도달하면 ES는 존재하지 않는 문서에 대한 partial update를 거부하고 [404] document_missing_exception을 반환한다. 이 실패한 HTTP 요청 자체가 cupixworks-elasticsearch APM span에 error로 태깅되어 Error Tracking에 집계되지만, searchable.rb:101-105가 이 NotFound 예외를 명시적으로 rescue → warn 로그 → _index_document 재색인으로 자가 치유한다. 즉 코드 결함이 아니라, ES update-before-index 순서 경합에 대한 정상적 fallback 경로이며 사용자 영향과 데이터 유실이 없다.

Technical Analysis#

Code Path#

  • Entry point: app/models/concerns/searchable.rb:16-18 — 레코드 update 후 after_commit 콜백
  • _update_documentclient.update 호출 (searchable.rb:94) = Failure point (여기서 ES가 404 반환)
  • rescue → 재색인 (searchable.rb:101-105)

update 콜백 등록:

app/models/concerns/searchable.rb:12-22ruby
    after_commit on: [:create] do
      _index_document
    end

    after_commit on: [:update] do
      _update_document
    end

update 요청과 404 rescue:

app/models/concerns/searchable.rb:86-105ruby
          begin
            request = {
              id: __elasticsearch__.id,
              body: { doc: attributes },
              retry_on_conflict: 5
            }
            request.merge!(type: __elasticsearch__.document_type) if __elasticsearch__.document_type

            results = __elasticsearch__.client.update(request.merge({ index: __elasticsearch__.index_name }))
            Cupix::Logger.debug(results.to_json, class: self.class.name, function: __method__)

            # NOTE: dual write to tmp_index while reindexing
            if (tmp_index = self.class.fetch_tmp_index_name)
              __elasticsearch__.client.update(request.merge(index: tmp_index))
            end
          rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
            Cupix::Logger.warn("NotFound - #{e.message}", class: self.class.name, function: __method__)

            _index_document
          end

기대 동작: update 대상 문서는 create 콜백에서 이미 색인되어 있어야 한다.

실제 동작: 문서가 아직 색인되지 않은 상태에서 update가 도달 → ES _update API가 partial update를 거부하고 document_missing_exception (404) 반환. client.update의 HTTP 요청 span이 error로 태깅됨. 하지만 즉시 rescue되어 _index_document(전체 색인)로 문서가 생성되므로 최종 상태는 정합적. _index_document 자체도 실패 시 BulkIndexWorker.perform_async로 비동기 재색인 큐잉(searchable.rb:50-53)되어 이중 안전망이 존재한다.

document_missing_exception이 왜 NotFound로 rescue되는지: elasticsearch-transport 7.5.0에서 HTTP 404 응답은 Elasticsearch::Transport::Transport::Errors::NotFound로 매핑된다. 따라서 rescue Elasticsearch::Transport::Transport::Errors::NotFound(searchable.rb:101)가 이 예외를 포착한다.

Log Evidence#

Error-level 검색은 0건 (완전히 rescue되어 error로 로깅되지 않음):

text
service:cupixworks-api "document_missing_exception" status:error   → 0 logs (now-14d)

APM span 검색도 0건 (rescue된 예외):

text
service:cupixworks-elasticsearch "document_missing_exception"       → 0 logs (now-14d)

실제 현재 발생은 warn 레벨의 _update_document rescue 로그로 확인됨. Representative가 지목한 editing_entities가 아니라 다양한 index에서 발생 (Representative STALE 증거):

text
service:cupixworks-api "NotFound - [404]"                           (now-14d)
json
{
  "timestamp": "2026-08-04 14:08:21",
  "status": "warn",
  "message": "NotFound - [404] {\"error\":{\"root_cause\":[{\"type\":\"document_missing_exception\",\"reason\":\"[_doc][139962]: document missing\",\"index_uuid\":\"bSC11WWjRPSm9k0Hcjlr8Q\",\"shard\":\"0\",\"index\":\"workareas\"}], ...,\"status\":404}",
  "class": "Workarea",
  "function": "_update_document"
}
json
{
  "timestamp": "2026-08-04 07:34:14",
  "status": "warn",
  "message": "NotFound - [404] {\"error\":{\"root_cause\":[{\"type\":\"document_missing_exception\",\"reason\":\"[_doc][3143880]: document missing\",\"index_uuid\":\"vLtrRdPyTd2p2xQFrCpDlw\",\"shard\":\"3\",\"index\":\"elements\"}], ...,\"status\":404}",
  "class": "Element",
  "function": "_update_document"
}

worker 측(cupixworks-worker)에서도 동일 패턴:

json
{
  "timestamp": "2026-08-04 01:07:21",
  "status": "warn",
  "message": "NotFound - [404] {\"error\":{\"root_cause\":[{\"type\":\"document_missing_exception\",\"reason\":\"[_doc][6940]: document missing\",\"index_uuid\":\"hU4UOnmzT420WlJJ403dyg\",\"shard\":\"0\",\"index\":\"cupix-cron-integrations\"}], ...,\"status\":404}",
  "class": "Cupix::Cron::Integration",
  "function": "_update_document"
}

24h 샘플 분포: Element 49건, Workarea 1건 (+ worker의 Cupix::Cron::Integration). 각기 다른 record ID와 다른 index에 걸쳐 산발적으로 발생 — 특정 레코드 재진입이 아니라 create/update 순서 경합의 광범위한 저빈도 패턴.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 update-before-index 순서 경합 → 미색인 문서에 partial update 시도 → 404, 코드가 rescue 후 재색인 (noise) searchable.rb:16-18 create/update 콜백 분리; :101-105 NotFound rescue → _index_document; 현재 로그 전량 status:warn _update_document Confirmed
H2 Representative의 editing_entities 특정 index/문서 문제 (스키마·매핑 오류 등) Representative message가 editing_entities, _doc][23575] 지목 현재(last_seen) 로그는 workareas/elements/cupix-cron-integrations 등 다수 index. editing_entities는 현재 미발생. ET가 document_missing_exception 변형을 하나로 묶어 오래된 sample을 pin (STALE) Rejected
H3 ES 클러스터 outage로 인한 update 실패 status-board에 2026-07-29 dep:elasticsearch resolved 인시던트 존재 last_seen(2026-08-04)는 그 이후; outage는 outage timeout/503으로 나타나며 document_missing_exception(문서 부재)와 무관. active 인시던트 없음 Rejected
H4 미처리 예외로 사용자 요청 500 실패 status:error 0건; 전량 warn + rescue; NotFound < Elasticsearch::Transport::Transport::Error이므로 :115 상위 rescue에도 절대 도달 안 함 (:101이 먼저 포착) Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 없음. 코드 결함이 아니며 사용자 영향/데이터 유실 없음. searchable.rb:101-105의 rescue → _index_document 경로가 이미 자가 치유를 수행한다.

단기 개선 (1주 이내)#

  • Error Tracking 이슈 mute/ignore 처리 권장: 이 이슈는 정상 fallback 경로가 남기는 APM span error이므로 알람·트래킹 노이즈다. Error Tracking에서 이 이슈(97df4ae0-514e-11ee-9822-da7ad0900002)를 ignore 처리.
  • (선택) APM span error 태깅 억제: cupixworks-elasticsearch instrumentation이 404 응답을 error로 태깅하지 않도록 datadog trace 설정에서 document_missing_exception을 error 상태에서 제외하는 것을 검토. 단, ES span 전반의 404를 무시하면 실제 404 문제를 놓칠 수 있으므로 신중히.

장기 개선 (재발 방지)#

  • update-before-index 경합 축소: create 색인과 update 부분 갱신이 별도 after_commit으로 분리되어 있어, 색인 지연(비동기 재색인 대기, tmp_index dual-write 진행, ES refresh 지연) 중 update가 앞서면 이 패턴이 재발한다. 근본적으로 update 경로가 문서 부재 시 곧바로 full index로 전환하는 현재 설계가 합리적이므로, 빈도가 낮다면 유지해도 무방. 빈도가 유의미하게 오르면 색인 파이프라인을 단일 큐(BulkIndexWorker) 기반으로 직렬화하는 방안을 검토.

Monitoring#

_update_document NotFound fallback 발생 추이 (warn 로그 기반). 이 값이 갑자기 급증하면 색인 파이프라인 지연을 의심:

text
service:cupixworks-api "NotFound - [404]" @function:_update_document

실제 미처리 실패(회귀) 감지 — 정상 상태에서는 0이어야 함:

text
service:cupixworks-api "ElasticsearchError" @function:_update_document status:error

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial (코드 변경 없음, ET ignore 처리만 권장)

Noise Verdict#

noise — document_missing_exception(404)은 update-before-index 순서 경합으로 발생하지만 searchable.rb:101-105가 즉시 rescue 후 _index_document로 재색인해 자가 치유하므로 사용자 영향·데이터 유실이 없는 정상 fallback 경로다.