Skip to content

ElasticSearch ‐ Managing Document (2)

woojin edited this page Aug 1, 2026 · 2 revisions

Create — 문서 생성

POST /my_index/_doc/100
{
  "title": "Elasticsearch Advanced",
  "author": "Anthony K",
  "publish_date": "2024-05-20",
  "tags": ["search", "analytics"]
}
  • 경로는 /{인덱스}/_doc/{문서 ID} 형태다.
  • 본문은 JSON 문서 전체를 그대로 보낸다.

Retrieve — 문서 조회

GET /my_index/_doc/100
  • 경로 마지막의 100Document Id다.
  • ID를 알면 해당 문서 하나를 바로 가져온다.

Update — 문서 수정

기존 필드 수정

POST /my_index/_update/100
{
  "doc": {
    "author": "Anthony K2"
  }
}

새 필드 추가

POST /my_index/_update/100
{
  "doc": {
    "price": 100
  }
}
  • _doc이 아니라 _update 엔드포인트를 쓴다.
  • 바꿀 내용을 doc 안에 담는다. 기존 필드 수정과 새 필드 추가가 같은 문법이다.

문서는 불변(IMMUTABLE)이다

  • 수정 요청이 들어와도 문서를 제자리에서 고치지 않는다. Elasticsearch 내부에서는 아래 순서로 처리한다.
  1. 기존 문서 조회(Fetch) — 인덱스에서 현재 버전의 문서를 가져온다.
  2. 변경 적용(Apply Changes) — 가져온 문서에 요청한 변경을 반영한다.
  3. 새 문서로 색인(Index) — 수정된 문서를 새 버전으로 색인한다.
  4. 기존 문서 삭제 표시(Mark as Deleted) — 이전 버전은 논리적 삭제(logical deletion) 처리한다. 인덱스에 남아 있되 삭제 플래그가 붙고 검색 대상에서 제외된다.
  5. 세그먼트 병합(Segment Merging) — 백그라운드 작업이 삭제 표시된 문서를 물리적으로 제거하고 공간을 회수한다.

불변성의 이점

  • 일관성(Consistency) — 제자리 수정을 하지 않으므로 분산 환경에서 race condition과 데이터 손상을 피할 수 있다.
  • 버전 관리(Versioning) — 수정할 때마다 새 버전이 생기므로 동시 수정 관리가 쉽고, optimistic concurrency control 같은 기능을 제공할 수 있다.
  • 성능(Performance) — Elasticsearch는 append-only 워크로드에 최적화되어 있어 색인과 검색 성능에 유리하다.

Delete — 문서 삭제

# delete a document
DELETE /my_index/_doc/100

# flush a document
POST /my_index/_flush
  • DELETE도 즉시 물리 삭제가 아니라 삭제 표시다. 실제 제거는 세그먼트 병합 시점에 일어난다.
  • _flush는 메모리의 변경분을 디스크에 반영한다.

Script란

  • 스크립트는 문서 색인, 수정, 검색 과정에서 더 복잡한 연산과 계산을 수행할 수 있게 해준다.

스크립트의 종류

  • Inline Scripts — 요청 안에 직접 작성하는 스크립트다.
  • Stored Scripts — 서버에 저장해두고 고유 ID로 참조하는 스크립트다.
  • File Scripts — 각 노드의 config/scripts 디렉터리에 저장하는 방식이다. 보안상의 이유로 잘 쓰이지 않고 권장되지도 않는다.

스크립트 언어

  • Elasticsearch는 여러 스크립트 언어를 지원하지만, Painless가 가장 권장되며 안전하다.
  • Groovy, JavaScript, Python도 사용할 수 있으나 성능과 보안 측면에서 Painless가 우선된다.

문서 수정 예시

POST /my_index/_update/100
{
  "script": {
    "source": "ctx._source.price += 10"
  }
}
  • ctx._source로 현재 문서에 접근해 값을 직접 계산한다.

커스텀 스코어링 예시

POST /my_index/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      },
      "script_score": {
        "script": {
          "source": "doc['price'].value * _score"
        }
      }
    }
  }
}

보안 고려사항

  • Painless를 사용한다 — 보안과 성능 모두에서 유리하다.
  • 스크립팅 권한을 신뢰할 수 있는 사용자로 제한한다.
  • 필요하지 않다면 운영 환경에서 동적 스크립팅을 비활성화한다.
  • 공통적이고 재사용되는 스크립트는 stored script로 만들어 공격 표면을 줄인다.

