티스토리 뷰
Elasticsearch(엘라스틱서치)는 강력한 검색 및 분석 엔진으로, 복잡한 검색 요구사항을 충족시키기 위해 다양한 쿼리 DSL(Domain Specific Language)을 제공합니다. 그중에서도 **Bool Query(불 쿼리)**는 여러 쿼리 조건을 조합하여 정교한 검색 로직을 구현하는 데 핵심적인 역할을 합니다. 마치 SQL의 AND, OR, NOT과 유사하지만, Elasticsearch만의 특징과 유연성을 더했습니다.
이 글에서는 Bool Query의 구조와 사용법, 그리고 하위에 포함될 수 있는 다양한 쿼리 유형에 대해 깊이 있게 알아보고, 실제 Kotlin 코드 예제를 통해 어떻게 활용할 수 있는지 살펴보겠습니다.
Bool Query란 무엇인가?
Bool Query는 이름에서 알 수 있듯이, 불리언 로직(Boolean logic)을 사용하여 여러 개의 리프(leaf) 쿼리 절 또는 복합(compound) 쿼리 절을 조합하는 쿼리입니다. 즉, 여러 조건을 '모두 만족'하거나, '하나 이상 만족'하거나, '만족하지 않아야' 하는 등의 복잡한 검색 시나리오를 구현할 수 있게 해줍니다.
Bool Query의 구조
Bool Query는 기본적으로 bool 키워드 아래에 다음과 같은 네 가지 주요 절(clause)을 가집니다.
{
"query": {
"bool": {
"must": [ /* 쿼리 조건들 */ ],
"filter": [ /* 쿼리 조건들 */ ],
"should": [ /* 쿼리 조건들 */ ],
"must_not": [ /* 쿼리 조건들 */ ],
"minimum_should_match": 1 // should 절이 있을 경우, 최소 몇 개를 만족해야 하는지 지정 (기본값: 1)
}
}
}
각 절의 역할은 다음과 같습니다.
- must: 여기에 포함된 모든 쿼리 조건을 반드시 만족해야 합니다. 결과 문서의 스코어(_score) 계산에 영향을 줍니다. SQL의 AND와 유사합니다.
- filter: 여기에 포함된 모든 쿼리 조건을 반드시 만족해야 합니다. 하지만 must와 달리 스코어 계산에 영향을 주지 않습니다. 성능상 이점이 있어 스코어링이 필요 없는 조건(예: 특정 기간, 특정 카테고리 필터링)에 주로 사용됩니다. 캐싱될 가능성이 높아 반복적인 필터링에 효율적입니다.
- should: 여기에 포함된 쿼리 조건 중 하나 이상을 만족하면 좋습니다. must나 filter 절이 없는 경우, should 절의 조건 중 최소 하나는 만족해야 문서가 반환됩니다 (minimum_should_match 기본값 1). must나 filter 절이 있다면, should 절은 스코어링에만 영향을 미치는 선택적 조건이 됩니다. SQL의 OR와 유사하며, 만족하는 조건이 많을수록 스코어가 높아집니다.
- must_not: 여기에 포함된 쿼리 조건들을 절대로 만족해서는 안 됩니다. filter 절과 마찬가지로 스코어 계산에 영향을 주지 않습니다. SQL의 NOT과 유사합니다.
- minimum_should_match: should 절에 여러 쿼리가 있을 때, 결과 문서가 되기 위해 최소한 만족해야 하는 should 조건의 개수 또는 비율을 지정합니다. (예: "2", "75%")
각 절의 사용법과 차이점
- must vs filter: 둘 다 'AND' 조건이지만, 스코어링 여부가 가장 큰 차이입니다. 검색 결과의 관련도(스코어)에 영향을 주어야 하는 조건은 must에, 단순 필터링 목적(예: '상태가 active인 문서만')은 filter에 넣는 것이 일반적이고 효율적입니다. filter는 캐싱 활용도가 높아 성능에 유리합니다.
- should: 'OR' 조건이나 관련도 부스팅에 사용됩니다. 단독으로 사용될 때는 최소 1개 이상 매칭되어야 하지만, must나 filter와 함께 사용되면 선택적 조건이 되어 스코어만 높이는 역할을 합니다. minimum_should_match 파라미터를 통해 '최소 N개 매칭' 조건을 조절할 수 있습니다.
- must_not: 특정 조건을 제외할 때 사용합니다. 스코어링 없이 제외하므로 성능에 유리합니다.
Bool Query 하위에 올 수 있는 쿼리들
Bool Query의 각 절 (must, filter, should, must_not) 안에는 거의 모든 종류의 Elasticsearch 쿼리가 중첩될 수 있습니다. 대표적인 예시는 다음과 같습니다.
- Full-text queries (전문 검색 쿼리):
- match: 표준 분석기를 사용한 텍스트 검색 (가장 일반적)
- match_phrase: 구(phrase) 단위 검색
- multi_match: 여러 필드를 대상으로 match 쿼리 실행
- query_string: Lucene 쿼리 구문을 사용한 복잡한 텍스트 검색
- Term-level queries (텀 레벨 쿼리):
- term: 정확히 일치하는 텀(분석되지 않은 값) 검색 (주로 keyword 타입 필드에 사용)
- terms: 여러 개의 텀 중 하나라도 일치하는 경우 검색
- range: 특정 범위 내의 값 검색 (숫자, 날짜 등)
- exists: 특정 필드가 존재하는 문서 검색
- prefix: 특정 접두사로 시작하는 텀 검색
- wildcard: 와일드카드(*, ?)를 사용한 패턴 매칭 검색
- regexp: 정규 표현식을 사용한 패턴 매칭 검색
- Compound queries (복합 쿼리):
- bool: 또 다른 Bool Query를 중첩하여 더 복잡한 논리 구조 생성
- boosting: positive 쿼리의 스코어는 유지하면서 negative 쿼리에 매칭되는 문서의 스코어를 낮춤
- constant_score: 내부 쿼리와 매칭되는 모든 문서에 동일한 고정 스코어를 부여 (주로 filter와 함께 사용)
- Joining queries (조인 쿼리):
- nested: 중첩된(nested) 타입의 필드 내부를 검색
- has_child, has_parent: 부모/자식 관계 문서를 검색
이 외에도 다양한 쿼리들이 Bool Query 내부에 사용될 수 있습니다.
Kotlin 코드 예제 (Elasticsearch High Level REST Client 사용)
다음은 Elasticsearch의 공식 Java High Level REST Client를 Kotlin에서 사용하는 예제입니다. (라이브러리 의존성 설정이 필요합니다.)
import org.elasticsearch.action.search.SearchRequest
import org.elasticsearch.action.search.SearchResponse
import org.elasticsearch.client.RequestOptions
import org.elasticsearch.client.RestHighLevelClient
import org.elasticsearch.index.query.BoolQueryBuilder
import org.elasticsearch.index.query.QueryBuilders.* // 정적 임포트로 QueryBuilders 사용 간편화
import org.elasticsearch.search.builder.SearchSourceBuilder
// RestHighLevelClient 인스턴스가 초기화되어 있다고 가정 (client: RestHighLevelClient)
fun searchDocuments(client: RestHighLevelClient): SearchResponse {
val indexName = "my_index" // 검색할 인덱스 이름
// Bool Query 생성 시작
val boolQuery = boolQuery()
// 1. must 조건: title 필드에 "elasticsearch" 포함하고, status 필드는 "published" 여야 함
boolQuery.must(matchQuery("title", "elasticsearch"))
boolQuery.must(termQuery("status", "published")) // termQuery는 분석되지 않은 정확한 값 매칭
// 2. filter 조건: creation_date 필드가 2024년 이후여야 함 (스코어링 불필요)
boolQuery.filter(rangeQuery("creation_date").gte("2024-01-01"))
// 3. should 조건: tags 필드에 "kotlin" 또는 "java"가 포함되면 스코어 상승
boolQuery.should(termQuery("tags", "kotlin"))
boolQuery.should(termQuery("tags", "java"))
// must/filter가 있으므로 should는 선택 사항, minimum_should_match 기본값은 1이지만 여기선 없어도 됨
// 만약 should 조건 중 최소 1개는 반드시 만족해야 한다면 아래 주석 해제
// boolQuery.minimumShouldMatch(1)
// 4. must_not 조건: category 필드가 "deprecated"가 아니어야 함
boolQuery.mustNot(termQuery("category", "deprecated"))
// SearchSourceBuilder 생성 및 쿼리 설정
val sourceBuilder = SearchSourceBuilder()
sourceBuilder.query(boolQuery)
sourceBuilder.from(0) // 페이징 시작 위치
sourceBuilder.size(10) // 가져올 문서 개수
// SearchRequest 생성
val searchRequest = SearchRequest(indexName)
searchRequest.source(sourceBuilder)
// 검색 실행
println("Executing Bool Query:")
println(sourceBuilder.toString()) // 생성된 쿼리 확인 (JSON 형태)
return client.search(searchRequest, RequestOptions.DEFAULT)
}
// --- main 함수 등에서 호출 ---
// val response = searchDocuments(client)
// response.hits.hits.forEach { hit ->
// println("Found document: ${hit.id} with score ${hit.score}")
// println(hit.sourceAsString)
// }
예제 설명:
- QueryBuilders.*를 정적 임포트하여 boolQuery(), matchQuery(), termQuery(), rangeQuery() 등을 바로 사용할 수 있습니다.
- BoolQueryBuilder 객체를 생성하고, .must(), .filter(), .should(), .mustNot() 메서드를 체이닝하여 각 절에 쿼리를 추가합니다.
- matchQuery는 텍스트 분석을 거쳐 검색하고, termQuery는 분석되지 않은 정확한 값을 검색합니다. 필드 타입(text, keyword 등)에 따라 적절한 쿼리를 선택해야 합니다.
- rangeQuery를 사용하여 날짜 범위를 필터링합니다.
- SearchSourceBuilder에 완성된 boolQuery를 설정하고, 페이징(from, size) 등을 지정합니다.
- SearchRequest를 생성하여 인덱스 이름과 sourceBuilder를 설정한 후, client.search()를 호출하여 검색을 실행합니다.
결론
Elasticsearch의 Bool Query는 여러 검색 조건을 유연하게 조합하여 복잡한 요구사항을 해결할 수 있는 강력한 도구입니다. must, filter, should, must_not 각 절의 역할과 차이점, 특히 filter의 스코어링 제외 및 캐싱 이점을 이해하는 것이 중요합니다. 다양한 리프 쿼리와 복합 쿼리를 Bool Query 내부에 중첩하여 사용함으로써, 사용자가 원하는 정확한 결과를 효율적으로 찾아낼 수 있습니다.
이 글에서 소개된 내용과 Kotlin 예제를 바탕으로 Elasticsearch Bool Query를 더욱 효과적으로 활용하여 검색 시스템의 성능과 정확도를 높여보시길 바랍니다.
'검색 시스템' 카테고리의 다른 글
엘라스틱서치 아키텍처 및 기본 구성 가이드 (2) | 2025.05.16 |
---|---|
elasticsearch 입문 (2) | 2025.03.05 |
- Total
- Today
- Yesterday
- cqrs
- 코프링
- springai
- RESTfull
- 언리얼엔진5
- 스브링부트
- 카프카 개념
- 스프링부트
- method Area
- generated_body()
- 코틀린
- 언리얼엔진
- Stack Area
- 디자인패턴
- 타입 안전성
- JVM
- 일급 객체
- react.js
- MCP
- model context protocol
- vite
- Heap Area
- First-class citizen
- 자바
- JAVA 프로그래밍
- Java
- unreal engjin
- ai통합
- 도커
- redis
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |