자주 사용하는 검색 기능

Yangray·2025년 6월 5일

Elasticsearch

목록 보기
3/3

검색 키워드가 포함된 데이터를 조회하고 싶을 때

✅ mathch 쿼리

  • text 타입의 필드에서만 사용하는 쿼리
    (keyword, Integer, long과 같은 타입의 필드에서는 사용하지 않는다.)

특정 값과 정확하게 일치하는 데이터를 조회하고 싶을 때(term, terms)

✅ term 쿼리

  • text타입의 필드를 제외한 모든 타입에서 사용한다.
  • 특정 값과 정확히 일치하는 모든 document를 조회한다.

    📌 SQL문으로 표현하면

    SELECT * FROM boards WHERE board_id = 1
    이와 같이 정확히 일치하는 값을 조회한다.

✅ 코드 예시

//인덱스 생성 / 매핑 정의

PUT /boards
{
  "mappings": {
    "properties": {
      "board_id": {
        "type": "long"
      },
      "category": {
        "type": "keyword"
      }
    }
  }
}

// 데이터 삽입
POST /boards/_doc
{
	"board_id" : 1,
    "category" : 자유 게시판
}

POST /boards/_doc
{
	"board_id" : 2,
    "category" : 익명 게시판
}

POST /boards/_doc
{
	"board_id" : 3,
    "category" : 광고 게시판
}


document 조회
GET /boards/_search
{
	query:{
    	"term":{
        	"category" : "자유"
        }
    }
}

GET /boards/_search
{
  "query": {
    "term": {
      "category": "자유게시판"
    }
  }
}

GET /boards/_search
{
  "query": {
    "term": {
      "board_id": 13
    }
  }
}

// 이와 같이 검색했을 때 정확히 일치하는 데이터가 존재하지 않기 때문에 검색이 되지 않는다.

GET /boards/_search
{
  "query": {
    "term": {
      "category": "자유 게시판"
    }
  }
}

GET /boards/_search
{
  "query": {
    "term": {
      "board_id": 1
    }
  }
}

// 정확한 값으로 조회했을 때 검색이 된다.

✅ terms 쿼리

  • 여러 개의 값 중 하나라도 일치하는 document 조회한다.
  • SQL의 IN과 비슷한 역할을 한다.

    SELECT * FROM boards WHERE category IN('자유 게시판', '익명 게시판');

  • 자유 게시판, 또는 익명 게시판인 카데고리를 갖는 모든 데이터를 조회할 때 사용
GET /boards/_search
{
  "query": {
    "terms": {
      "category": ["자유 게시판", "익명 게시판"]
    }
  }
}

// 배열로 검색

✅ bool

  • 2가지 이상의 조건을 만족시키는 데이터를 조회하고 싶을 때
  • 여러 쿼리를 조합하기 위해서 사용하는 개념
  • 크게 4가지 기능이 존재

    must : SQL문에서의 AND 역할을 함
    filter : SQL문에서의 AND 역할을 함
    must_not : SQL문에서의 NOT 역할을 함
    should : 조건을 만족하면 좋고, 아니면 말고

GET /boards/_search
{
  "query": {
    "bool": {
      "filter": [
        {
		      "term": {
		        "category": "자유 게시판"
		      }
		    },
		    {  
		      "term": {
  		      "board_id": 1
  		    }
  		  }
  		]
    }
  }
}

GET /boards/_search
{
  "query": {
    "bool": {
      "must": [
        {
		      "term": {
		        "category": "자유 게시판"
		      }
		    },
		    {  
		      "term": {
  		      "board_id": 1
  		    }
  		  }
  		]
    }
  }
}
// 잘못된 must 쿼리 사용

✅ filter vs must

  • 검색을 하면 관련도가 얼마나 높은지에 따라 score(점수)라는 값이 존재한다.
  • filter는 이 score에 영향을 주지 않는다.
  • must는 score가 활용되는 대표적인 쿼리로 score를 활용해 관련도가 높은 document를 우선적으로 조회한다.

filter와 must의 사용 용도는 score에 영향을 주는 쿼리인지 아닌지로 구분해서 사용해야 한다.
filter : score와 상관없는 쿼리 -> term
must : score와 상관있는 쿼리 -> match

