검색 엔진의 성능 개선 (전적 통계 시스템)
elasticsearch로 테스트 하고 있지만, 배포시 AWS Opensearch로 구동할려고 계획중
형태소 분석이나 동의어 설정같은 tokenizer가 필요 없다고 판단했다.
사용자의 이름은 규칙성이 없으며 챔피언 종류별 통계는 리그오브레전드도 130개 남짓이다.
- 검색 쿼리 개선
- minimum_should_match=100%
- match, term 함께 사용(term은 analyzer를 거치지 않아서 더 빠름)
- ElasticSearch Configuration 튜닝: thread_count 설정
- 인덱싱 설계
- user_name : keyword
- champion : 자주 검색되면 keyword
- items : 어떤 아이템을 사용했을때 승률이 가장 높았는지?
- 사용자의 닉네임과 정확히 일치하는 경우에만 검색되야하므로 match가 아닌 term 사용
ElasticSearch의 분석기(Analyzer)를 통한 부분검색
엘라스틱 서치가 Full Text Search에 유리하기 때문에, 완전 만능으로 생각하는 경우가 많은데 조심해야한다.
데이터 타입 keyword와 text간의 비교
- keyword: keyword 타입은 완전 일치 검색을 위해 사용
- 토큰화(tokenization)를 하지 않고 전체 문자열을 그대로 저장해서 정확히 입력한 값만 찾는다
- keyword 타입은 정확히 일치하는 값만 반환하고, 텍스트 분석 없이 검색할 때 유용
- text: text 타입은 분석(analysed)을 위해 사용
- Elasticsearch의 standard analyzer를 사용하여 띄어쓰기를 기준으로 토큰화
- 텍스트 내용의 일부라도 일치하면 검색 가능
와일드카드 검색
간단하게 검색할때는 매핑 정의에서 와일드 카드('%문자열%')를 이용할 수 있다.
keyword 타입은 완전 일치만 가능하기에, text 필드를 이용해서 모든 컨텍스트를 확인하고 내가 원하는 값을 찾는다.
keyword : “남석”로 검색하는건 RDB와 같으니 굳이 엘라스틱을 사용할 이유도 없고 매칭 범위가 커져서 성능이 저하된다.
N-gram
Elasticsearch는 데이터를 쓰기/읽기 작업시 분석기(analyzer)를 통해 분석하는 과정을 갖는다. (토큰화→가공)
- 1-gram (unigram): ["e", "l", "a", "s", "t", "i", "c"]
- 2-gram (bigram): ["el", "la", "as", "st", "ti", "ic"]
- 3-gram (trigram): ["ela", "las", "ast", "sti", "tic"]
문자열을 갯수만큼 나눠서 별도로 역색인(reversed Index)를 만들기 때문에 바로 찾을 수 있다.
속도가 빠른만큼 disk를 더 많이 사용하지만 말이다.
Elasticsearch에서는 N-gram과 Edge N-gram을 분석기(Analyzer) 를 통해 지원한다
Edge N-gram 비교
방식 설명 예시 ("apple")
| N-gram | 전체 단어에서 N개의 연속된 문자 조합을 생성 | ["ap", "pp", "pl", "le"] |
| Edge N-gram | 단어의 앞부분을 기준으로 조합을 생성 (검색 자동완성용) | ["a", "ap", "app", "appl", "apple"] |
성능이 부담되는 N-gram, 입력값의 앞부분만 분석하는 Edge N-gram
Completion Suggester
빠른 자동완성을 구현하기 위해 만들어진 Completion 방식을 채택했다.
Completion 타입을 이용하는 경우, 메모리에 저장되어서 빠르지만 동적 업데이트가 어려움
꼭 자동완성에 대한 별도의 문서를 만들 필요는 없어서, 검색 성능과 유연성을 고려해서 분리할지 고민중
속도가 중요하다면 별도의 인덱스를 사용하는게 맞는듯?
대규모 트래픽을 처리하는 서비스라면 별도 인덱스를 만들고 배치 업데이트하는 것이 일반적이다.
전적 서비스에서 검색시 자동완성 기능을 구현해보자
요구사항
첫 부분부터 일치하는 단어나 완전 일치하는 키워드를 검색 결과에 상위 표출한다.
가장 먼저 자동완성 기능을 구현하려면, GameFinishedEvent가 발생할 때 ClientRequest의 user_name을 Player(document)에 저장하는 작업을 해야한다.
이를 위해 saveResult 메서드 내에서 Player를 저장하는 로직을 추가하고, Player 객체를 Elasticsearch에 자동으로 인덱싱하도록 설정해야한다.
자동완성 인덱스 생성
PUT autocomplete_index
{
"settings": {
"index": {
"number_of_shards": 1,
"number_of_replicas": 0
}
},
"mappings": {
"properties": {
"suggest": {
"type": "completion"
}
}
}
}
Player 엔티티(autocomplete_index)
@Getter
@Builder
@Document(indexName = "autocomplete_index")
public class Player {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "simple", searchAnalyzer = "simple")
private String nickname;
@Field(type = FieldType.Text, analyzer = "simple", searchAnalyzer = "simple")
private Completion suggest;
}
하지만 Spring 자체에서 completion 필드는 지원하지 않아서 다음과 같이 명시했다.
실제 비즈니스 로직인 AutoCompletePlayerPort의 구현체
@Override
public Flux<String> getAutoCompleteSuggestions(String query) {
String redisKey = "autocomplete:names:" + query;
return findRedisPort.findString(redisKey)
.switchIfEmpty(
playerRepository.findByNicknameStartingWith(query)
.map(Player::getNickname)
.collectList()
.flatMapMany(nicknames -> {
if (!nicknames.isEmpty()) {
return pushRedisPort.pushString(redisKey, nicknames)
.thenMany(Flux.fromIterable(nicknames));
}
return Flux.empty();
})
);
}
게임 종료시 전적을 업데이트할때, ElasticSearch의 autocomplete 문서에 없는 사용자 닉네임인 경우 새로 등록하고 캐시된 내용을 지우는 전략을 적용하고 있다.
그래서 위처럼 입력값에 대해 자동완성한 내용을 캐싱하는 로직이 나올 수 있다.
전적 검색 서비스의 프론트엔드 시연
프론트엔드는 할줄도 모르고 귀찮아서 바닐라로 진행했다.(thymeleaf)


