도서 요약 / / 2022. 12. 22. 20:08

03. Managing Documents

complete guide to elasticsearch를 정리한 자료입니다.
https://www.udemy.com/course/elasticsearch-complete-guide/

Documents 관리하기

18. Indices 생성 및 삭제

생성

PUT products
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 2
  }
}

삭제

DELETE products

19. Documents 인덱싱

POST prodcts/_doc
{
  "name": "Coffee Maker",
  "price": 64,
  "in_stock": 10
}

결과

{
  "_index" : "prodcts",
  "_type" : "_doc",
  "_id" : "sRA_l4IBt484IutFSnQZ",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 7,
  "_primary_term" : 1
}

아래와 같이 수정을 하는 경우에도 새로 생성이 된다.

PUT products/_doc/100
{
  "name": "Toaster",
  "price": 49,
  "in_stock": 10
}

action.auto_create_index setting에서 존재하지 않는 문서를 새로 추가할 것인지 설정이 된다. (default: true)

20. document의 ID로 조회하기

조회

GET products/_doc/100

결과

{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "100",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "Toaster",
    "price" : 49,
    "in_stock" : 10
  }
}

21. Documents 수정

기존 필드 수정

POST products/_update/100
{
  "doc": {
    "in_stock": 3
  }
}

없는 필드를 수정하는 경우에도 사용할 수 있다.

POST products/_update/100
{
  "doc": {
    "tags": ["electronics"]
  }
}

Documents는 immutable하다.

  • Elasticsearch Documents는 immutable하다. 기존 document를 수정할 수 없다.
  • 기존 문서를 수정하는 경우는? 사실은 수정이 아니라 새로 만들었다.
  • Update API는 문서를 update하는 것처럼 보인다. (Create -> delete)
  • Update API는 단순하고 네트워크 트래픽을 줄여준다.
    • 문서가 저장된 샤드 내에 1번의 요청만 발생,
    • update api가 없다면 2번 요청을 해야 한다. (create -> delete)

Update API는 어떻게 동작하는가?

  • 현재 문서가 조회된다.
  • 필드값이 변경된다.
  • 기존 문서는 수정된 문서로 대체된다.
  • Application 수준에서 동일하게 작업할 수 있다.

22. Script로 수정하기

  • ctx는 context의 약어이다.
  • _source를 통해 기존 문서에 접근할 수 있다.
POST products/_update/100
{
  "script": {
    "source": "ctx._source.in_stock--"
  }
}

기존 필드에 값을 대입하려면

POST products/_update/100
{
  "script": {
    "source": "ctx._source.in_stock = 10"
  }
}

params를 통해 값을 대입받을 수 있다.

POST products/_update/100
{
  "script": {
    "source": "ctx._source.in_stock -= params.quantity",
    "params": {
      "quantity": 4
    }
  }
}

_update api는 변경사항이 없으면 result가 noop이다.

하지만 scripted update는 항상 updated로 표시가 된다.

source에 조건문을 추가하려면

POST products/_update/100
{
  "script": {
    "source": """
      if (ctx._source.in_stock == 0) {
        ctx.op = 'noop';
      }

      ctx._source.in_stock--;
    """,
    "params": {
      "quantity": 4
    }
  }
}

in_stock이 0보다 적으면 result가 noop이고 그렇지 않으면 updated로 표시될 것이다.

혹은 아래와 같이 표현할 수 있다.

POST products/_update/100
{
  "script": {
    "source": """
      if (ctx._source.in_stock > 0) {
        ctx._source.in_stock--;
      }
    """,
    "params": {
      "quantity": 4
    }
  }
}

또는 in_stock이 1보다 적으면 문서를 delete하게 하는 방법이다.

POST products/_update/100
{
  "script": {
    "source": """
      if (ctx._source.in_stock < 1) {
        ctx.op = 'delete';
      }

      ctx._source.in_stock--;
    """,
    "params": {
      "quantity": 4
    }
  }
}

23. Upsert

upsert는 문서가 존재하는지 여부에 따라 조건적으로 update하거나 insert하는 것을 의미한다.

POST products/_update/101
{
  "script": {
    "source": "ctx._source.in_stock++"
  },
  "upsert": {
    "name": "Blender",
    "price": 399,
    "in_stock": 5
  }
}

