Home [엘라스틱서치 바이블] 2. 엘라스틱 서치 기본 동작과 구조
Post
Cancel

[엘라스틱서치 바이블] 2. 엘라스틱 서치 기본 동작과 구조

2. 엘라스틱 서치 기본 동작과 구조

  • 엘라스틱서치의 구조를 어느정도 먼저 이해한 상태에서 상세 내용을 학습해야 전체 학습에 깊이가 생긴다.
  • 이번 장을 마치고 나면 엘라스틱서치가 어떻게 동작하는지 큰 그림을 머릿속에 그릴수 있을것이다.

2.1 엘라스틱서치 기본 동작 빠르게 둘러보기

  • 실습 과정에서는 기본적으로 REST API를 호출해 진행한다.
  • Kibana에 접속하여 화면 상단의 햄버거 버튼을 클릭해 management 메뉴 아래의 dev tools 하위 메뉴로 들어가면 API의 자동완성도 해주기 떄문에 편리하게 실습할수 있다.

스크린샷 2023-11-18 오후 6 04 34

  • 문서에서는 Intellij Http Client을 이용해서 테스트를 진행한다.

2.1.1 문서 색인

_id를 지정하여 색인

1
2
3
4
PUT [인덱스 이름]/_doc/[_id값]
{
         [문서 내용]
}
1
2
3
4
5
6
7
8
9
10
PUT /my_index/_doc/2
Host: localhost:9200
Content-Type: application/json

{
  "title": "Hello world2",
  "views": 1234,
  "public": true,
  "created" : "2023-11-18T18:05:00.000Z"
}

_id를 지정하지 않고 색인

1
2
3
4
PUT [인덱스 이름]/_doc/
{
         [문서 내용]
}
1
2
3
4
5
6
7
8
9
10
POST /my_index/_doc
Host: localhost:9200
Content-Type: application/json

{
  "title": "Hello world3",
  "views": 1234,
  "public": true,
  "created" : "2023-11-18T18:06:00.000Z"
}

2.1.2 문서 조회

1
2
3
4
GET [인덱스 이름]/_doc/[_id값]
{
         [문서 내용]
}
1
2
GET /my_index/_doc/1
Host: localhost:9200

2.1.3 문서 업데이트

1
2
3
4
POST [인덱스 이름]/_update/[_id값]
{
         [문서 내용]
}
1
2
3
4
5
6
7
8
9
POST /my_index/_update/1
Host: localhost:9200
Content-Type: application/json

{
    "doc": {
      "title": "Hello world updated"
    }
}

2.1.4 문서 검색

1
2
GET [인덱스 이름]/_search
POST [인덱스 이름]/_search
  • 아래 예제는 match 쿼리를 사용한 요청이다.
1
2
3
4
5
6
7
8
9
10
11
GET /my_index/_search
Host: localhost:9200
Content-Type: application/json