간단하게 입력창에 단어를 입력하면 자동완성한 결과를 표시하도록 했다.
아. 지금은 입력창에서 autocomplete=false 설정해서 옆의 'redisson'은 안뜬다...

잠깐 전적 검색 서비스를 꾸미고 싶은 욕심이 생겼는데 겨우 참았다.
더미 데이터라 한명씩 갖고 있고, 아직 디자인을 꾸미지 않아서 그냥 json 파일을 그대로 텍스트로 출력하고 있다.
나중에 아이템 종류나 챔피언 종류에 따른 아이콘 이미지를 설정하고 실제 게임 전적 데이터를 가져오도록 하면 이쁘게 전적을 표시할 수 있다.
추가 - 사실 좀 꾸몄다.....ㅎ
못참고 해당 사용자의 통계 정보도 위에 표시하고 이쁘게 나오도록 했다.
참고로 난 프론트 개발자도 아니고 할줄도 모른다.

기능을 더 개선하기 전까지는 여기서 더 꾸미지 않겠다고 약속하마
자동완성시 동일한 결과값이 n개 이상인 경우 가장 검색 빈도수가 많거나 가장 높은 티어의 사용자가 우선적으로 나오게 해야한다.
이 부분도 추후 개선하겠다
'project > wargame' 카테고리의 다른 글
| [꿀팁대방출] Kafka Topic 재설계와 EDA 도입을 통한 획기적인 클라우드 비용 개선 (3) | 2025.07.12 |
|---|---|
| 전적 검색 서비스 배포 및 문제 해결 (feat. Redis 도입 논의, 이벤트 소싱에 대한 오해) (1) | 2025.06.21 |
| 게임종료시 전적 갱신에 대한 트랜잭션 롤백 처리 (Saga 패턴, Axon Framework) (0) | 2025.03.16 |
| 데이터에 기반한 사용자 행위 분석 - 어떤 데이터를 수집해야할까? (0) | 2025.03.12 |
| Hexagonal Architecture 마이그레이션 (0) | 2025.02.11 |