LLM WikiAccess-protected knowledge portal
← 스터디 홈
3편 · 약 13분

매핑(Mapping)과 스키마 설계 전략

매핑이란 무엇인가

Elasticsearch에서 매핑(Mapping)은 인덱스의 스키마 정의다. 관계형 데이터베이스의 DDL(CREATE TABLE)에 해당하며, 각 필드의 데이터 타입, 분석기 설정, 색인 방식을 선언한다. 잘못 설계된 매핑은 나중에 수정하기 어렵다. 프라이머리 샤드에 이미 색인된 필드의 타입 변경은 원칙적으로 불가능하고, 변경이 필요할 때는 새 인덱스를 만들어 _reindex로 데이터를 재색인해야 한다. 처음에 제대로 설계하는 것이 이후 운영 부담을 크게 줄인다.

동적 매핑 vs 명시적 매핑

Elasticsearch는 매핑을 미리 선언하지 않아도 문서를 색인할 수 있다. 처음 들어오는 문서의 값을 보고 자동으로 타입을 추론하는 것이 동적 매핑(Dynamic Mapping)이다.

동적 매핑 (dynamic: true) 문서 색인 시 새 필드 자동 감지 JSON 타입으로 추론: string → text+keyword, number → long/float, boolean → boolean 장점: 개발 초기 빠른 프로토타입 단점: 예측 불가 타입·mapping explosion 위험
명시적 매핑 (dynamic: strict) PUT /index { "mappings": { "properties": { ... } } } 미정의 필드 색인 시 오류 발생 장점: 프로덕션 안전, 스토리지 최적화 단점: 스키마 변경 시 재인덱스 필요
동적 매핑과 명시적 매핑 비교

동적 매핑의 기본 타입 추론 규칙은 다음과 같다.

JSON 값추론 타입주의 사항
"hello" (문자열)text + keyword sub-field항상 두 개의 인덱스가 생성됨
123 (정수)longinteger가 충분해도 long으로 추론
1.5 (소수)float
true / falseboolean
"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⁶³-1Unix 타임스탬프(ms), 큰 ID
float32비트 부동소수일반 소수
double64비트 부동소수고정밀 연산
keywordID, 버전 번호 (범위 검색 불필요 시)

날짜 필드는 date 타입을 사용하고, format으로 허용 패턴을 명시한다.

"created_at": {
  "type": "date",
  "format": "strict_date_optional_time||epoch_millis"
}

object와 nested: 중첩 구조의 함정

JSON 문서 안에 배열이나 객체가 있을 때 타입 선택이 중요하다.

원본 문서 "comments": [ { "user":"Alice","score":5 }, { "user":"Bob", "score":2 } ] object 타입 (기본) 내부 평탄화(flatten): comments.user → ["Alice","Bob"] comments.score → [5, 2] ⚠ user-score 상관관계 소실 nested 타입 숨겨진 별도 문서로 저장: doc: {user:"Alice", score:5} doc: {user:"Bob", score:2} → nested 쿼리로 상관관계 보존 검색 단순 필드 집합, 배열 내 관계 불필요 시 사용 "Alice가 5점 준 리뷰" 검색 필요 시 색인 비용 ↑
object 타입 vs nested 타입: 내부 저장 방식 차이

object 타입은 배열을 내부적으로 평탄화(flatten)하므로, "Alice가 score 5점을 준 리뷰"를 정확히 검색할 수 없다. nested 타입은 각 배열 요소를 숨겨진 별도 문서로 저장해 상관관계를 유지하지만, 색인 비용과 쿼리 복잡도가 높아진다. 배열 내 요소 간 관계를 쿼리해야 할 때만 nested를 선택한다.

Mapping Explosion: 필드 수 폭발

동적 매핑 환경에서 문서마다 예측 불가능한 필드가 들어오면 인덱스의 매핑이 수천 개 이상의 필드로 팽창한다. 이를 Mapping Explosion이라 한다. 클러스터 메모리를 압박하고 마스터 노드에 부하를 준다.

방어 전략:

  1. dynamic: strict 또는 dynamic: false: 미정의 필드를 거부하거나 색인만 건너뜀.
  2. index.mapping.total_fields.limit: 기본값 1000. 필요에 따라 조정하되 수천은 위험 신호.
  3. 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도 없다.

타입 변경이 필요하면 다음 순서로 진행한다.

  1. 새 매핑으로 신규 인덱스 생성
  2. POST /_reindex API로 데이터 복사
  3. 알리아스(alias)를 기존 인덱스에서 신규 인덱스로 전환
  4. 기존 인덱스 삭제

인덱스 알리아스를 처음부터 사용하면 애플리케이션 코드 변경 없이 재인덱스 전환이 가능하다. 서비스 코드가 인덱스 이름 대신 알리아스를 가리키도록 설계하는 것이 프로덕션의 표준 패턴이다.

매핑 설계 체크리스트

결정 사항권장 선택
프로덕션 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/