Upsert란

  • Elasticsearch의 upsert는 update와 insert를 결합한 연산이다.
  • 지정한 ID의 문서가 있으면 수정(update) 한다.
  • 없으면 제공한 내용으로 새 문서를 생성(insert) 한다.

doc + upsert

POST /my_index/_update/5
{
  "doc": {
    "author": "John Doe"
  },
  "upsert": {
    "title": "Elasticsearch Basics",
    "author": "Jane Doe",
    "publish_date": "2024-05-09",
    "tags": ["search", "analytics"]
  }
}
  • doc — 문서가 이미 있을 때 적용할 변경 내용이다. 여기서는 author만 바뀐다.
  • upsert — 문서가 없을 때 새로 생성할 문서 전체다.
  • 즉 문서 존재 여부에 따라 둘 중 하나만 사용된다.

script + upsert

POST /my_index/_update/7
{
  "script": {
    "source": "ctx._source.price++"
  },
  "upsert": {
    "title": "Elasticsearch Basics",
    "author": "Jane Doe",
    "publish_date": "2024-05-09",
    "price": 100,
    "tags": ["search", "analytics"]
  }
}
  • script — 문서가 있을 때 실행할 로직이다. 기존 값을 읽어 계산한다(price++).
  • upsert — 문서가 없을 때 생성할 초기 문서다. 초기값 price: 100을 함께 넣어둔다.
  • 카운터처럼 기존 값을 증가시키되 첫 요청에서는 초기값을 세워야 하는 경우에 쓰는 형태다.

Replace — 문서 전체 교체

PUT /my_index/_doc/20
{
  "title": "Elasticsearch Basics",
  "author": "John Doe",
  "publish_date": "2024-05-09",
  "tags": ["search", "analytics"]
}
  • 같은 ID에 다시 PUT을 보내면 문서 전체가 교체된다.
  • PUT /{index}/_doc/{id}부분 수정이 아니라 전체 덮어쓰기다.
  • 요청 본문에 없는 필드는 사라진다.
PUT /my_index/_doc/20
{
  "title": "Updated Elasticsearch Basics",
  "author": "Jane Doe",
  "publish_date": "2024-06-01",
  "tags": ["search", "analytics", "updated"]
}

Routing이란

  • Routing은 인덱스 안에서 문서가 shard들에 어떻게 분산되는지를 결정하는 메커니즘이다.
  • 제대로 활용하면 Elasticsearch 작업의 성능과 효율을 크게 개선할 수 있다. 특히 대용량 데이터셋이나 특정한 쿼리 패턴에서 효과가 크다.

두 가지 방식

  • Default Routing — 문서 ID를 해싱해 shard를 정한다.
  • Custom Routing — shard를 결정할 커스텀 값(routing key) 을 직접 지정한다. 더 효율적인 쿼리가 가능해진다.

Custom Routing의 이점

  • 쿼리 성능 향상(Improved Query Performance)
    • 연관된 문서들이 같은 shard에 저장되도록 보장하면, Elasticsearch가 질의해야 할 shard 수를 줄일 수 있어 응답이 빨라진다.
  • 데이터 지역성(Data Locality)
    • 연관 데이터를 함께 저장하므로, 멀티테넌시나 데이터 분할 시나리오를 가진 애플리케이션에 특히 유용하다.

사용 예시

색인할 때 routing key 지정

POST /my_index/_doc/1?routing=user123
{
  "title": "Elasticsearch Basics",
  "author": "John Doe",
  "publish_date": "2024-05-09",
  "tags": ["search", "analytics"]
}

검색할 때 같은 routing key 지정

GET /my_index/_search?routing=user123
{
  "query": {
    "match": {
      "author": "John Doe"
    }
  }
}
  • 쿼리 파라미터 ?routing= 으로 값을 넘긴다.
  • 색인과 검색에 같은 키를 쓰면, 검색이 모든 shard가 아니라 해당 shard만 조회한다.

고려사항

  • shard 불균형(Shard Imbalance)
    • custom routing을 조심해서 쓰지 않으면 데이터가 고르지 않게 분포할 수 있다. routing key가 shard 전체에 고르게 퍼지는 값인지 확인해야 한다.
  • routing key 변경(Routing Key Changes)
    • 문서를 routing key와 함께 색인하고 나면, 문서를 재색인하지 않고는 routing key를 바꿀 수 없다.

