Elasticsearch 마스터 노트 시리즈 2편. Mapping이 Elasticsearch의 스키마인 이유, text vs keyword의 결정적 차이, Dynamic Mapping의 함정과 명시 매핑 권장, 13 데이터 타입(text·keyword·long·integer·float·double·boolean·date·geo_point·nested·object 등), Multi-field로 한 필드 다중 인덱싱, Mapping 변경 한계까지.
이 글은 Elasticsearch 마스터 노트 시리즈의 두 번째 편입니다. 1편(기초)에서 큰 그림을 봤다면, 이번엔 데이터 구조 정의 — Mapping.
text vs keyword 한 글자 차이가 검색 동작 결정. Dynamic Mapping은 편하지만 함정. 이 두 인식이 운영의 시작.
처음 Mapping이 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, text vs keyword가 막연합니다. 둘째, Dynamic Mapping의 함정이 직관적이지 않습니다.
해결법은 한 가지예요. "text = 분석·검색 / keyword = 정확 매칭·집계" 한 줄. 검색은 text, 집계·정렬·필터는 keyword. 이 매핑만 잡으면 끝.
Mapping이란
GET /products/_mapping
{
"products": {
"mappings": {
"properties": {
"name": { "type": "text" },
"price": { "type": "integer" },
"tags": { "type": "keyword" }
}
}
}
}
각 필드의 타입 정의. RDB의 스키마 비슷하지만 더 유연.
Dynamic Mapping — 자동 추론
# Mapping 정의 안 하고 문서 추가
POST /products/_doc
{
"name": "Spring Boot",
"price": 30000,
"tags": ["spring", "java"],
"released_at": "2024-01-15"
}
# Elasticsearch가 자동 매핑:
# name → text + keyword (multi-field)
# price → long
# tags → text + keyword
# released_at → date
여기서 정말 중요한 시험 함정 — Dynamic Mapping = 운영 위험. 첫 문서가 정확한지 확신 X. 잘못 추론 시 변경 어려움. 운영 = 명시 Mapping.
명시 Mapping
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
},
"price": { "type": "integer" },
"tags": { "type": "keyword" },
"released_at": { "type": "date" }
}
}
}
명시적·예측 가능.
text vs keyword — 가장 중요한 차이
text — 분석·검색
"description": { "type": "text" }
- Analyzer가 동작 — 토큰화·소문자·stopword 제거
- Full-text Search
- 정렬·집계 X
- 예: "Spring Boot Framework" → ["spring", "boot", "framework"]
keyword — 정확 매칭
"category": { "type": "keyword" }
- 분석 X — 원본 그대로
- 정확 매칭만
- 정렬·집계 O
- 필터·aggregation 표준
- 예: "Spring Boot" → ["Spring Boot"]
비교
# text 검색 — 단어 매칭
GET /products/_search
{
"query": {
"match": { "description": "spring" }
}
}
# → "Spring Boot Framework" 매칭
# keyword 검색 — 정확 매칭
GET /products/_search
{
"query": {
"term": { "category": "IT" }
}
}
# → "IT" 정확히. "it"는 매칭 X.
여기서 정말 중요한 시험 함정 — 검색 = text / 집계·필터·정렬 = keyword. 잘못 선택 시 동작 안 함. 자주 헷갈리는 부분.
Multi-field — 한 필드 다중 인덱싱
같은 데이터를 다르게 인덱싱:
"title": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" },
"raw": { "type": "text", "analyzer": "english" }
}
}
# Full-text 검색 (분석)
GET /_search
{
"query": { "match": { "title": "spring" } }
}
# 정확 매칭 (keyword)
GET /_search
{
"query": { "term": { "title.keyword": "Spring Boot Book" } }
}
# 영어 분석기 (stemming 등)
GET /_search
{
"query": { "match": { "title.raw": "running" } }
# → "run"·"ran" 매칭 (stem)
}
여기서 시험 함정이 하나 있어요. Multi-field = 같은 데이터·다른 인덱스. 저장 공간 증가. 자주 검색·집계 둘 다 하면 가치 있음.
13 핵심 데이터 타입
문자열
| 타입 | 용도 |
|---|---|
| text | 검색 (분석·토큰화) |
| keyword | 정확 매칭·집계 |
숫자
| 타입 | 범위 |
|---|---|
| long | 64-bit |
| integer | 32-bit |
| short | 16-bit |
| byte | 8-bit |
| double | 64-bit float |
| float | 32-bit float |
| half_float | 16-bit float |
| scaled_float | scale 적용 (예: 가격 *100 = 정수) |
날짜·시간
"created_at": { "type": "date" }
기본 형식 — yyyy-MM-dd'T'HH:mm:ssZ 또는 epoch_millis.
Boolean
"active": { "type": "boolean" }
Geo
"location": { "type": "geo_point" }
"area": { "type": "geo_shape" }
지리 검색·반경 등. 지도 API.
IP
"client_ip": { "type": "ip" }
IPv4·IPv6 자동 처리.
Object — 중첩
{
"user": {
"name": "Alice",
"age": 30
}
}
자동으로 user.name·user.age 평면화.
Nested — 객체 배열
{
"comments": [
{ "author": "Alice", "text": "Hello" },
{ "author": "Bob", "text": "Hi" }
]
}
type: nested로 명시. 객체 단위 독립 인덱싱.
여기서 정말 중요한 시험 함정 — 객체 배열은 기본 평면화 = 잘못된 매칭. 예: comments.author = Alice AND comments.text = Hi도 매칭 (배열 평면화). nested 타입으로 객체 단위 보존.
Mapping 변경 — 한계
✓ 새 필드 추가
✓ 기존 필드 옵션 추가 (ignore_malformed 등)
✗ 기존 필드 타입 변경
✗ 필드 이름 변경
✗ 필드 삭제
여기서 정말 중요한 시험 함정 — 타입 변경 = Reindex 필요. 새 인덱스 생성 → _reindex API → 기존 인덱스 삭제·alias 전환. 운영 부담.
Reindex 패턴
# 1. 새 인덱스 생성 (정확한 mapping)
PUT /products-v2
{
"mappings": { ... }
}
# 2. 데이터 복사
POST /_reindex
{
"source": { "index": "products" },
"dest": { "index": "products-v2" }
}
# 3. Alias 전환
POST /_aliases
{
"actions": [
{ "remove": { "index": "products", "alias": "products-current" } },
{ "add": { "index": "products-v2", "alias": "products-current" } }
]
}
여기서 시험 함정이 하나 있어요. Alias = 인덱스 이름의 별칭. 클라이언트는 alias 사용 → 인덱스 교체 무중단. 운영 표준.
ignore_malformed
"price": {
"type": "integer",
"ignore_malformed": true
}
타입 안 맞아도 무시 (필드만 누락, 문서는 인덱싱). 견고성 ↑.
copy_to — 다중 필드 결합
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
first_name·last_name 자동으로 full_name에도 복사. 통합 검색에 편리.
index — 인덱싱 비활성
"raw_data": {
"type": "text",
"index": false
}
저장만, 검색 X. 디스크 절감.
doc_values — 정렬·집계
"category": {
"type": "keyword",
"doc_values": false # 정렬·집계 비활성
}
기본 활성. 사용 안 하면 비활성으로 디스크 절감.
Date Format — 다중 형식
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
}
||로 다중 형식 허용.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 2편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- Mapping = ES 스키마
- Dynamic Mapping = 자동 추론 (운영 위험)
- 운영 = 명시 Mapping
- text vs keyword = 가장 중요
- text = 분석·검색·정렬 X / keyword = 정확 매칭·집계 O
- 검색 = text / 필터·집계·정렬 = keyword
- Multi-field = 한 데이터·다중 인덱스 (저장 ↑·검색 유연)
- 데이터 타입 — 문자열(text·keyword)·숫자·date·boolean·geo_point·ip
- Object vs Nested — Object 자동 평면화·Nested = 객체 단위 보존
- 객체 배열의 잘못된 매칭 방지 → nested
- Mapping 변경 — 타입 변경·삭제 X → Reindex 필요
- Reindex 패턴 — 새 인덱스 +
_reindexAPI + Alias 전환 - Alias = 인덱스 이름 별칭 (무중단 교체 표준)
ignore_malformed= 타입 안 맞아도 무시copy_to= 다중 필드 결합index: false= 저장만, 검색 Xdoc_values: false= 정렬·집계 비활성- Date
format= 다중 형식 (||) - 운영 — 명시 Mapping + Alias + Reindex 워크플로우
시리즈 다른 편
- 1편 — 기본 개념·Cluster·Shard
- 2편 — Mapping·데이터 타입 (현재 글)
- 3편 — Analyzer·Tokenizer·한국어
- 4편 — Query DSL
- 5편 — Full-text Search·Relevance
- 6편 — Aggregations
- 7편 — Bulk API·Reindex
- 8편 — Spring Data Elasticsearch
- 9편 — 검색 엔진 프로젝트 설계
- 10편 — Security·인증·인가·TLS
공식 문서: Mapping 에서 더 깊이.
다음 글(3편)에서는 Analyzer — text 필드의 토큰화 과정, Standard·Korean Analyzer, 커스텀 분석기 구성까지 풀어 갑니다.