Elasticsearch 입문 21편 Vector Search·kNN. dense_vector·sparse_vector·HNSW·hybrid retrieval·RRF·Inference API.
이 글은 Elasticsearch 입문에서 운영까지 시리즈 38편 중 21편이에요. 19편(Search Features) 과 20편(Suggesters) 으로 전통 풀텍스트 검색·자동완성 자리를 한 번 정리했으니, 이번 편은 LLM·임베딩 시대의 새로운 검색 자리 — Vector Search 와 kNN 으로 넘어갑니다.
이 글은 Elasticsearch 8.x 공식 docs 중 kNN search 와 dense_vector·sparse_vector 챕터를 한국어 학습 노트로 풀어쓴 자료예요.
RAG 인프라가 한 자리 잡힌 회사라면 별도 벡터 DB 를 빼고 ES 하나로 묶는 결정이 운영 비용을 크게 줄여 줘요.
LLM·임베딩 시대, ES 가 벡터 DB 자리까지 가져가는 이유
ChatGPT·Claude 가 2023년부터 일상 으로 자리 잡으면서 모든 회사 백엔드에 임베딩 벡터를 저장하고 유사도로 검색 하는 자리가 생겼어요. RAG (Retrieval-Augmented Generation, 검색 기반 생성) — 사용자 질문을 임베딩으로 바꾼 뒤, 비슷한 문서 k 개 를 찾아 LLM 프롬프트에 끼워 넣는 패턴이 표준 자리.
그 자리를 처음엔 Pinecone · Weaviate · Qdrant 같은 벡터 전용 DB 가 가져갔어요. 다만 회사 운영 입장에서 검색용 ES + 벡터용 Pinecone 두 도구를 따로 굴리는 비용이 무겁고, 전통 풀텍스트 + 벡터 유사도 를 한 쿼리로 묶기가 어색했어요.
Elasticsearch 8.x 가 그 자리를 가져왔어요. dense_vector 필드 + kNN 쿼리 + HNSW 인덱스 로 벡터 전용 DB 와 같은 성능 을 내고, 전통 BM25 검색과 한 쿼리로 묶는 hybrid retrieval 까지 제공해요. 작성 시점(2026-05-19) 기준으로 AWS OpenSearch · Elastic Cloud 양쪽 다 production 수준에 도달해서, 벡터 DB 별도 도입 결정이 점점 줄어드는 추세예요.
이번 편이 다루는 자리를 한 호흡에 정리하면 — dense_vector 필드 정의 · kNN 쿼리 구문 · HNSW 알고리즘 파라미터 · sparse_vector 와 ELSER · hybrid retrieval (RRF) · Inference API 와 외부 모델 연결. 22편(RAG 패턴) 에서 이 도구들을 실제 RAG 아키텍처로 묶는 운영 패턴 을 깊이 다루니, 이 편은 부품 단위 정확한 이해 에 집중합니다.
dense_vector 필드 — 벡터를 어떻게 저장하는가
dense_vector 는 Elasticsearch 가 고정 차원의 부동소수점 배열 을 저장·검색하기 위해 제공하는 필드 타입이에요. 임베딩 모델 출력 이 그대로 들어가는 자리고, 모든 벡터 검색의 출발점입니다.
기본 매핑은 이렇게 잡아요.
PUT my-vector-index
{
"mappings": {
"properties": {
"title": { "type": "text" },
"embedding": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine"
}
}
}
}
핵심 파라미터 네 개를 짚어요.
dims 는 벡터 차원 수예요. 임베딩 모델이 출력하는 차원 과 정확히 맞아야 해요. text-embedding-3-small 은 1,536, text-embedding-3-large 는 3,072, BERT-base 는 768, BGE-M3 는 1,024 — 모델 docs 를 먼저 확인하고 박는 게 표준 순서예요. 한 번 정하면 재색인 없이는 변경 불가 라서 Mapping Explosion 사고 와 같은 비중으로 신중해야 해요.
index 는 HNSW 인덱스를 만들 것인가 의 토글이에요. true 면 ANN (Approximate Nearest Neighbor, 근사 최근접) 검색 을 위한 그래프 구조를 디스크에 만들어 두고, false 면 exact kNN (brute force) 만 가능해요. 운영 사이즈는 사실상 true 가 기본, false 는 수백~수천 건 작은 데이터 거나 집계만 하는 경우 에만 쓰면 됩니다.
similarity 는 벡터 거리 함수 예요. 세 가지 선택지가 있어요.
cosine— 코사인 유사도. 벡터의 방향 만 비교 (크기 무시). LLM 임베딩 검색의 사실상 표준. OpenAI · Cohere · BGE 가 모두 cosine 가정 학습.dot_product— 내적. 벡터가 L2 정규화 되어 있으면 cosine 과 결과 동일, 속도 더 빠름. 정규화 안 한 벡터에 쓰면 결과가 크기에 휘둘려 망가져요.l2_norm— 유클리드 거리. 이미지 임베딩·전통 ML 피처 벡터에 주로 쓰여요.
element_type 은 벡터 원소 타입 이에요. 기본 float (32-bit) 외에 byte (int8 양자화) 와 bit (1-bit 양자화) 가 있어요. 8.7+ 에서 byte, 8.12+ 에서 bit 가 GA — 메모리 4배·32배 절약 인 대신 recall 약간 손실. 대규모 인덱스 운영에서 매우 중요해요.
매핑 잡았으면 색인은 평소대로 PUT/POST 로 들어가요.
POST my-vector-index/_doc/1
{
"title": "Elasticsearch 입문",
"embedding": [0.12, -0.34, 0.56, /* ... 768개 ... */ ]
}
벡터는 클라이언트가 임베딩 모델을 호출해서 만들어 넘기는 게 기본 패턴 이에요. Inference API (뒤에서 다룸) 를 쓰면 ES 가 직접 모델을 호출해서 만들어 주기도 해요.
kNN 검색 — _search 의 knn 절
벡터 인덱스 위에서 가까운 문서 k 개 를 찾는 쿼리가 kNN search 예요. 8.x 기준으로 _search API 의 knn 절을 쓰는 게 표준 자리.
가장 단순한 형태는 이래요.
POST my-vector-index/_search
{
"knn": {
"field": "embedding",
"query_vector": [0.05, -0.12, /* ... 768개 ... */ ],
"k": 10,
"num_candidates": 100
}
}
여기서 핵심 파라미터 세 개를 짚어요.
k 는 최종적으로 반환할 문서 수 예요. RAG 에서 LLM 프롬프트에 끼워 넣을 문서 갯수 와 직결되니까 — 보통 5~20 사이 가 일반이에요. 너무 크면 LLM 컨텍스트 윈도우가 터지고, 너무 작으면 답변이 빈약.
num_candidates 는 각 샤드에서 후보로 추릴 문서 수 예요. k 보다 충분히 큰 값 (보통 50~200) 으로 잡아야 HNSW 의 ANN 근사가 recall 떨어뜨리는 사고 가 줄어들어요. ES 권장 식 = max(k * 1.5, 100) 가 거친 출발점.
filter 는 벡터 검색 전에 후보를 좁히는 필터 예요. 카테고리·언어·날짜 같은 구조화 필드 로 먼저 좁히고 그 안에서 벡터 유사도를 계산해요. 이 자리가 벡터 DB 와 ES 의 결정적 차이 — 대부분의 벡터 전용 DB 는 metadata 필터 가 후처리(post-filter) 라 k 가 깨지는 사고가 잦은데, ES 는 pre-filter 라 정확한 k 가 보장돼요.
POST my-vector-index/_search
{
"knn": {
"field": "embedding",
"query_vector": [/* ... */],
"k": 10,
"num_candidates": 100,
"filter": [
{ "term": { "category": "tech" } },
{ "range": { "published_at": { "gte": "2025-01-01" } } }
]
}
}
boost 는 hybrid retrieval 에서 벡터 점수의 가중치를 잡을 때 써요. 뒤에서 hybrid 절 깊이 갈 때 다시 등장.
여러 벡터 필드를 한 번에 검색하는 multi-knn 도 8.7+ 부터 지원 — knn 을 배열로 넘기면 여러 임베딩 (예: 제목 임베딩 + 본문 임베딩) 점수를 합쳐서 정렬해요. 23편(Bulk Ingest) 에서 multi-vector 문서 색인 패턴 을 같이 다루니 이 글에선 단일 벡터 자리만 잡고 가요.
HNSW — Approximate Nearest Neighbor 의 표준 알고리즘
index: true 로 dense_vector 를 만들면 ES 가 내부에서 HNSW (Hierarchical Navigable Small World) 그래프를 만들어 둬요. 이름이 길고 무서워 보이지만 개념은 층층이 쌓인 도로망 으로 비유 가능.
전체 데이터의 상위 층에 듬성듬성 쌓이는 노드 부터 하위 층에 빽빽이 쌓이는 노드 까지 여러 층 의 그래프를 만들어 둬요. 검색 시작 시점에 맨 위 층의 진입점 에서 가장 가까운 이웃 으로 점프하며 내려오고, 마지막 층에서 k 개 후보 를 추려요. 모든 벡터와 거리 계산 (= brute force, exact kNN) 대비 수십~수백 배 빠르고 recall 95%+ 가 나와요 — 대규모 벡터 검색의 사실상 표준.
ES 가 노출하는 HNSW 파라미터는 두 개예요. 매핑 시점에 박아요.
"embedding": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "hnsw",
"m": 16,
"ef_construction": 100
}
}
m 은 각 노드가 가질 이웃 수 예요. 그래프 연결 밀도 를 결정 — 크면 recall 올라가지만 메모리·색인 비용 폭증, 작으면 빠르지만 recall 폭락. ES 기본값 16 이 대부분 자리에서 균형이 좋고, recall 이 안 나오면 24~32 까지 올리는 게 일반.
ef_construction 은 색인 시점에 각 노드가 탐색할 이웃 후보 수 예요. 크면 그래프 품질 좋아지지만 색인 시간 폭증, 작으면 빠르지만 그래프가 엉성. 기본값 100, 운영 사이즈는 200~400 권장.
검색 시점의 탐색 폭 은 num_candidates 가 잡아요 (검색 시 사용되는 ef_search 에 해당). m·ef_construction 은 그래프 구조 자체 를 결정하니까 한 번 정해서 색인하면 변경 불가 — 매우 신중하게 박아요.
HNSW 외에 ES 가 Flat (brute force, exact) 과 int8_hnsw · int8_flat (양자화) 같은 변형도 8.12+ 부터 노출해요. byte element_type 과 결합 하면 메모리 4배 절약 + 속도 약간 빠름 — 대규모 운영의 표준 권장 패턴이 8.13+ 부터는 int8_hnsw 로 자리잡고 있어요.
sparse_vector 와 ELSER — learned sparse retrieval
벡터 검색의 또 다른 갈래로 sparse_vector (8.11+) 가 있어요. dense_vector 가 모든 차원이 값이 채워진 작은 차원의 벡터 라면, sparse_vector 는 수만~수십만 차원 인데 대부분 0 인 sparse 벡터 자리예요.
대표 모델은 Elastic 이 자체 개발한 ELSER (Elastic Learned Sparse EncodeR). BERT 계열 모델 인데 출력이 각 토큰의 가중치를 가진 sparse 벡터 라서 — 전통 BM25 의 inverted index 위에 그대로 얹어 쓸 수 있고 해석 가능 (어느 단어가 매칭됐는지 보임) 한 장점이 있어요.
사용 자리를 한 호흡에 비교.
| dense_vector | sparse_vector | |
|---|---|---|
| 차원 | 작음 (128~3,072) | 매우 큼 (~30k), 대부분 0 |
| 대표 모델 | OpenAI · Cohere · BGE · BERT | ELSER · SPLADE |
| 인덱스 | HNSW (그래프) | inverted index |
| 해석 가능성 | 낮음 (블랙박스) | 높음 (어느 토큰이 매칭) |
| 다국어 지원 | 모델에 따라 다름 | ELSER v2 영어 위주 |
| 운영 비용 | GPU 권장 색인 | CPU 만으로 가능 |
매핑은 이렇게 박아요.
"text_expansion_field": {
"type": "sparse_vector"
}
쿼리는 sparse_vector 절을 써요.
POST my-index/_search
{
"query": {
"sparse_vector": {
"field": "text_expansion_field",
"inference_id": "elser-v2-elastic",
"query": "한국 회사 elasticsearch 도입"
}
}
}
ELSER 모델은 Inference API 로 미리 deploy 해두고 inference_id 로 참조하는 게 8.x 표준 패턴. ELSER 가 sparse_vector 를 질의 시점에도 만들어 줘요.
작성 시점 기준 ELSER 는 주로 영어 에 최적화돼 있고 한국어 검색에는 약해요. 한국어 자리는 Nori 기반 BM25 + dense_vector (BGE-M3 같은 다국어 임베딩) 의 hybrid 가 현실 권장. 22편(RAG 패턴) 에서 한국어 RAG 의 검증된 조합 을 깊이.
Hybrid Retrieval — BM25 + kNN 한 쿼리로
벡터 검색만으로 모든 자리가 잡히진 않아요. 전통 BM25 풀텍스트 가 잘 잡는 자리 (정확한 키워드·고유명사·코드 검색) 와 벡터 가 잘 잡는 자리 (의미 유사성·자연어 질의) 가 서로 다르거든요.
두 점수를 한 쿼리로 묶는 방식이 Hybrid Retrieval 이에요. ES 가 제공하는 표준 방법 두 가지를 짚어요.
방법 1 — boost 기반 단순 합산
query 와 knn 을 같이 보내면 두 점수가 boost 가중치로 합산 돼서 정렬돼요.
POST my-index/_search
{
"query": {
"match": {
"title": { "query": "Elasticsearch 입문", "boost": 0.3 }
}
},
"knn": {
"field": "embedding",
"query_vector": [/* ... */],
"k": 50,
"num_candidates": 100,
"boost": 0.7
},
"size": 10
}
장점은 간단함. 단점은 BM25 점수 (0~∞) 와 cosine 유사도 (-1~1) 의 스케일이 달라서 boost 조정이 눈치 작업 이 된다는 점.
방법 2 — RRF (Reciprocal Rank Fusion)
ES 8.8+ 가 정식 지원하는 RRF 가 hybrid retrieval 의 사실상 표준이에요. 점수를 직접 합치지 않고 순위를 합치는 방식이라 스케일 차이 문제가 자동으로 사라져요.
POST my-index/_search
{
"retriever": {
"rrf": {
"retrievers": [
{
"standard": {
"query": { "match": { "title": "Elasticsearch 입문" } }
}
},
{
"knn": {
"field": "embedding",
"query_vector": [/* ... */],
"k": 50,
"num_candidates": 100
}
}
],
"rank_window_size": 50,
"rank_constant": 60
}
},
"size": 10
}
수식은 단순해요. 각 검색기에서 문서 d 가 r 위 일 때 점수를 1 / (rank_constant + r) 로 변환하고 두 점수를 합산. rank_constant 기본 60 이 Cormack et al. 2009 논문 권장값이라 거의 안 바꿔요.
22편(RAG 패턴) 에서 RRF + reranker (Cohere Rerank·BGE Reranker) 의 두 단계 패턴을 깊이 다루니 — 이 글에선 RRF 가 hybrid 의 표준 자리 라는 점만 잡고 가요.
Inference API — 외부 임베딩 모델 연결
벡터 색인의 가장 큰 운영 부담이 임베딩 생성 이에요. 매번 클라이언트가 OpenAI 호출하고, 토큰 사용량 추적하고, 실패 재시도 짜고 — 이 자리를 ES 8.11+ 의 Inference API 가 대신 잡아 줘요.
지원 provider 한 호흡에. OpenAI · Cohere · Hugging Face · Azure OpenAI · Google Vertex AI · Anthropic (생성용) · ELSER (Elastic 자체) · ELAND (자체 호스팅). 작성 시점(2026-05-19) 기준 거의 모든 주요 임베딩 모델이 직접 붙어요.
inference endpoint 생성은 이렇게.
PUT _inference/text_embedding/openai-small
{
"service": "openai",
"service_settings": {
"api_key": "sk-...",
"model_id": "text-embedding-3-small"
}
}
그 다음 ingest pipeline 에 박으면 색인 시점에 자동으로 임베딩이 만들어져요.
PUT _ingest/pipeline/embedding-pipeline
{
"processors": [
{
"inference": {
"model_id": "openai-small",
"input_output": {
"input_field": "title",
"output_field": "embedding"
}
}
}
]
}
색인 시 ?pipeline=embedding-pipeline 만 붙이면 title 필드 텍스트 가 자동으로 embedding 필드의 벡터 가 돼요. 23편(Ingest Pipeline) 에서 깊이.
질의 시점 임베딩은 query_vector_builder 절로 잡아요.
POST my-index/_search
{
"knn": {
"field": "embedding",
"query_vector_builder": {
"text_embedding": {
"model_id": "openai-small",
"model_text": "한국 회사가 elasticsearch 를 도입하는 이유"
}
},
"k": 10,
"num_candidates": 100
}
}
이 패턴의 장점은 애플리케이션에서 임베딩 호출을 직접 안 해도 된다는 점 — API 키 관리·재시도·rate limit 가 모두 ES 안으로 들어가요. 단점은 벤더 락인 과 임베딩 비용 가시성 저하 — 대규모 운영 에선 클라이언트에서 직접 호출하고 batch · cache 까지 직접 잡는 패턴 이 여전히 일반이에요.
자주 만나는 사고
사고 1 — Vector dims mismatch
원인 — 매핑 시점에 dims: 1536 으로 박았는데 다른 모델 (예: 768 차원 BGE) 출력을 색인하려 하면 every document fails 사고. 운영 중에 임베딩 모델을 바꾸려고 할 때 가장 자주 만나는 사고예요.
해결 — dims 는 변경 불가 이므로 새 인덱스 만들고 reindex 가 표준. alias 기반 운영 으로 다운타임 0 으로 갈아끼우는 패턴은 5편(Index 관리) 에서 깊이.
사고 2 — HNSW m 너무 작아 recall 폭락
원인 — m 을 4 · 8 같이 너무 작게 박으면 그래프 연결성이 부족해서 recall 70% 이하 로 떨어져요. 대규모 데이터 (1억 건 이상) 일수록 더 두드러져요.
해결 — m 기본값 16 유지하거나, recall 측정 후 24~32 까지 올림. 한 번 색인하면 변경 불가니까 베타 환경에서 recall 측정 후 운영 박는 순서가 표준.
사고 3 — num_candidates 부족으로 결과 불안정
원인 — num_candidates 를 k 와 같게 (예: k=10, num_candidates=10) 잡으면 HNSW 탐색 폭이 좁아 매 쿼리마다 결과가 조금씩 달라지는 사고. 사용자가 "새로 고침할 때마다 검색 결과가 바뀐다" 컴플레인 들어와요.
해결 — num_candidates 를 k * 10 ~ k * 20 로 잡거나 최소 100 이상. recall 과 latency 트레이드오프를 프로덕션 데이터로 측정 해서 박아요.
사고 4 — 메모리 폭증 (1억 벡터 × 1,536 차원 × 4 byte)
원인 — 1억 건 × 1,536 차원 × float (4 byte) = 약 614 GB 가 벡터 데이터만의 메모리 요구량. HNSW 그래프 메타데이터까지 합치면 Heap + Off-heap 모두 폭증 해서 GC stall · OOM.
해결 — 양자화 (quantization) 가 표준 답. element_type: byte (int8) 면 메모리 4배 절약, bit 면 32배 절약. recall 손실은 2~5%p 수준 이라 대부분 운영에서 수용 가능. 8.13+ 의 int8_hnsw 가 권장 기본값 자리.
사고 5 — Filter 잘못 적용해 k 가 깨짐
원인 — query 절에 filter 를 넣고 knn 절을 분리해서 보내면, 벡터 검색은 전체 인덱스에서 k 개 뽑고 그 뒤에 query 필터 가 적용돼서 최종 결과가 k 보다 훨씬 작거나 0개 가 나와요.
해결 — vector 검색의 filter 는 반드시 knn.filter 안에 박아야 pre-filter 가 돼요. 위 kNN 검색 절 의 filter 예제 형태를 표준으로.
사고 6 — Cosine similarity 인데 정규화 안 한 벡터를 색인
원인 — similarity: dot_product 로 박고 L2 정규화 안 한 벡터 를 색인하면 벡터 크기가 큰 문서 만 항상 상위에 나와요. 검색 품질 무너짐.
해결 — cosine 을 명시 하거나 클라이언트에서 미리 L2 정규화 (각 원소를 벡터 노름으로 나눔). 8.x 부터 cosine 도 내부적으로 dot_product 와 같은 속도 라서 그냥 cosine 박는 게 안전한 기본값.
사고 7 — Inference API rate limit 으로 색인 stall
원인 — ingest pipeline 으로 OpenAI Inference 를 박았는데, bulk 색인 시점에 매 문서마다 API 호출 이 일어나서 OpenAI rate limit (RPM·TPM) 에 걸려 색인이 수시간 stall.
해결 — 클라이언트에서 batch 임베딩 (한 번 호출에 100 문서) 으로 처리 후 임베딩 박힌 문서를 색인. 또는 inference endpoint 의 num_threads · batch_size 를 조정. 23편(Bulk·Ingest) 에서 깊이.
운영 권장 패턴
처음 도입하는 자리는 OpenAI text-embedding-3-small (1,536 dims) + cosine + HNSW 기본값 (m=16, ef_construction=100) 부터 시작해요. 작성 시점 기준 성능·비용·운영 단순성 의 균형이 가장 좋은 조합이고, 문제가 보이면 거기서부터 튜닝 하는 게 표준 순서.
대규모 (1억 건 이상) 운영은 int8_hnsw 양자화 를 처음부터 박아요. 메모리 4배 절약에 recall 손실 2~3%p 수준이라 대부분 자리에서 수용 가능. float HNSW 로 시작했다가 메모리 폭증으로 재색인하는 사고 보다 처음부터 int8 로 가는 결정이 운영 부담이 훨씬 적어요.
Hybrid retrieval 은 처음부터 RRF 로 짜요. 벡터만으로는 잡히지 않는 자리 (정확한 상품 코드·고유명사 검색) 가 반드시 있고, boost 기반 단순 합산 은 스케일 튜닝의 함정 이 너무 깊어요. 8.8+ 의 retriever DSL 이 표준 자리.
임베딩 비용·rate limit 가시성 을 처음부터 잡아요. 클라이언트에서 batch + cache 패턴이면 임베딩 비용 모니터링 이 자연스럽게 가능하지만 Inference API 로 ES 가 직접 호출 하면 비용이 ES 운영 로그 밖에서 발생 해서 월말에 청구서 보고 놀라는 사고 가 잦아요.
recall 측정 셋업 을 베타 단계에 만들어 둬요. brute force (index: false) 의 결과를 ground truth 로, HNSW recall@k 를 주기적으로 측정 하는 스크립트. m·ef_construction·num_candidates 튜닝의 근거가 돼요.
시험 직전 한 번 더 — 압축 노트
- dense_vector = 고정 차원 부동소수점 벡터 필드. dims·index·similarity·element_type 4개 파라미터.
- dims = 임베딩 모델 차원과 정확히 맞아야 함. 변경 불가.
- similarity = cosine (LLM 표준) · dot_product (정규화 전제) · l2_norm.
- element_type = float (기본) · byte (int8, 4배 절약) · bit (1-bit, 32배 절약).
- kNN 검색 =
_search의knn절. k · num_candidates · filter · boost. - k = 최종 반환 수 (RAG 는 5~20).
- num_candidates = 각 샤드 후보 수 (k * 10 ~ 20, 최소 100).
- filter = pre-filter 가 ES 의 차별점. metadata 조건이 정확.
- HNSW = Hierarchical Navigable Small World. ANN 알고리즘 표준.
- m = 노드 이웃 수 (기본 16, 24~32 까지).
- ef_construction = 색인 시 탐색 폭 (기본 100, 운영 200~400).
- int8_hnsw = 8.13+ 권장 기본값. 메모리 4배 절약 + recall 2~3%p 손실.
- sparse_vector = ELSER 모델용 (8.11+). 수만 차원·대부분 0·해석 가능.
- Hybrid Retrieval = BM25 + kNN. RRF 가 사실상 표준.
- RRF = Reciprocal Rank Fusion. rank_constant 60 (Cormack et al. 2009).
- Inference API = 외부 임베딩 모델 (OpenAI·Cohere·BGE) 을 ES 안에서 호출.
- query_vector_builder = 질의 시점 임베딩을 ES 가 대신 만들어 줌.
- 7대 사고: dims mismatch · m 작아 recall 폭락 · num_candidates 부족 · 메모리 폭증 · filter 위치 오류 · 정규화 누락 · rate limit stall.
- 운영 권장: text-embedding-3-small + cosine + int8_hnsw + RRF + recall 측정.
시리즈 다른 편
- 이전 글 = 20편 Suggesters — 자동완성·오타 교정·완성형 검색
- 다음 글 = 22편 RAG 패턴 — Elasticsearch 로 LLM 검색 인프라 짜기
- 4편 = Quickstart — Docker·curl·Kibana 첫 화면
- 8편 = Mapping Deep — Static·Dynamic·Multi-field·Runtime
- 12편 = Search API — Query DSL·Compound·Filter Context
- 19편 = Search Features — search_after·scroll·highlight
- 23편 = Bulk·Ingest Pipeline — 대용량 색인
- 28편 = Snapshot — 백업·복구
- 32편 = Spring Data Elasticsearch — Repository·Template·POJO
- 38편 = 시리즈 마무리 — 결정 트리·체크리스트·자격증
한 줄 정리 — Vector Search = dense_vector 필드 + kNN 쿼리 + HNSW 그래프 인덱스. RRF 로 BM25 와 묶으면 별도 벡터 DB 없이 RAG 검색 인프라의 표준 자리가 완성돼요.