읽기 요청의 처리 순서

  • Client Request — 클라이언트가 Elasticsearch 노드(coordinating node)로 요청을 보낸다. 검색 쿼리, ID로 문서 조회, 집계 등이 모두 해당한다.
  • Coordinating Node — 요청 처리를 담당한다. 요청을 파싱해 어떤 인덱스와 shard를 조회해야 하는지 판단한다.
  • Routing — 쿼리에 관련된 각 shard에 대해, coordinating node가 적절한 shard 복사본으로 요청을 전달한다. 각 인덱스는 shard로 나뉘고 각 shard는 primary 하나와 0개 이상의 replica를 가지므로, 어느 복사본을 조회할지 결정해야 한다.
  • Shard Level Execution — 요청이 primary shard 또는 replica shard 중 하나로 전달된다(연산 종류와 부하 분산 설정에 따라 달라진다). 해당 shard가 로컬에서 쿼리나 조회를 수행한다.
  • Aggregation and Reduction — 검색 쿼리의 경우 각 shard가 쿼리를 실행하고 결과를 coordinating node로 돌려준다. coordinating node는 이 결과들을 취합해(검색 결과 병합, 최종 집계 수행) 완전한 응답을 만든다.
  • Response to Client — coordinating node가 최종 취합된 응답을 클라이언트에 보낸다.

Adaptive Replica Selection (ARS)

  • 읽기 요청을 처리할 최적의 shard 복사본(primary 또는 replica)을 동적으로 선택해 읽기 성능과 효율을 개선하도록 설계된 기능이다.
  • 이 선택은 노드의 현재 성능과 부하 지표를 근거로 이루어진다.

동작 방식

  • 지표 수집(Metrics Collection) — Elasticsearch는 각 shard 복사본(primary와 replica 모두)에 대해 응답 시간, 부하, 큐 크기 같은 지표를 수집한다.
  • 동적 선택(Dynamic Selection) — 읽기 요청이 오면 특정 복사본으로 정적으로 라우팅하지 않고, coordinating node가 이 지표들을 이용해 가장 빠른 응답이 기대되는 복사본을 동적으로 선택한다.

효과

  • 부하 분산(Load Balancing) — 가장 덜 바쁘거나 가장 빠르게 응답하는 shard를 고르므로, 노드 간 부하가 더 효과적으로 분산되고 전체 시스템 성능이 최적화된다.
  • 지연 개선(Improved Latency) — 읽기 요청을 가장 응답성이 좋은 복사본으로 보내므로, 지연이 줄고 클러스터의 응답성이 향상된다.

쓰기 요청의 처리 순서

  • Client Request — 클라이언트가 문서를 쓰기 위해 Elasticsearch 노드(coordinating node)로 색인 요청을 보낸다. 요청에는 문서 내용과 저장할 인덱스가 담긴다.
  • Document Routing — coordinating node가 어느 shard가 이 문서를 담아야 하는지 결정한다. 기본적으로 문서 ID의 해시로 shard를 정하며, custom routing을 설정할 수도 있다.
  • Primary Shard Processing — 요청이 해당 문서를 담당하는 primary shard로 전달된다. 인덱스가 샤딩되어 있으면 각 문서는 routing에 따라 하나의 primary shard에 배정된다.
    • primary shard가 쓰기를 처리하고 sequence number를 부여한다.
    • 현재 primary shard의 primary term이 함께 포함된다.
    • primary shard는 연산을 영속화한 뒤 local checkpoint를 갱신한다.
  • Replication — primary shard가 쓰기를 처리하고 나면, sequence number와 primary term을 포함해 모든 replica shard로 연산을 전달한다. 각 replica가 쓰기를 처리하고 자신의 local checkpoint를 갱신한다. primary와 모든 replica가 쓰기를 확인해야만 쓰기 연산이 성공으로 간주된다.
  • Global Checkpoint Update — primary shard가 모든 replica의 local checkpoint를 추적한다. in-sync 상태인 replica 전부가 쓰기를 확인하면, primary shard가 global checkpoint를 모든 replica가 확인한 가장 높은 sequence number로 갱신한다.
  • Acknowledgment — coordinating node가 primary와 모든 replica의 확인 응답을 기다린다. 전부 확인되면 클라이언트에 성공 응답을 보낸다.

핵심 개념 네 가지

개념 의미
Translog write-ahead log 역할을 한다
Checkpoint translog에서 커밋 성공 지점을 표시하는 marker
Global Checkpoint shard의 in-sync 복사본 전체가 도달한 지점
Local Checkpoint 개별 shard 하나가 도달한 지점
Primary Term primary shard가 재할당될 때마다 증가하는 카운터
Sequence Number 연산을 순서대로 처리하기 위한 카운터

