본문 바로가기
project/Photogram

Photogram 태그 기반 게시글 검색 기능

by setung 2025. 4. 13.

Post & Tag ERD

Post는 N개의 태그를 가질 수 있도록 설계를 하였다.

tag는 중복된 이름이 많을 것을 대비해, 기존의 태그를 사용할 수 있도록 post와 tag 사이 post_tag라는 중간 테이블을 추가하였다.

 

 

RDB 기반 태그 검색의 장점과 한계

실제 테스트를 위해 약 100만 건의 데이터를 대상으로 RDB와 Elasticsearch의 검색 속도를 비교해본 결과, RDB 쪽이 더 빠른 성능을 보였다.
(물론 이는 내가 아직 Elasticsearch를 완전히 최적화하지 못했기 때문일 수도 있다.)

하지만, 다음과 같은 상황에서는 RDB 방식에 한계가 있었다.

  • 부분 검색 (LIKE '%태그%')
    → 인덱스를 활용하지 못해 검색 속도가 급격히 느려짐
  • 철자가 조금씩 다른 경우
    → 예: "운!동", "운 동", "운동기록" 등
  • 연관 태그 검색
    → 예: "운동"과 함께 자주 쓰이는 "헬스", "다이어트" 등의 태그를 함께 보여주고 싶을 때 RDB로는 구현이 어려움

 

Elasticsearch 도입

Post-service

  • 게시글 업로드, 업데이트, 삭제 요청을 처리하는 핵심 서비스
  • 처리된 요청을 기반으로 Kafka에 이벤트 발행

Kafka

  • 게시글 관련 이벤트를 비동기적으로 전파하는 메시지 브로커

 

Post-Search-service

  • Kafka로부터 수신한 이벤트에 따라 Elasticsearch Document를 생성/수정/삭제하여 검색 인덱스를 유지함
  • 태그 기반 검색 요청을 처리하고, Elasticsearch에서 검색한 게시글 목록을 클라이언트에 반환

 

 

photogram_index & mapping

{
  "settings": {
    "index": {
      "max_ngram_diff": 10
    },
    "analysis": {
      "tokenizer": {
        "ngram_tokenizer": {
          "type": "ngram",
          "min_gram": 2,
          "max_gram": 10,
          "token_chars": [
            "letter",
            "digit",
            "whitespace",
            "punctuation",
            "symbol"
          ]
        }
      },
      "analyzer": {
        "ngram_analyzer": {
          "type": "custom",
          "tokenizer": "ngram_tokenizer",
          "filter": [
            "lowercase"
          ]
        },
        "fuzzy_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "asciifolding"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "thumbnailUrl": {
        "type": "keyword"
      },
      "isVisible": {
        "type": "boolean"
      },
      "writerId": {
        "type": "keyword"
      },
      "tags": {
        "type": "text",
        "analyzer": "ngram_analyzer",
        "search_analyzer": "ngram_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "fuzzy": {
            "type": "text",
            "analyzer": "fuzzy_analyzer"
          }
        }
      }
    }
  }
}

tags 필드 매핑

  • 기본 필드
    • n_gram analyzer -> 부분 검색 지원용
  • tags.keyword
    • 정확한 일치 검색용
  • tags.fuzzy
    • fuzzy_analyzer -> 오타 대응용

 

Spring boot data elasticsearch repository 검색 구현

class PostDocCustomRepositoryImpl(
    private val elasticsearchClient: ElasticsearchClient
) : PostDocCustomRepository {

    override fun searchByTags(keyword: String, lastPostId: Long, size: Int): SearchResponse<PostDocument> {
        val exactMatch = TermQuery.Builder()
            .field("tags.keyword")
            .value(keyword)
            .boost(3.0f)
            .build()._toQuery()

        val fuzzyMatch = MatchQuery.Builder()
            .field("tags.fuzzy")
            .query(keyword)
            .fuzziness("AUTO")
            .build()._toQuery()

        val ngramMatch = MatchQuery.Builder()
            .field("tags")
            .query(keyword)
            .build()._toQuery()

        val visibilityFilter = TermQuery.Builder()
            .field("isVisible")
            .value(true)
            .build()._toQuery()

        val boolQuery = BoolQuery.Builder()
            .should(listOf(exactMatch, fuzzyMatch, ngramMatch))
            .minimumShouldMatch("1")
            .filter(listOf(visibilityFilter))
            .build()._toQuery()

        val request = SearchRequest.Builder()
            .index("photogram_posts")
            .query(boolQuery)
            .size(size)
            .sort { s ->
                s.field { f -> f.field("id").order(SortOrder.Desc) }
            }
            .searchAfter(lastPostId)
            .build()

        return elasticsearchClient.search(request, PostDocument::class.java)
    }
}

 

 

https://github.com/setung/photogram

'project > Photogram' 카테고리의 다른 글

[Photogram] Redis + Resilience4j로 MSA 장애 전파 막기  (0) 2025.04.23
Photogram Push 기반 피드 구현  (0) 2025.04.14
Photogram 인증 구현하기  (1) 2025.04.13
Photogram 아키텍처  (0) 2025.04.13

댓글