<aside> 📌
검색 시스템은 Elasticsearch로 구동되는 고성능 풀텍스트 검색 엔진을 제공합니다. 사용자는 한국어 형태소 분석, 고급 필터링(장르, 지역), 커스터마이즈 가능한 정렬(정확도, 인기도)을 통해 밴드, 공연, 게시물을 탐색할 수 있습니다. 시스템은 이벤트 기반 재인덱싱 파이프라인을 통해 관계형 데이터베이스와 검색 인덱스 간의 데이터 정합성을 유지합니다.
| Document | Index Name | 주요 검색 필드 |
|---|---|---|
| BandDocument | bands | name, description, genre, region |
| PerformanceDocument | performances | title, bandName, venue, description |
| PostDocument | posts | title, bandName, tags, description |
SearchService는 데이터 조회를 위한 ElasticsearchOperations와 이력 관리를 위한 RecentSearchService를 연결하는 주요 조율자 역할을 하며, NativeQuery를 사용하여 복잡한 Elasticsearch DSL을 구성합니다.
| 정렬 모드 | 정렬 기준 |
|---|---|
| ACCURACY | _score(내림차순) → date(내림차순) → docId(내림차순) |
| POPULAR | popularity(내림차순) → _score(내림차순) → docId(내림차순) |
POPULAR의 popularity 정의는 밴드는 팔로워 수, 공연은 관심 표시 수, 게시물은 소속 밴드의 팔로워 수입니다(SearchSortType.java 5~6번째 줄).
단일 타입 검색의 경우 search_after 페이지네이션을 사용합니다. API를 무상태로 유지하고 내부 구조를 불투명하게 감추기 위해, SearchCursor 유틸리티는 마지막 문서의 정렬 값들을 Base64URL 문자열로 인코딩합니다.
RecentSearchService는 FanModeRecentSearch 엔티티에 저장된 사용자 검색어 이력을 관리합니다.
| Field | ALL 모드 | 단일 타입 모드 |
|---|---|---|
| totalCount | 모든 섹션의 합계 | 특정 타입의 개수 |
| bands / performances / posts | 각각 최대 3건 | 페이지네이션된 목록 |
| nextCursor | null | 인코딩된 search_after 토큰 |
| hasNext | null | 추가 결과가 있으면 true |
flowchart LR
A["도메인 변경"] --> B["ApplicationEventPublisher\n(BandChangedEvent 등)"]
B --> C["SearchIndexEventListener"]
C --> D["SearchIndexReader\n(읽기 전용 트랜잭션)"]
D --> E["SearchIndexService\n(upsert / delete)"]
E --> F["Elasticsearch"]
이러한 단계 분리는 느린 Elasticsearch HTTP 호출이 DB 커넥션을 계속 점유하지 않도록 보장합니다.
| Port | 구현체 | 책임 |
|---|---|---|
| search.port.PerformancePort | performance.adapter.SearchAdapter | 밴드 조인과 관심 표시 수를 포함하여 활성 공연을 조회합니다. |
| search.port.PostPort | post.adapter.SearchAdapter | 밴드 및 태그 조인을 포함하여 게시물을 조회합니다. |
| search.port.FollowPort | follow.adapter.SearchAdapter | 인기도 스코어링을 위해 팔로워 수를 집계합니다. |