Global Checkpoint의 진행

1단계 — 첫 쓰기

Global checkpoint: 99
Primary:   99 → 100
Replica1:  99 → 100
Replica2:  99 → 99 (in progress)

2단계 — 두 번째 쓰기

Global checkpoint: 99
Primary:   99 → 100 → 101
Replica1:  99 → 100 → 101
Replica2:  99 → 99 (in progress)

3단계 — 뒤처진 replica가 따라잡음

Global checkpoint: 99 → 101
Primary:   99 → 100 → 101
Replica1:  99 → 100 → 101
Replica2:  99 → 99 → 100 → 101
  • primary와 replica1이 101까지 앞서가도, replica2가 99에 머물러 있는 동안 global checkpoint는 99에서 움직이지 않는다.
  • replica2가 101까지 따라잡은 뒤에야 global checkpoint가 101로 올라간다.
  • 즉 global checkpoint는 가장 뒤처진 in-sync 복사본이 결정한다. 이 지점까지는 모든 복사본이 동일함이 보장되므로, 장애 복구 시 어디부터 재전송해야 하는지의 기준이 된다.

동시성 처리가 왜 필요한가?

  • Elasticsearch에서 동시성 문제를 다루는 것은 데이터 일관성과 정확성을 보장하기 위해 반드시 필요하다. 특히 처리량이 많은 환경에서 중요하다.

문제 상황 — 갱신 유실(lost update)

  • 두 클라이언트가 같은 문서를 동시에 다룰 때 일어나는 일이다.
  • 각자 자기가 읽은 값(10)을 기준으로 계산했기 때문에, 나중 쓰기가 앞선 쓰기를 덮어쓴다.

Optimistic Concurrency Control (OCC)

  • OCC는 Elasticsearch가 여러 클라이언트의 동시 읽기는 허용하되, 특정 시점에 쓸 수 있는 클라이언트는 하나뿐임을 보장하는 방식이다.
  • if_primary_termif_seq_no 로 구현한다.
POST /store/_update/1?if_primary_term=1&if_seq_no=3
  • 성공 — 문서가 여전히 _seq_no: 3, _primary_term: 1이면 갱신된다.
  • 실패 — 그 사이 다른 클라이언트가 문서를 바꿔 _seq_no가 달라졌다면 거부되고 version_conflict_engine_exception 오류가 발생한다.
  • 즉 "내가 읽은 그 버전이 아직 그대로일 때만 써라"는 조건부 쓰기다.

Update multiple documents

  • 쿼리를 기준으로 문서들을 수정하려면 _update_by_query API를 사용한다.

전체 문서 수정 — 쿼리 없이

POST /store/_update_by_query
{
  "script": {
    "source": "ctx._source.stock -= 1"
  }
}
  • query를 생략하면 인덱스의 모든 문서에 스크립트가 적용된다.

조건에 맞는 문서만 수정 — 쿼리 지정

POST /store/_update_by_query
{
  "script": {
    "source": "ctx._source.stock -= 1"
  },
  "query": {
    "term": {
      "product": "peach"
    }
  }
}
  • query를 함께 주면 매칭된 문서에만 스크립트가 적용된다.
  • 여기서는 productpeach인 문서들의 stock만 1씩 줄어든다.

Delete multiple documents

  • 쿼리를 기준으로 문서들을 삭제하려면 _delete_by_query API를 사용한다.
POST /store/_delete_by_query
{
  "query": {
    "term": {
      "product": "apple"
    }
  }
}
  • productapple인 문서들이 삭제된다.

Bulk란

  • Elasticsearch의 bulk action은 여러 건의 index, update, delete, create 연산을 하나의 API 호출로 수행하는 방법이다.
  • 개별 요청을 여러 번 보내는 오버헤드를 줄여 효율적이다. 대용량 데이터 색인, 다건 문서 수정, 문서 일괄 삭제에 특히 유용하다.

네 가지 Action Type

Action 동작 문서가 이미 있으면
Index 문서를 추가하거나 갱신한다 교체(replace) 된다
Update 문서를 부분 수정한다 — (없으면 upsert로 생성 가능)
Delete ID로 문서를 삭제한다
Create 새 문서를 추가한다 실패한다
  • Index — 있으면 덮어쓰고 없으면 만든다. 존재 여부를 신경 쓰지 않을 때 쓴다.
  • Update — 지정한 필드만 바꾼다. 문서가 없을 때를 대비하려면 upsert를 함께 쓴다.
  • Delete — 문서 ID를 지정해 삭제한다.
  • Create — 새로 만드는 경우에만 성공한다. 중복 생성을 막고 싶을 때 쓴다.

