Elasticsearch 란?
Elasticsearch는 분산형 검색 및 분석 엔진으로 JSON 문서를 저장하고 검색 및 분석할 수 있게 해주는 오픈소스형 NoSQL 데이터베이스입니다. 아파치 루씬을 기반으로 구축되었으며 대용량 데이터에서 초고속 검색을 할 수 있도록 설계 되어 있습니다.
RestAPI를 지원하며 HTTP을 통해 CRUD(Create, Read, Update, Delete) 작업이 가능합니다.
인덱스(Index) 란?
Elasticsearch에서 데이터를 저장하는 기본 단위 입니다. RDB에서 테이블과 유사한 개념이지만 내부적으로 역색인 구조를 사용합니다.
하나의 인덱스는 여러 개의 샤드로 분할되어 저장됩니다. 예를들어서 책(Books) 이라는 인덱스를 만든다면 이렇게 만들 수 있습니다.
curl -X PUT "http://localhost:9200/books" -H "Content-Type: application/json" -d '
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"author": {
"type": "keyword"
},
"published_year": {
"type": "integer"
},
"genre": {
"type": "keyword"
}
}
}
}'
books 라는 인덱스를 만드는데 책의 속성을 정의한 것입니다.
도큐먼트(Document) 란?
도큐먼트는 RDB로 비유하자면 테이블에 저장되는 하나의 Row(행)과 같은 구조입니다.
curl -X POST "http://localhost:9200/books/_doc/1" -H "Content-Type: application/json" -d '
{
"title": "햄릿",
"author": "윌리엄 셰익스피어",
"published_year": 1603,
"genre": "비극"
}'
이렇게 쿼리하면 Books 라는 인덱스에 하나의 도큐먼트(책)이 저장됩니다.
샤드(Shard) & 레플리카(Replica)
샤드는 엘라스틱서치에서 색인을 위해 데이터를 나누어 저장하는 형식입니다. 예를들면 Books 라는 큰 데이터를 여러개로 쪼개어 저장하는 형식을 생각하면 쉽습니다. 각각의 샤드에는 루씬의 역색인을 적용할 수 있고, 샤드가 여러 개라서 병렬 색인 및 검색이 가능합니다.
샤드는 각각의 노드에 나누어 저장됩니다. 각 샤드는 중요한 데이터이기 때문에 복제본(Replica)를 가지고 있습니다. 그리고 레플리카 샤드는 원본 샤드가 있는 노드에 배치되지 않는 특성을 가지고 있습니다.
📖 예)Books 백업 - (레플리카 샤드 배포)
노드1 → 샤드1(기본), 샤드4(Replica)
노드2 → 샤드3(기본), 샤드5(Replica)
노드3 → 샤드5(기본), 샤드1(Replica)
→ 특정 노드가 다운되더라도 Replica 덕분에 데이터를 잃지 않음!
정리하자면 샤드&레플리카를 사용함으로써 데이터 유실 방지, 병렬색인, 병렬검색, 읽기 성능 개선이 가능하기에 고성능 검색이 가능합니다.
ElasticSearch의 노드 구조
Elasticsearch는 마스터노드, 워커노드로 구성되어 있습니다. 싱글노드로 마스터+워커 노드를 동시에 수행할 수도 있습니다.
마스터 노드 -> 클러스터 전체 관리
워커노드1 -> 샤드1,2,3 + 샤드5 레플리카
워커노드2 -> 샤드4,5 + 샤드1 레플리카
이런식으로 각각의 노드는 샤드 + 레플리카 형태로 데이터를 보관하고 있습니다.
세그먼트란(Segment)?
엘라스틱서치가 데이터를 색인할 때 먼저 메모리 버퍼를 통해서 색인 되면서 이를 세그먼트 형태로 디스크에 flush 하게 됩니다. 이 세그먼트들은 Immutable(불변성) 특성을 가지고 있어서 한번 플러시된 세그먼트는 디스크에 영구 저장되며 수정이 불가합니다. 즉 세그먼트는 샤드의 조각이라고 생각하면 쉽습니다.
├── 샤드 1
│ ├── 세그먼트 A
│ ├── 세그먼트 B
│ ├── 세그먼트 C
├── 샤드 2
│ ├── 세그먼트 D
│ ├── 세그먼트 E
각 노드에는 샤드가 저장되어 있고 이 샤드를 구성하는 것은 세그먼트입니다. 이 세그먼트 조각이 많으면 많은 세그먼트를 탐색 해야 하기에 검색성능이 저하될 수 있습니다. 이러한 세그먼트를 병합하면 검색 속도를 높일 수도 있습니다.
├── 기존 세그먼트
│ ├── Segment A (100MB)
│ ├── Segment B (150MB)
│ ├── Segment C (120MB)
├── 병합 후 세그먼트
│ ├── Segment D (370MB) ✅ (병합 완료)
역색인(Inverted Index)란?
엘라스틱서치는 역색인 구조를 사용합니다. 위에서 설명한 샤드는 루씬의 인덱스(역색인 구조)를 가지고 있습니다. 각각의 샤드마다 역색인 구조를 가지고 있기 때문에 병렬 검색이 가능합니다.
역색인 구조는 단어 -> 문서로 매핑하는 방식입니다. 예를들면
단어 → 문서 ID
---------------
검색 → Doc1, Doc3, Doc7
엔진 → Doc2, Doc3, Doc6
Elasticsearch → Doc1, Doc4
이런 방식으로 저장되기 때문에 어떤 문서에 검색한 단어가 포함되어 있는지 빠르게 찾을 수 있습니다.
TF-IDF 알고리즘
근데 위의 역색인 구조를 살펴보면 너무 자주 등장하는 단어는 어떻게 처리할까요? 지금 글에서 보듯이 "어떻게", "그래서", "왜냐하면", "해당" 이러한 단어들은 너무 자주 등장하기 때문에 문서에서 중요도가 낮겠지요?
TF(단어빈도수)
단어 빈도수는 기본적으로 해당 문서에서 단어가 많이 등장할수록 더 중요한 단어라고 판단하는 방식입니다.
TF는 해당 문서에서 단어t의 등장 횟수 / 해당 문서의 총 단어수 입니다.
예를들어 Elasticsearch is a powerful search engine 이라는 문서에서 search 단어가 2번 등장 했다면 2/7 = 0.285 가 됩니다.
특정 문서에서 단어가 많이 등장할수록 더 중요한 단어라고 간주합니다.
IDF(역문서 빈도수)
IDF는 역문서 빈도수입니다. 특정 단어가 전체 문서에서 얼마나 드물게 등장하는지 측정합니다.
IDF가 필요한 이유는 "그래서", "해당", "합니다" 이러한 키워드들은 거의 모든 문서에 등장하지만 검색할 때 중요한 단어가 아닙니다. 그래서 자주 등장하는 흔한 단어의 중요도를 낮추고, 드물게 등장하는 단어의 중요도를 높이는 역할을 하는 것이 IDF입니다.
N은 전체 문서의 수
df(t)는 특정 단어 t가 등장한 문서 수
IDF 값이 클수록 해당 단어가 희귀함을 의미합니다.
예를들어서 10,000개의 문서가 있다고 가정합니다.
Elasticsearch는 50개의 문서에서만 등장합니다.
"The"는 9500개의 문서에서 등장합니다.
계산해보면, Elasticsearch의 IDF 값은
the의 IDF 값은
Elasticsearch의 단어가 IDF 값이 더 높기 때문에 중요한 단어라고 판단됩니다.
the 같은 흔한 단어는 IDF 값이 낮아지기 때문에 가중치가 낮아지게 됩니다.
이제 IDF가 높은(희귀한 단어)가 포함된 문서중에서 TF(단어빈도)가 높은 문서가 희귀한 단어가 많은 문서이기 때문에 높은 가중치를 받게 되어 중요한 문서라고 판단하게 됩니다.
이러한 알고리즘이 뒤에 있기 때문에 검색한 단어에 대해 유사한 문서들만 잘 골라서 가져올 수 있는 것입니다.
최종정리
Elasticsearch는 Lucene 기반의 분산형 검색 및 분석 엔진이고, Json 형태로 문서를 저장한다. 샤드와 세그먼트를 활용하여 데이터를 분산 저장하고, 레플리카를 통해 데이터를 보장한다. 각각의 샤드마다 역색인 구조를 가지고 있기 때문에 병렬 초고속 검색이 가능하다. Restful API를 통해 CRUD를 수행할 수 있으며 기본적으로 TF-IDF 알고리즘을 사용하여 문서 중요도를 계산한다. 하지만 최근 버전에서는 BM25 알고리즘을 기본 검색 점수 계산 방식으로 채택하고 있다. 이는 또 나중에 자세히 다뤄보도록 하겠습니다.
'ELK' 카테고리의 다른 글
[ECK] Kubernetes 환경에서 Elasticsearch 설치 (0) | 2025.03.15 |
---|