Elasticsearch는 오픈 소스 분산, RESTful 검색 및 분석 엔진입니다. 확장 가능한 데이터 저장소이자 벡터 데이터베이스로 활용됩니다.
Elasticsearch는 크게 2가지 용도로 활용됩니다.
로그와 같은 대규모 데이터를 수집하고 분석하는 데 최적화되어 있습니다. 일반적으로 다음 구성 요소들과 함께 사용됩니다:
- Elasticsearch: 데이터 저장
- Logstash: 데이터 수집 및 가공
- Kibana: 데이터 시각화
- 대규모 데이터에서도 뛰어난 검색 속도 제공
- 오타나 동의어를 고려한 유연한 검색 기능 지원
전통적인 관계형 데이터베이스는 대규모 텍스트 검색과 실시간 분석에 한계가 있습니다. Elasticsearch는 다음 문제들을 해결하기 위해 등장했습니다:
- 검색 성능: 대용량 데이터에서도 빠른 전문 검색(Full-text Search) 제공
- 유연한 검색: 오타 허용, 동의어 처리, 자동완성 등 고급 검색 기능 지원
- 확장성: 분산 아키텍처로 수평 확장 가능
- 실시간 분석: 로그 및 메트릭 데이터의 실시간 수집과 분석
- Docker 및 Docker Compose 설치
- 최소 4GB 이상의 메모리 권장
mkdir docker-elasticsearch
cd docker-elasticsearchservices:
elastic:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.4
ports:
- 9200:9200
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=falsedocker compose upPostman 또는 curl을 사용하여 다음 요청을 보냅니다:
curl -X GET "localhost:9200"예상 응답:
{
"name": "9d35788d88ef",
"cluster_name": "docker-cluster",
"cluster_uuid": "T8TQn-cGTWyvghdpWuQ3rg",
"version": {
"number": "8.17.4",
"build_flavor": "default",
"build_type": "docker",
"build_hash": "c63c7f5f8ce7d2e4805b7b3d842e7e792d84dda1",
"build_date": "2025-03-20T15:39:59.811110136Z",
"build_snapshot": false,
"lucene_version": "9.12.0",
"minimum_wire_compatibility_version": "7.17.0",
"minimum_index_compatibility_version": "7.0.0"
},
"tagline": "You Know, for Search"
}Kibana는 Elasticsearch를 위한 GUI 도구입니다. 기본 포트는 5601입니다.
services:
elastic:
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.4
ports:
- 9200:9200
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
kibana:
image: docker.elastic.co/kibana/kibana:8.17.4
ports:
- 5601:5601
environment:
- ELASTICSEARCH_HOSTS=["http://elastic:9200"]docker compose up -d브라우저에서 localhost:5601에 접속합니다.
Elasticsearch의 주요 개념을 MySQL과 비교하여 이해할 수 있습니다:
| 개념 | MySQL | Elasticsearch |
|---|---|---|
| 데이터 저장소 | 데이터베이스 | 인덱스(Index) |
| 스키마 정의 | 스키마(Schema) | 매핑(Mapping) |
| 데이터 단위 | 레코드/로우(Row) | 도큐먼트(Document) |
| 컬럼 | 컬럼(Column) | 필드(Field) |
공통점: 데이터를 저장, 조회, 수정, 삭제할 수 있습니다.
Elasticsearch는 REST API를 통해 통신합니다.
MySQL:
INSERT INTO users (name, email) VALUES ('John', '[email protected]');Elasticsearch:
curl -X POST "localhost:9200/users/_doc" \
-H 'Content-Type: application/json' \
-d '{
"name": "John",
"email": "[email protected]"
}'MySQL:
SELECT * FROM users;Elasticsearch:
curl -X GET "localhost:9200/users/_search" \
-H 'Content-Type: application/json' \
-d '{
"query": {
"match_all": {}
}
}'Elasticsearch는 다음과 같은 고급 검색 기능을 제공합니다:
- 동의어 검색: 유사한 의미의 단어로 검색 가능
- 오타 허용 검색: 철자 오류가 있어도 검색 가능
- 자동완성: 검색어 자동완성 기능
- 관련성 순위: 검색어와 가장 관련성이 높은 순서대로 결과 정렬
PUT /usersGET /users인덱스가 존재하지 않으면 404 - Not Found를 반환합니다.
DELETE /users매핑은 인덱스에 저장될 데이터의 타입을 정의합니다:
PUT /users/_mappings
{
"properties": {
"name": { "type": "keyword" },
"age": { "type": "integer" },
"is_active": { "type": "boolean" }
}
}성공 시 200 - OK와 함께 "acknowledged": true가 반환됩니다.
POST /users/_doc
{
"name": "Alice",
"age": 20,
"is_active": true
}POST /users/_create/1
{
"name": "Hee",
"age": 30,
"is_active": true
}참고: 이미 존재하는 ID로 생성 시도 시 409 - Conflict 오류가 발생합니다.
GET /users/_search응답 예시:
{
"took": 209,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "users",
"_id": "_DTGlZoBr2OXTjfP9GON",
"_score": 1,
"_source": {
"name": "Alice",
"age": 20,
"is_active": true
}
},
{
"_index": "users",
"_id": "_TTGlZoBr2OXTjfP_2MB",
"_score": 1,
"_source": {
"name": "Bob",
"age": 20,
"is_active": true
}
}
]
}
}GET /users/_doc/1ID에 해당하는 데이터가 있으면 업데이트하고, 없으면 새로 삽입합니다:
PUT /users/_doc/2
{
"name": "Son",
"age": 7,
"is_active": true
}동일한 ID로 다시 실행하면 업데이트됩니다:
PUT /users/_doc/2
{
"name": "Son",
"age": 8,
"is_active": true
}POST /users/_update/1
{
"doc": {
"age": 10,
"is_active": false
}
}DELETE /users/_doc/2유연한 검색 기능을 활용하려면 필드 타입을 text로 지정해야 합니다:
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text" }
}
}
}참고: Elasticsearch는 문자열에 대해 keyword와 text 두 가지 타입을 지원합니다. text 타입은 전문 검색을 위해 토큰화되지만, keyword 타입은 정확히 일치하는 검색에만 사용됩니다.
POST /products/_doc
{
"name": "Apple 2025 맥북 에어 13 M4 10코어"
}GET /products/_search
{
"query": {
"match": {
"name": "맥북 에어 13 M4"
}
}
}"name": "맥북 에어 M4"로 검색해도 관련 도큐먼트를 찾을 수 있습니다.
역인덱스는 Elasticsearch의 빠른 검색을 가능하게 하는 핵심 메커니즘입니다.
역인덱스는 필드 값을 단어(토큰)별로 분해하여 저장하는 색인 구조입니다. 각 토큰이 어떤 도큐먼트에 포함되어 있는지를 매핑합니다.
다음 도큐먼트들을 저장한다고 가정해봅시다:
POST /products/_create/1
{
"name": "Apple 2025 맥북 에어 13 M4 10코어"
}
POST /products/_create/2
{
"name": "Apple 2024 에어팟 4세대"
}
POST /products/_create/3
{
"name": "Apple 2024 아이패드 mini Pro"
}1단계: 토큰화
각 문자열을 단어 단위로 분리합니다:
"Apple 2025 맥북 에어 13 M4 10코어"→["Apple", "2025", "맥북", "에어", "13", "M4", "10코어"]"Apple 2024 에어팟 4세대"→["Apple", "2024", "에어팟", "4세대"]"Apple 2024 아이패드 mini Pro"→["Apple", "2024", "아이패드", "mini", "Pro"]
2단계: 역인덱스 구축
| 토큰(Token) | 도큐먼트 ID |
|---|---|
| Apple | [1, 2, 3] |
| 2025 | [1] |
| 맥북 | [1] |
| 에어 | [1] |
| 13 | [1] |
| M4 | [1] |
| 10코어 | [1] |
| 2024 | [2, 3] |
| 에어팟 | [2] |
| 4세대 | [2] |
| 아이패드 | [3] |
| mini | [3] |
| Pro | [3] |
참고: 생성된 역인덱스는 시스템 내부적으로만 관리되며 직접 확인할 수 없습니다.
"Apple 2024 아이패드"로 검색하면:
- 검색어를 토큰화:
["Apple", "2024", "아이패드"] - 각 토큰과 일치하는 도큐먼트 확인:
- ID 1: "Apple" 1개 일치
- ID 2: "Apple", "2024" 2개 일치
- ID 3: "Apple", "2024", "아이패드" 3개 일치
- 일치하는 토큰이 많을수록 높은 점수 부여
Elasticsearch는 다음 요소들을 고려하여 점수를 계산합니다:
- TF (Term Frequency): 문서 내에서 검색어가 자주 등장할수록 높은 점수
- IDF (Inverse Document Frequency): 전체 문서에서 희귀한 단어일수록 높은 점수
- FLN (Field Length Normalization): 문서가 짧을수록 높은 점수
이러한 역인덱스 구조 덕분에 단어의 순서와 관계없이 관련 도큐먼트를 효율적으로 검색할 수 있습니다.
중요: 역인덱스는 text 타입 필드에만 적용됩니다.
애널라이저는 문자열을 토큰으로 변환하는 장치입니다.
애널라이저는 다음 세 가지 구성 요소로 이루어집니다:
문자열 입력
↓
[캐릭터 필터] - 문자열 전처리
↓
[토크나이저] - 토큰으로 분리
↓
[토큰 필터] - 토큰 후처리
↓
최종 토큰 출력
문자열을 토큰화하기 전에 전처리합니다.
HTML Stripper 필터 예시:
<p>아이폰 15 사용 후기</p> → 아이폰 15 사용 후기
기타 캐릭터 필터:
- Mapping Filter: 특정 문자를 다른 문자로 변환
- Pattern Replace Filter: 정규표현식 패턴으로 문자 치환
문자열을 토큰으로 분리합니다.
Standard 토크나이저 예시 (공백, 쉼표, 마침표 등으로 분리):
The Brown-Foxes jumped over the roof
→ [The, Brown, Foxes, jumped, over, the, roof]
기타 토크나이저:
- Classic Tokenizer: 이메일, URL 등을 인식
- Keyword Tokenizer: 전체 문자열을 하나의 토큰으로 처리
- Pattern Tokenizer: 정규표현식 패턴으로 분리
토큰을 최종적으로 정제합니다.
처리 단계 예시:
-
Lowercase 필터 적용:
[The, Brown, Foxes, jumped, over, the, roof] → [the, brown, foxes, jumped, over, the, roof] -
Stop 필터 적용 (불용어 제거):
[the, brown, foxes, jumped, over, the, roof] → [brown, foxes, jumped, roof] -
Stemmer 필터 적용 (어간 추출):
[brown, foxes, jumped, roof] → [brown, fox, jump, roof]
Elasticsearch의 기본 애널라이저는 Standard Analyzer입니다:
- 캐릭터 필터: 없음
- 토크나이저: Standard Tokenizer
- 토큰 필터: Lowercase Filter
동작: 공백이나 문장 부호를 기준으로 분리한 후 소문자로 변환합니다.
일반 애널라이저 테스트:
GET /_analyze
{
"text": "Apple 2024 아이패드 mini Pro",
"analyzer": "standard"
}커스텀 구성 테스트:
GET /_analyze
{
"char_filter": [],
"tokenizer": "standard",
"filter": ["lowercase"],
"text": "Apple 2024 아이패드 mini Pro"
}인덱스에 커스텀 애널라이저를 정의할 수 있습니다:
PUT /products
{
"settings": {
"analysis": {
"analyzer": {
"products_name_analyzer": {
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "products_name_analyzer"
}
}
}
}GET /products/_analyze
{
"field": "name",
"text": "Apple 2025 맥북 에어 13 M4 10코어"
}중요: 애널라이저에 lowercase 토큰 필터가 적용되면 검색어도 동일한 애널라이저를 거쳐 비교됩니다. 따라서 대문자로 검색해도 소문자로 저장된 토큰을 찾을 수 있습니다.
HTML 태그를 제거하는 html_strip 필터의 동작을 확인해봅시다.
DELETE /boards
PUT /boards
{
"settings": {
"analysis": {
"analyzer": {
"board_content_analyzer": {
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "board_content_analyzer"
}
}
}
}POST /boards/_doc
{
"content": "<h1>Running cats, jumpping quickly - over the lazy dogs!</h1>"
}GET /boards/_search
{
"query": {
"match": {
"content": "h1"
}
}
}결과: HTML 태그는 제거되었으므로 검색되지 않습니다.
GET /boards/_analyze
{
"field": "content",
"text": "<h1>Running cats, jumpping quickly - over the lazy dogs!</h1>"
}확인 사항: HTML 태그가 제거되고 순수 텍스트만 토큰화됩니다.
의미 없는 불용어(a, the, is 등)를 제거하는 stop 필터의 동작을 확인해봅시다.
DELETE /boards
PUT /boards
{
"settings": {
"analysis": {
"analyzer": {
"board_content_analyzer": {
"char_filter": [],
"tokenizer": "standard",
"filter": ["lowercase", "stop"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "board_content_analyzer"
}
}
}
}POST /boards/_doc
{
"content": "The cat and the dog are friends"
}GET /boards/_search
{
"query": {
"match": {
"content": "friends"
}
}
}GET /boards/_analyze
{
"field": "content",
"text": "The cat and the dog are friends"
}확인 사항: "the", "and", "are" 같은 불용어가 제거되고 "cat", "dog", "friends"만 토큰으로 생성됩니다.
단어를 원형으로 변환하는 stemmer 필터의 동작을 확인해봅시다.
DELETE /boards
PUT /boards
{
"settings": {
"analysis": {
"analyzer": {
"board_content_analyzer": {
"char_filter": [],
"tokenizer": "standard",
"filter": ["lowercase", "stemmer"]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "board_content_analyzer"
}
}
}
}POST /boards/_doc
{
"content": "Running cats, jumpping!"
}GET /boards/_search
{
"query": {
"match": {
"content": "jumpping"
}
}
}GET /boards/_analyze
{
"field": "content",
"text": "Running cats, jumpping!"
}확인 사항: "Running" → "run", "cats" → "cat", "jumpping" → "jump"로 어간이 추출됩니다.
동의어를 처리하는 synonym 필터의 동작을 확인해봅시다.
DELETE /products
PUT /products
{
"settings": {
"analysis": {
"filter": {
"products_synonym_filter": {
"type": "synonym",
"synonyms": [
"notebook, 노트북, 랩탑, 휴대용 컴퓨터, laptop",
"samsung, 삼성"
]
}
},
"analyzer": {
"products_name_analyzer": {
"char_filter": [],
"tokenizer": "standard",
"filter": [
"lowercase",
"products_synonym_filter"
]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "products_name_analyzer"
}
}
}
}POST /products/_doc
{
"name": "Samsung Notebook"
}GET /products/_search
{
"query": {
"match": {
"name": "삼성"
}
}
}결과: "Samsung"으로 저장했지만 동의어인 "삼성"으로도 검색 가능합니다.
GET /products/_analyze
{
"field": "name",
"text": "Samsung Notebook"
}확인 사항: "Samsung"과 "Notebook"이 각각의 동의어들로 확장됩니다.
한국어 형태소 분석을 위한 Nori 플러그인을 사용해봅시다.
1단계: Dockerfile 작성
FROM docker.elastic.co/elasticsearch/elasticsearch:8.17.4
RUN bin/elasticsearch-plugin install analysis-nori2단계: docker-compose.yml 수정
services:
elastic:
build:
context: .
dockerfile: Dockerfile
ports:
- 9200:9200
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- xpack.security.http.ssl.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
kibana:
image: docker.elastic.co/kibana/kibana:8.17.4
ports:
- 5601:5601
environment:
- ELASTICSEARCH_HOSTS=["http://elastic:9200"]3단계: 컨테이너 재구동
docker compose down
docker compose up -d --buildGET /_analyze
{
"text": "백화점에서 쇼핑을 하다가 친구를 만났다.",
"analyzer": "nori"
}GET /_analyze
{
"text": "백화점에서 쇼핑을 하다가 친구를 만났다.",
"char_filter": [],
"tokenizer": "nori_tokenizer",
"filter": ["nori_part_of_speech", "nori_readingform", "lowercase"]
}Nori 구성 요소:
- nori_tokenizer: 한국어를 형태소 단위로 분리
- nori_part_of_speech: 특정 품사 필터링
- nori_readingform: 한자를 한글 발음으로 변환
DELETE /boards
PUT /boards
{
"settings": {
"analysis": {
"analyzer": {
"board_content_analyzer": {
"char_filter": [],
"tokenizer": "nori_tokenizer",
"filter": [
"nori_part_of_speech",
"nori_readingform",
"lowercase"
]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "board_content_analyzer"
}
}
}
}POST /boards/_doc
{
"content": "백화점에서 쇼핑을 하다가 친구를 만났다."
}GET /boards/_search
{
"query": {
"match": {
"content": "백화점"
}
}
}GET /boards/_analyze
{
"field": "content",
"text": "백화점에서 쇼핑을 하다가 친구를 만났다."
}확인 사항: 한국어가 형태소 단위로 정확하게 분리됩니다.
한국어와 영어가 혼용된 텍스트에 여러 필터를 조합해봅시다.
DELETE /boards
PUT /boards
{
"settings": {
"analysis": {
"analyzer": {
"board_content_analyzer": {
"char_filter": [],
"tokenizer": "nori_tokenizer",
"filter": [
"nori_part_of_speech",
"nori_readingform",
"lowercase",
"stop",
"stemmer"
]
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "board_content_analyzer"
}
}
}
}GET /boards/_analyze
{
"field": "content",
"text": "오늘 영어 책에서 'It depends on the result.'이라는 문구를 봤다."
}확인 사항:
- 한국어는 형태소 분석
- 영어는 불용어 제거 및 어간 추출
- 모든 텍스트가 소문자로 변환
Elasticsearch를 더 깊이 학습하려면:
- 공식 문서의 Aggregation 기능 학습
- Logstash와 Kibana를 활용한 ELK 스택 구축
- 실제 프로젝트에 검색 기능 적용해보기