Elasticsearch 입문 9편 Field Types. text vs keyword 함정·nested vs object·date format·dense_vector·completion 까지.
이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 9편이에요. 8편(Mapping Deep) 에서 Static·Dynamic·Multi-field·Runtime 매핑 네 갈래를 잡았다면, 이번 9편은 그 매핑 안에 들어가는 필드 타입 자체를 한 줄씩 까서 언제 무엇을 쓰는지 까지 박는 자리예요.
이 글은 Elasticsearch 8.x 공식 docs 의 Field data types 페이지를 한국어 학습 노트로 풀어쓴 자료예요.
Kibana Dev Tools 에 한 줄씩 PUT 매핑을 박으면서 손가락으로 익히는 게 가장 빠르게 박혀요.
도입 — 필드 타입 선택이 검색·집계 동작을 결정
Elasticsearch 에서 왜 검색이 이상하게 잡히지·왜 집계가 터지지·왜 디스크가 폭증하지 같은 질문이 들어왔을 때, 거의 절반 이상이 필드 타입을 잘못 잡았기 때문 이에요. PG 라면 VARCHAR 와 TEXT 차이가 거의 의미 없는데, ES 는 같은 문자열이라도 text 로 박으면 풀텍스트 검색, keyword 로 박으면 정확 일치·집계·정렬이 가능해요. 문자열 하나가 두 가지 다른 인덱스로 들어가요.
이번 글은 8.x 공식 docs 의 Field data types 페이지를 따라가면서 — 한국 회사 백엔드에서 진짜 자주 만나는 타입들만 골라 깊이를 잡아요. text · keyword 가 가장 큰 함정이고, 그다음이 numeric · date · object vs nested, 마지막이 dense_vector · completion · geo 같은 특수 타입이에요. 8편 매핑 큰 그림 위에 9편 필드 타입 디테일이 얹히면, 운영 인덱스 설계의 90% 가 잡혀요.
text vs keyword — 가장 큰 함정
ES 매핑 첫 번째 결정이 "이 문자열 필드를 어떻게 쓸 거냐" 예요. 답은 두 갈래 — 풀텍스트 검색을 할 거면 text, 정확 일치·집계·정렬을 할 거면 keyword. 그리고 운영 표준은 둘 다 쓰는 multi-field 패턴이에요.
text 는 analyzer (분석기) 를 통과해요. "이태원 맛집 추천" 을 색인하면 standard analyzer 가 "이태원·맛집·추천" 세 토큰으로 쪼개서 Inverted Index 에 박아요. 한국어라면 11편에서 다룰 Nori 분석기를 깔아야 형태소 단위로 제대로 쪼개져요. text 필드는 풀텍스트 매치 (match 쿼리) 에는 강하지만, 집계 (aggregation)·정렬 (sort) 가 기본적으로 안 돼요. 굳이 켜려면 fielddata: true 가 필요한데 메모리 비용이 커서 운영에서는 거의 안 씁니다.
keyword 는 analyzer 를 통과 안 해요. "이태원 맛집 추천" 을 박으면 그대로 한 덩어리 토큰이 돼요. "이태원" 으로 검색해도 안 잡히고, 정확히 "이태원 맛집 추천" 으로만 잡혀요. 대신 term 쿼리·집계·정렬 이 모두 빠르게 돌아가요. 카테고리·태그·이메일·상품 SKU·국가 코드 — 이런 값이 그대로 의미 있는 필드가 keyword 자리예요.
운영 표준은 multi-field — 한 필드를 둘로 박아 두는 패턴이에요. title 을 text 로 색인하면서 동시에 title.keyword 를 keyword 로 박아 두면, 검색은 title 로, 집계·정렬은 title.keyword 로 분리해서 쓸 수 있어요. 8.x 의 동적 매핑 기본값 이 이미 이 패턴이에요 — 문자열을 그냥 색인하면 자동으로 text + keyword (256 글자 제한) 가 같이 생겨요.
PUT /products
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "nori",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}
}
}
}
여기서 ignore_above: 256 옵션이 중요한데 — 256자 넘는 문자열은 keyword 인덱스에 안 들어가요. 검색은 되지만 집계에는 안 잡혀요. 상품명 같은 짧은 텍스트는 256 으로 충분하지만, 본문·설명처럼 긴 텍스트는 keyword 자체를 박지 말거나 길이 한도를 늘려야 해요.
한 가지 더 — constant_keyword 와 wildcard 라는 keyword 변종이 있어요. constant_keyword 는 인덱스 전체에서 단 하나의 값만 갖는 필드 (예: 환경 = "prod") 에 쓰는 디스크 절약형 이고, wildcard 는 *search* 같은 와일드카드 검색을 빠르게 하는 변종이에요. 둘 다 8.x 부터 정식 GA.
Numeric — byte·short·integer·long·half_float·float·double·scaled_float
숫자 타입은 정수 5종 + 실수 4종 으로 갈라져요. 값의 범위 와 디스크·메모리 비용 이 다 다르고, 잘못 잡으면 디스크가 두세 배로 폭증 하거나 오버플로 가 터져요.
정수형 은 byte (-128~127) · short (-32K~32K) · integer (-2.1B~2.1B) · long (-9.2E·9.2E) 네 종이에요. 기본 권장은 long 보다 작은 타입 으로 잡는 것 — 나이 는 byte 로, 상품 카테고리 ID 가 1만 안쪽이면 short 로 박아요. 정렬·집계 메모리 와 디스크 압축률 이 타입 크기에 비례하기 때문에, long 으로 전부 박는 게 게으른 운영 이에요. 다만 PK· 외래 키처럼 미래에 범위가 늘어날 가능성이 있는 값 은 long 으로 안전하게 가는 게 표준 — 한 번 박은 타입은 변경이 까다로워서.
실수형 은 float (32bit) · double (64bit) · half_float (16bit) · scaled_float 네 종. 일반 통계·평균 점수처럼 정밀도 5~6자리면 충분 하면 float 으로, 금융·과학 계산 처럼 15자리 정밀도 가 필요하면 double 로 가요. half_float 는 16비트 낮은 정밀도 라 임베딩 벡터 일부 또는 대량 통계 에 디스크를 절약하는 자리.
가장 자주 놓치는 게 scaled_float. 가격 (소수점 2자리)·평점 (소수점 1자리) 같이 정밀도가 고정 인 실수는 scaling_factor 를 잡아서 정수로 저장 하는 게 압축이 훨씬 좋아요.
{
"price": {
"type": "scaled_float",
"scaling_factor": 100
}
}
위 설정이면 19,900원 이 내부적으로는 1,990,000 (정수) 로 저장돼요. float·double 보다 디스크 30~50% 절감 이 일반 케이스예요. 가격·평점·환율·확률 같이 소수 자릿수가 고정 인 값은 거의 무조건 scaled_float 가 답.
운영에서 가장 자주 만나는 가이드 — 장기적으로 안전한 기본값 은 integer (PK 가 아니면) · long (PK·외래 키) · scaled_float (금융) · float (그 외 실수). 8편 매핑 strict 와 묶어 모든 숫자 필드의 타입을 명시 해야 동적 매핑이 무조건 long·float 으로 박는 비효율을 막을 수 있어요.
Date — strict_date_optional_time, date format, epoch_millis, date_nanos
날짜는 ES 에서 내부적으로 long (epoch millis) 으로 저장돼요. 입력 형식 만 우리가 정해 주고, 색인 시점에 알아서 ms 정수로 변환돼요. 그래서 집계·정렬·범위 쿼리 가 정수만큼 빠르고, 시계열 데이터 의 핵심 타입이 됩니다.
기본 포맷이 strict_date_optional_time 인데 — 2026-05-19T12:30:00.000Z 같은 ISO 8601 형식이 표준이에요. strict 가 붙는 이유는 연·월·일 자릿수가 정확히 4·2·2 여야 통과한다는 것 — 2026-5-19 (월이 한 자리) 는 받지 않아요.
여러 입력 형식을 받고 싶으면 format 으로 파이프 구분 해서 박아요.
{
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis||yyyy-MM-dd HH:mm:ss"
}
}
세 형식 어느 쪽으로 들어와도 epoch millis 로 통일 저장 돼요. epoch_millis 는 유닉스 타임스탬프 (ms) — 자바·자바스크립트 클라이언트가 timestamp 그대로 보낼 때 자주 쓰여요. epoch_second 도 있어요.
ms 단위 정밀도로 부족할 때 — 고빈도 트레이딩·분산 트레이싱 같은 자리 — 가 date_nanos 자리예요. 나노초 정밀도 로 저장되고 long 대신 long 두 개 를 쓰기 때문에 디스크 비용이 약간 더 들어요. 일반 로그·이커머스 주문 에는 date 면 충분 하고, APM·트레이싱·금융 체결 같은 자리에만 date_nanos 가 답.
ES 의 강력한 기능 하나가 date math — 쿼리에서 now-7d/d (지금에서 7일 전 자정) 같은 상대 시각 표현 을 그대로 쓸 수 있어요. 시계열 검색 의 핵심 무기.
GET /logs/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
위 쿼리는 현재로부터 1시간 전부터 지금까지 의 로그를 가져와요. Kibana 시간 필터가 내부적으로 이걸 씁니다.
자주 만나는 함정 — 타임존. 입력 데이터에 타임존이 없으면 ES 가 UTC 로 가정 해요. 클라이언트가 KST 로 보내면서 Z 표시 를 안 붙이면 9시간 차이 가 나서 집계가 어긋나요. 표준 권장은 모든 timestamp 를 UTC + Z 로 보내고, 표시 단계에서만 KST 로 변환. 30편(Monitoring) 에서 다시.
Boolean, Binary, Range — boolean·binary·integer_range·date_range·ip_range
자잘하지만 자주 만나는 타입 세 갈래예요.
boolean 은 true / false 만 받아요. 문자열 "true" 도 받아 주지만 8.x 부터 deprecated 라 boolean 그대로 보내는 게 표준. 디스크 비용이 가장 적어서 플래그성 필드 (is_active·is_deleted·is_premium) 에 부담 없이 박을 수 있어요. 다만 주의할 점 — boolean 으로 박은 필드는 null 과 false 가 다른 값 으로 취급돼요. missing 처리 가 필요하면 null_value: false 옵션을 같이.
binary 는 Base64 인코딩된 이진 데이터 를 저장해요. 기본적으로 검색·집계 X, 그냥 보관용. 이미지 썸네일·파일 해시·암호화된 페이로드 같은 자리에 가끔. 대용량 이진은 절대 binary 에 박지 말 것 — ES 는 이진 저장소가 아니에요. 1MB 넘으면 S3 같은 별도 객체 저장소 에 두고 ES 에는 URL·키 만 박는 게 표준.
Range 타입 은 시작·끝 두 값을 한 필드로 묶은 특수 타입이에요. integer_range · long_range · float_range · double_range · date_range · ip_range 여섯 종.
{
"promo_period": {
"type": "date_range",
"format": "yyyy-MM-dd"
}
}
// 색인
PUT /promos/_doc/1
{
"promo_period": {
"gte": "2026-05-19",
"lte": "2026-05-31"
}
}
이렇게 박아 두면 "2026-05-25 가 어느 promo 에 걸리는지" 같은 범위 포함 쿼리 가 한 번에 잡혀요. 일반 date 두 개 (start_at·end_at) 로도 흉내는 가능하지만 Range 타입 이 전용 인덱스 구조 라 훨씬 빠르고 표현이 깔끔해요. 프로모션·예약·요금제 같은 자리에 자주.
ip 자체 는 별도 타입이 있어요 — IPv4·IPv6 둘 다 받고, CIDR 표기 검색 (192.168.1.0/24) 도 가능. 7장(특수 타입) 에서 다시.
Object vs Nested — object 가 평탄화돼서 배열 의미 손실. nested 가 배열 객체 보존. 비용 차이.
ES 의 가장 큰 함정 하나가 object 와 nested 의 차이예요. JSON 으로 그냥 던지면 모두 object 로 색인 되는데, 배열로 객체를 넣으면 의미가 깨져요.
object 는 기본 동작 이에요. 중첩 JSON 을 평탄화 (flatten) 해서 한 flat 키-값 으로 풀어 박아요. 예를 들어 이런 문서가 있다고 해 볼게요.
{
"user": {
"first": "Jinwoo",
"last": "Oh"
}
}
이건 내부적으로 user.first = "Jinwoo" · user.last = "Oh" 두 flat 필드 로 색인돼요. 단순 객체에는 문제 없어요.
문제는 배열 객체 예요. 다음 문서를 보면 —
{
"users": [
{ "first": "Jinwoo", "last": "Oh" },
{ "first": "Minji", "last": "Park" }
]
}
이걸 기본 object 로 색인하면 내부적으로는 users.first = ["Jinwoo", "Minji"] · users.last = ["Oh", "Park"] 두 배열로 평탄화돼요. 각 요소가 어느 짝이었는지 가 사라져 버려요. 그 결과 first = "Jinwoo" AND last = "Park" 같은 짝이 맞지 않는 쿼리가 매치돼 버리는 사고가 터져요.
nested 가 이걸 잡아 줘요. 배열의 각 객체를 별도 숨겨진 문서로 색인 해서 짝 관계를 유지 합니다.
PUT /teams
{
"mappings": {
"properties": {
"users": {
"type": "nested",
"properties": {
"first": { "type": "keyword" },
"last": { "type": "keyword" }
}
}
}
}
}
이렇게 박으면 Jinwoo - Oh · Minji - Park 두 짝이 따로 색인되고, nested 쿼리 로 "first = Jinwoo AND last = Park" 을 던지면 제대로 매치 안 되어요. 짝 의미가 살아 있어요.
비용 차이 는 무시 못 해요. nested 객체는 각각이 숨겨진 별도 문서 라서 Lucene 세그먼트 안에 N+1 개 문서가 생겨요. 디스크 비용·색인 비용·쿼리 비용 이 모두 object 대비 1.5~3배 늘어요. 그래서 운영 표준은 "배열 객체에 짝 의미가 필요하면 nested, 아니면 object" — 마구잡이로 nested 박지 말고 짝 의미가 정말 필요한 자리에만 박는 게 절약 포인트.
한 가지 더 — nested 필드는 기본적으로 한 문서당 10,000개로 제한 돼 있어요 (index.mapping.nested_objects.limit). 한 문서에 댓글 10만개 같은 패턴은 nested 로 박으면 안 되고, 부모-자식 관계 (parent-join) 로 분리하거나, 별도 인덱스 로 빼는 게 답.
flattened, geo_point, geo_shape, ip, completion, dense_vector — 특수 타입 한 줄씩
자주 만나는 특수 타입 여섯 갈래를 한 줄씩.
flattened 는 키가 무작위로 늘어나는 JSON 덩어리 를 한 필드 로 색인하는 타입이에요. 예: 사용자 메타데이터·서드파티 API 응답 덩어리 처럼 키 종류를 미리 모를 때. 모든 키-값을 하나의 keyword 배열 로 압축해서 Mapping Explosion 사고 를 막아 줘요. 단점은 서브키 별 집계가 제한적 이고 분석기를 못 박아요. 원하는 키만 풀텍스트가 필요하면 별도 text 필드로 빼서 멀티 필드 하는 패턴.
geo_point 는 위·경도 한 쌍 이에요. 지구 위 한 점 을 표현하고, 반경·박스·다각형 검색 이 가능해요. 매장 검색·주변 추천·물류 트래킹 자리에 거의 표준. lat·lon 두 숫자로 박을 수도 있고 "37.5,127.0" 문자열 또는 GeoHash 로도 받아요.
geo_shape 는 점·선·다각형 같은 복잡한 도형을 저장하는 타입이에요. 행정구역·상권 폴리곤·배달 권역 같이 영역 자체가 데이터인 자리에 써요. geo_point 보다 비용이 훨씬 크니 반경 검색이면 geo_point 로 충분.
ip 는 IPv4 · IPv6 주소 를 그대로 받는 전용 타입이에요. CIDR 표기 매칭·서브넷 집계 가 표준 keyword 보다 깔끔. 로그·보안 자리에 거의 무조건 ip 로 박아요.
completion 은 자동완성 (auto-complete) 전용 타입이에요. Finite State Transducer (FST) 라는 자료구조를 메모리에 올려 μs 단위 응답 으로 접두사 매칭 을 해 줘요. 검색창 자동완성·태그 제안 자리에. 단점은 메모리 — 인덱스 크기만큼 힙 메모리 를 먹어요. 수백만 건 이상 이면 메모리 비용을 미리 계산.
dense_vector 는 임베딩 벡터 를 저장하는 타입이에요. float 배열 을 그대로 받아 kNN (k-Nearest Neighbors) 으로 유사도 검색 을 해 줘요. RAG·시맨틱 검색·추천 시스템 의 핵심. 차원 수 (dims) 와 유사도 함수 (similarity: cosine | dot_product | l2_norm) 를 매핑 시점에 박아야 해요. 21·22편에서 깊이.
{
"embedding": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine"
}
}
dims 가 임베딩 모델 출력 차원 과 정확히 일치해야 하고 (예: OpenAI text-embedding-3-small = 1536, BGE-base = 768), 나중에 변경 불가 라 모델 선택을 먼저 확정 하고 박는 게 표준.
자주 만나는 사고
사고 1 — text 만 박고 집계 시도
원인 — category 필드를 text 로 박은 채로 terms 집계를 시도하면 "Fielddata is disabled on text fields" 에러가 나요.
해결 — 매핑을 multi-field 로 바꿔 category.keyword 를 추가하고, 집계는 keyword 쪽으로. 이미 색인된 데이터가 있으면 reindex 가 필요해요. 5편(Index 관리) 에서 깊이.
사고 2 — date format mismatch
원인 — 클라이언트가 "2026-05-19 12:30:00" (공백 구분) 으로 보내는데, 매핑에 strict_date_optional_time 만 박혀 있으면 T 가 없어서 파싱 실패 라 색인이 거절돼요.
해결 — format 에 "yyyy-MM-dd HH:mm:ss" 를 추가 하거나, 클라이언트를 ISO 8601 로 통일. 운영 권장은 클라이언트 통일 — 매핑에 형식이 많을수록 디버깅이 어려워져요.
사고 3 — nested vs object 잘못 선택
원인 — 주문 안에 상품 라인 배열 을 object 로 박았는데, "브랜드 A 의 빨간색 상품" 쿼리 가 브랜드 A + 다른 색깔 빨간색 인 주문까지 매치되는 짝 깨짐 사고.
해결 — 상품 라인을 nested 로 박고 nested 쿼리로 감싸요. 이미 데이터가 있으면 reindex 필요. 수십 GB 인덱스라면 다운타임 비용이 큼 — 7편(Reindex) 의 alias swap 패턴.
사고 4 — scaled_float 미사용으로 디스크 폭증
원인 — 가격 필드를 double 로 박았더니 10억 문서 에 디스크가 수백 GB 까지 부풀어 올라요.
해결 — scaled_float (scaling_factor: 100) 로 박으면 디스크 30~50% 절감. 동적 매핑이 자동으로 float 으로 박는 함정 이 흔해서, 명시적 매핑 으로 박는 게 표준.
사고 5 — completion 메모리 폭증
원인 — 자동완성용 suggest 필드를 수천만 건 인덱스에 깔았더니 힙 메모리 OOM (Out of Memory). FST 가 디스크가 아닌 힙 에 올라가요.
해결 — suggest 인덱스를 별도로 두고 상위 10만건 인기 쿼리만 색인하거나, search-as-you-type 타입 또는 edge_ngram 분석기로 대체. 19편(Suggesters) 에서 깊이.
사고 6 — Mapping Explosion (flattened 미사용)
원인 — 사용자 메타데이터처럼 키가 자유롭게 추가되는 JSON 을 동적 매핑 + object 로 색인하면, 매핑 필드 수가 폭증해 1,000 제한 에 걸리거나 클러스터 메모리 OOM.
해결 — 그런 자리는 flattened 타입으로 박아 한 필드로 압축. 8편(Mapping Deep) 에서 다룬 dynamic: strict 와 묶어 운영.
사고 7 — dense_vector dims 잘못 박음
원인 — 처음에 BGE-base (768차원) 으로 박았는데 OpenAI text-embedding-3-small (1536차원) 으로 모델을 바꾸면, 매핑 변경이 불가능해서 인덱스를 새로 만들고 reindex 해야 해요.
해결 — 임베딩 모델은 프로젝트 초기에 확정 하고, 모델 교체 가능성 이 있는 자리는 별도 vector 인덱스 로 분리. 21편(Vector Search) 에서 깊이.
운영 권장 패턴
운영 인덱스에서는 동적 매핑을 꺼요 (dynamic: strict). 모든 필드의 타입을 명시적 으로 박아 Mapping Explosion · 자동 long·float 비효율 을 동시에 잡아요. 8편의 핵심 결론이 여기서도 그대로.
문자열 필드는 multi-field 가 기본 — text + keyword 둘 다 박는 패턴. 풀텍스트 검색·집계·정렬을 동시에 잡아 줘요. 동적 매핑 기본값이 이미 이 패턴이라 그대로 따라가는 게 표준.
숫자 필드는 타입을 보수적으로 작게. byte·short·integer 로 갈 수 있으면 가고, 가격·평점은 scaled_float. 모든 숫자를 long·double 로 박는 게으른 매핑 이 운영 단계에 디스크·메모리 비용으로 돌아와요.
날짜 필드는 UTC + ISO 8601 통일. 클라이언트도 서버도 KST 가 아닌 UTC 로 통신하고, Z 를 붙여 명시. 표시 단계에서만 KST 로 변환하는 단방향 흐름이 표준.
배열 객체는 짝 의미가 있으면 nested. 없으면 object 로 둬서 디스크·메모리 절약. 모든 배열 객체를 nested 로 박는 패턴 은 비용 폭증의 지름길이라 짝 의미 필요 가 검증된 자리에만.
특수 타입은 자리를 명확히 — flattened 는 키 무작위, geo_point 는 반경 검색, completion 은 자동완성, dense_vector 는 임베딩. 옆 사람이 이미 잘 돌아가는 자리에 그대로 따라가는 게 정답이고, 새 시도는 21·22편 같은 깊이 글* 을 보고 천천히.
시험 직전 한 번 더 — 압축 노트
- text = analyzer 통과·풀텍스트 검색. 기본 집계·정렬 X.
- keyword = analyzer X·정확 일치·집계·정렬 OK.
ignore_above: 256주의. - 운영 표준 = text + keyword multi-field (동적 매핑 기본값).
- constant_keyword·wildcard = keyword 변종. 단일 값·와일드카드 빠른 검색.
- 정수형 = byte·short·integer·long. PK 는 long, 나머지는 작게.
- 실수형 = float·double·half_float·scaled_float. 가격·평점은 scaled_float.
- date = 내부적으로 long (epoch millis).
strict_date_optional_time기본. - date_nanos = 나노초 정밀도. APM·트레이싱·금융 자리.
- boolean = true/false 만. 문자열 deprecated.
- Range = integer_range·date_range·ip_range 등. 시작-끝 한 필드.
- object = 평탄화. 배열 객체에서 짝 의미 손실.
- nested = 배열 객체 짝 보존. 비용 1.5~3배. limit 10,000.
- flattened = 키 무작위 JSON 압축. Mapping Explosion 방어.
- geo_point = 위·경도 한 쌍. 반경·박스·다각형 검색.
- geo_shape = 다각형·선·점. 행정구역·배달 권역.
- ip = IPv4·IPv6 전용. CIDR 검색.
- completion = 자동완성 (FST 메모리). 힙 OOM 주의.
- dense_vector = 임베딩 벡터. dims · similarity 매핑 시점 고정.
- 7대 사고 = text 집계·date format·nested 잘못 · scaled_float 미사용 · completion OOM · Mapping Explosion · dense_vector dims.
- 운영 6원칙 = strict 매핑 · multi-field · 작은 숫자 타입 · UTC 통일 · nested 신중 · 특수 타입 자리 명확.
시리즈 다른 편
- 이전 글 = 8편 Mapping Deep — Static·Dynamic·Multi-field·Runtime
- 다음 글 = 10편 Analyzer & Tokenizer — char filter·tokenizer·token filter
- 5편 = Index 관리 — Create·Settings·Alias·Reindex
- 7편 = Document CRUD — Index·Get·Update·Delete·Bulk
- 11편 = Korean Analyzer — Nori·mecab-ko·사용자 사전
- 12편 = Search API 큰 그림
- 14편 = Term-level Query — term·terms·range·exists
- 21편 = Vector Search — dense_vector·kNN·HNSW
- 22편 = RAG with Elasticsearch — 임베딩·하이브리드 검색
- 32편 = Spring Data Elasticsearch — @Document·@Field·Repository
한 줄 정리 — Elasticsearch 필드 타입 = 문자열은 text·keyword multi-field, 숫자는 작게·가격은 scaled_float, 날짜는 UTC ISO, 배열 객체는 짝 의미면 nested, 무작위 키는 flattened, 자동완성은 completion, 임베딩은 dense_vector. 매핑 시점에 박은 타입이 검색·집계·디스크 비용 을 끝까지 결정해요.