OpenSearch
OpenSearch의 Query DSL과 Redis의 Sorted Set 자료구조를 이용해 간단히 검색 순위를 구현해 봤는데 이전에 조사했던 다양한 순위 알고리즘이 존재하는데 일단은 알고리즘을 적용하지 않고 OpenSearch에 저장되어 있는 검색 데이터의 개수만을 이용해 검색 순위 기능을 구현해 봤다.
이번 포스팅은 검색 순위를 구현하기 전 OpenSearchClient의 Query DSL을 이용해 OpenSearch에 저장된 데이터를 가져오는 방법에 대해 알아보자.
OpenSearch에서 데이터를 가져오는 방법 중 여러가지 방법이 존재했지만 OpenSearch에 대한 레퍼런스가 부족했기 때문에 삽질을 많이 했다.
크게 java에서는 RestHighLevelClient, OpenSearchClient 두 가지를 이용하는 방법과 SpringDataOpenSearch를 이용하는 방법이 있다. 나는 OpenSearchClient를 이용했다.
OpenSearchClient를 설정하는 방법에 대해서는 아래 문서를 참고해서 설정했다.
https://opensearch.org/docs/latest/clients/java/
아래는 Query DSL을 이용해 OpenSearch의 데이터를 가져오는 쿼리를 작성한 예제이다.
하나씩 살펴보자.
Map<String, Integer> companies = new HashMap<>();
String index = "index-name";
Query rangeQuery = new Query.Builder().range(r -> r.field("age").gte(20).lte(25)).build();
Query termQuery = new Query.Builder().match(f -> f.field("name").query(v -> v.stringValue("Jun"))).build();
Query existQuery = new Query.Builder().exists(f -> f.field("isPerson")).build();
Query query = new Query.Builder().bool(m -> m.must(rangeQuery).must(termQuery)).build();
SearchRequest searchRequest = new SearchRequest.Builder()
.index(index)
.query(query)
.size(MAX_SIZE)
.build();
SearchResponse<ObjectNode> searchResponse = openSearchClient.search(searchRequest, ObjectNode.class);
우선 OpenSearch에서 데이터를 가져올 index 이름을 선언해준다.
그리고 작성할 쿼리를 작성해 주면 되는데 쿼리 종류는 아래 문서를 참고하면 된다.
https://opensearch.org/docs/latest/query-dsl/
여기서는 간단하게 term-level query와 bool 쿼리를 작성했다.
Term-level Query
term-level 쿼리는 검색할 필드와 가져올 값을 지정하면 필드의 값이 지정한 값과 완전히 일치하는 데이터를 가져온다.
예를 들어, "title" 필드에서 "apple"이라는 단어를 찾기 위해 term 쿼리를 사용할 수 있다.
반면에 full-text 쿼리도 있는데 이 쿼리는 term-level 쿼리와 다르게 부분적으로 일치하는 값을 검색한다. 주로 문서 내에서 검색할 때 사용한다고 한다. 검색어와 유사한 용어도 검색될 수 있다고 하는데 해보지는 않아서 어떻게 작동하는지는 모르겠다.
term-level query의 타입에는 여러 타입이 존재하는데 위 예제에서는 range, exists 쿼리를 사용해 봤다.
range
range 쿼리는 범위를 지정하는 쿼리인데 gte(Greater than or equal to)와 lte(Less than or equal to) 메서드를 이용해 범위를 지정할 수 있다.
exists
exists 쿼리는 필드의 값이 boolean 필드를 지정하면 해당 필드의 값이 ture인지 false인지 검색할 수 있다.
Compound queries
쿼리를 중첩시켜 동작시키고 싶을 때는 compound 쿼리를 사용하면 된다.
boolean, boosting 등등 여러 타입의 쿼리가 존재하는데 여기서는 간단하게 boolean 쿼리를 사용해 봤다. boolean query의 must를 이용해 and 연산자 역할을 하도록 했다.
방금 작성한 rangeQuery와 termQuery를 중첩시켜 두 조건을 모두 만족시키는 데이터를 가져온다.
https://opensearch.org/docs/latest/query-dsl/compound/bool/
위 문서를 보면 and, not, or 연산자에 대한 메서드도 설명되어 있으니 필요하면 찾아보면 될 것 같다.
이제 작성한 쿼리를 OpenSearch에 요청을 보낼 때 같이 보내야 하는데 SearchRequest의 Builder를 이용해서 요청할 수 있다.
요청 시 size() 메서드를 이용해 가져올 데이터의 개수도 지정해 줄 수 있다. 사이즈를 지정하지 않을 경우 기본 값이 10개이기 때문에 더 많은 데이터를 가져오기 위해서는 size를 지정해줘야 한다. 최대로 가져올 수 있는 데이터의 개수는 10,000개이다.
OpenSearchClient의 search 메서드를 이용해 OpenSearch에 요청을 보낼 수 있다. 요청 결과는 SearchResponse를 이용해 객체 형태로 받아올 수 있다.
아래 코드는 OpenSearch에서 가져온 데이터의 개수를 hashMap에 저장하는 코드이다.
쿼리를 날려 가져온 OpenSearch response의 source 필드 안에서 우리가 원하는 name, age 같은 필드 값을 가져올 수 있다.
그리고 순위 점수를 위해 가져온 데이터의 개수만큼 +1을 HashMap에 이름과 같이 저장해 준다.
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < searchResponse.hits().hits().size(); i++) {
String name = searchResponse.hits().hits().get(i).source().get("name").toString();
map.put(name, map.getOrDefault(name, 0) + 1);
}
다음 포스팅에서는 순위 기능 구현을 위한 Redis sorted set자료구조에 대해 알아보자.
Reference
'Database' 카테고리의 다른 글
[E-commerce] 쿼리 분석 및 인덱스 설계를 통한 성능 개선 (0) | 2024.05.10 |
---|---|
[E-commerce] 동시성 문제 해결하기 (비관적 락, 네임드 락, 분산 락) (0) | 2024.05.01 |
함수적 종속성 (FD, Functional Dependency) (0) | 2024.04.14 |
[Redis] Redis 캐시(Cache)를 적용해 조회 성능 개선하기 (0) | 2023.02.16 |
[Redis] SpringBoot Redis 적용기 (1) | 2023.02.15 |