{
  "query": {
    "match": {
      "title": "hello world"
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
  "took": 45,
  "timed_out": false,
  "_shards": {
	"total": 1,
	"successful": 1,
	"skipped": 0,
	"failed": 0
  },
  "hits": {
	"total": {
	  "value": 3,
	  "relation": "eq"
	},
	"max_score": 0.7026868,
	"hits": [
	  {
		"_index": "my_index",
		"_id": "1",
		"_score": 0.7026868,
		"_source": {
		  "title": "Hello world updated",
		  "views": 1234,
		  "public": true,
		  "created": "2023-11-18T18:04:00.000Z"
		}
	  },
	  {
		"_index": "my_index",
		"_id": "2",
		"_score": 0.110377684,
		"_source": {
		  "title": "Hello world2",
		  "views": 1234,
		  "public": true,
		  "created": "2023-11-18T18:05:00.000Z"
		}
	  },
	  {
		"_index": "my_index",
		"_id": "ZPyw4YsBcsUuSBAASzRQ",
		"_score": 0.110377684,
		"_source": {
		  "title": "Hello world3",
		  "views": 1234,
		  "public": true,
		  "created": "2023-11-18T18:06:00.000Z"
		}
	  }
	]
  }
}
  • title 값이 일치하지 않는 항목도 조회된것을 확인할수 있는데, _score에서 유사도 점수를 확인할 수 있다.
  • 단순히 주어진 텍스트와 매칭되는 문서를 찾는 것이 아니라 문서를 분석해서 역색인을 만들어두고 검색어를 분석해서 둘 사이의 유사도가 높은 문서를 찾을수 있다.

2.1.5 문서 삭제

1
DELETE [인덱스 이름]/_doc/[_id값]
1
2
3
DELETE /my_index/_doc/1
Host: localhost:9200
Content-Type: application/json

Response

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "_index": "my_index",
  "_id": "1",
  "_version": 3,
  "result": "deleted",
  "_shards": {
	"total": 2,
	"successful": 2,
	"failed": 0
  },
  "_seq_no": 4,
  "_primary_term": 1
}

2.2 엘라스틱서치 구조 개괄

스크린샷 2023-11-18 오후 6 27 07

  • 책에 있는 이미지와는 다르지만, 하나의 index의 문서가 여러 node에 복제본을 어떻게 유지하는지에 대한 모습이다.

엘라스틱서치 주요 용어

  • 문서(document) : 엘락스틱서치가 저장하고 색인을 생성하는 JSON 문서
  • 인덱스 : 문서를 모아 놓은 단위가 인덱스다. 클라이언트는 이 인덱스 단위로 ES에 검색을 요청하게 된다.
  • 샤드 : 인덱스는 그 내용을 여러 샤드로 분리하여 분산 저장한다. 또한 ES는 고가용성을 제공하기 위해 샤드의 내용을 복제해둔다. 원본 역할을 담당하는 샤드를 주 샤드(primary shard)라고 하고, 복제본을 복제본 샤드(replication shared)라고 한다.
  • _id : 인덱스 내 문서에 부여되는 고유한 구분자다. 인덱스 이름과 _id의 조합은 엘라스틱서치 클러스터 내에서 고유하다.
  • 타입 : ES는 과거에 하나의 인덱스 안에 있는 여러 문서를 묶어서 타입이라는 논리 단위로 나눴다. 현재는 이 개념은 폐기 됐으며 더 이상 사용되지 않는다.
    • 지금은 문서의 묶음을 논리적으로 구분해야 할 필요가 있다면 별도의 인덱스로 독립시켜야 한다. ES6부터 인덱스 하나에 타입 하나만 둘 수 있도록 제한되었다.
    • ES7부터 이와 관련된 api가 deprecated되었고, 타입 이름이 들어가야 하던 자리에는 기본값이 _doc이 들어간 API를 사요해야 한다.
  • 노드 : ES 프로세스 하나가 노드 하나를 구성한다. 노드 하나는 여러 개의 샤드를 가진다. 고용성을 제공하기 위해 같은 종류의 샤드를 같은 노드에 배치하지 않는다.
  • 클러스터 : ES 노드 여러개가 모여 하나의 클러스터를 구성한다.
  • 노드의 역할 : ES 노드는 데이터 노드, 마스터 노드, 조정 노드 등 여러 역할 중 하나 이상의 역할을 맡아 수행한다.
    • 데이터 노드 : 샤드를 보유하고샤드에 실제 읽기와 쓰기 작업을 수행하는 노드이다.
    • 마스터 노드 : 클러스터를 관리하는 중요한 역할 수행, 마스터 노드는 마스터 후보 노드 중에서 1대가 선출된다.
    • 조정 노드 : 클라이언트의 요청을 받아서 데이터 노드에 요청을 분배하고, 클라이언트에게 응답을 돌려주는 노드이다.

2.3 엘라스틱서치 내부 구조와 루씬

  • ES는 아파치 루씬을 코어 라이브러리로 사용하고 있다.
  • 루씬은 문서를 색인하고 검색하는 라이브러리이다.

2.3.1 루씬 flush

image

  • 문서 색인 요청이 들어오면 루씬은 문서를 분석해서 역색인을 생성한다.
  • 최초 생성 자체는 메모리 버퍼에 들어가고, 색인 & 업데이트 & 삭제 등의 작업이 수행되면 루씬은 이러한 변경들을 메로리에 들고 있다가 주기적으로 디스크에 flush 한다.
  • 루씬은 색인한 정보를 파일로 저장하기 때문에 루씬에서 검색을 하려면 먼저 파일을 열어야 한다.
  • 엘라스틱서치는 내부적으로 루씬의 DirectoryReader라는 클래스를 이용해 파일을 열고 루씬의 색인에 접근할 수 있는 IndexReader 객체를 얻는다.
  • DirectoryReader.openIfChanged를 호출해 변경 사항이 적용된 새 IndexReader를 열어 준 뒤 기존 IndexReader를 안전하게 닫는다.
    • ES에서는 refresh라고 부름.

2.3.2 루씬 commit

  • 루씬의 flush는 시스템의 페이지 캐시에 데이터를 넘겨주는 것까지만 보장할 뿐 디스크에 파일이 실제로 안전하게 기록되는것까지는 보장하지 않는다. 따라서 루씬은 fsync 시스템 콜을 주기적으로 커널 시스템의 페이지 캐시의 내용과 실제로 디스크에 기록된 내용의 싱크를 맞추는 작업을 수행한다.
    • 이를 루씬의 commit이라고 한다.
  • ES의 flush 작업은 이 루씬의 commit을 거친다. 루씬의 flush와 ES의 flush는 다른 개념이므로 혼동하지 말아야 한다.
  • ES flush는 refresh 보다 훨씬 비용이 많이 드는 작업이다. 적절한 주기로 실행됨.

image

  • 간혹 fsync 중에도 하드웨어 캐시를 사용하는 일부 저장 장치가 있다. 루씬은 그러한 장비를 사용하는 환경에서는 하드웨어 실패가 나는 상황까지 데이터 정합성을 보장하지는 않는다.

2.3.3 세그먼트

  • 앞의 작업을 거쳐 디스크에 기록된 파일들이 모이면 세그먼트라는 단위가 된다.(세그먼트가 루씬의 검색 대상)
  • 세그먼트 자체는 불변(immutable)인 데이터로 구성되어 있다.
  • 문서 생성시 새로운 세그먼트를 만들지만, 기존 문서를 삭제하는 경우 삭제 플래그만 표시하고, 업데이트의 경우에도 기존 문서에 삭제 플래그를 표시하고, 새 세그먼트를 생성한다.
  • 루씬의 검색은 모든 세그먼트를 대상으로 수행되는데 불변인 세그먼트 개수를 무작정 늘려갈 수 없기 때문에 중간중간 적당한 세그먼트 병합을 수행한다.
    • 이 과정에서 삭제된 플래그가 표시된 데이터를 삭제하는 작업도 수행된다.
    • 세그먼트 병합은 비용이 비싸지만, 병합을 하고나면 검색 성능 향상을 기대해볼 수 있다.
    • forcemerge API를 통해 명시적으로 세그먼트 병합을 수행할 수도 있다.
      • 더 이상 추가 데이터 색인이 없을것이 보장될떄만 수행하는것을 권장한다.
      • 크기가 매우 큰 세그먼트가 이미 존재하는 상황에서 추가적인 데이터 변경이 일어나면 추가된 작은 세그먼트는 자동 세그먼트 병합 대상 선정에서 영원히 누락될 수 있음.

2.3.4 루씬 인덱스와 엘라스틱서치 인덱스

  • 여러 세그먼트가 모이면 하나의 루씬 인덱스가 된다.
  • ES 샤드는 이 루씬 인덱스 하나를 래핑(warp)한 단위이다.
  • ES 샤드 여러개가 모이면 ES 인덱스가 된다.
  • ES 레벨에서는 여러 샤드에 있는 문서를 모두 검색해볼 수 있다.

ES가 하는 주된 역할

  • 새 문서가 들어오면 해당 내용을 라우팅하여 여러 샤드에 분산시켜 저장/색인
  • 이후 클라이언트가 검색요청을 보내면 각 샤드를 대상을 검색을 하고, 그 결과를 모아 병합하여 최종 응답을 생성한다.
  • 이런 구조를 통해 루씬에서 불가능한 분산 검색을 ES레벨에서는 가능하게 만든것이다.

2.3.5 translog

  • 엘라스틱서치에 색인된 문서들은 루씬 commit까지 완료되어야 디스크에 안전하게 기록된다.
  • 그렇다고 문서 변경사항이 있을때마다 루씬 commit을 수행하기에는 비용이 많이 든다. 또 변경사항을 모아서 commit한다면, 장애가 발생할 때 미처 commit되지 않은 데이터가 유실될 우려가 있다.
  • ES에서는 위와 같은 문제를 방지하기 위해 translog라는 이름의 작업 로그를 남긴다.

translog 과정

  • translog는 색인, 삭제 작업이 루씬 인덱스에 수행된 직후에 기록된다.
  • translog 기록까지 끝난 이후에야 작업 요청이 성공으로 승인된다.

ES 장애복구과정에서 translog 활용

  • ES에 장애가 발생한 경우 ES 샤드 복구 단계에서 translog를 읽는다.
  • translog 기록은 성공했지만 루씬 commit에 포함되지 못했던 작업 내용이 있다면 샤드 복구 단계에서 복구된다.
  • translog가 너무 커지면 샤드 복구에 시간이 오래 걸리게 되는데, 이르 방지하기 위해 translog의 크기를 적절히 유지해줄 필요가 있다.

translog와 ES flush

  • ES flush는 루씬 commit을 수행하고, 새로운 translog를 만드는 작업이다.
  • ES flush가 백그라운드에서 주기적으로 수행되며, translog의 크기를 적절한 수준으로 유지한다.

translog fsync 주기 관련 옵션

  • 일반적인 상황에서는 설정하지 않는 옵션이지만, index.translog.sync_interval 옵션을 설정한 경우 특정 주기에 의해서만 translog fsync를 수행할 수 있다.
  • translog fsync 기본 값은 5초인데, 이 사이에 문제가 발생하면 데이터가 유실될 수 있다.

Reference

  • .
This post is licensed under CC BY 4.0 by the author.

[엘라스틱서치 바이블] 1. 엘라스틱 서치 소개

[엘라스틱서치 바이블] 3. 인덱스 설계 - 1

Comments powered by Disqus.