Elasticsearch
- Java 오픈소스 분산 검색 엔진
- 방대한 양의 데이터를 신속하게, 거의 실시간( NRT, Near Real Time )으로 저장, 검색, 분석할 수 있다.
- ES는 역색인을 지원하기에 기존 RDB가 지원하지 않는 비정형 데이터를 인덱싱 + 검색하는 것에 특화되어있다.
- 비정형 데이터: 규칙이 없는 데이터. ex) 음성, 텍스트, 영상
- 역색인 : 키워드를 통해 데이터를 찾는 방식
- 검색을 위해 단독으로 사용되기도 하며, ELK( Elasticsearch / Logstatsh / Kibana )스택으로 사용되기도 한다.
ELK
- Logstash : 다양한 소스( DB, csv파일 등 )의 로그 또는 트랜잭션 데이터를 수집, 집계, 파싱하여 Elasticsearch로 전달
- Elasticsearch: Logstash로부터 받은 데이터를 검색 및 집계를 하여 필요한 관심 있는 정보를 획득
- Kibana : Elasticsearch의 빠른 검색을 통해 데이터를 시각화 및 모니터링
RDB와 비교
- RDB의 경우 모든 row를 읽어서 스페이스를 딜리미터로 파싱한 뒤, 홍길동 포함여부를 조사해야한다. 따라서 모든 row를 읽어야만 하기에 상당한 검색시간이 소요된다.
- 반면 ES는 저장할 때부터 토큰화하여 역색인 인덱스에 (id, 단어, doc_id) 형태로 저장한다. 따라서 특정 row만 읽고 검색이 가능하다. 단어를 포함하는 doc_id 를 쉽게 알 수 있으며 빠른 검색이 가능하다.
Elasticsearch 사용 방식
데이터 넣기 (Indexing)
- ES에서 데이터를 저장하는 단위를 “문서(Document)”라고 부르고, 이 문서는 JSON 형식으로 저장
- 내부적으로 역색인(inverted index) 구조를 사용해 텍스트 기반 검색을 최적화된 방식으로 저장
1
2
3
4
5
6
7
POST /products/_doc/1
{
"name": "MacBook Pro",
"price": 2999,
"category": "laptop",
"description": "Apple laptop with M2 chip"
}
- products: 인덱스 이름 (RDB의 테이블 역할)
- _doc: 타입 (ES 7.x 이후 거의 고정됨)
- 1: 문서 ID (없으면 ES가 자동 생성)
- 본문: JSON 데이터
검색하기 (Querying)
검색 시에는 SQL이 아닌 ES DSL(Domain Specific Language)이라는 JSON 기반 쿼리를 사용
전체 텍스트 검색 (Full-text Search) 예:
1
2
3
4
5
6
7
8
GET /products/_search
{
"query": {
"match": {
"description": "apple laptop"
}
}
}
조건 검색 (Filtering) 예:
1
2
3
4
5
6
7
8
9
10
11
12
13
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "category": "laptop" } }
],
"filter": [
{ "range": { "price": { "lt": 3000 } } }
]
}
}
}
match
는 분석기(analyzer)를 거쳐 자연어 처리 기반 검색을 수행합니다.filter
는 분석 없이 조건 매칭만 수행하므로 캐싱이 가능하고 빠르다.- Analyzer: 문장을 쪼개고(lowercase, stemming 등) 인덱싱할 때 쓰는 도구
RDB와 비교
✅ ES vs RDB 비교
구분 | RDB (예: MySQL, Oracle) | ElasticSearch |
---|---|---|
목적 | 데이터의 정합성 & 트랜잭션 중심 저장 | 빠르고 유연한 검색과 집계 |
데이터 구조 | 테이블 - 행(row), 열(column) 기반 | 인덱스 - 문서(document, JSON 기반) |
스키마 | 엄격한 스키마 기반 (DDL) | 스키마리스, 유연한 매핑 (자동 추론 가능) |
질의 언어 | SQL | DSL (JSON 기반 도메인 쿼리) |
검색 기능 | 정확한 값 비교 중심 (LIKE 포함) | 자연어 기반 Full-Text Search, 정규화된 색인 기반 |
인덱싱 구조 | B-Tree 기반 (주로 PK, FK 인덱스) | Inverted Index (역색인, 텍스트 검색 최적화) |
성능 | 수직적 확장 (Scale-up 위주) | 수평적 확장 (Scale-out, 샤딩/레플리카) |
정합성 | ACID 지원 (트랜잭션 강함) | 기본은 Eventually Consistent (최종 일관성) |
사용 예 | 금융 트랜잭션, ERP, OLTP | 검색엔진, 로그 분석, 대시보드, 추천 |
✅ 주요 차이점
1. 🔍 검색 방식
- RDB는
LIKE
,=
,IN
,BETWEEN
같은 정확한 조건 검색 위주- 예:
WHERE title LIKE '%laptop%'
- 예:
- ES는 match, full-text search, fuzzy, n-gram, autocomplete 등 다양한 검색이 자연어 기반으로 지원됩니다.
- 예: “애플 랩탑” → “apple laptop”을 자동 매칭
2. ⚙️ 인덱싱 구조
- RDB: B-Tree 인덱스는 “정렬/범위 기반 탐색”에 특화됨
- ES: Inverted Index는 “단어 → 문서 ID 리스트”를 미리 저장해놓고 검색 최적화에 탁월
3. 💾 데이터 저장과 정합성
- RDB는
BEGIN
,COMMIT
,ROLLBACK
등 트랜잭션 처리로 정합성 보장 - ES는 기본적으로 트랜잭션이 없으며, 분산 구조에서 일관성은 eventual consistency
4. 🧠 모델링 철학
- RDB는 정규화 중심 (관계로 데이터 분리, Join으로 결합)
- ES는 비정규화 중심 (Join이 없거나 제한적 → 필요한 모든 필드를 한 문서에 넣는 방식)
✅ RDB의 Join과 비교해보면
ElasticSearch는 전통적인 RDB의 Join 기능이 매우 제한적이라서, 보통은 다른 방식으로 우회하거나 데이터 모델링 자체를 바꾸는 경우가 많다고 한다.
구분 | RDB | ElasticSearch |
---|---|---|
Join 방식 | INNER, LEFT, RIGHT, OUTER 등 자유로운 조인 | Join 자체가 없음 (제한된 nested, parent-child만 지원) |
관계 설정 | 외래키 기반 다대일, 일대다, 다대다 관계 명확 | 대부분 비정규화하거나, nested/parent-child 구조로 우회 |
성능 영향 | Join 많아지면 느려질 수 있음 | Join 자체가 없으므로 검색에 최적화됨 |
✅ ES에서 Join을 대체하는 3가지 방법
1. 데이터 비정규화 (Denormalization) ✅ 가장 일반적
예시 상황:
user
테이블 (id, name)order
테이블 (id, user_id, amount)
→ ES에서는 order 문서에 user 정보까지 통째로 포함하는 방식으로 해결
1
2
3
4
5
6
{
"order_id": 123,
"user_id": 1,
"user_name": "헤밍웨이",
"amount": 10000
}
📌 장점: 검색 속도 매우 빠름
📌 단점: user name이 바뀌면 관련된 모든 order 문서를 갱신해야 함 (중복 데이터 관리 필요)
2. Nested 타입 (JSON 배열 내부에 문서 형태로 저장)
예시 상황:
- 상품(product)에 리뷰가 여러 개 붙는 구조 (1:N)
1
2
3
4
5
6
7
{
"product_name": "노트북",
"reviews": [
{ "author": "개츠비", "score": 5 },
{ "author": "골드문트", "score": 4 }
]
}
📌 특징:
nested
타입으로 선언해야 함nested query
를 사용해 내부 필드 조건으로 검색 가능
쿼리 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"query": {
"nested": {
"path": "reviews",
"query": {
"bool": {
"must": [
{ "match": { "reviews.author": "개츠비" } },
{ "match": { "reviews.score": 5 } }
]
}
}
}
}
}
📌 일반 query로 하면 "개츠비"
와 "5"
가 서로 다른 리뷰 객체에서 나와도 매칭될 수 있어서 nested가 필요함