위의 스크립트를 실행하면 101문서는 존재하지 않기 때문에 새로 생성이 될 것이다.

다시 한번 실행이 되면 source에 있는 스크립트가 실행이 될 것이다.

24. Replacing Documents

문서의 내용을 변경하는 기능이다.

기존에 있던 모든 컬럼은 수정하는 문서의 내용으로 변경이 된다.

PUT products/_doc/100
{
  "name": "Toaster",
  "price": 79,
  "in_stock": 10
}

25. Deleting Documents

DELETE products/_doc/101

26. Routing의 이해

Routing 소개

  • Elasticsearch가 문서를 어디에 저장할지를 어떻게 알 수 있는가?
  • 문서는 인덱싱 된 후 어떻게 찾을 수 있는가?
  • 그 해답은 Routing이다.
  • Routing은 문서의 샤딩을 찾는 과정이다.

shard_num = hash(_routing) % num_primary_shards

_routing은 기본적으로 문서의 ID이다.

Custom routing

  • Routing은 Elasticsearch를 사용할 때 100% 투명하다.
  • Elasticsearch를 사용하고 배우기 쉽다.
  • 여러가지 목적으로, routing을 커스트마이징하는 것이 가능하다.
  • 기본 routing 전략은 문서들을 골고루 분배되는 것을 보장한다.
  • shard의 수는 인덱스가 생성된 후에는 변경될 수 없다.
    • shard의 수가 변경되면 routing되는 shard가 변경되어서 문서가 제대로 찾을 수 없기 때문이다.
    • Shard 수를 변경하려면 인덱스를 새로 만들어야 한다. (Reindex)

27. Elasticsearch가 데이터를 어떻게 읽는가?

  • 읽기 요청을 받는 노드는 요청을 처리하는 책임을 갖고 이 노드는 코디네이팅 노드라고 한다.
  • 첫번째 단계는 문서가 어디에 저장되어 있는지 찾는 것이다.
  • 이는 라우팅을 통해 해결된다.

  • Elasticsearch가 primary 샤드에서 직접 요청을 받는다면, 모든 요청은 동일 샤드에서 처리될 것이다.
  • 복제노드에서 요청을 받으면 elasticsearch는 Adaptive Replica Selection(ARS)라고 불리는 기술을 사용한다.
  • ARS는 최적의 복제본을 선택하는 것이다.
  • ARS는 응답시간을 줄여준다.
  • 코디네이팅 노드는 응답을 모아서 클라이언트로 보낸다.

28. Elasticsearch가 데이터를 어떻게 저장하는가?

  • 복제 그룹내의 샤드에 요청을 라우팅 하지 않고 항상 primary 샤드로 라우팅한다.
  • primary 샤드는 요청을 검증하는 책임을 가지고 있다.
  • 요청의 구조를 검증하고 필드 값을 검증한다.
  • 예를 들어 숫자 필드에 객체 유형을 추가할 때 에러를 유발할 수 있다.
  • 그리고 나서, primary 샤드는 복제 샤드로 복제하기 전에 로컬에 쓰기작업을 수행한다.
  • 성능향상을 위해서 primary 샤드는 복제샤드로 병렬로 전달한다.
  • 복제노드에 복제될 수 없더라도 이 동작은 성공한다.

Primary terms

  • 이전 샤드와 새로운 primary 샤드를 구분하는 방법
  • primary 샤드가 얼마나 많이 변경되었는지에 대한 수치
  • primary term은 쓰기 작업에서 추가된다.

현재 Primary 샤드가 다운되고 다른 샤드가 primary 샤드가 되면 Primary Term이 증가한다.

Sequence numbers

  • primary term과 같이 쓰기 동작에서 추가된다.
  • 쓰기 작업에 증가된 수치
  • primary 샤드는 sequence number를 증가시킨다.
  • elasticsearch가 쓰기 작업을 가능하게 한다.