점수(score)를 활용하는 쿼리는 text 타입을 기반으로 검색을 유연하게 해주는 쿼리(match, match_phrase, multi_match 등) 밖에 없다. 즉, 정확하게 일치하지 않더라도 관련성이 있는 데이터까지 조회하는 쿼리에 대해서만 must를 사용하면 된다. 그 외에는 전부 filter를 쓰면 된다.

  • 유연한 검색 필요text 타입, match 쿼리 → boolmust
  • 정확한 검색 필요 (정확한 값 비교)
    → `text` 이외의 타입, `term` 쿼리 
    
    → `bool`의 `filter`
    
    
    #### ✅ bool : must_not
    - 특정 조건을 만족하지 않는 데이터를 조회하고 싶을 때
    ```
    GET /boards/_search
    {
    "query": {
    "bool": {
    "must": [
    { "match": { "title": "검색엔진" } } // 유연한 검색이 필요 (must)
    ],
    "filter": [
    { "term": { "is_notice": false } } // 정확한 검색이 필요 (filter)
    ],
    "must_not": [
    { "term": { "category": "광고 게시판" } }
    ]
    }
    }
    }
    ```

    ✅ bool : should

    • 특정 조건을 만족하는 데이터 위주로 상위 노출 시키고 싶을 때
    • 조건을 만족하지 않는 데이터도 조회되기도 한다.
    • 다만, should의 조건을 만족시키는 데이터는 가산점을 부여해 상위 데이터에 노출될 가능성이 높다.
    • 무조건이라는 느낌 보다는 있으면 좋고 아니면 말고의 느낌이 강하다.
      GET /products/_search
      {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "name": "무선 이어폰" 
              }
            }
          ],
          "should": [
            {
              "range": {
                "rating": {
                  "gte": 4.5 // 4.5 이상의 평점의 상품일 경우 score에 가산점 부여
                }
              }
            },
            {
              "range": {
                "likes": {
                  "gte": 100 // 좋아요 수가 100개 이상인 상품일 경우 score에 가산점 부여
                }
              }
            }
          ]
        }
      }
      }
      // 결과
      {
      "took": 6,
      "timed_out": false,
      "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": {
          "value": 3,
          "relation": "eq"
        },
        "max_score": 2.5864,
        "hits": [
          {
            "_index": "products",
            "_id": "0E-AlZYBWhYNXJPWlr56",
            "_score": 2.5864,
            "_source": {
              "name": "갤럭시 버즈2 무선 이어폰",
              "rating": 4.8,
              "likes": 310
            }
          },
          {
            "_index": "products",
            "_id": "zk-AlZYBWhYNXJPWjb70",
            "_score": 2.1297402,
            "_source": {
              "name": "무선 충전기 C타입",
              "rating": 4.9,
              "likes": 300
            }
          },
          {
            "_index": "products",
            "_id": "z0-AlZYBWhYNXJPWkb6p",
            "_score": 0.6409958,
            "_source": {
              "name": "소니 무선 이어폰 WF",
              "rating": 3.8,
              "likes": 15
            }
          }
        ]
      }
      }
      ``

1번째 데이터 검색 키워드와 관련성이 높고, 평점이 높고, 좋아요 수도 높아 상위에 노출
2번째 데이터 검색 키워드와 관련성이 조금 낮지만 평점과 좋아요가 높아 3번째 데이터보다 상위에 노출
3번째 데이터 검색 키워드와 관련성이 높지만, 평점과 좋아요 수가 낮아 데이터가 제일 하위에 노출

이와 같이 score에 가산점을 주는 bool 쿼리의 should를 활용해 원하는 조건을 갖추고 있는 데이터를 상단에 노출되게 만들 수 있다.

✅ fuzziness

  • 오타가 있지만 유사한 단어를 포함 시켜 검색할 때
GET /boards/_search
{
  "query": {
    "match": {
      "title": {
        "query": "elastiksearch",
        "fuzziness": "AUTO"
      }
    }
  }
}

fuzziness : AUTO : 단어 길이에 따라 오타 허용 개수를 자동으로 설정

✅ mult-match

-여러 필드 (예 제목/내용 등) 에서 검색 키워드가 포함된 데이터를 조회할 때

GET /boards/_search
{
  "query": {
    "multi_match": {
      "query": "엘라스틱서치 적용 후기",
      "fields": ["title", "content"]
    }
  }
}
  • title이랑 content 필드에서 검색 키워드가 포함된 데이터를 조회
  • title, content 둘 다 포함되지 않는 경우는 제외
  • 검색 키워드가 데이터와 관련성을 점수 score를 매겨 데이터 조회함
    • 검색 키워드가 문서에서 자주 등장할 수록 점수가 높게 책정된다.
    • 전체 문서 중 검색어가 희귀한 검색어가 일치할 수록 점수가 높게 책정된다.
    • 필드 값의 길이가 작은데도 불구하고 키워드가 등장했다면 점수가 높게 책정된다.
  • 가중치를 활용해 검색 가능