📖 Java🔥

📖 Kotlin⭐

📖 Coroutine📎

📖 Spring🔥

📖 Spring Security⭐

📖 Spring Security OAuth2⭐

📖 Spring Batch📎

📖 Database🔥

📖 MySQL🔥

📖 Redis⭐

📖 JPA⭐

📖 QueryDsl📎

📖 MSA⭐

📖 Kafka⭐

📖 Apache Flink📎

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP🔥

📖 AWS⭐

📖 Docker⭐

📖 Kubernetes⭐

📖 Github Actions📎

📖 Jenkins📎

📖 Nginx⭐

📖 Monitoring📎

📖 Test(feat. Load Testing)📎

📖 Test(feat. Java)⭐

📖 Spring AI📎

📖 gRPC📎

  • [gRPC - Writing .proto Files with Protocol Buffers]
  • [gRPC - Various Communication Patterns in gRPC]
  • [gRPC - gRPC Optimization Techniques and Advanced Features]

📖 TDD(Test-Driven-Development)⭐

📖 PostgreSQL📎

  • [PostgreSQL - Docker만을 사용하는 경량화된 환경 구성 방법]
  • [PostgreSQL - PostgreSQL에서 제공하는 데이터 타입]
  • [PostgreSQL - PostgreSQI의 JSONB, 역인덱싱과 활용 방법]
  • [PostgreSQL - 데이터베이스 성능을 위한 최적화 패턴 및 전략]
  • [PostgreSQL - 트랜잭션과 ACID, Isolation 수준별 차이]
  • [PostgreSQL - Database Lock 교착상태와 읽기/쓰기 성능을 보장하는 MVCC 모델]
  • [PostgreSQL - pgvector와 벡터 저장, 유사도 검색 패턴 개념]
  • [PostgreSQL - 벡터 인덱스 최적화와 벡터 검색과 전문 검색 결합 패턴]
  • [PostgreSQL - PostgreSQL 플러그인]
  • [PostgreSQL - PostGIS - 공간 쿼리와 GIST 인덱스, 지리 타입과 공간 쿼리를 위한 타입과 기본 함수]
  • [PostgreSQL - pg_search - 검색 엔진 없이 텍스트 검색 구현과 주의사항]
  • [PostgreSQL - 단일 인스턴스 한계를 극복하는 분산 패턴과 스케줄링, 분산 환경 구축 방법]
  • [PostgreSQL - Citus - 분산 테이블과 분산 쿼리를 위한 Extension과 데이터 분산 처리]
  • [PostgreSQL - pg_cron - PostgreSQL로 구성하는 CronJob]
  • [PostgreSQL - 스케줄러 + 분산 처리를 동시에 도입하는 주기적 집계 쿼리 패턴]

📖 Workflow-Driven Techniques for Large-Scale Traffic Processing📎

  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka + Debezium을 활용한 CDC 패턴 설계]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Temporal을 활용한 워크플로우 패턴]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Docker와 경량 이미지를 활용한 환경 구축 방법]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Kafka에서의 메시지 Delivery Guarantee]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - 실시간 동기화의 핵심 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - MySQL Binary Log 기반의 CDC]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Binary Log 기반의 CDC 구현 플랫폼 Debezium이란?]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture]
  • [Workflow-Driven Techniques for Large-Scale Traffic Processing - Debezium Architecture Best Practice와 주의사항]

📖 Reactive Programming📎

📖 ElasticSearch📎

📖 Design Pattern📎

📖 Clean Spring📎

  • [Clean Spring - Domain-Driven Development]
  • [Clean Spring - Domain-Driven Development with Design Patterns]
  • [Clean Spring - Developing Membership Application with Hexagonal Architecture]
  • [Clean Spring - JPA and Domain Model Patterns]
  • [Clean Spring - Designing a Consistent Domain Model with Aggregates]
  • [Clean Spring - Web API Adapter]
  • [Clean Spring - Hexagonal Architecture: Ports]
  • [Clean Spring - Hexagonal Architecture: Application Components]
  • [Clean Spring - Test Improvement & Architecture Validation]
  • [Clean Spring - Developing Application Components]
  • [Real MySQL 8.0 - 인덱스]
  • [Real MySQL 8.0 - 실행 계획]
  • [Real MySQL 8.0 - 아키텍처]
  • [Real MySQL 8.0 - 트랜잭션과 잠금]

Clone this wiki locally