primary 샤드가 실패할 때 복구하기

  • Primary terms와 sequence number는 elsticsearch가 primary 샤드의 다운으로부터 복구하는데 필요한 키이다.
    • elasticsearch가 쓰기 동작이 어디에서 적용되어야 할지 알 수 있다.
  • 대규모 인덱스에서 이 과정은 비싼 작업일 수 있다.
    • 속도를 증가시키기 위해 elasticsearch는 checkpoint를 사용한다.

global/local checkpoints

  • Sequence number
  • 각 복제 그룹은 global checkpoint를 가지고 있다.
  • 각 복제 샤드는 local checkpoint를 가지고 있다.
  • global checkpoint
    • 복제그룹 내의 모든 active 샤드에서 sequence number는 오름차순으로 정렬되어 있다. (이는 seqeunce number를 포함하는 모든 동작은 global checkpoint보다 낮다는 뜻이다)
  • local checkpoints
    • 마지막 쓰기 동작이 수행된 sequence number

29. Document versioning 이해하기

versioning 소개

  • 문서의 리비전 이력이 아니다.
  • elasticsearch는 모든 문서에 _version 메타데이터 필드를 저장한다.
    • 값은 interger이다.
    • 문서를 수정할 때 1씩 증가한다.
    • 문서를 삭제할 때 값은 60초동안 유지된다.
      • 새 문서를 만들 때 60초 안에 동일한 ID로 만들면 _version이 유지된다.
      • index.gc_deletes 설정
    • _version필드는 문서를 조회할 때 출력된다.
{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "101",
  "_version" : 4,
  "_seq_no" : 6,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "Blender",
    "price" : 399,
    "in_stock" : 8
  }
}
  • 기본 versioning 타입은 internal 버저닝이라고 한다.
  • external 버저닝 타입도 있다.
    • 버전이 Elasticsearch 외부에서 관리될 때 유용하다.
    • 예) 문서가 RDBMS에 저장될 때

versioning의 요점은 무엇인가?

  • 문서가 얼마나 많이 수정되었는가 확인할 수 있다.
    • 유용하지는 않을 수 있다.
  • 버저닝은 더이상 사용되지 않고 이전에 있었던 것이다.
  • 이전에는 optimistic 동시성 제어를 하기 위한 방법이었다.
    • 지금은 더 나은 방법이 있다.
  • 이 필드는 이전 버전을 사용하는 클러스터에서 확인할 수 있을 것이다.

30. 낙관적 동시성 제어 (Optimistic concurrency control)

소개

  • 동시 작업으로 인한 의도치 않은 문서 수정을 방지한다.
  • 이런 일이 발생할 많은 시나리오가 있다.
    • 예) 웹 애플리케이션에서 동시 방문자 처리

  • elasticsearch는 위의 시나리오가 잘못된 것으로 인지하지 못하고 in_stock 필드를 5로 업데이트를 한다.
  • 어떻게 수정할 수 있는가?
  • 문서를 조회한 이후에 수정이 되었다면 수정작업이 실패할 필요가 있다.

  • 과거 방식은 update 요청 시 _version 필드를 사용하는 것이다.
  • version정보가 저장된 version과 맞지 않을 경우 Update 작업은 실패할 것이다.
  • 이러한 접근 방식은 대부분 잘 동작하지만 잘못된 경우가 있다.
  • primary terms와 sequence number가 해결하는 문제들이 더 많다.
  • 그래서 새로운 접근 방식은 primary terms와 sequence number를 사용하는 것이다.

  • elasticsearch는 기존 문서가 의도치 않게 수정하는 것을 방지하도록 primary_term과 seq_no을 사용한다.

아래 문서를 조회하면

GET products/_doc/100

다음과 같은 결과가 출력된다.

{
  "_index" : "products",
  "_type" : "_doc",
  "_id" : "100",
  "_version" : 1,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "Toaster",
    "price" : 79,
    "in_stock" : 10
  }
}

위에서 _seq_no과 _primary_term값으로 문서를 수정하자.

POST products/_update/100?if_primary_term=1&if_seq_no=1
{
  "doc": {
    "in_stock": 123
  }
}

문서가 정상적으로 수정이 된다.

한번 더 실행해보면 수정이 실패하는 것을 알 수 있다.