✅ highlight

  • 검색한 키워드를 하이라이팅 처리
GET /boards/_search
{
  "query": {
    "multi_match": {
      "query": "엘라스틱서치 적용 후기",
      "fields": ["title", "content"]
    }
  },
  "highlight": {
    "fields": {
      "title": {
        "pre_tags": ["<mark>"],
        "post_tags": ["</mark>"]
      },
      "content": {
        "pre_tags": ["<b>"],
        "post_tags": ["</b>"]
      }
    }
  } 
}


{
  "took": 19,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": 3.79424,
    "hits": [
      {
        "_index": "boards",
        "_id": "2k-SmJYBWhYNXJPWbr6u",
        "_score": 3.79424,
        "_source": {
          "title": "엘라스틱서치 적용 후기",
          "content": "회사 프로젝트에 엘라스틱서치를 적용한 후기를 공유합니다."
        },
        "highlight": {
          "title": [
            "<mark>엘라스틱</mark><mark>서치</mark> <mark>적용</mark> <mark>후기</mark>"
          ],
          "content": [
            "회사 프로젝트에 <b>엘라스틱</b><b>서치</b><b>적용</b><b>후기</b>를 공유합니다."
          ]
        }
      },
      {
        "_index": "boards",
        "_id": "3E-SmJYBWhYNXJPWd740",
        "_score": 1.7749425,
        "_source": {
          "title": "검색엔진 도입 사례",
          "content": "이번 프로젝트에 엘라스틱서치를 적용한 후 많은 개선 효과가 있었습니다."
        },
        "highlight": {
          "content": [
            "이번 프로젝트에 <b>엘라스틱</b><b>서치</b><b>적용</b>한 후 많은 개선 효과가 있었습니다."
          ]
        }
      },
      {
        "_index": "boards",
        "_id": "20-SmJYBWhYNXJPWc75z",
        "_score": 1.3862942,
        "_source": {
          "title": "엘라스틱서치를 사용해보니",
          "content": "검색 엔진 도입 후 성능이 향상되었습니다."
        },
        "highlight": {
          "title": [
            "<mark>엘라스틱</mark><mark>서치</mark>를 사용해보니"
          ]
        }
      }
    ]
  }
}

// 검색 결과에 HTML 태그로 감싼 뒤 결과값을 반환.
// 이 결과값을 활용해서 웹 페이지에서 하이라이팅 처리를 하면 된다. 

✅ pagination, sorting

  • 데이터가 많으면 서버 과부화로 조회 시 필수적으로 페이지네이션이 필요
GET /boards/_search
{
  "query": {
    "match": {
      "title": "글"
    }
  },
  "size": 3,
  "from": 0
}

size : 한 페이지에 불러올 데이터 개수 (SQL문의 LIMIT과 동일)
from : 몇 번째 데이터부터 불러올 지 (SQL문의 OFFSET과 동일, 0부터 시작)
 - from = (페이지 수 - 1) x size
  • 데이터 정렬
GET /boards/_search
{
  "query": {
    "match": {
      "title": "글"
    }
  },
  "sort": [
    {
      "likes": {
        "order": "desc"
      }
    }
  ]
}

좋아요 수 기준 내림 차순 조회

✅ multi_fields

  • 하나의 필드에 keyword 타입과 text 타입을 동시에 사용하고 싶을 때
    • text 타입은 유연한 검색에 사용, keyword는 정확한 검색에 사용

✅ search_as_you_type

  • 검색어 자동완성 기능을 필요할 때 사용
  • 내부적으로 _2gram(두 개씩 묶어서), _3gram(세 개씩 묶어서) 토큰을 만드는 multi_fields 도 같이 만든다.
GET /products/_search
{
  "query": {
    "multi_match": {
      "query": "돌김", 
      "type": "bool_prefix", 
      "fields": [
        "name",
        "name._2gram",
        "name._3gram"
      ]
    }
  }
}

서버의 자동완성 API를 타이핑 할 때마다 호출하여 자동완성 데이터를 반환한다.

profile
시작은 미약하나 그 끝은 창대하리라

0개의 댓글