엘라스틱서치
Docker 배포
컨테이너 구성 (docker-compose.yml)
version: "3.1"
# 서비스 구성
services:
elasticsearch:
container_name: elasticsearch-8.8.1
image: docker.elastic.co/elasticsearch/elasticsearch:8.8.1
# 컨테이너 root 권한 부여 (보안에 취약하므로 실제 운영 환경에서는 제거)
privileged: true
# Linux의 ulimit 명령으로 프로세스 리소스 제한
ulimits:
memlock:
soft: -1
hard: -1
environment:
- "ES_JAVA_OPTS=-Xms1024m -Xmx1024m"
- "http.host=0.0.0.0"
- "node.name=elastic01"
- "cluster.name=cluster_elasticsearch"
- "discovery.type=single-node"
ports:
- "9200:9200"
- "9300:9300"
volumes:
# - ./elasticsearch/config:/usr/share/elasticsearch/config
- ./elasticsearch/data:/usr/share/elasticsearch/data
# - ./elasticsearch/plugin/xxx:/usr/share/elasticsearch/plugins/xxx
networks:
- elastic_net
kibana:
container_name: kibana-8.8.1
image: docker.elastic.co/kibana/kibana:8.8.1
ports:
- "5601:5601"
# volumes:
# - ./kibana/config:/usr/share/kibana/config
networks:
- elastic_net
# 네트워크 구성
networks:
elastic_net:
driver: bridge
---배포---
- 서비스 시작
docker-compose up -d - 파일 생성
# 1. kibana 매핑 디렉토리 생성
# 2. elasticsearch 설정 복사
# 3. kibana 설정 복사
mkdir kibana
docker cp elasticsearch-8.8.1:/usr/share/elasticsearch/config ./elasticsearch
docker cp kibana-8.8.1:/usr/share/kibana/config ./kibana
- elasticsearch 설정 (config/elasticsearch.yml)
# 클러스터 노드 이름
node.name: "elastic01"
# 클러스터 이름 설정
cluster.name: "cluster_elasticsearch"
# 네트워크 접근 제한
network.host: 0.0.0.0
# 단일 노드 모드로 시작
discovery.type: single-node
# CORS 지원 여부
http.cors.enabled: true
# 모든 도메인 허용
http.cors.allow-origin: "*"
# 메모리 스와핑 옵션, 공식 문서 추천값
bootstrap.memory_lock: true
# 보안 설정 수정 인증서 검증 비활성화
xpack.security.http.ssl:
enabled: false
xpack.security.transport.ssl:
enabled: false
- kibana 설정 (kibana.yml)
# 한어
i18n.locale: zh-CN
- docker-compose.yml 주석 해제
- ./elasticsearch/config:/usr/share/elasticsearch/config
- ./kibana/config:/usr/share/kibana/config
# 컨테이너 재시작
docker compose restart
# 접속 주소:
elastic:http://localhost:9200
kibana:http://localhost:5601
- elastic 비밀번호 설정
docker exec -it elasticsearch-8.8.1 /usr/share/elasticsearch/bin/elasticsearch-reset-password -uelastic
- kibana 비밀번호 설정
docker exec -it elasticsearch-8.8.1 /usr/share/elasticsearch/bin/elasticsearch-reset-password -ukibana_system
- kibana 설정 추가
elasticsearch.username: kibana_system
elasticsearch.password: Ux*7RMHX0ErLEn4=RMmx
- 인증 코드 획득
docker exec -it kibana-8.8.1 /usr/share/kibana/bin/kibana-verification-code
- kibana 로그인
elastic
xxxx elastic 비밀번호
기본 연산
ES8.x:URL 구성
- /인덱스/문서/내용ID
인덱스 Index
# 인덱스 목록 조회
GET /_cat/indices?v=true&pretty
# 샤드 상황 조회
GET /_cat/shards?v=true&pretty
# 인덱스 생성 (Create Index)
PUT /<index_name>
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1
}
}
# 인덱스 존재 여부 확인
HEAD /<index_name>
# 인덱스 정보 조회 (Get Index)
GET /<index_name>
# 인덱스 설정 업데이트 (Update Index Settings)
PUT /<index_name>/_settings
{
"settings": {
"number_of_replicas": 2
}
}
# 인덱스 삭제 (Delete Index):
DELETE /<index_name>
문서 Document
# 문서 조회
GET /xdclass_shop/_doc/1
# 문서 추가 (ID 지정)
PUT /xdclass_shop/_doc/1
{
"id":5555,
"title":"content111",
"pv":144
}
# 문서 추가 (ID 미지정, 자동 생성)
POST /xdclass_shop/_doc
{
"id":123,
"title":"content222",
"pv":244
}
# 수정 (put과 post 모두 사용 가능, ID 지정 필요)
PUT /xdclass_shop/_doc/1
{
"id":999,
"title":"content111v2",
"pv":999,
"uv":55
}
POST /xdclass_shop/_doc/1
{
"id":999,
"title":"content222v2",
"pv":999,
"uv":559
}
# 검색
GET /xdclass_shop/_search
# 필드 설명
# took 필드는 해당 작업의 소요 시간(밀리초 단위)을 나타냅니다.
# timed_out 필드는 시간 초과 여부를 나타냅니다.
# hits 필드는 검색된 기록을 배열 형태로 나타냅니다.
# total:반환된 기록 수, 예에서는 1개입니다.
# max_score:최고 일치 정도, 예에서는 1.0입니다.
# 데이터 삭제
DELETE /xdclass_shop/_doc/1
Mapping과 일반 필드 유형
-
Mapping이란
-
데이터베이스의 테이블 구조 정의인 schema와 유사합니다,
-
인덱스의 필드 이름과 데이터 유형(예: 문자열, 숫자, 부울 등)을 정의합니다
-
인덱스의 필드 유형 조회
# GET /[index]/_mapping
GET /my_index/_mapping
-
동적 매핑 (Dynamic Mapping)
-
인덱스 문서 생성 시 자동으로 필드 데이터 유형을 감지하고 정의하는 기능
-
인덱스에 새 문서를 추가할 때 Elasticsearch는 문서의 각 필드를 자동으로 감지하고 값에 따라 필드 유형을 추론합니다
-
일반적인 필드 유형에는 텍스트(text), 키워드(keyword), 날짜(date), 숫자(numeric) 등이 있습니다
-
동적 매핑은 자동으로 필드를 생성하고 분석하는 편의성을 제공하지만, 필드 유형의 불확실성으로 인해 문제가 발생할 수 있습니다
-
필드 유형이 명확히 요구되는 경우에는 인덱스 생성 전 명시적 매핑 정의를 통해 지정하는 것이 좋습니다
-
Elasticsearch 일반 데이터 유형
-
ES 7.X 이후에는 두 가지 문자열 유형이 있습니다: Text와 Keyword
-
Text 유형: 전체 텍스트 검색을 위한 문자열 유형으로, 토큰화 및 인덱싱을 지원합니다
-
Keyword 유형: 정확한 일치를 위한 문자열 유형으로, 토큰화를 수행하지 않으며 필터링 및 집계 작업에 적합합니다
-
Numeric 유형: 정수 유형(long, integer, short, byte)와 부동 소수점 유형(double, float)을 포함합니다
-
Date 유형: 날짜와 시간을 저장하는 유형입니다
-
Boolean 유형: 불리언 값(true 또는 false)을 저장하는 유형입니다
-
Binary 유형: 이진 데이터를 저장하는 유형입니다
-
Array 유형: 배열 또는 목록 데이터를 저장하는 유형입니다
-
Object 유형: 복잡한 구조의 데이터를 저장하는 유형입니다
-
인덱스 필드 유형 mapping 지정
PUT /my_index
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}
-
가장 자주 사용되는 데이터 유형
-
text 필드 유형
-
text 유형은 주로 전체 텍스트 검색에 사용되며, 전체 텍스트 토큰화가 필요한 콘텐츠(예: 기사, 뉴스 등)를 저장하는 데 적합합니다
-
text 필드는 텍스트 콘텐츠를 토큰(tokens)으로 분리하여 인덱스를 생성하고, 이를 통해 유연하고 효율적인 검색이 가능합니다
-
text 필드는 검색 시 토큰화된 결과와 일치시키고 관련성 점수를 계산하여 최적의 일치 결과를 반환합니다
-
keyword 필드 유형
-
keyword 유형은 정확한 일치 및 집계 작업에 주로 사용되며, 토큰화가 필요 없는 정확한 값(예: ID, 태그, 키워드 등)을 저장하는 데 적합합니다
-
keyword 필드는 토큰화를 수행하지 않고 전체 필드를 하나의 단위로 인덱싱하고 검색합니다
-
이를 통해 검색은 정확한 값만 일치시킬 수 있으며, 토큰을 기반으로 콘텐츠를 모호하게 검색할 수 없습니다
-
keyword 필드는 필터링 및 정확한 일치에 적합하며, 정확한 값을 기반으로 빠른 집계 작업을 수행할 수 있습니다
-
요약
-
text 필드 유형과 keyword 필드 유형을 선택할 때는 구체적인 요구 사항에 따라 균형을 맞춰야 합니다:
-
전체 텍스트 검색이 필요하고 토큰화된 결과를 기반으로 관련성 점수를 계산하여 최적의 일치 결과를 얻고자 한다면 text 필드 유형을 선택합니다
-
정확한 일치, 정렬 또는 집계 작업이 필요하며 콘텐츠를 토큰화할 필요가 없다면 keyword 필드 유형을 선택합니다
-
사례 실습
-
인덱스 생성 및 문서 삽입
PUT /my_index
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"tags": {
"type": "keyword"
},
"publish_date": {
"type": "date"
},
"rating": {
"type": "float"
},
"is_published": {
"type": "boolean"
},
"author": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
}
}
},
"comments": {
"type": "nested",
"properties": {
"user": {
"type": "keyword"
},
"message": {
"type": "text"
}
}
}
}
}
}
POST /my_index/_doc/1
{
"title": "작은 강의실 최근에 새로운 과정 Elasticsearch 소개",
"tags": ["검색", "빅데이터", "분산 시스템", "작은 강의실"],
"publish_date": "2025-01-01",
"rating": 4.5,
"is_published": true,
"author": {
"name": "John Doe",
"age": 30
},
"comments": [
{
"user": "Alice",
"message": "훌륭한 글입니다!"
},
{
"user": "Bob",
"message": "매우 정보가 많습니다."
}
]
}
- text 필드 유형 일치 쿼리 (토큰화)
GET /my_index/_search
{
"query": {
"match": {
"title": "Elasticsearch"
}
}
}
- keyword 필드 유형 일치 쿼리 (미토큰화)
GET /my_index/_search
{
"query": {
"match": {
"tags": "빅데이터"
}
}
}
토큰화기
-
검색 엔진의 토큰화란
-
Elasticsearch 8.X에서 토큰화(tokenization)는 텍스트 콘텐츠를 독립적인 단어 또는 토큰(tokens)으로 분리하는 과정입니다
-
토큰화는 검색 엔진에서 인덱스를 구축하고 쿼리를 실행할 때의 핵심 단계로, 텍스트를 단어로 분리하고 역색인(inverted index)을 구축하여 더 나은 검색 및 검색 효과를 구현합니다
-
사례
두 가지 제품 제목이 있다고 가정해 봅시다:
"Apple iPhone 12 Pro Max 256GB"
"Samsung Galaxy S21 Ultra 128GB"
기본 표준 토큰화기(Standard Tokenizer)를 사용하면 이 제목들은 다음과 같이 토큰으로 분할됩니다:
제목1:["Apple", "iPhone", "12", "Pro", "Max", "256GB"]
제목2:["Samsung", "Galaxy", "S21", "Ultra", "128GB"]
토큰화기는 구두점과 공백을 기준으로 제목을 독립적인 단어로 분할합니다. 검색을 실행할 때 쿼리를 토큰화하고 이를 제목의 토큰과 일치시킵니다.
예를 들어
"iPhone 12"를 검색할 때 기본 토큰화기를 사용하면 쿼리가 ["iPhone", "12"]로 분해되고, 이를 토큰과 일치시킵니다.
제목1의 토큰 ["iPhone", "12"]는 쿼리와 일치합니다. 제목2에는 쿼리와 일치하는 토큰이 없습니다
-
토큰화 규칙은 텍스트를 분할하는 규칙과 알고리즘을 정의합니다
-
Elasticsearch는 일련의 토큰화기(analyzer)와 토큰 생성기(tokenizer)를 사용하여 텍스트 콘텐츠를 처리합니다
-
토큰화기는 일반적으로 하나 이상의 토큰 생성기로 구성되어 토큰화의 구체적인 규칙을 정의합니다
-
다음은 토큰화의 일반적인 과정입니다:
-
토큰화(Tokenization):
-
토큰화의 첫 단계는 텍스트 콘텐츠를 개별 토큰으로 분리하는 것입니다. 토큰은 단어, 숫자, 특수 문자 등일 수 있습니다.
-
토큰화 과정은 토큰 생성기(tokenizer)에 의해 수행되며, 토큰 생성기는 일련의 규칙을 기반으로 텍스트를 토큰으로 분할합니다.
-
필터링(Filtering):
-
토큰화 후, 토큰은 추가적으로 필터(filters)에 의해 처리됩니다.
-
필터는 소문자 변환, 불용어(stop words) 제거, 어간 추출(stemming), 동의어 확장 등 다양한 변환 및 작업을 수행합니다.
-
역색인(Inverted Indexing):
-
토큰화 처리가 완료되면, Elasticsearch는 토큰화 결과를 저장하기 위해 역색인(inverted index)을 사용합니다.
-
역색인은 토큰을 해당 문서와 매핑하여 특정 토큰을 포함하는 문서를 빠르게 식별할 수 있는 데이터 구조입니다.
-
쿼리 일치:
-
쿼리를 실행할 때 쿼리 텍스트도 토큰화 처리됩니다.
-
Elasticsearch는 역색인을 사용하여 쿼리 토큰을 포함하는 문서를 빠르게 찾고 관련성 점수를 계산합니다.
-
일반적인 토큰화기로는 Standard 토큰화기, Simple 토큰화기, Whitespace 토큰화기, IK 토큰화기 등이 있으며, 사용자 정의 토큰화기도 지원합니다
-
기본 Standard 토큰화기의 토큰화 규칙
-
구두점 분할:
-
구두점은 삭제되고 하이픈은 두 개의 독립적인 단어로 분할됩니다.
-
예를 들어, "Let's go!"는 "Let", "s", "go"로 분할됩니다.
-
소문자 변환:
-
모든 텍스트는 소문자 형태로 변환됩니다.
-
예를 들어, "Hello World"는 "hello", "world"로 분할됩니다.
-
불용어 필터링:
-
불용어(stop words)는 검색에서 실제 의미가 없는 일반적인 단어로, "a", "an", "the" 등이 있습니다.
-
불용어는 필터링되어 독립적인 단어로 인덱싱 및 검색되지 않습니다.
-
어간 추출:
-
Porter2 어간 추출 알고리즘을 적용하여 단어를 원래 형태로 환원합니다.
-
예를 들어, running -> run, swimming -> swim, jumped -> jump
-
단어 분리:
-
공백을 기준으로 텍스트를 단어 토큰(tokens)으로 분할합니다.
-
ES 토큰화 저장 효과를 어떻게 확인하는가?
-
analyzeAPI를 사용하여 텍스트를 토큰화 처리하고 토큰화 결과를 확인할 수 있으며, 기본 구문은 다음과 같습니다
GET /_analyze
{
"analyzer": "토큰화기 이름",
"text": "분석할 텍스트"
}
- 사례
# 필드가 text 유형인 경우
POST /my_index/_analyze
{
"field": "title",
"text": "This is some text to analyze"
}
# 필드가 text 유형인 경우
POST /my_index/_analyze
{
"field": "title",
"text": "오늘은 작은 강의실에서 아키텍처 대강의를 배웠습니다"
}
# 필드가 keyword 유형인 경우
POST /my_index/_analyze
{
"field": "tags",
"text": "This is some text to analyze"
}
# 필드가 keyword 유형인 경우
POST /my_index/_analyze
{
"field": "tags",
"text": ["This is","작은 강의실","Spring Boot" ]
}
- 각 토큰화 결과 객체에는 다음이 포함됩니다
- 토큰화된 단어(token)
- 시작 위치(start_offset)
- 끝 위치(end_offset)
- 유형(type)
- ALPHANUM은 데이터 유형으로, 문자열 필드가 문자와 숫자만 포함하고 다른 토큰화나 처리를 수행하지 않음을 나타냅니다
- 필드의 모든 비 문자 숫자 문자(예: 구두점, 공백 등)를 무시하고 문자와 숫자 문자만 유지합니다
- 원본 텍스트에서의 단어 위치(position)
IK 중국어 토큰화기
-
배경
-
Elasticsearch 8.X에서 토큰화(tokenization)는 텍스트 콘텐츠를 독립적인 단어 또는 토큰(tokens)으로 분리하는 과정입니다
-
기본 Standard 토큰화기는 중국어 지원이 다소 제한적입니다. 예를 들어
# 필드가 text 유형인 경우
POST /my_index/_analyze
{
"field": "title",
"text": "오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다"
}
# 결과는 다음과 같으며, 중국어는 각자 하나의 단어로 처리됩니다
{
"tokens": [
{
"token": "오",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "늘",
},
{
"token": "은",
},
......
{
"token": "spring",
"start_offset": 10,
"end_offset": 16,
"type": "<ALPHANUM>",
"position": 10
},
{
"token": "cloud",
"start_offset": 17,
"end_offset": 22,
"type": "<ALPHANUM>",
"position": 11
},
{
"token": "프",
"start_offset": 22,
"end_offset": 23,
"type": "<IDEOGRAPHIC>",
"position": 12
},
{
"token": "로",
"start_offset": 23,
"end_offset": 24,
"type": "<IDEOGRAPHIC>",
"position": 13
},
]
}
-
IK 토큰화기란
-
Java로 개발된 오픈 소스 중국어 토큰화기로, 중국어 텍스트를 개별 단어(토큰)로 분리하는 데 사용됩니다
-
중국어 언어의 특성과 요구 사항을 고려하여 설계되었으며, 중국어 토큰화의 복잡성과 다양성을 효과적으로 처리할 수 있습니다
-
주소
-
여러 버전: https://github.com/medcl/elasticsearch-analysis-ik/releases
-
본문에서 사용하는 버전: https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.8.1
-
참고: Elasticsearch 버전과 IK 토큰화기 버전이 일치해야 합니다
-
특징
-
효율적이고 유연함
-
IK 토큰화기는 다양한 알고리즘과 데이터 구조를 사용하여 높은 토큰화 속도를 제공합니다.
-
세밀한 토큰화를 지원하며 애플리케이션 요구에 따라 유연하게 구성할 수 있습니다.
-
토큰화 정확도
-
IK 토큰화기는 사전과 규칙을 사용하여 토큰화하여 문장을 정확하게 단어로 분할할 수 있습니다.
-
또한 품사 태깅 기능을 제공하여 단어의 다양한 의미와 용도를 식별하는 데 도움이 됩니다.
-
원격 사전 확장 지원
-
IK 토큰화기는 구성 및 외부 사전을 로드하여 토큰화 능력을 확장할 수 있습니다
-
사용자는 실제 요구에 따라 사용자 정의 사전을 추가하여 토큰화 정확성과 커버리지를 향상시킬 수 있습니다.
-
호환성 및 통합성
-
IK 토큰화기는 Lucene 및 Elasticsearch와 같은 주요 검색 엔진과 호환되어 이러한 시스템에 쉽게 통합할 수 있습니다.
-
해당 플러그인 및 구성 옵션을 제공하여 토큰화기 통합이 간편해집니다.
-
설치
-
압축을 풀고 로컬/elasticsearch/plugins 디렉토리에 배치합니다
-
docker-compose.yml의 마운트 주석을 해제합니다
-
Elasticsearch를 업데이트하면 됩니다
docker compose up -d
-
IK에는 두 가지 세밀도의 분할이 있습니다
-
ik_smart: 가장 거친 세밀도로 분할합니다
-
ik_max_word(일반적으로 사용됨): 텍스트를 가장 세밀하게 분할합니다
-
사례 실습
GET /_analyze
{
"text":"오늘은 월요일, 오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다",
"analyzer":"ik_smart"
}
GET /_analyze
{
"text":"오늘은 월요일, 오늘은 작은 강의실에서 spring cloud 프로젝트 대강의를 배웠습니다",
"analyzer":"ik_max_word"
}
구문 및 응용
-
Query DSL이란
-
Query DSL(Domain-Specific Language)은 검색 쿼리를 구축하기 위한 강력한 도메인별 쿼리 언어입니다
-
관계형 데이터베이스의 SQL 쿼리 구문과 유사합니다,
-
Elasticsearch에서 JSON 구조화 방식으로 다양한 쿼리 작업을 정의하고 실행하며, Elasticsearch에서 고급 검색 및 필터링을 수행합니다
-
기본 구문
GET /인덱스명/_search
{
"query":{
"쿼리 유형":{
}
}
-
일반적인 Query DSL 쿼리 문장 및 기능
-
match쿼리: 전체 텍스트 검색을 실행하며, 지정된 필드의 텍스트와 검색 쿼리를 일치시킵니다
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
term쿼리: 지정된 필드의 키워드를 정확히 일치시키며, 토큰화 처리를 수행하지 않습니다.
{
"query": {
"term": {
"category": "books"
}
}
}
-
요약
-
Query DSL은 더 다양한 종류의 쿼리 및 필터 문장을 제공하여 다양한 검색 요구를 충족합니다.
-
구체적인 비즈니스 요구 사항과 데이터 구조에 따라 다양한 쿼리 방식을 결합하여 복잡한 검색 및 필터링 작업을 구축할 수 있습니다
-
데이터 준비
-
인덱스 생성
PUT /xdclass_shop_v1
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "keyword"
},
"summary": {
"type": "text"
},
"price": {
"type": "float"
}
}
}
}
# 데이터 삽입
PUT /xdclass_shop_v1/_bulk
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "1", "title": "Spring Boot","summary":"this is a summary Spring Boot video", "price": 9.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "2", "title": "java","summary":"this is a summary java video", "price": 19.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "3", "title": "Spring Cloud","summary":"this is a summary Spring Cloud video", "price": 29.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "4", "title": "Spring_Boot", "summary":"this is a summary Spring_Boot video","price": 59.99 }
{ "index": { "_index": "xdclass_shop_v1" } }
{ "id": "5", "title": "SpringBoot","summary":"this is a summary SpringBoot video", "price": 0.99 }
match
-
전체 데이터 쿼리 (match_all)
-
인덱스의 모든 문서와 일치하는 간단한 쿼리입니다
GET /xdclass_shop_v1/_search
{
"query": {
"match_all": {}
}
}
-
조건부 데이터 쿼리
-
match는 쿼리 내용을 토큰화한 후 쿼리를 수행하며, 여러 용어 간의 관계는 OR 관계입니다
-
그 후 문서 내의 토큰과 일치시키며, 일치도가 높을수록 점수가 높고 앞에 표시됩니다
GET /xdclass_shop_v1/_search
{
"query": {
"match": {
"summary": "Spring"
}
}
}
# 여러 단어 포함
GET /xdclass_shop_v1/_search
{
"query": {
"match": {
"summary": "Spring Java"
}
}
}
-
전체 키워드 쿼리
-
term 쿼리는 쿼리 조건을 토큰화하지 않고 문서 내의 토큰과 직접 일치시킵니다
-
match 쿼리도 가능하지만 match 쿼리는 토큰화를 추가로 수행하므로 리소스가 낭비됩니다
#keyword 유형 필드, ES는 토큰화를 수행하지 않음
GET /xdclass_shop_v1/_search
{
"query": {
"term": {
"title": {
"value": "Spring Boot"
}
}
}
}
-
지정된 필드 가져오기
-
특정 시나리오에서는 전체 필드 반환을 원하지 않을 수 있으며, 리소스 낭비를 방지하기 위해 _source를 사용하여 해당 필드를 지정할 수 있습니다
GET /xdclass_shop_v1/_search
{
"_source":["price","title"],
"query": {
"term": {
"title": {
"value": "Spring Boot"
}
}
}
}
-
요약
-
match는 검색 시 키워드를 토큰화한 후 토큰 일치 검색을 수행하고, term은 키워드를 직접 검색합니다
-
일반 비즈니스에서 모호한 검색이 필요할 때는 match를 선택하고, 정확한 검색이 필요할 때는 term 쿼리를 선택합니다
불-범위 및 페이지네이션-정렬
-
일반 Query DSL 구문 사례 실습
-
range쿼리 -
범위 조건에 따라 쿼리하며, 예를 들어 가격 특정 구간 내의 상품을 지정합니다
-
범위 기호
-
gte: 이상
-
gt: 초과
-
lte: 이하
-
lt: 미만
GET /xdclass_shop_v1/_search
{
"query": {
"range": {
"price": {
"gte": 5,
"lte": 100
}
}
}
}
- 페이지네이션 쿼리
from및size매개변수를 사용하여 페이지네이션 쿼리를 수행할 수 있습니다- 건너뛰어야 할 문서 수(
from)와 반환해야 할 문서 수(size)를 지정할 수 있습니다
GET /xdclass_shop_v1/_search
{
"size": 10,
"from": 0,
"query": {
"match_all": {}
}
}
- 쿼리 결과 정렬
sort필드를 사용하여 정렬할 수 있으며,desc와asc가 있습니다
GET /xdclass_shop_v1/_search
{
"size": 10,
"from": 0,
"sort": [
{
"price": "asc"
}
],
"query": {
"match_all": {}
}
}
-
bool쿼리 -
여러 쿼리 조건을 부울 논리(AND, OR, NOT)로 조합하여 복잡한 쿼리 작업을 수행합니다
-
구문 형식
-
"must" 키워드는 일치해야 할 조건을 지정하며, 모든 조건을 모두 만족해야 합니다
-
"must_not" 키워드는 일치하지 않아야 할 조건을 지정하며, 모든 조건을 만족해서는 안 됩니다
-
"should" 키워드는 선택적 일치 조건을 지정하며, 최소한 하나의 조건을 만족해야 합니다
{
"query": {
"bool": {
"must": [
// 반드시 일치해야 할 조건
],
"must_not": [
// 반드시 일치하지 않아야 할 조건
],
"should": [
// 선택적 일치 조건
],
"filter": [
// 필터 조건
]
}
}
}
- 사례 실습
GET /xdclass_shop_v1/_search
{
"query": {
"bool": {
"must": [
{ "match": { "summary": "Cloud" }},
{ "range": { "price": { "gte": 5 }}}
]
}
}
}
쿼리 필터링 Filter
-
filter쿼리 -
검색 결과를 필터링하고 필터링하여 특정 조건을 만족하는 문서만 반환하며, 검색 점수를 변경하지 않습니다
-
Filter 쿼리는 결과를 캐싱하여 쿼리 성능을 향상시키며, 숫자 범위, 날짜 범위, 부울 논리, 존재성 검사 등 다양한 필터링 작업에 사용됩니다.
-
구문 형식
-
"filter" 키워드는 필터 조건을 지정하며, term, range 등과 같은 구체적인 필터 또는 중첩된 bool 필터일 수 있습니다
{
"query": {
"bool": {
"filter": {
// 필터 조건
}
}
}
}
-
쿼리 사례 데이터 환경 준비
-
인덱스 라이브러리 생성
PUT /product
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"product_id": {
"type": "integer"
},
"product_name": {
"type": "text"
},
"category": {
"type": "keyword"
},
"price": {
"type": "float"
},
"availability": {
"type": "boolean"
}
} }
}
-
데이터 삽입
-
단일 POST 요청을 통해 배치 삽입을 구현합니다
-
각 문서는
index지시문과product_id로 구성되며,product_id는 문서의 고유 식별자입니다 -
삽입 후
GET /product/_search로 조회
POST /product/_bulk
{ "index": { "_id": "1" } }
{ "product_id": 1, "product_name": "Product 1", "category": "books", "price": 19.99, "availability": true }
{ "index": { "_id": "2" } }
{ "product_id": 2, "product_name": "Product 2", "category": "electronics", "price": 29.99, "availability": true }
{ "index": { "_id": "3" } }
{ "product_id": 3, "product_name": "Product 3", "category": "books", "price": 9.99, "availability": false }
{ "index": { "_id": "4" } }
{ "product_id": 4, "product_name": "Product 4", "category": "electronics", "price": 49.99, "availability": true }
{ "index": { "_id": "5" } }
{ "product_id": 5, "product_name": "Product 5", "category": "fashion", "price": 39.99, "availability": true }
- 사례 1:
term필터를 사용하여category가books인 제품을 쿼리합니다:
GET /product/_search
{
"query": {
"bool": {
"filter": {
"term": {
"category": "books"
}
}
}
}
}
- 사례 2:
range필터를 사용하여 가격price가 30에서 50 사이인 제품을 쿼리합니다:
GET /product/_search
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"gte": 30,
"lte": 50
}
}
}
}
}
}
-
요약
-
필터 조건은 일반적으로 결과를 필터링하는 데 사용되며, 쿼리 조건보다 더 효율적입니다
-
bool 쿼리는 구체적인 요구에 따라 여러 조건, 필터 및 쿼리 하위 절을 조합할 수 있습니다
다중 필드 일치 및 구문 검색
-
다중 필드 검색 일치
-
비즈니스 쿼리에서 여러 필드에서 텍스트 검색이 필요한 경우, multi_match를 사용합니다
-
match를 기반으로 여러 필드에서 텍스트 쿼리 일치를 지원합니다
-
구문 형식
GET /index/_search
{
"query": {
"multi_match": {
"query": "검색할 텍스트",
"fields": ["필드1", "필드2", ...]
}
}
}
# query:일치시킬 쿼리 텍스트입니다.
# fields:일치시킬 필드 목록을 포함하는 배열입니다.
-
구문 검색 일치
-
Elasticsearch에서 제공하는 고급 일치 쿼리 유형으로, 정확한 구문 검색을 수행합니다
-
match쿼리와 비교하여match_phrase는 단어 간의 순서와 위치를 고려하여 일치시킵니다 -
구문 형식
GET /index/_search
{
"query": {
"match_phrase": {
"field_name": {
"query": "검색할 구문"
}
}
}
}
# field_name:일치시킬 필드 이름입니다.
# query:검색할 구문입니다.
- 데이터 환경 준비
# 인덱스 라이브러리 생성
PUT /product_v2
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"product_name": {
"type": "text"
},
"description": {
"type": "text"
},
"category": {
"type": "keyword"
}
}
}
}
#데이터 배치 삽입
POST /product_v2/_bulk
{ "index": { "_index": "product_v2", "_id": "1" } }
{ "product_name": "iPhone 12", "description": "Apple의 최신 iPhone 모델", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "2" } }
{ "product_name": "Samsung Galaxy S21", "description": "고성능 Android 스마트폰", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "3" } }
{ "product_name": "MacBook Pro", "description": "전문가용 강력한 노트북", "category": "electronics" }
{ "index": { "_index": "product_v2", "_id": "4" } }
{ "product_name": "해리 포터와 마법사의 돌", "description": "J.K. Rowling의 판타지 소설", "category": "books" }
{ "index": { "_index": "product_v2", "_id": "5" } }
{ "product_name": "위대한 게츠비", "description": "F. Scott Fitzgerald의 고전 소설", "category": "books" }
-
다중 필드 검색 사례 실습
-
product_name및description필드에서multi_match쿼리를 실행합니다 -
쿼리 텍스트를 "iPhone"으로 설정하여 이 두 필드를 검색하고, 일치하는 문서를 반환합니다. 이는 OR 관계이며 최적의 일치가 됩니다
GET /product_v2/_search
{
"query": {
"multi_match": {
"query": "iPhone",
"fields": ["product_name", "description"]
}
}
}
-
구문 검색 사례 실습
-
match_phrase쿼리를 사용하여description필드에서 구문 검색을 수행합니다. 검색할 구문을 "classic novel"로 설정합니다. -
match_phrase쿼리를 사용하면 Elasticsearch는 "classic novel" 구문을 포함하는 문서를 반환합니다
#match_phrase 구문 검색
GET /product_v2/_search
{
"query": {
"match_phrase": {
"description": "classic novel"
}
}
}
#match 검색, 토큰화를 수행합니다
GET /product_v2/_search
{
"query": {
"match": {
"description": "classic novel"
}
}
}
fuzzy 모호 검색
-
fuzzy 모호 일치란
-
fuzzy쿼리는 Elasticsearch에서 제공하는 모호 일치 쿼리 유형으로, 검색 시 일부 오타 또는 근접 일치를 허용합니다 -
fuzzy쿼리를 사용하면 지정된 편집 거리(즉, 단어 간의 다른 문자 수)에 따라 쿼리 단어를 모호하게 일치시킬 수 있습니다 -
확장: 편집 거리
-
한 용어를 다른 용어로 변환하기 위해 필요한 문자 변경 횟수입니다.
-
예를 들어
-
문자 변경(box→fox)
-
문자 삭제(black→lack)
-
문자 삽입(sic→sick)
-
인접한 두 문자 전환(dgo→dog)
-
fuzzy 모호 검색은 오타에 대한 간단한 해결책이지만, CPU 오버헤드가 매우 높고 정확도가 매우 낮습니다
-
사용법은 match와 기본적으로 동일하며, Fuzzy 쿼리는 토큰화를 수행하지 않습니다
-
기본 구문 형식
GET /index/_search
{
"query": {
"fuzzy": {
"field_name": {
"value": "검색할 단어",
"fuzziness": "모호도"
}
}
}
}
-
설명
-
field_name:모호 일치를 수행할 필드 이름입니다. -
value:검색할 단어 -
fuzziness매개변수는 모호도를 지정하며, 일반적인 값은 다음과 같습니다 -
0, 1, 2 -
숫자를 지정하면 허용되는 최대 편집 거리를 나타내며, 낮은 숫자는 더 엄격한 일치를 의미하고, 높은 숫자는 더 느슨한 일치를 의미합니다
-
fuzziness 값은 각 단어에 대한 것이며, 전체 오류 수가 아닙니다
-
AUTO:Elasticsearch가 단어의 길이에 따라 자동으로 모호도를 선택합니다 -
문자열 길이가 5보다 크면 fuzziness 값이 자동으로 2로 설정됩니다
-
문자열 길이가 2보다 작으면 fuzziness 값이 자동으로 0으로 설정됩니다
-
사례 작업
# 모호도 2 지정, 더 느슨한 일치
GET /xdclass_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "2"
}
}
}
}
# 모호도 1 지정, 더 엄격한 일치
GET /xdclass_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "clo",
"fuzziness": "1"
}
}
}
}
# 자동 검사 사용, 1개 단어 오타
GET /xdclass_shop_v1/_search
{
"query": {
"fuzzy": {
"summary": {
"value": "Sprina",
"fuzziness": "auto"
}
}
}
}
검색 하이라이트 표시
-
요구 사항
-
일반적으로 제품을 검색할 때 키워드가 다른 색상으로 표시되어 사용자가 직관적으로 차이를 볼 수 있습니다
-
Elastic Search 검색 엔진이 하이라이트 표시를 구현하는 방법
-
ES에서 하이라이트 구문은 검색 결과에서 쿼리와 일치하는 키워드를 강조 표시하는 데 사용됩니다
-
하이라이트 표시는 일반적으로
<em>또는 다른 HTML 태그로 일치하는 텍스트를 감싸서 구현됩니다 -
기본 사용법: highlight 안에 하이라이트 표시할 필드를 작성하며, 여러 개를 작성할 수 있습니다
-
사례 실습
-
환경 및 데이터 준비
#인덱스 라이브러리 생성
PUT /xdclass_high_light_test
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word"
}
}
},
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
#데이터 삽입
PUT /xdclass_high_light_test/_doc/1
{
"title": "작은 강의실 2028년 최신 추천 영화",
"content": "매년 새로운 영화가 상영되며, 2028년 최신 추천 영화는 많습니다. 작은 강의실에서 《아키텍처 대강의》, 《로코드 플랫폼》, 《왕님의 과거》 등 훌륭한 영화가 상영됩니다"
}
PUT /xdclass_high_light_test/_doc/2
{
"title": "좋아하는 영화를 적어보세요",
"content": "모두가 많은 영화를 보았으며, 최근 10년간 본 비교적 좋은 영화를 말해보세요. 예를 들어 《아키텍처 대강의》, 《대용량 데이터 프로젝트 대강의》, 《빙빙과 왕님의 이야기》 등"
}
- 단일 조건 쿼리 하이라이트 표시
GET /xdclass_high_light_test/_search
{
"query": {
"match": {
"content": "영화"
}
},
"highlight": {
"fields": {
"content": {}
}
}
}
- 조합 다중 조건 쿼리, highlight 안에 하이라이트 표시할 필드 작성
GET /xdclass_high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "강의실"
}
},
{
"match": {
"content": "왕님"
}
}
]
}
},
"highlight": {
"fields": {
"title": {},
"content": {}
}
}
}
-
match 쿼리, highlight 속성을 사용하여 속성을 추가하고 하이라이트 스타일을 수정할 수 있습니다
-
pre_tags: 접두 태그
-
post_tags: 접미 태그
-
fields: 하이라이트 표시할 필드
-
사례 실습
GET /xdclass_high_light_test/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "강의실"
}
},
{
"match": {
"content": "왕님"
}
}
]
}
},
"highlight": {
"pre_tags": "<font color='yellow'>",
"post_tags": "</font>",
"fields": [{"title":{}},{"content":{}}]
}
}
검색 집계 쿼리
-
집계 쿼리란
-
대량의 데이터에 대한 집계 통계 처리로, MySQL 데이터베이스 작업의 group by 그룹화, sum, avg, max 등 함수 처리와 유사합니다
-
Elasticsearch의 강력한 기능 중 하나로, 데이터를 그룹화, 필터링, 계산 및 통계하여 데이터 세트에 대한 정보를 추출하고 데이터 분석을 수행합니다
-
데이터 시각화 대시보드의 파이 차트, 막대 차트, 선 차트, 대시보드 데이터 등은 모두 집계 쿼리의 핵심 응용입니다
-
용어 1:데이터 세트에 대한 최대, 최소, 합계, 평균 등 지표의 집계를 지표 집계 metric라고 합니다
-
기본 구문 형식은 다음과 같습니다
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"aggregation_type": {
"aggregation_field": "field_name"
// 선택적 매개변수
}
}
// 더 많은 집계를 추가할 수 있습니다
}
}
# 설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
size: 0으로 설정하여 집계 결과만 반환하고 실제 검색 결과는 반환하지 않습니다. 여기서 hits를 0으로 변경하면 원본 데이터가 0으로 변경됩니다
aggs:집계 작업을 지정하는 컨테이너입니다.
aggregation_name:집계 이름, 사용자 정의할 수 있습니다.
aggregation_type:집계 작업 유형, 예를 들어 terms, avg, sum 등입니다.
aggregation_field:집계 작업의 대상 필드, 어떤 필드에 대해 집계를 수행할지
-
용어 2:데이터 세트를 그룹화 group by한 후 그룹에서 지표 집계를 수행하는 ES에서는 버킷, 버킷 집계 bucketing이라고 합니다
-
기본 구문 형식은 다음과 같습니다(단순히 알아둡니다, 나중에 더 자세히 설명)
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"bucket_type": {
"bucket_options": {
"bucket_option_name": "bucket_option_value",
...
},
"aggs": {
"sub_aggregation_name": {
"sub_aggregation_type": {
"sub_aggregation_options": {
"sub_aggregation_option_name": "sub_aggregation_option_value",
...
}
}
}
}
}
}
}
}
#설명
index: 집계 쿼리를 실행할 인덱스 이름으로 대체합니다.
aggregation_name: 사용자 정의 집계 이름으로 대체합니다.
bucket_type: 특정 버킷 집계 유형(예: terms, date_histogram, range 등)으로 대체합니다.
bucket_option_name 및 bucket_option_value: 특정 버킷 집계 옵션의 이름과 값으로 대체합니다.
sub_aggregation_name: 하위 집계의 이름으로 대체합니다.
sub_aggregation_type: 특정 하위 집계 유형(예: sum, avg, max, min 등)으로 대체합니다.
sub_aggregation_option_name 및 sub_aggregation_option_value: 특정 하위 집계 옵션의 이름과 값으로 대체합니다
-
일반적인 집계 용도 및 응용 사례 사례
-
지표 집계(Aggregation Metrics):
-
Avg Aggregation:문서 필드의 평균값을 계산합니다.
-
Sum Aggregation:문서 필드의 합계를 계산합니다.
-
Min Aggregation:문서 필드의 최소값을 찾습니다.
-
Max Aggregation:문서 필드의 최대값을 찾습니다.
-
버킷 집계(Aggregation Buckets):
-
Terms Aggregation:필드 값을 기준으로 문서를 다른 버킷으로 그룹화합니다.
-
Date Histogram Aggregation:날짜/시간 필드를 기준으로 시간 간격의 버킷을 생성합니다.
-
Range Aggregation:필드 값의 범위를 기준으로 버킷을 생성합니다.
-
중첩 집계(Nested Aggregations)、집계 필터링(Aggregation Filtering) 등...
-
사례 실습
# 인덱스 생성
PUT /sales
{
"mappings": {
"properties": {
"product": {
"type": "keyword"
},
"sales": {
"type": "integer"
}
}
}
}
# 배치 데이터 삽입
POST /sales/_bulk
{"index": {}}
{"product": "iPhone", "sales": 4}
{"index": {}}
{"product": "Samsung", "sales": 60}
{"index": {}}
{"product": "iPhone", "sales": 100}
{"index": {}}
{"product": "Samsung", "sales": 80}
{"index": {}}
{"product": "작은 폰", "sales": 50}
{"index": {}}
{"product": "작은 폰", "sales": 5000}
{"index": {}}
{"product": "작은 폰", "sales": 200}
- 집계 쿼리를 실행하여 각 상품 이름(
product)을 기준으로 그룹화합니다
GET /sales/_search
{
"aggs":{//집계 작업
"product_group":{//이름, 임의로 지정
"terms":{//그룹화
"field":"product"//그룹화 필드
}
}
}
}
- 각 그룹의 판매 총액을 계산하며,
terms집계와sum집계를 사용하여 구현합니다 - 쿼리 결과는 각 제품의 이름과 판매 총액을 반환합니다
GET /sales/_search
{
"size": 0,
"aggs": {
"product_sales": {
"terms": {
"field": "product"
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
}
}
}
}
}
지표metric 집계
-
지표 집계란
-
데이터 세트에 대한 최대, 최소, 합계, 평균 등 지표의 집계를 지표 집계 metric라고 합니다
-
max, min, avg, sum 등 함수 사용
-
사례 실습
-
집계 쿼리
max적용 사례: -
데이터 준비:이커머스 웹사이트의 판매 기록 인덱스를 가정합니다. 제품 이름과 판매 가격 필드가 포함됩니다
POST /sales_v1/_doc
{ "product_name": "핸드폰", "price": 1000 }
POST /sales_v1/_doc
{ "product_name": "TV", "price": 1500 }
POST /sales_v1/_doc
{ "product_name": "작은 강의실 왕님의 검은 스타킹", "price": 4500 }
- 사례 설명:
max집계 쿼리를 사용하여 제품 가격의 최고값을 가져옵니다.
GET /sales_v1/_search
{
"size": 0,
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
-
집계 쿼리 -
min적용 사례: -
데이터 준비:학생 시험 점수 인덱스를 가정합니다. 학생 이름과 시험 점수 필드가 포함됩니다.
POST /exam_scores/_doc
{ "student_name": "작은 강의실-대소", "score" : 80 }
POST /exam_scores/_doc
{ "student_name": "왕님", "score" : 90 }
POST /exam_scores/_doc
{ "student_name": "작은 강의실-D님", "score" : 40 }
- 사례 설명:
min집계 쿼리를 사용하여 학생의 최저 시험 점수를 가져옵니다.
GET /exam_scores/_search
{
"size": 0,
"aggs": {
"min_score": {
"min": {
"field": "score"
}
}
}
}
-
집계 쿼리 -
avg적용 사례: -
데이터 준비(동일):학생 시험 점수 인덱스를 가정합니다. 학생 이름과 시험 점수 필드가 포함됩니다.
-
avg집계 쿼리를 사용하여 학생의 평균 시험 점수를 계산합니다
GET /exam_scores/_search
{
"size": 0,
"aggs": {
"avg_score": {
"avg": {
"field": "score"
}
}
}
}
-
집계 쿼리 -
sum적용 사례: -
데이터 준비:이커머스 웹사이트의 판매 기록 인덱스를 가정합니다. 제품 이름과 판매 수량 필드가 포함됩니다.
POST /sales_order/_doc
{ "product_name": "핸드폰", "sales_count" : 100 }
POST /sales_order/_doc
{ "product_name": "TV", "sales_count" : 50 }
POST /sales_order/_doc
{ "product_name": "작은 강의실 영구 회원권", "sales_count" : 999 }
- 사례 설명:
sum집계 쿼리를 사용하여 판매 기록의 총 판매 수량을 계산합니다.
GET /sales_order/_search
{
"size": 0,
"aggs": {
"total_sales": {
"sum": {
"field": "sales_count"
}
}
}
}
버킷 집구문법 및 Terms
-
버킷bucket 집계란
-
데이터 세트를 그룹화 group by한 후 그룹에서 지표 집계를 수행하는 ES에서는버킷, 버킷 집계 bucketing이라고 합니다
-
기본 구문 형식은 다음과 같습니다
GET /index/_search
{
"size": 0,
"aggs": {
"aggregation_name": {
"bucket_type": {
"bucket_options": {
"bucket_option_name": "bucket_option_value",
...
},
"aggs": {
"sub_aggregation_name": {
"sub_aggregation_type": {
"sub_aggregation_options": {
"sub_aggregation_option_name": "sub_aggregation_option_value",
...
}
}
}
}
}
}
}
}
#설명
index: 집계 쿼리를 실행할 인덱스 이름입니다.
aggregation_name: 사용자 정의 집계 이름입니다.
bucket_type: 특정 버킷 집계 유형(예: terms, date_histogram, range 등)입니다.
bucket_option_name 및 bucket_option_value: 특정 버킷 집계 옵션의 이름과 값입니다.
sub_aggregation_name: 하위 집계의 이름입니다.
sub_aggregation_type: 특정 하위 집계 유형(예: sum, avg, max, min 등)입니다.
sub_aggregation_option_name 및 sub_aggregation_option_value: 특정 하위 집계 옵션의 이름과 값입니다
-
사례 실습
-
버킷 집계 쿼리 -
Terms사례: -
데이터 준비:온라인 서점의 도서 판매 기록 인덱스를 가정합니다. 도서 이름과 판매 수량 필드가 포함됩니다.
#인덱스 라이브러리 생성
PUT /book_sales
{
"mappings": {
"properties": {
"book_title": {
"type": "keyword"
},
"sales_count": {
"type": "integer"
}
}
},
"settings": {
"number_of_shards": 2,
"number_of_replicas": 0
}
}
# 배치 데이터 삽입
POST /book_sales/_bulk
{ "index": {} }
{ "book_title": "Elasticsearch in Action", "sales_count" : 100 }
{ "index": {} }
{ "book_title": "작은 강의실 마이크로서비스 최적 실천", "sales_count" : 50 }
{ "index": {} }
{ "book_title": "대용량 데이터 프로젝트 대강의", "sales_count" : 80 }
{ "index": {} }
{ "book_title": "작은 강의실 면접 보급서", "sales_count" : 120 }
{ "index": {} }
{ "book_title": "자료 구조와 알고리즘의 아름다움", "sales_count" : 90 }
{ "index": {} }
{ "book_title": "Python 프로그래밍 빠른 시작", "sales_count" : 70 }
{ "index": {} }
{ "book_title": "작은 강의실 면접 보급서", "sales_count" : 110 }
{ "index": {} }
{ "book_title": "작은 강의실 Java 핵심 기술", "sales_count" : 200 }
{ "index": {} }
{ "book_title": "컴퓨터 시스템 깊이 이해", "sales_count" : 150 }
{ "index": {} }
{ "book_title": "작은 강의실 Java 핵심 기술", "sales_count" : 80 }
- 사례 설명:
terms집계 쿼리를 사용하여 도서를 판매 수량에 따라 버킷으로 분할하고 각 버킷 내의 판매 수량 합계를 가져옵니다.
GET /book_sales/_search
{
"size": 0,
"aggs": {
"book_buckets": {
"terms": {
"field": "book_title",
"size": 10
},
"aggs": {
"total_sales": {
"sum": {
"field": "sales_count"
}
}
}
}
}
}
버킷 집계 Date Histogram
-
버킷 집계 쿼리 -
Date Histogram -
날짜 유형 필드를 고정된 시간 간격으로 버킷화하고 각 시간 간격 내의 문서에 대해 추가 작업 및 계산을 수행합니다
-
기본 구문은 다음과 같습니다
GET /index/_search
{
"size": 0,
"aggs": {
"date_histogram_name": {
"date_histogram": {
"field": "date_field_name",
"interval": "interval_expression"
},
"aggs": {
"sub_aggregation": {
"sub_aggregation_type": {}
}
}
}
}
}
#설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
date_histogram_name:사용자 정의 date_histogram 집계 이름입니다.
date_field_name:집계할 날짜 유형 필드 이름입니다.
interval_expression:버킷화할 시간 간격을 지정합니다. 시간 간격은 유효한 날짜 형식(예: 1d, 1w, 1M) 또는 숫자와 시간 단위의 조합(예: 7d는 7일, 1h는 1시간)일 수 있습니다.
sub_aggregation:각 날짜 버킷 내에서 수행하는 하위 집계 작업을 지정합니다.
sub_aggregation_type:개별 하위 집계 작업의 유형으로, 모든 유효한 하위 집계 유형일 수 있습니다.
- 데이터 준비:이커머스 웹사이트의 주문 인덱스로, 주문 날짜와 주문 금액 필드가 포함됩니다.
POST /order_history/_bulk
{ "index": {} }
{ "order_date": "2025-01-01", "amount" : 100 ,"book_title": "작은 강의실 Java 핵심 기술"}
{ "index": {} }
{ "order_date": "2025-02-05", "amount" : 150, "book_title": "작은 강의실 면접 보급서" }
{ "index": {} }
{ "order_date": "2025-03-02", "amount" : 500 ,"book_title": "작은 강의실 Java 핵심 기술"}
{ "index": {} }
{ "order_date": "2025-05-02", "amount" : 250 , "book_title": "작은 강의실 면접 보급서"}
{ "index": {} }
{ "order_date": "2025-05-05", "amount" : 10 ,"book_title": "작은 강의실 마이크로서비스 최적 실천"}
{ "index": {} }
{ "order_date": "2025-02-18", "amount" : 290 , "book_title": "작은 강의실 마이크로서비스 최적 실천"}
- 사례 설명:
date_histogram집계 쿼리를 사용하여 주문을 날짜별로 버킷화하고 각 버킷 내의 주문 금액 합계를 계산합니다.
GET /order_history/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "order_date",
"calendar_interval": "month",
"format": "yyyy-MM"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
}
}
}
}
}
버킷 집계 Range
-
버킷 집계 쿼리 -
Range -
필드 값을 다른 범위로 나누고 각 범위 내의 문서를 해당 버킷에 할당하여 이러한 범위에 대해 다양한 작업 및 계산을 수행합니다.
-
구문 소개
GET /index/_search
{
"size": 0,
"aggs": {
"range_name": {
"range": {
"field": "field_name",
"ranges": [
{ "key": "range_key_1", "from": from_value_1, "to": to_value_1 },
{ "key": "range_key_2", "from": from_value_2, "to": to_value_2 },
...
]
},
"aggs": {
"sub_aggregation": {
"sub_aggregation_type": {}
}
}
}
}
}
#설명
index:집계 쿼리를 실행할 인덱스 이름입니다.
range_name:사용자 정의 range 집계 이름입니다.
field_name:집계할 필드 이름입니다.
ranges:범위 배열을 지정하며, 각 범위는 key, from 및 to 매개변수를 사용하여 정의합니다.
key:범위의 고유 식별자입니다.
from:범위의 시작 값(포함).
to:범위의 끝 값(미포함).
sub_aggregation:각 범위 내에서 수행하는 하위 집계 작업을 지정합니다.
sub_aggregation_type:개별 하위 집계 작업의 유형으로, 모든 유효한 하위 집계 유형일 수 있습니다.
- 데이터 준비:온라인 상점의 제품 인덱스로, 제품 이름과 가격 필드가 포함됩니다
POST /product_v4/_bulk
{ "index": {} }
{ "product_name": "작은 강의실 영구 회원권", "price" : 2000 }
{ "index": {} }
{ "product_name": "JVM 주제 과정", "price" : 200 }
{ "index": {} }
{ "product_name": "SpringBoot3.X 최적 실천", "price" : 300 }
{ "index": {} }
{ "product_name": "고동시 프로젝트 대강의", "price" : 1500 }
{ "index": {} }
{ "product_name": "대용량 데이터 프로젝트 대강의", "price" : 4120 }
{ "index": {} }
{ "product_name": "모니터링 경보 Prometheus 최적 실천", "price" : 180 }
{ "index": {} }
{ "product_name": "풀스택 엔지니어 학습 경로", "price" : 250 }
{ "index": {} }
{ "product_name": "자동화 테스트 플랫폼 대강의", "price" : 4770 }
{ "index": {} }
{ "product_name": "작은 강의실-왕님 헤어짐 최적 실천", "price" : 400 }
{ "index": {} }
{ "product_name": "작은 강의실-대소 마사지 이야기", "price" : 150 }
-
사례 설명:
range집계 쿼리를 사용하여 제품을 가격 범위로 버킷화하고 각 버킷 내의 제품 수량을 계산합니다. -
key를 작성하지 않으면 기본으로 생성됩니다
GET /product_v4/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 200 },
{ "from": 200 }
]
},
"aggs":{
"total_price":{
"sum":{
"field":"price"
}
}
}
}
}
}
스프링부트 3.X 통합 ES
-
ElasticSearch는 검색 엔진으로, 서버 측 프로그램으로 HTTP Restful 인터페이스를 제공하여 연결합니다
-
따라서 여러 다른 언어로 쉽게 ES 검색 기능에 연결할 수 있습니다
-
ES 공식은 Java 클라이언트를 통해 ES에 연결하며, 두 가지 유형으로 나뉩니다
-
이전 버전의 ES는 TransportClient(7.0 버전에서 사용 중지 표시)를 사용합니다
-
Java Low Level REST Client(계속 유지 관리)
-
저 수준 REST 클라이언트를 기반으로 HTTP 요청을 Elasticsearch로 보내 통신합니다.
-
사용자가 직접 조합한 문자열이며, 반환 결과를 직접 파싱합니다; 모든 Elasticsearch 버전과 호환됩니다
-
Java High Level REST Client(7.1 버전에서 사용 중지 표시)
-
저 수준 REST 클라이언트를 기반으로 더 높은 수준의 추상화를 제공하여 Elasticsearch와의 상호작용을 단순화합니다.
-
더 쉬운 API를 제공하며, 저수준 요청 및 응답 처리 논리를 캡슐화하고 더 친숙하고 가독성이 높은 코드를 제공합니다.
-
JSON 데이터의 직렬화 및 역직렬화를 자동으로 처리하며, 인덱싱, 검색, 집계 등 대부분의 일반 작업에 적합합니다.
-
더 복잡한 고급 기능 및 사용자 정의 작업의 경우 저 수준 REST 클라이언트 또는 원본 Elasticsearch REST API를 사용해야 할 수 있습니다
-
Java API Client(8.X 버전부터 권장 사용)
-
Elasticsearch 7.1 버전 이전에 사용된 Java 클라이언트는 Java REST Client입니다
-
7.1 버전부터 Elasticsearch 공식은 Java REST Client를 폐지(deprecated) 표시하고 새 버전 Java 클라이언트인 Java API Client를 권장합니다
-
새 버전의 Java API Client는 Elasticsearch 서버와 통신하기 위한 Java 클라이언트 라이브러리입니다
-
저수준 Transport 통신을 캡슐화하고 동기 및 비동기 호출, 스트림 및 함수형 호출 방법을 제공합니다
-
공식 문서 주소
-
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.5/introduction.html
통합(springData)
-
Spring Data의 표준화된 데이터 액세스 기술을 기반으로 Elasticsearch 통합을 단순화합니다.
-
풍부한 CRUD 작업 및 쿼리 방법을 제공하며 데이터 액세스를 단순화하고 자동화된 인덱스 관리 및 매핑을 포함합니다
-
Spring Data Elasticsearch는 일부 고급 기능 및 복잡한 쿼리에 대해 유연성이 부족하여 추가 사용자 정의 처리가 필요할 수 있습니다
-
Spring Data 프레임워크란
-
데이터 액세스 및 지속성 개발을 단순화하기 위한 프레임워크로, 일관된 API 및 추상화 세트를 제공합니다
-
다양한 데이터 저장 기술(예: 관계형 데이터베이스, NoSQL 데이터베이스, Elasticsearch 등)과 상호작용을 훨씬 쉽게 만듭니다
-
공식 사이트:https://spring.io/projects/spring-data
-
springBoot3.x와 SpringData 프레임워크 통합
-
의존성
<!-- spring-data-elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-M3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!--spring boot and web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring boot test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--테스트 구성 요소-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mqtt-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.integration</groupId>-->
<!-- <artifactId>spring-integration-mqtt</artifactId>-->
<!-- </dependency>-->
<!--spring data elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
- 구성
spring.elasticsearch.uris=http://127.0.0.1:9200
spring.elasticsearch.username=elastic
spring.elasticsearch.password=hXtO*Lzi2GGJ5wUmUA2c
인덱스 라이브러리 작업
-
ElasticsearchTemplate란
-
Spring Data Elasticsearch가 제공하는 핵심 클래스로, ElasticsearchClient의 구체적인 구현입니다
-
Spring Boot에서 데이터의 저장, 조회 및 쿼리를 위해 Elasticsearch를 조작하는 데 사용됩니다
-
문서 저장, 업데이트, 삭제 및 쿼리, 집계 작업 등을 수행하기 위한 메서드 세트를 제공합니다
-
ElasticsearchTemplate의 일반적인 메서드
-
save(Object): 객체를 Elasticsearch에 저장합니다. -
index(IndexQuery): IndexQuery 객체를 사용하여 인덱싱 작업을 수행합니다. -
delete(String, String): 지정된 인덱스 및 유형의 문서를 삭제합니다. -
get(String, String): 지정된 인덱스 및 유형의 문서를 가져옵니다. -
update(UpdateQuery): UpdateQuery 객체를 사용하여 업데이트 작업을 수행합니다. -
search(SearchQuery, Class): 검색 쿼리를 실행하고 결과를 지정된 유형의 객체로 매핑합니다. -
count(SearchQuery, Class): 검색 쿼리를 실행하고 결과의 카운트를 반환합니다 -
ElasticsearchTemplate 일반 주석 구성(Spring Data Elasticsearch에 해당)
-
@Id 주 키 지정
-
@Document 엔티티 클래스와 인덱스 대응 관계 지정
indexName:인덱스 이름
- @Field 일반 속성 지정
type Elasticsearch에서 속성 유형에 해당하며, FieldType 열거형을 사용하여 빠르게 가져옵니다.
text 유형은 토큰화 가능
keywords는 토큰화 불가능
index 검색 조건으로 사용할 경우 index는 반드시 true여야 함
analyzer 토큰화기 유형 지정.
- DTO
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "video")
public class VideoDTO {
@Id
@Field(type = FieldType.Text, index = false)
private Long id;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String description;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Integer)
private Integer duration;
@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)
private LocalDateTime createTime;
public VideoDTO(Long id, String title, String description, Integer duration,String category) {
this.id = id;
this.title = title;
this.description = description;
this.duration = duration;
this.createTime = LocalDateTime.now();
this.category = category;
}
}
}
- 테스트
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class EsTest {
@Autowired
private ElasticsearchTemplate restTemplate;
/**
* 인덱스 존재 여부 확인
*/
@Test
public void existsIndex() {
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
boolean exists = indexOperations.exists();
System.out.println(exists);
}
/**
* 인덱스 생성
*/
@Test
public void createIndex() {
// spring data es 모든 인덱스 작업은 이 인터페이스에 있음
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
// 존재하면 삭제
if(indexOperations.exists()){
indexOperations.delete();
}
// 인덱스 생성
indexOperations.create();
log.info("테스트--인덱스 생성 성공");
//매핑 설정: 실제 개발에서는 거의 프레임워크를 사용하여 인덱스를 생성하거나 매핑을 설정하지 않으며, 이는 아키텍처 또는 관리자의 작업이며 코드 구현에 적합하지 않습니다
restTemplate.indexOps(VideoDTO.class).putMapping();
}
/**
* 인덱스 삭제
*/
@Test
public void deleteIndex() {
IndexOperations indexOperations = restTemplate.indexOps(VideoDTO.class);
boolean delete = indexOperations.delete();
System.out.println(delete);
}
/**
* 추가
*/
@Test
public void insert(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("작은 강의실 아키텍처 대강의와 Spring Cloud");
videoDTO.setCreateTime(LocalDateTime.now());
videoDTO.setDuration(100);
videoDTO.setCategory("백엔드");
videoDTO.setDescription("이것은 종합 대형 과정으로 jvm, redis, 신버전 spring boot3.x, 아키텍처, 모니터링, 성능 최적화, 알고리즘, 고동시 등 다양한 내용을 포함합니다");
VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}
/**
* 업데이트
*/
@Test
public void update(){
VideoDTO videoDTO = new VideoDTO();
videoDTO.setId(1L);
videoDTO.setTitle("작은 강의실 아키텍처 대강의와 Spring Cloud V2");
videoDTO.setCreateTime(LocalDateTime.now());
videoDTO.setDuration(102);
videoDTO.setCategory("백엔드");
videoDTO.setDescription("이것은 종합 대형 과정으로 jvm, redis, 신버전 spring boot3.x, 아키텍처, 모니터링, 성능 최적화, 알고리즘, 고동시 등 다양한 내용을 포함합니다");
VideoDTO saved = restTemplate.save(videoDTO);
System.out.println(saved);
}
/**
* 배치 추가
*/
@Test
public void batchInsert() {
List<VideoDTO> list = new ArrayList<>();
list.add(new VideoDTO(2L, "왕님이 녹화한 마사지 과정", "주로 마사지와 클럽 추천", 123, "백엔드"));
list.add(new VideoDTO(3L, "빙빙의 프론트엔드 성능 최적화", "프론트엔드 고수 시리즈", 100042, "프론트엔드"));
list.add(new VideoDTO(4L, "대용량 데이터 프로젝트 대강의", "D님의 백엔드 + 빅데이터 종합 과정", 5432345, "백엔드"));
list.add(new VideoDTO(5L, "작은 강의실 영구 회원권", "대량의 주제 과정을 볼 수 있으며, IT 기술 지속적 충전 플랫폼", 6542, "백엔드"));
list.add(new VideoDTO(6L, "대소-프론트엔드 로코드 플랫폼", "고효율 개발底层 기반 플랫폼, 효율 플랫폼 사례", 53422, "프론트엔드"));
list.add(new VideoDTO(7L, "자동화 테스트 플랫폼 대강의", "마이크로서비스 아키텍처 하의 spring cloud 아키텍처 대강의, jvm, 효율 플랫폼 포함", 6542, "백엔드"));
Iterable<VideoDTO> result = restTemplate.save(list);
System.out.println(result);
}
/**
* 주 키로 검색
*/
@Test
public void searchById(){
VideoDTO videoDTO = restTemplate.get("3", VideoDTO.class);
assert videoDTO != null;
System.out.println(videoDTO);
}
/**
* Id로 삭제
*/
@Test
public void deleteById() {
String delete = restTemplate.delete("2", VideoDTO.class);
System.out.println(delete);
}
}
다중 사례 검색
- 새 버전의 ElasticSearch의 Query 인터페이스
- Query는 Spring Data Elasticsearch의 인터페이스로, 여러 구체적 구현이 있으며새 버전 공식 문서에 내용이 부족하므로, 여기서 소스 코드를 보고 사례 실습을 진행합니다
- CriteriaQuery
- Criteria를 생성하여 데이터를 검색하며, Elasticsearch 쿼리 구문 또는 기본 지식을 이해할 필요가 없습니다
- 사용자가 단순히 연결하고 조합하여 검색 문서가 반드시 만족해야 하는 객체를 지정하여 쿼리를 구축할 수 있습니다
- StringQuery
- Elasticsearch 쿼리를 JSON 문자열로 사용하며, Elasticsearch 쿼리 구문에 익숙한 사람에게 더 적합합니다
- 또한 kibana 또는 postman과 같은 클라이언트 도구를 사용하여 디버깅하기에도 더 편리합니다
- NativeQuery
- 복잡한 쿼리 또는 Criteria API로 표현할 수 없는 쿼리 시 사용되는 클래스이며, 예를 들어 쿼리 구성 및 집계 사용 시나리오
NativeQuery 검색
-
새 버전 검색 구문 사례, 쿼리는 새 버전의 lambda 표현식 구문을 사용하여 더 간결합니다
-
전체 검색
/**
* 모든 것을 검색
*/
@Test
public void searchAll(){
SearchHits<VideoDTO> search = restTemplate.search(Query.findAll(), VideoDTO.class);
List<SearchHit<VideoDTO>> searchHits = search.getSearchHits();
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
- 일치 검색
/**
* match 쿼리
*/
@Test
public void matchQuery(){
Query query = NativeQuery.builder().withQuery(q -> q
.match(m -> m
.field("description") //필드
.query("spring") //값
)).build();
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
- 페이지네이션 검색
/**
* 페이지네이션 검색
*/
@Test
public void pageSearch() {
Query query = NativeQuery.builder().withQuery(Query.findAll())
.withPageable(Pageable.ofSize(3).withPage(0)).build();
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
- 정렬
#ascending():기본값, 오름차순
#descending():내림차순
/**
* 정렬 검색, 시간 길이에 따라 내림차순 정렬
*/
@Test
public void sortSearch() {
Query query = NativeQuery.builder().withQuery(Query.findAll())
.withPageable(Pageable.ofSize(10).withPage(0))
.withSort(Sort.by("duration").descending()).build();
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
원본 StringQuery 검색
-
StringQuery란
-
Elasticsearch 쿼리를 JSON 문자열로 사용하며, Elasticsearch 쿼리 구문에 익숙한 사람에게 더 적합합니다
-
또한 kibana 또는 postman과 같은 클라이언트 도구를 사용하여 디버깅하기에도 더 편리합니다
-
사례 실습
-
사례 1:bool must 쿼리, 제목에 아키텍처 키워드가 있고, 설명에 spring 키워드가 있으며, 시간 길이는 10~6000 사이인
-
원본 DSL 쿼리
GET /video/_search
{
"query": {
"bool": {
"must": [{
"match": {
"title": "아키텍처"
}
}, {
"match": {
"description": "spring"
}
}, {
"range": {
"duration": {
"gte": 10,
"lte": 6000
}
}
}]
}
}
}
- SpringBoot+SpringData 쿼리
@Test
public void stringQuery() {
//제목에 아키텍처 키워드가 있고, 설명에 spring 키워드가 있으며, 시간 길이는 10~6000 사이인
String dsl = """
{"bool":{"must":[{"match":{"title":"아키텍처"}},{"match":{"description":"spring"}},{"range":{"duration":{"gte":10,"lte":6000}}}]}}
""";
Query query = new StringQuery(dsl);
List<SearchHit<VideoDTO>> searchHitList = restTemplate.search(query, VideoDTO.class).getSearchHits();
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHitList.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}
집계 검색
-
집계 검색 사례
-
방안 1:원본 DSL을 사용하여 처리할 수 있습니다
-
방안 2:NativeQuery를 사용하여 집계 검색을 완료합니다
-
다른 분류별 비디오 수 통계
GET /video/_search
{
"size": 1,
"aggs": {
"category_group": {
"terms": {
"field": "category"
}
}
}
}
/**
* 집계 쿼리
*/
@Test
void aggQuery() {
Query query = NativeQuery.builder()
.withAggregation("category_group", Aggregation.of(a -> a
.terms(ta -> ta.field("category").size(2))))
.build();
SearchHits<VideoDTO> searchHits = restTemplate.search(query, VideoDTO.class);
//집계 데이터 가져오기
ElasticsearchAggregations aggregationsContainer = (ElasticsearchAggregations) searchHits.getAggregations();
Map<String, ElasticsearchAggregation> aggregations = Objects.requireNonNull(aggregationsContainer).aggregationsAsMap();
//해당 이름의 집계 가져오기
ElasticsearchAggregation aggregation = aggregations.get("category_group");
Buckets<StringTermsBucket> buckets = aggregation.aggregation().getAggregate().sterms().buckets();
//집계 정보 출력
buckets.array().forEach(bucket -> {
System.out.println("그룹명:"+bucket.key().stringValue() + ", 값" + bucket.docCount());
});
// searchHits를 얻어 순회하여 content를 가져옵니다
List<VideoDTO> videoDTOS = new ArrayList<>();
searchHits.forEach(hit -> {
videoDTOS.add(hit.getContent());
});
System.out.println(videoDTOS);
}