{
  "error" : {
    "root_cause" : [
      {
        "type" : "version_conflict_engine_exception",
        "reason" : "[100]: version conflict, required seqNo [1], primary term [1]. current document has seqNo [8] and primary term [1]",
        "index_uuid" : "Ige3pk0ZRLacmIoeV1wDFg",
        "shard" : "0",
        "index" : "products"
      }
    ],
    "type" : "version_conflict_engine_exception",
    "reason" : "[100]: version conflict, required seqNo [1], primary term [1]. current document has seqNo [8] and primary term [1]",
    "index_uuid" : "Ige3pk0ZRLacmIoeV1wDFg",
    "shard" : "0",
    "index" : "products"
  },
  "status" : 409
}

실패를 어떻게 처리할 수 있는가?

  • 애플리케이션 레벨에서 이 상황을 처리한다.
    • 문서를 다시 조회한다.
    • 새로운 update 요청에 _primary_term과 _seq_no을 사용한다.

31. update by query

소개

  • 한번에 한개의 문서를 업데이트하는 방법을 배웠다.
  • 단일 쿼리로 여러 문서를 업데이트 해보자.
    • RDBMS에서 WHERE절에서 UPDATE하는 것과 유사하다.
  • 쿼리는 방금 배웠던 3가지 개념을 사용한다.
    • Primary terms
    • Sequence numbers
    • 낙관적 동시성 제어(Optimistic concurrecy control)
  • 어떤 사람이 여러 개의 상품을 구입했고 "in_stock"을 줄여야 한다고 가정하자.
  • 각 문서를 업데이트 할수 있지만 한번의 쿼리로 업데이트 하다고 하자.
  • 이를 위해 update_by_query를 사용할 수 있다.
POST products/_update_by_query
{
  "script": {
    "source": "ctx._source.in_stock--"
  },
  "query": {
    "match_all": {}
  }
}
{
  "took" : 46,
  "timed_out" : false,
  "total" : 2,
  "updated" : 2,
  "deleted" : 0,
  "batches" : 1,
  "version_conflicts" : 0,
  "noops" : 0,
  "retries" : {
    "bulk" : 0,
    "search" : 0
  },
  "throttled_millis" : 0,
  "requests_per_second" : -1.0,
  "throttled_until_millis" : 0,
  "failures" : [ ]
}
  • 2개의 문서가 수정되었음을 알 수 있다.

update_by_query 실행 시 내부적으로 어떤 일이 일어나는지 알아보자.

  • update_by_query가 처리될 때 index의 snapshot이 만들어진다.
  • snapshot이 만들어질때 쿼리와 맞는 문서를 찾기 위해서 각 쿼리가 인덱스 샤드에 보내진다.
  • 검색 쿼리가 문서와 일치할 때마다 bulk request가 문서를 update하기 위해 보내진다.
  • 쿼리는 수천개의 결과를 처리하기 위해서 내부적으로 scroll API를 사용한다.
  • 검색(search)과 bulk request가 순차적으로 보내진다. (한번에 하나씩)
  • 모든 것을 동시에 하지 않는 이유는 에러를 어떻게 처리하는냐와 관련이 있다.
  • 검색과 bulk쿼리를 수행 중 에러가 있다면 elasticsearch는 자동으로 10번 재시도한다.
  • 재시도 수는 "retries"에 표시되어 있다.
  • query가 정상적이지 않다면 전체 쿼리가 취소된다.
  • 에러가 발생할 때 많은 문서가 수정된다면 요청이 실패하더라도 수정된 상태로 남아있게 된다.
  • 많은 데이터베이스에서 보았던 트랜잭션으로 실행되지 않는다.
  • API에서 부분적으로 성공/실패를 확인할 수 있다.

snapshot이 어떻게 사용되는가

  • snapshot이 만들어진 후 변경된 것을 덮어쓰는 것을 방지한다.
    • 많은 문서를 업데이트 한다면 오래 걸릴 수도 있다.
  • 각 문서의 primary term과 sequence number가 사용된다.
    • 문서는 값이 snapshot과 같은 것일 때만 수정된다.
    • 알고 있는 것지만, 이런 방식을 낙관적 동시성 제어 (optimistic concurrency control)라고 한다.
  • 버전의 #은 version_conflicts 키에서 확인할 수 있다.
