SH380 Logo
2025-08-30

검색 성능 개선을 위한 Elasticsearch 인덱스 구조와 쿼리 최적화

#Elasticsearch#infra

elasticsearch-logo

출처 : https://techblog.woowahan.com/20161/

대형 셀러의 지속적인 추가 → 색인 문서의 양의 약 3배 증가

검색 API에 다양한 필터와 검색어 매칭 필드가 추가

리스팅 API를 새롭게 제공하면서 검색 및 리스팅 API 호출수 약 1.5배 증가

-> 서버가 처리해야 할 기능과 요청량이 급격히 증가. 성능 개선 필요

-> 검색 API의 레이턴시를 감소시키고 ES 인덱스 구조 최적화 및 쿼리 최적화

1. 현상

2. 문제 원인

3. 해결 방법

"categoryId": {
  "type": "integer",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}
{
  "term": {
    "categoryId.keyword": 1000
  }
}

4. 개선 결과

구분 개선 전 개선 후

categoryId 필드 타입integerinteger + keyword 서브필드
쿼리term on integerterm on keyword
API 응답 시간980ms104ms

5. 핵심 포인트

1. 현상

2. 문제 원인

"function_score": {
  "query": { "match_all": {} },
  "functions": [ ... ]
}

3. 해결 방법

"function_score": {
  "query": {
    "bool": {
      "filter": [
        상품상태 및 키워드 매칭 조건
      ]
    }
  },
  "functions": [ ... ]
}

4. 개선 결과

5. 핵심 포인트

1. 현상

2. 문제 원인

3. 해결 방법

3-1. Keyword Matching Score

{
  "bool": {
    "should": [
      { "constant_score": { "filter": { productName 조건 } }, "boost": 10 * 보정연산 },
      { "constant_score": { "filter": { sellerName 조건 } }, "boost": 20 * 보정연산 },
      { "constant_score": { "filter": { shopName 조건 } }, "boost": 30 * 보정연산 }
    ]
  }
}

3-2. CTR Score

{
  "woowa_payload_score": {
    "query": { "span_term": { "ctrScore": { "value": "검색키워드" } } },
    "score_mode": "max",
    "decode_type": "float",
    "include_span_score": false}
}

3-3. Recommend Score

{
  "function_score": {
    "query": { "term": { "recommendScore.enable": { "value": true } } },
    "functions": [
      { "filter": { "match_all": {} },
        "field_value_factor": { "field": "recommendScore", "factor": 1, "missing": 0 } }
    ],
    "score_mode": "sum",
    "boost_mode": "replace"
  }
}

3-4. 최종 구조

4. 개선 결과

5. 핵심 포인트

1. 현상

2. 문제 원인

3. 해결 방법

1. track_scores 옵션 변경

"track_scores": false

2. 분석되는 term 개수에 따른 쿼리 분기

if (tokens.size() == 1) {
  return QueryBuilders.matchQuery(fieldName, keyword);
} else {
  return QueryBuilders.matchPhraseQuery(fieldName, keyword).slop(0);
}

3. analyzer 라이브러리화

4. 개선 결과

구분 개선 전 개선 후

track_scorestruefalse
검색 쿼리모든 term → match_phraseterm 개수 분기 적용 (match / match_phrase)
analyze API외부 호출내부 라이브러리 호출
레이턴시p99.9/p99.99 높음2배 개선
슬로우 쿼리존재0.7초 이상 모두 제거
ES 데이터노드 CPU피크 기준 20%~85%10~13% 감소
analyze rejected/timeout발생제거

5. 핵심 포인트

1. 카테고리 필터categoryId로 필터 시 레이턴시 지연integer 타입, term 쿼리 사용keyword 타입, categoryId.keyword로 term 쿼리API 응답 980ms → 104ms, 슬로우 쿼리 제거
2. 포켓몬 키워드특정 키워드(function_score) 부스팅 시 전체 문서 연산match_all + function_scorefunction_score query filter에 기존 filter 포함슬로우 쿼리 사라짐, 쿼리 가독성 향상
3. Painless 스크립트aggregation top_hits 정렬 시 스크립트 부하_score 기반 Painless 스크립트 사용keywordMatchingScore, ctrScore, recommendScore를 쿼리 단계에서 미리 계산/반영aggregation 속도 2배 향상, p99.9/p99.99 응답 20% 개선
4. track_scores & term 최적화스코어 계산, 일부 키워드에서 슬로우 쿼리track_scores: true, match_phrase 무조건 적용track_scores: false, 단일 term → match, 다중 term → match_phraseAPI 레이턴시 2배 개선, 0.7초 이상 슬로우 쿼리 제거, CPU 10% 감소
5. analyzer 라이브러리화analyze API 호출 → 네트워크 비용/ES 부하/timeoutES analyze API 사용분석기 라이브러리화, 내부 호출ES CPU 20% → 13%, analyze rejected/timeout 문제 제거
목록으로 돌아가기