매핑(Mapping)과 스키마 설계 전략
매핑이란 무엇인가
Elasticsearch에서 매핑(Mapping)은 인덱스의 스키마 정의다. 관계형 데이터베이스의 DDL(CREATE TABLE)에 해당하며, 각 필드의 데이터 타입, 분석기 설정, 색인 방식을 선언한다. 잘못 설계된 매핑은 나중에 수정하기 어렵다. 프라이머리 샤드에 이미 색인된 필드의 타입 변경은 원칙적으로 불가능하고, 변경이 필요할 때는 새 인덱스를 만들어 _reindex로 데이터를 재색인해야 한다. 처음에 제대로 설계하는 것이 이후 운영 부담을 크게 줄인다.
동적 매핑 vs 명시적 매핑
Elasticsearch는 매핑을 미리 선언하지 않아도 문서를 색인할 수 있다. 처음 들어오는 문서의 값을 보고 자동으로 타입을 추론하는 것이 동적 매핑(Dynamic Mapping)이다.
동적 매핑의 기본 타입 추론 규칙은 다음과 같다.
| JSON 값 | 추론 타입 | 주의 사항 |
|---|---|---|
"hello" (문자열) | text + keyword sub-field | 항상 두 개의 인덱스가 생성됨 |
123 (정수) | long | integer가 충분해도 long으로 추론 |
1.5 (소수) | float | |
true / false | boolean | |
"2025-01-01" (ISO 날짜) | date | 날짜 패턴 매칭 시만 해당 |
프로덕션 인덱스에는 "dynamic": "strict"를 권장한다. 알 수 없는 필드가 들어오면 예외를 던져 예상치 못한 스키마 팽창을 막는다. 개발 단계에서는 "dynamic": true로 자유롭게 탐색하다가, 안정화된 뒤 명시적 매핑으로 고정하는 전략이 일반적이다.
핵심 필드 타입
text와 keyword: 문자열 타입의 두 갈래
문자열 데이터의 용도에 따라 타입을 나눠야 한다.
text: 전문(full-text) 검색용. 분석기로 토큰화되어 역 인덱스에 저장된다.match,match_phrase쿼리에 사용. 집계·정렬 불가.keyword: 정확값(exact value) 검색·집계·정렬용. 분석하지 않고 원문 그대로 저장.term,terms,sort,aggs에 사용.
상태값, ID, 이메일, 카테고리 같은 필드는 항상 keyword를 사용한다. 전문 검색과 집계 모두 필요한 필드는 multi-field 매핑으로 두 가지를 동시에 선언한다(챕터 2 참고).
숫자·날짜 타입
숫자는 가장 좁은 타입을 선택한다. long은 8바이트지만 integer는 4바이트다. 필드 수백만 개가 쌓이면 차이가 크다.
| 타입 | 범위 | 용도 |
|---|---|---|
integer | -2³¹ ~ 2³¹-1 | 카운트, 나이, 횟수 |
long | -2⁶³ ~ 2⁶³-1 | Unix 타임스탬프(ms), 큰 ID |
float | 32비트 부동소수 | 일반 소수 |
double | 64비트 부동소수 | 고정밀 연산 |
keyword | — | ID, 버전 번호 (범위 검색 불필요 시) |
날짜 필드는 date 타입을 사용하고, format으로 허용 패턴을 명시한다.
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}object와 nested: 중첩 구조의 함정
JSON 문서 안에 배열이나 객체가 있을 때 타입 선택이 중요하다.
object 타입은 배열을 내부적으로 평탄화(flatten)하므로, "Alice가 score 5점을 준 리뷰"를 정확히 검색할 수 없다. nested 타입은 각 배열 요소를 숨겨진 별도 문서로 저장해 상관관계를 유지하지만, 색인 비용과 쿼리 복잡도가 높아진다. 배열 내 요소 간 관계를 쿼리해야 할 때만 nested를 선택한다.
Mapping Explosion: 필드 수 폭발
동적 매핑 환경에서 문서마다 예측 불가능한 필드가 들어오면 인덱스의 매핑이 수천 개 이상의 필드로 팽창한다. 이를 Mapping Explosion이라 한다. 클러스터 메모리를 압박하고 마스터 노드에 부하를 준다.
방어 전략:
dynamic: strict또는dynamic: false: 미정의 필드를 거부하거나 색인만 건너뜀.index.mapping.total_fields.limit: 기본값 1000. 필요에 따라 조정하되 수천은 위험 신호.- Dynamic Templates: 와일드카드 패턴으로 특정 이름 패턴의 필드 타입을 제어.
"dynamic_templates": [
{
"strings_as_keyword": {
"match_mapping_type": "string",
"mapping": { "type": "keyword" }
}
}
]위 템플릿은 동적으로 들어오는 모든 문자열 필드를 keyword로 강제한다. text+keyword 이중 색인을 막아 저장 공간을 절반으로 줄인다.
실무 매핑 설계 패턴
로그·이벤트 인덱스 (시계열)
PUT /app-logs
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"dynamic": "strict",
"properties": {
"@timestamp": { "type": "date" },
"level": { "type": "keyword" },
"service": { "type": "keyword" },
"message": { "type": "text" },
"trace_id": { "type": "keyword", "index": false },
"duration_ms": { "type": "integer" }
}
}
}trace_id처럼 검색 없이 조회만 하는 필드는 "index": false로 역 인덱스 생성을 생략해 저장 공간을 줄인다.
상품·콘텐츠 인덱스 (검색 + 집계)
상품명은 전문 검색(text)과 정렬(keyword)이 모두 필요하므로 multi-field로, 가격은 범위 쿼리용으로 integer, 카테고리는 집계용 keyword를 사용한다.
"properties": {
"name": {
"type": "text",
"analyzer": "standard",
"fields": { "keyword": { "type": "keyword" } }
},
"price": { "type": "integer" },
"category": { "type": "keyword" },
"tags": { "type": "keyword" },
"in_stock": { "type": "boolean" }
}매핑 업데이트와 재인덱스
기존 인덱스에 새 필드를 추가하는 것은 가능하다. 하지만 기존 필드의 타입을 변경하는 것은 불가능하다. 필드를 삭제하는 API도 없다.
타입 변경이 필요하면 다음 순서로 진행한다.
- 새 매핑으로 신규 인덱스 생성
POST /_reindexAPI로 데이터 복사- 알리아스(alias)를 기존 인덱스에서 신규 인덱스로 전환
- 기존 인덱스 삭제
인덱스 알리아스를 처음부터 사용하면 애플리케이션 코드 변경 없이 재인덱스 전환이 가능하다. 서비스 코드가 인덱스 이름 대신 알리아스를 가리키도록 설계하는 것이 프로덕션의 표준 패턴이다.
매핑 설계 체크리스트
| 결정 사항 | 권장 선택 |
|---|---|
| 프로덕션 dynamic 설정 | strict |
| 검색만 하는 필드 | text |
| 집계·정렬·exact match 필드 | keyword |
| 둘 다 필요한 필드 | multi-field (text + keyword) |
| 검색 없이 저장만 하는 필드 | index: false |
| 배열 내 요소 간 관계 필요 | nested |
| 배열 내 관계 불필요 | object (기본) |
| 숫자 타입 | 가장 좁은 타입 선택 |
| 날짜 | date + format 명시 |
한 줄 정리
매핑은 인덱스의 스키마 계약이다. 프로덕션에서는 dynamic: strict으로 명시적 매핑을 강제하고, 문자열은 text/keyword 용도를 나눠 선언하며, 숫자는 가장 좁은 타입을 선택한다. 기존 필드 타입 변경이 필요하면 재인덱스가 유일한 방법이므로 처음 설계가 중요하다.
References
- https://thelinuxcode.com/mapping-types-and-field-data-types-in-elasticsearch-a-practical-2026-guide/
- https://pulse.support/kb/what-is-elasticsearch-mapping
- https://medium.com/@andre.luiz1987/dynamic-vs-explicit-mapping-best-practices-and-performance-implications-780b672883a2
- https://opster.com/guides/elasticsearch/data-architecture/elasticsearch-dynamic-mapping/
- https://www.elastic.co/docs/manage-data/data-store/mapping/dynamic-templates
- https://medium.com/elasticsearch-demystified/best-practices-recommendations-on-elasticsearch-mapping-7d3f4a32991d
- https://www.elastic.co/blog/3-ways-to-prevent-mapping-explosion-in-elasticsearch
- https://www.elastic.co/docs/troubleshoot/elasticsearch/mapping-explosion
- https://opster.com/guides/elasticsearch/data-structuring/elasticsearch-nested-field-object-field/