POST products/_update_by_query
{
  "conflicts": "proceed", 
  "script": {
    "source": "ctx._source.in_stock--"
  },
  "query": {
    "match_all": {}
  }
}

conflicts는 버전 충돌 시 진행할지 여부이다.

32. Delete by query

소개

  • 단일 쿼리로 여러개의 문서를 삭제해보자.
  • 쿼리는 update by query API와 매우 유사하다.
{
  "query": {
    "match_all": {}
  }
}

33. Batch processing

Bulk API 소개

  • 하나의 쿼리로 여러 문서에 특정 동작을 수행할 수 있는 방법을 알아보자.
  • Bulk API는 NDJSON 명세를 사용한다.
  • action_and_metadata \n optional_source\n action_and_metadata\n optional_source\n

새 문서를 인덱싱해보자.

POST _bulk
{ "index": { "_index": "products", "_id": 200 } }
{ "name": "Espresso Machine", "price": 199, "in_stock": 5 }
{ "create": { "_index": "products", "_id": 201 } }
{ "name": "Milk Frother", "price": 149, "in_stock": 14 }
  • index와 create의 차이점은 무엇인가?
    • create는 문서가 존재한다면 실패한다.
    • index는 실패하지 않는다. 즉, 문서가 없다면 추가되지만 있다면 수정이 된다.

수정 및 삭제를 해보자.

products에 bulk를 하는 것이므로 url에 products를 추가하면 _index 필드는 필요가 없어진다.

POST products/_bulk
{ "update": { "_id": 201 } }
{ "doc": { "price": 129} }
{ "delete": { "_id": 200 } }

알아야 할 내용 (1/3)

  • HTTP content-Type 헤더가는 다음과 같이 설정되어야 한다.
    • Content-Type: application/x-ndjson
    • application/json은 잘못된 방법이다.
  • Console tool은 알아서 처리해준다.
    • elasticsearch SDK 또한 알아서 처리해준다.
    • HTTP client를 사용할 때는 직접 명시해야 한다.

알아야 할 내용 (2/3)

  • 각 라인은 new line 문자로 끝나야 한다. (\n 혹은 \r\n)
    • 마지막 라인은
      • 텍스트 에디터에서, 마지막 라인은 공백이어야 한다는 것을 나타낸다.
    • console tool에서는 자동으로 처리된다.
    • 일반적으로 bulk 파일을 만드는 script에서는 이러한 것들을 처리해야 한다.
    • 마지막 라인에 \n 혹은 \r\n으로 끝나지 말아야 한다.

알아야 할 내용 (3/3)

  • 하나의 액션이 실패가 되면 다른 액션에 영향을 주지 않는다. 정상실행이 된다.
    • bulk request 전체가 실패한 것으로 하지 않는다.
  • bulk api는 각 액션에 대한 상세 정보를 리턴한다.
    • 액션이 성공했는지 알아보려면 items 키를 확인하라.
      • 순서는 요청된 액션과 동일하다.
    • errors 키는 에러가 발생할 때 알려준다.

bulk API를 언제 사용해야 할까

  • 동시에 많은 쓰기 작업을 수행할 때
    • 예) 많은 데이타 임포트 혹은 수정
  • bulk API는 개별 쓰기 요청을 보내는 것보다 더 효율적이다.
    • 많은 네트워크 라운드트립을 피할 수 있다.

2가지 더

  • Routing은 문서 샤드를 결정할 때 사용된다.
    • routing은 필요하다면 커스트마이징 될 수 있다.
  • Bulk API는 낙관적 동시성 제어(optimistic concurrency control)를 지원한다.
    • action metadata 내에 if_primary_term과 if_seq_no을 포함하라.

34. cURL로 데이터 임포트하기

products-bulk.json 파일을 products 인덱스로 임포트

curl -H "Content-Type: application/x-ndjson" -XPOST http://localhost:9200/products/_bulk --data-binary "@products-bulk.json"

제대로 임포트 되었는지 확인

GET _cat/shards?v
index                  shard prirep state       docs   store ip         node
products               0     p      STARTED     1000 395.1kb 172.20.0.2 02743a720ed3
products               0     r      UNASSIGNED                          

총 1000개의 문서가 임포트되었다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유