
본 포스팅은 인프런 JSCODE 박재성님의 실전에서 바로 써먹는 Elasticsearch 입문 (검색 최적화편)
강의를 들은 후 해당 강의를 참고하여 작성되었습니다.
https://inf.run/767Nk
검색 키워드가 포함된 데이터를 조회하고 싶을 때
text 타입의 필드를 조회할 때만 사용 가능
PUT /boards
{
"mappings": {
"properties": {
"title": {
"type": "text"
}
}
}
}
PUT boards/_doc/1
{
"title":"편의점 과자 내돈내산 후기"
}
GET boards/_search
{
"query": {
"match": {
"title": "편의점 후기"
}
}
}
# 결과
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.5753642,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 0.5753642,
"_source": {
"title": "편의점 과자 내돈내산 후기"
}
}
]
}
}
특정 값과 정확하게 일치하는 데이터를 조회하고 싶을 때
text를 제외한 필드를 검색하고자할 때 사용
PUT /boards
{
"mappings": {
"properties": {
"board_id": {
"type": "long"
},
"category": {
"type": "keyword"
}
}
}
}
PUT /boards/_doc/1
{
"board_id": 1,
"category": "자유 게시판"
}
PUT /boards/_doc/2
{
"board_id": 2,
"category": "익명 게시판"
}
PUT /boards/_doc/3
{
"board_id": 3,
"category": "광고 게시판"
}
GET boards/_search
{
"query": {
"term": {
"category": "자유 게시판"
}
}
}
# 결과
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.9808291,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 0.9808291,
"_source": {
"board_id": 1,
"category": "자유 게시판"
}
}
]
}
}
GET boards/_search
{
"query": {
"terms": {
"category": ["자유 게시판", "익명 게시판"]
}
}
}
# 결과
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 1,
"_source": {
"board_id": 1,
"category": "자유 게시판"
}
},
{
"_index": "boards",
"_id": "2",
"_score": 1,
"_source": {
"board_id": 2,
"category": "익명 게시판"
}
}
]
}
}
두가지 이상의 조건을 만족시키는 데이터를 조회하고 싶을 때
GET boards/_search
{
"query": {
"bool": {
"filter": [
{
"terms" : {
"category": ["자유 게시판", "익명 게시판"]
}
},
{
"term": {
"board_id": 1
}
}
]
}
}
}
# 결과
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 0,
"_source": {
"board_id": 1,
"category": "자유 게시판"
}
}
]
}
}
GET boards/_search
{
"query": {
"terms": {
"category": ["자유 게시판", "익명 게시판"]
}
}
}
# 결과
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 1,
"_source": {
"board_id": 1,
"category": "자유 게시판"
}
},
{
"_index": "boards",
"_id": "2",
"_score": 1,
"_source": {
"board_id": 2,
"category": "익명 게시판"
}
}
]
}
}
filter는 score에 영향을 주지 않고
must는 score에 영향을 준다.
match처럼 score와 상관있는 쿼리인 경우mustterm처럼 socre와 상관이 없는 쿼리인 경우filter를 사용한다.
자유 게시판의 게시글 중에서 '검색엔진'이랑 관련된 글을 찾고 싶다.
그리고 글은 공지글이 아니였으면 좋겠다.
PUT /boards
{
"mappings": {
"properties": {
"board_id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "nori"
},
"category": {
"type": "keyword"
},
"is_notice": {
"type": "boolean"
},
"created_at": {
"type": "date"
}
}
}
}
POST /boards/_doc
{
"board_id": 1,
"title": "엘라스틱서치는 정말 강력한 검색엔진이에요",
"category": "자유 게시판",
"is_notice": false,
"created_at": "2025-05-01T12:00:00"
}
GET /boards/_search
{
"query": {
"bool": {
// match는 must와 쌍을 이룸
"must": [
{
"match": {
"title" : "검색엔진"
}
}
],
// score에 해당하지 않는 필드는 filter와 쌍을 이룸
"filter": [
{
"term": {
"category": "자유 게시판"
}
},
{
"term": {
"is_notice": false
}
}
]
}
}
}
# 결과
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.5753642,
"hits": [
{
"_index": "boards",
"_id": "Bue6x5wBJYnPpoQ1cAY2",
"_score": 0.5753642,
"_source": {
"board_id": 1,
"title": "엘라스틱서치는 정말 강력한 검색엔진이에요",
"category": "자유 게시판",
"is_notice": false,
"created_at": "2025-05-01T12:00:00"
}
}
]
}
}
특정 조건을 만족하지 않는 데이터를 조회하고 싶을 때
광고게시판 의 글이 아니면서, 공지글이 아니면서
검색엔진의 키워드와 관련된 게시물을 조회
PUT /boards
{
"mappings": {
"properties": {
"board_id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "nori"
},
"category": {
"type": "keyword"
},
"is_notice": {
"type": "boolean"
},
"created_at": {
"type": "date"
}
}
}
}
POST /boards/_doc/1
{
"board_id": 1,
"title": "엘라스틱서치는 정말 강력한 검색엔진이에요",
"category": "자유 게시판",
"is_notice": false,
"created_at": "2025-05-01T12:00:00"
}
POST /boards/_doc/2
{
"board_id": 2,
"title": "이벤트 참여 방법 안내드립니다",
"category": "광고 게시판",
"is_notice": false,
"created_at": "2025-05-02T10:30:00"
}
POST /boards/_doc/3
{
"board_id": 3,
"title": "익명으로 질문하고 답변 받을 수 있어요",
"category": "익명 게시판",
"is_notice": true,
"created_at": "2025-05-03T08:20:00"
}
GET boards/_search
{
"query": {
"bool": {
"must_not": [
{"term": {
"category": "광고 게시판"
}
}
],
"filter": [
{
"term": {
"is_notice": false
}
}
],
"must": [
{
"match": {
"title": "검색엔진"
}
}
]
}
}
}
# 결과
{
"took": 13,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.9155619,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 1.9155619,
"_source": {
"board_id": 1,
"title": "엘라스틱서치는 정말 강력한 검색엔진이에요",
"category": "자유 게시판",
"is_notice": false,
"created_at": "2025-05-01T12:00:00"
}
}
]
}
}
숫자/날짜의 값에 대해 범위 조건으로 데이터를 조회할 때
나이가 30살 이상이면서
회원가입 일자가 2025년 1월1일 이후인 사용자
PUT /users
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"created_at": {
"type": "date"
}
}
}
}
PUT /users/_doc/1
{
"name": "kim_jisoo",
"age": 28,
"created_at": "2024-09-01"
}
POST /users/_doc/2
{
"name": "lee_joon",
"age": 35,
"created_at": "2024-12-15"
}
POST /users/_doc/3
{
"name": "park_saejin",
"age": 32,
"created_at": "2025-03-25"
}
GET /users/_search
{
"query": {
"bool": {
"filter": [
{
"range": {
"age": {
"gte": 30
}
}
},
{
"range": {
"created_at": {
"gte" : "2025-01-01"
}
}
}
]
}
}
}
# 결과
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0,
"hits": [
{
"_index": "users",
"_id": "3",
"_score": 0,
"_source": {
"name": "park_saejin",
"age": 32,
"created_at": "2025-03-25"
}
}
]
}
}
특정 조건을 만족하는 데이터를 상위에 노출시키고 싶을 때
사용자가 특정 키워드로 검색했을때, 키워드와 관련된 데이터를 조회한다.
이때, 평점이 높거나 좋아요 수가 많은 상품이 상단에 노출되어야 한다.
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "nori"
},
"rating": {
"type": "double"
},
"likes": {
"type": "integer"
}
}
}
}
// 키워드("무선 이어폰")와 관련성은 적지만, 좋아요 수가 높고, 평점이 좋은 경우
POST /products/_doc/1
{
"name": "무선 충전기 C타입",
"rating": 4.9,
"likes": 300
}
// 키워드("무선 이어폰")와 관련성은 높지만, 좋아요 수가 낮고, 평점도 낮은 경우
POST /products/_doc/2
{
"name" : "소니 무선 이어폰 WF",
"rating": 3.8,
"likes": 15
}
// 키워드("무선 이어폰")와 관련성도 높고, 좋아요 수도 높고, 평점도 높은 경우
POST /products/_doc/3
{
"name": "갤럭시 버즈2 무선 이어폰",
"rating": 4.8,
"likes": 310
}
// 키워드("무선 이어폰")와 관련성이 아예 없는데, 좋아요 수는 높고, 평점도 높은 경우
POST /products/_doc/4
{
"name": "삼성 노트북 13인치",
"rating": 5.0,
"likes": 1000
}
GET products/_search
{
"query": {
"bool": {
"must": [
{
"match" : {
"name": "무선 이어폰"
}
}
],
"should": [
{
"range": {
"rating": {
"gte": 4.5
}
}
},
{
"range": {
"likes": {
"gte": 100
}
}
}
]
}
}
}
# 결과
{
"took": 5,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 3.0276947,
"hits": [
{
"_index": "products",
"_id": "3",
"_score": 3.0276947,
"_source": {
"name": "갤럭시 버즈2 무선 이어폰",
"rating": 4.8,
"likes": 310
}
},
{
"_index": "products",
"_id": "1",
"_score": 2.3491573,
"_source": {
"name": "무선 충전기 C타입",
"rating": 4.9,
"likes": 300
}
},
{
"_index": "products",
"_id": "2",
"_score": 1.1223162,
"_source": {
"name": "소니 무선 이어폰 WF",
"rating": 3.8,
"likes": 15
}
}
]
}
}
오타가 있어도 유사한 단어를 포함한 데이터를 조회하고 싶을 때
사용자가 특정 키워드로 검색했을때, 키워드와 관련된 데이터를 조회한다.
이때, 평점이 높거나 좋아요 수가 많은 상품이 상단에 노출되어야 한다.
PUT /boards
{
"mappings": {
"properties": {
"title": {
"type": "text"
}
}
}
}
POST /boards/_doc/1
{ "title": "elasticsearch 사용법" }
GET /boards/_search
{
"query": {
"match": {
"title": {
"query": "elastiksearch",
"fuzziness": 1
}
}
}
}
# 결과
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.2655527,
"hits": [
{
"_index": "boards",
"_id": "1",
"_score": 0.2655527,
"_source": {
"title": "elasticsearch 사용법"
}
}
]
}
}
하나의 필드에 text와 keyword 타입을 동시에 사용하고 싶을 때
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "nori"
},
"category": {
"type": "text",
"analyzer": "nori",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
POST /products/_doc/1
{
"name": "삼성 세탁기",
"category": "특수 가전제품"
}
GET /products/_search
{
"query": {
"term": {
"category.raw": "특수 가전제품"
}
}
}
# 결과
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.2876821,
"hits": [
{
"_index": "products",
"_id": "1",
"_score": 0.2876821,
"_source": {
"name": "삼성 세탁기",
"category": "특수 가전제품"
}
}
]
}
}
검색 키워드를 일부 입력했을 때 검색어를 추천
search_as_you_type
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "search_as_you_type",
"analyzer": "nori"
}
}
}
}
POST /products/_doc
{
"name": "곱창 돌김생김"
}
POST /products/_doc
{
"name": "구운 돌김"
}
POST /products/_doc
{
"name": "완도 곱창 돌김 100매"
}
POST /products/_doc
{
"name": "삼성 노트북"
}
POST /products/_doc
{
"name": "Nike 신발"
}
GET /products/_search
{
"query": {
"multi_match": {
"query": "돌김 곱창",
// you have th 라고 검색하면
// you have 는 인덱스에 저장되는 토큰과 일치하는 데이터를 검색
// th는 역인덱스 토큰중 th로 시작하는 데이터를 검색
"type": "bool_prefix",
"fields": [
// 연속적으로 단어가 일치할수록 score 향상
"name",
"name._2gram",
"name._3gram"
]
}
}
}
# 결과
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 3,
"relation": "eq"
},
"max_score": 4.303034,
"hits": [
{
"_index": "products",
"_id": "E-2tzJwBx8CjKePGQB0I",
"_score": 4.303034,
"_source": {
"name": "곱창 돌김생김"
}
},
{
"_index": "products",
"_id": "Fe2tzJwBx8CjKePGQB0c",
"_score": 3.9317808,
"_source": {
"name": "완도 곱창 돌김 100매"
}
},
{
"_index": "products",
"_id": "FO2tzJwBx8CjKePGQB0V",
"_score": 1.8573356,
"_source": {
"name": "구운 돌김"
}
}
]
}
}