본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 29. · 18 Views
고급 검색 쿼리와 스코어링 완벽 가이드
Elasticsearch의 고급 검색 기능을 활용하여 다중 필드 검색, 오타 허용, 스코어 조정까지 마스터합니다. 검색 품질을 한 단계 끌어올리는 실무 노하우를 담았습니다.
목차
- multi_match 다중 필드 검색
- wildcard와 regexp
- fuzzy 오타 허용 검색
- boost로 스코어 조정
- function_score 커스텀
- explain API로 스코어 분석
1. multi match 다중 필드 검색
김개발 씨는 쇼핑몰 검색 기능을 개발하고 있었습니다. 사용자가 "아이폰 케이스"를 검색하면 상품명뿐만 아니라 상품 설명, 카테고리, 브랜드까지 모두 살펴봐야 원하는 결과를 보여줄 수 있었습니다.
"이걸 필드마다 따로 검색해야 하나요?" 김개발 씨가 고민에 빠졌습니다.
multi_match는 한 번의 검색으로 여러 필드를 동시에 탐색하는 강력한 쿼리입니다. 마치 도서관에서 책을 찾을 때 제목, 저자, 목차를 한꺼번에 훑어보는 것과 같습니다.
이것을 활용하면 사용자가 어느 필드에 정보가 있든 원하는 결과를 찾아줄 수 있습니다.
다음 코드를 살펴봅시다.
// 여러 필드에서 동시에 검색하는 multi_match 쿼리
const searchQuery = {
query: {
multi_match: {
query: "아이폰 케이스",
// 검색 대상 필드들 - ^2는 가중치 2배
fields: ["title^3", "description^2", "category", "brand"],
// best_fields: 가장 잘 매칭되는 필드의 스코어 사용
type: "best_fields",
// 모든 검색어가 포함된 문서만 반환
operator: "and"
}
}
};
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 오늘 그에게 주어진 미션은 쇼핑몰의 통합 검색 기능을 개선하는 것이었습니다.
기존 검색은 상품명만 살펴보다 보니, 사용자들이 원하는 상품을 찾지 못하는 경우가 많았습니다. 선배 개발자 박시니어 씨가 조언을 건넸습니다.
"검색할 때 상품명만 보면 안 돼요. 사용자가 뭘 입력할지 모르잖아요.
설명에만 있는 키워드로 검색할 수도 있고요." 그렇다면 여러 필드를 검색하려면 어떻게 해야 할까요? 가장 단순한 방법은 각 필드별로 match 쿼리를 작성하고 bool 쿼리로 묶는 것입니다.
하지만 이 방법은 코드가 길어지고 관리하기도 번거롭습니다. 바로 이런 상황을 위해 multi_match 쿼리가 존재합니다.
multi_match는 마치 여러 개의 match 쿼리를 하나로 합쳐놓은 것과 같습니다. 검색어를 한 번만 지정하면 여러 필드를 동시에 탐색합니다.
쉽게 비유하자면, 도서관에서 책을 찾는 상황을 떠올려 보세요. 어떤 책을 찾고 싶은데 정확한 제목이 기억나지 않습니다.
이럴 때 사서가 제목뿐만 아니라 저자명, 목차, 심지어 책 뒷면의 소개글까지 모두 확인해준다면 얼마나 편리할까요? multi_match가 바로 그런 역할을 합니다.
위 코드에서 fields 배열을 살펴봅시다. title^3이라고 적힌 부분이 보입니다.
여기서 ^3은 해당 필드의 가중치를 의미합니다. 상품명에서 검색어가 발견되면 스코어를 3배로 높여주겠다는 뜻입니다.
상품 설명은 2배, 나머지는 기본 1배입니다. type 옵션은 스코어 계산 방식을 결정합니다.
best_fields는 가장 높은 스코어를 받은 필드의 점수를 최종 스코어로 사용합니다. 반면 most_fields는 모든 필드의 스코어를 합산합니다.
cross_fields는 마치 모든 필드가 하나의 큰 필드인 것처럼 동작합니다. 실제 현업에서는 어떤 type을 선택해야 할까요?
대부분의 경우 best_fields가 적합합니다. 하지만 사람 이름처럼 first_name과 last_name으로 나뉜 데이터를 검색할 때는 cross_fields가 더 좋은 결과를 보여줍니다.
operator 옵션도 중요합니다. 기본값은 or입니다.
"아이폰 케이스"를 검색하면 "아이폰"만 포함된 문서도 반환됩니다. 하지만 operator를 and로 설정하면 두 단어가 모두 포함된 문서만 반환합니다.
주의할 점이 있습니다. 필드 개수가 너무 많으면 성능이 저하될 수 있습니다.
꼭 필요한 필드만 선별해서 사용하는 것이 좋습니다. 또한 가중치를 너무 극단적으로 설정하면 검색 결과가 편향될 수 있으니 적절한 균형을 찾아야 합니다.
박시니어 씨의 조언을 듣고 multi_match를 적용한 김개발 씨는 검색 결과가 눈에 띄게 개선된 것을 확인할 수 있었습니다. "이렇게 간단한 방법이 있었군요!"
실전 팁
💡 - 가중치는 테스트를 통해 최적값을 찾으세요. 처음에는 제목 3배, 설명 2배 정도로 시작하는 것이 좋습니다.
- minimum_should_match 옵션을 활용하면 검색어 중 최소 몇 개가 매칭되어야 하는지 지정할 수 있습니다.
2. wildcard와 regexp
김개발 씨에게 새로운 요청이 들어왔습니다. "사용자가 정확한 상품명을 모를 때도 검색할 수 있게 해주세요.
예를 들어 'iph*'만 입력해도 iPhone 관련 상품이 나와야 해요." 패턴 기반 검색이 필요한 순간이었습니다.
wildcard와 regexp는 패턴 매칭을 가능하게 하는 쿼리입니다. wildcard는 별표와 물음표를 사용해 간단한 패턴을 표현하고, regexp는 정규표현식으로 복잡한 패턴을 정의합니다.
마치 전화번호부에서 "김*"으로 시작하는 모든 이름을 찾는 것과 같습니다.
다음 코드를 살펴봅시다.
// wildcard: *는 여러 문자, ?는 한 문자 매칭
const wildcardQuery = {
query: {
wildcard: {
product_code: {
value: "PRD-2024-*",
// 대소문자 구분 없이 검색
case_insensitive: true
}
}
}
};
// regexp: 정규표현식으로 복잡한 패턴 검색
const regexpQuery = {
query: {
regexp: {
email: {
// gmail 또는 naver 도메인 이메일 찾기
value: ".*@(gmail|naver)\\.com",
flags: "ALL"
}
}
}
};
김개발 씨는 이번에 상품 코드 검색 기능을 만들어야 했습니다. 상품 코드는 "PRD-2024-001234" 같은 형식인데, 사용자가 앞부분만 기억하는 경우가 많았습니다.
"PRD-2024-"로 시작하는 모든 상품을 찾을 수 있어야 했습니다. 정규표현식이 떠올랐지만, Elasticsearch에서 어떻게 사용하는지 몰랐습니다.
박시니어 씨가 다가와 화면을 보더니 말했습니다. "패턴 검색이 필요하군요.
wildcard나 regexp 쿼리를 써보세요." wildcard 쿼리는 가장 간단한 패턴 매칭 방법입니다. 두 가지 특수문자를 사용합니다.
별표는 0개 이상의 문자를 의미하고, 물음표는 정확히 한 개의 문자를 의미합니다. 예를 들어 "PRD-*"는 "PRD-"로 시작하는 모든 문자열과 매칭됩니다.
"PRD-2024-001234"도, "PRD-A"도 모두 매칭됩니다. 반면 "PRD-????"는 "PRD-" 뒤에 정확히 네 글자가 오는 문자열만 매칭됩니다.
"PRD-ABCD"는 매칭되지만 "PRD-AB"는 매칭되지 않습니다. 더 복잡한 패턴이 필요하다면 regexp 쿼리를 사용합니다.
정규표현식의 강력한 기능을 그대로 활용할 수 있습니다. 위 코드에서는 gmail이나 naver 도메인의 이메일 주소를 찾는 패턴을 사용했습니다.
하지만 여기서 박시니어 씨가 중요한 경고를 했습니다. "wildcard와 regexp는 정말 조심해서 써야 해요.
성능이 많이 느려질 수 있거든요." 왜 그럴까요? 일반적인 match 쿼리는 역인덱스를 활용합니다.
미리 만들어둔 인덱스를 사용하니 빠릅니다. 하지만 wildcard와 regexp는 패턴에 따라 인덱스를 효과적으로 사용하지 못할 수 있습니다.
특히 "로 시작하는 패턴"은 최악입니다. "*phone"처럼 별표가 앞에 오면 모든 문서의 해당 필드를 일일이 확인해야 합니다.
문서가 수백만 개라면 검색 시간이 몇 초씩 걸릴 수도 있습니다. 그래서 가능하면 "phone*"처럼 별표를 뒤에 배치하는 것이 좋습니다.
이렇게 하면 "phone"으로 시작하는 단어들만 확인하면 되므로 훨씬 빠릅니다. 실무에서는 prefix 쿼리나 ngram 분석기를 대안으로 고려해보세요.
자동완성 기능을 만든다면 edge_ngram 분석기가 wildcard보다 훨씬 효율적입니다. regexp도 마찬가지입니다.
복잡한 정규표현식은 성능을 크게 저하시킵니다. 정말 필요한 경우에만 사용하고, 가능하면 패턴을 단순하게 유지하세요.
김개발 씨는 결국 상품 코드 검색에는 prefix 쿼리를 사용하고, 정말 필요한 관리자 기능에만 wildcard를 적용하기로 했습니다. "성능과 기능 사이의 균형이 중요하군요."
실전 팁
💡 - wildcard에서 별표는 가능하면 패턴 뒤쪽에 배치하세요. 앞에 오면 성능이 크게 저하됩니다.
- 자동완성 기능이 목적이라면 wildcard 대신 edge_ngram 분석기를 검토해보세요.
3. fuzzy 오타 허용 검색
"선배, 사용자가 '삼성갤럭시'라고 붙여 쓰거나 '삼셩'이라고 오타를 내도 검색되게 할 수 있나요?" 김개발 씨가 물었습니다. 실제 사용자들은 생각보다 오타를 많이 냅니다.
완벽한 철자를 요구하면 검색 경험이 나빠질 수밖에 없습니다.
fuzzy 검색은 오타를 허용하는 검색입니다. 편집 거리 알고리즘을 사용해서 원래 단어와 비슷한 단어도 매칭시켜줍니다.
마치 똑똑한 비서가 "혹시 이걸 찾으셨나요?"라고 추천해주는 것과 같습니다. 사용자 경험을 크게 향상시키는 기능입니다.
다음 코드를 살펴봅시다.
// fuzzy 검색: 오타를 허용하는 관대한 검색
const fuzzyQuery = {
query: {
fuzzy: {
brand: {
value: "삼셩",
// 허용할 편집 거리 (auto가 권장됨)
fuzziness: "AUTO",
// 처음 몇 글자는 정확히 일치해야 함
prefix_length: 1,
// 확장할 최대 단어 수 (성능 관련)
max_expansions: 50
}
}
}
};
// match 쿼리와 결합하여 사용하기
const matchWithFuzzy = {
query: {
match: {
product_name: {
query: "갤럭시폰",
fuzziness: "AUTO"
}
}
}
};
어느 날 마케팅팀에서 불만이 들어왔습니다. "고객들이 검색을 제대로 못 해요.
조금만 다르게 입력해도 결과가 안 나와요." 김개발 씨가 로그를 분석해보니 정말 놀라운 사실을 발견했습니다. 검색어의 약 15%가 오타를 포함하고 있었습니다.
"삼성"을 "삼셩"으로, "아이폰"을 "아이폰"으로, "갤럭시"를 "갤럭씨"로 입력하는 경우가 많았습니다. 스마트폰 키보드로 빠르게 입력하다 보면 흔히 발생하는 실수입니다.
박시니어 씨가 해결책을 알려줬습니다. "fuzzy 검색을 적용해봐요.
편집 거리라는 개념을 이용해서 비슷한 단어도 찾아주거든요." 편집 거리란 무엇일까요? 한 단어를 다른 단어로 바꾸는 데 필요한 최소 편집 횟수입니다.
여기서 편집이란 문자 삽입, 삭제, 교체를 의미합니다. 예를 들어 "삼성"과 "삼셩"은 편집 거리가 1입니다.
"성"을 "셩"으로 바꾸는 한 번의 교체면 되니까요. 쉽게 비유하면, 친구에게 문자를 보냈는데 오타가 났다고 상상해보세요.
"내일 봐!"라고 보내려다 "내일 봐1"이라고 보냈습니다. 친구는 맥락을 통해 무슨 뜻인지 이해합니다.
fuzzy 검색도 이처럼 "대충 비슷하면 찾아주는" 관대한 검색입니다. fuzziness 옵션은 몇 글자까지의 오차를 허용할지 결정합니다.
숫자로 직접 지정할 수도 있고, AUTO로 설정하면 단어 길이에 따라 자동으로 결정됩니다. AUTO는 1-2글자 단어는 정확히 일치해야 하고, 3-5글자는 편집 거리 1까지, 6글자 이상은 편집 거리 2까지 허용합니다.
prefix_length는 중요한 성능 옵션입니다. 첫 몇 글자는 반드시 정확히 일치해야 한다고 지정합니다.
이렇게 하면 검색해야 할 후보 단어 수가 크게 줄어들어 성능이 향상됩니다. 보통 1이나 2로 설정합니다.
max_expansions는 fuzzy 매칭으로 확장할 최대 단어 수입니다. fuzzy 검색은 내부적으로 비슷한 단어들의 목록을 만들어서 검색합니다.
이 목록이 너무 커지면 성능이 저하되므로 제한을 둡니다. 기본값은 50입니다.
실무에서는 fuzzy 검색을 match 쿼리와 함께 사용하는 경우가 많습니다. 두 번째 코드 예제처럼 match 쿼리에 fuzziness 옵션만 추가하면 됩니다.
이렇게 하면 형태소 분석과 fuzzy 검색이 함께 적용되어 더 좋은 결과를 얻을 수 있습니다. 주의할 점이 있습니다.
fuzzy 검색은 의도치 않은 결과를 반환할 수 있습니다. 예를 들어 "cat"을 검색했는데 "car", "bat", "can" 같은 단어도 매칭될 수 있습니다.
따라서 정확도가 중요한 검색에서는 신중하게 사용해야 합니다. 김개발 씨는 상품 검색에 fuzziness: "AUTO"를 적용했습니다.
오타가 있어도 원하는 상품을 찾을 수 있게 되자 고객 만족도가 크게 올랐습니다. "작은 변화가 큰 차이를 만드는군요!"
실전 팁
💡 - fuzziness는 AUTO로 시작하세요. 대부분의 경우 적절한 균형을 제공합니다.
- prefix_length를 1 이상으로 설정하면 성능을 크게 개선할 수 있습니다.
4. boost로 스코어 조정
검색 결과는 잘 나오는데, 순서가 마음에 들지 않았습니다. 김개발 씨는 신상품이나 인기 상품이 먼저 나오게 하고 싶었습니다.
"특정 조건에 맞는 문서의 점수를 높일 수 있는 방법이 없을까요?"
boost는 특정 조건이나 필드의 중요도를 높여 검색 결과 순서를 조정하는 기능입니다. 마치 시험에서 가산점을 주는 것과 같습니다.
특정 조건을 만족하면 추가 점수를 받아 더 높은 순위에 노출됩니다. 비즈니스 요구사항에 맞게 검색 결과를 튜닝할 때 필수적인 기능입니다.
다음 코드를 살펴봅시다.
// bool 쿼리에서 boost 활용하기
const boostedQuery = {
query: {
bool: {
must: [
{ match: { title: "스마트폰" } }
],
should: [
// 신상품이면 스코어 2배 가산
{ term: { is_new: { value: true, boost: 2.0 } } },
// 베스트셀러면 스코어 1.5배 가산
{ term: { is_bestseller: { value: true, boost: 1.5 } } },
// 특정 브랜드 우대
{ match: { brand: { query: "삼성", boost: 1.3 } } }
]
}
}
};
김개발 씨가 만든 검색 기능은 잘 동작했습니다. 하지만 사업팀에서 새로운 요청이 들어왔습니다.
"신상품이 먼저 나오게 해주세요. 그리고 베스트셀러도 위쪽에 보여줬으면 좋겠어요." 단순히 등록일 순으로 정렬하면 될 것 같지만, 그러면 검색 관련성이 무시됩니다.
"스마트폰"을 검색했는데 관련 없는 신상품이 먼저 나오면 안 됩니다. 관련성도 유지하면서 특정 조건에 가점을 주고 싶었습니다.
박시니어 씨가 힌트를 줬습니다. "boost를 사용해봐요.
시험에서 가산점 받는 것처럼, 특정 조건을 만족하면 스코어에 보너스를 주는 거예요." boost의 원리는 간단합니다. Elasticsearch는 각 문서에 관련성 점수를 계산합니다.
boost는 이 점수에 곱해지는 가중치입니다. boost가 2.0이면 해당 조건에서 얻은 점수가 2배가 됩니다.
위 코드를 살펴봅시다. bool 쿼리의 must 절에는 반드시 만족해야 하는 조건이 들어갑니다.
"스마트폰"이라는 검색어가 제목에 포함되어야 합니다. 이건 기본 조건입니다.
should 절에는 만족하면 좋은 조건들이 들어갑니다. 만족하지 않아도 결과에서 제외되지 않습니다.
다만 만족하면 추가 점수를 받습니다. 여기에 boost를 적용해서 조건별로 다른 가중치를 줄 수 있습니다.
첫 번째 조건은 is_new가 true인 문서입니다. boost가 2.0이므로 신상품은 상당한 추가 점수를 받습니다.
두 번째 조건은 베스트셀러로 boost가 1.5입니다. 세 번째는 삼성 브랜드에 1.3배 가점을 줍니다.
이렇게 하면 어떤 일이 일어날까요? "스마트폰"을 검색하면 기본적으로 제목과의 관련성으로 점수가 매겨집니다.
여기에 신상품이면 추가 점수, 베스트셀러면 추가 점수, 삼성이면 추가 점수가 더해집니다. 결과적으로 신상품이면서 베스트셀러인 삼성 스마트폰이 가장 높은 순위에 오릅니다.
boost 값은 어떻게 정해야 할까요? 정답은 없습니다.
테스트와 조정의 반복이 필요합니다. 처음에는 1.5, 2.0 같은 값으로 시작해서 실제 검색 결과를 보며 조정합니다.
너무 높은 boost는 관련성을 무시하게 만들 수 있으니 주의하세요. 또한 boost는 상대적인 값입니다.
모든 조건에 boost 10을 주면 아무 효과가 없습니다. 조건 간의 상대적인 중요도를 반영해야 합니다.
multi_match에서도 boost를 사용할 수 있습니다. 앞서 봤던 "title^3"이 바로 boost입니다.
제목 필드에서 매칭되면 점수를 3배로 높이겠다는 의미입니다. 김개발 씨는 A/B 테스트를 통해 최적의 boost 값을 찾아냈습니다.
신상품 boost는 1.8, 베스트셀러는 1.5, 할인 상품은 1.3으로 설정했습니다. 전환율이 15% 상승했다는 보고를 받고 뿌듯해했습니다.
실전 팁
💡 - boost 값은 테스트를 통해 조정하세요. 처음에는 1.5~2.0 범위에서 시작하는 것이 좋습니다.
- 모든 조건에 높은 boost를 주면 의미가 없습니다. 조건 간 상대적 중요도를 고려하세요.
5. function score 커스텀
boost로는 한계가 있었습니다. "최근에 등록된 상품일수록 점수를 높이고 싶은데, 날짜에 따라 점진적으로 감소하는 방식으로요." 김개발 씨의 요구는 단순한 가산점을 넘어 복잡한 계산이 필요했습니다.
function_score는 스코어 계산에 사용자 정의 함수를 적용하는 강력한 쿼리입니다. 단순한 가중치를 넘어 수학적 함수, 스크립트, 필드 값 등을 활용해 복잡한 스코어링 로직을 구현할 수 있습니다.
마치 요리사가 레시피를 직접 만드는 것처럼, 검색 결과의 순위를 정밀하게 제어할 수 있습니다.
다음 코드를 살펴봅시다.
// function_score로 복잡한 스코어링 구현
const functionScoreQuery = {
query: {
function_score: {
query: { match: { title: "노트북" } },
functions: [
// 최근 등록일수록 점수 높음 (지수 감쇠)
{
exp: {
created_at: {
origin: "now",
scale: "30d",
decay: 0.5
}
}
},
// 판매량에 비례해서 점수 상승
{
field_value_factor: {
field: "sales_count",
factor: 1.2,
modifier: "log1p",
missing: 1
}
},
// 평점 4.5 이상이면 가산점
{
filter: { range: { rating: { gte: 4.5 } } },
weight: 1.5
}
],
// 함수 결과들을 곱해서 최종 점수 계산
score_mode: "multiply",
boost_mode: "multiply"
}
}
};
김개발 씨는 더 정교한 검색 랭킹을 원했습니다. 단순히 신상품에 가점을 주는 것을 넘어서, 등록된 지 얼마나 됐는지에 따라 점수가 점진적으로 변하길 원했습니다.
어제 등록된 상품과 한 달 전 등록된 상품은 다르게 취급되어야 했습니다. 또한 판매량과 평점도 반영하고 싶었습니다.
하지만 판매량이 100인 상품과 10,000인 상품의 점수 차이가 100배가 되면 곤란합니다. 적당히 로그 스케일로 조정하고 싶었습니다.
박시니어 씨가 말했습니다. "이런 복잡한 요구사항에는 function_score가 딱이에요.
수학 함수까지 적용할 수 있거든요." function_score는 Elasticsearch에서 가장 강력한 스코어링 도구입니다. 기본 쿼리의 스코어에 다양한 함수를 적용해서 최종 점수를 계산합니다.
위 코드에서 첫 번째 함수는 exp 입니다. 지수 감쇠 함수입니다.
origin은 기준점으로 "now"는 현재 시각입니다. scale은 점수가 절반으로 줄어드는 기간입니다.
30일이 지나면 점수가 decay 값인 0.5배가 됩니다. 60일이 지나면 0.25배, 90일이 지나면 0.125배...
이런 식으로 시간이 지날수록 점수가 자연스럽게 감소합니다. 두 번째 함수는 field_value_factor입니다.
필드 값을 점수에 반영합니다. sales_count 필드의 값을 사용하는데, modifier를 "log1p"로 설정했습니다.
이는 log(1 + 값)을 계산합니다. 판매량이 100이든 10,000이든 점수 차이가 적당해집니다.
missing 옵션은 필드가 없을 때 사용할 기본값입니다. 세 번째 함수는 filter + weight 조합입니다.
특정 조건을 만족하면 가중치를 곱합니다. 평점 4.5 이상인 상품에 1.5배 가점을 주는 로직입니다.
score_mode는 여러 함수의 결과를 어떻게 합칠지 결정합니다. multiply는 모두 곱합니다.
sum은 더하고, avg는 평균을 냅니다. first는 첫 번째 함수만 사용합니다.
boost_mode는 함수 결과와 원래 쿼리 점수를 어떻게 합칠지 결정합니다. multiply는 곱하고, replace는 원래 점수를 무시하고 함수 결과만 사용합니다.
function_score는 강력하지만 복잡합니다. 디버깅하기도 어렵습니다.
따라서 먼저 단순한 boost로 시작하고, 정말 필요할 때만 function_score를 사용하는 것이 좋습니다. 또한 function_score는 성능에 영향을 줄 수 있습니다.
모든 매칭 문서에 대해 함수를 계산해야 하니까요. 특히 스크립트 함수는 무겁습니다.
가능하면 내장 함수를 사용하세요. 김개발 씨는 function_score를 적용한 후 검색 결과가 훨씬 자연스러워졌음을 확인했습니다.
신상품이면서 인기 있는 상품이 상위에 노출되고, 오래된 상품도 완전히 사라지지 않고 적절한 위치에 표시됐습니다.
실전 팁
💡 - 시간 기반 감쇠에는 exp 함수를, 인기도 반영에는 field_value_factor를 사용하세요.
- score_mode와 boost_mode의 조합을 잘 이해하고 사용하세요. 결과가 크게 달라질 수 있습니다.
6. explain API로 스코어 분석
검색 결과 순위가 이상했습니다. 분명히 더 관련 있어 보이는 문서가 아래에 있었습니다.
"왜 이 문서의 점수가 더 높은 거지?" 김개발 씨는 스코어 계산 과정을 직접 들여다보고 싶었습니다.
explain API는 특정 문서의 스코어가 어떻게 계산되었는지 상세하게 보여주는 디버깅 도구입니다. 마치 수학 문제의 풀이 과정을 보여주는 것처럼, 각 단계별로 점수가 어떻게 결정되었는지 분석할 수 있습니다.
검색 품질을 튜닝할 때 반드시 알아야 하는 필수 도구입니다.
다음 코드를 살펴봅시다.
// 검색 결과에 explain 추가하기
const searchWithExplain = {
explain: true,
query: {
multi_match: {
query: "삼성 스마트폰",
fields: ["title^2", "description"],
type: "best_fields"
}
}
};
// 특정 문서의 스코어 분석 (API 호출)
// GET /products/_explain/doc_id_123
const explainRequest = {
query: {
match: { title: "삼성 스마트폰" }
}
};
// explain 결과 예시 (응답 구조)
const explainResult = {
_index: "products",
_id: "doc_id_123",
matched: true,
explanation: {
value: 5.234,
description: "sum of:",
details: [/* 상세 계산 과정 */]
}
};
김개발 씨는 당혹스러웠습니다. "삼성 스마트폰"을 검색했는데, 제목에 정확히 그 단어가 있는 상품이 3위에 있고, 설명에만 언급된 상품이 1위에 있었습니다.
분명히 뭔가 잘못된 것 같은데, 원인을 알 수가 없었습니다. 박시니어 씨가 해결책을 알려줬습니다.
"explain을 써봐요. 스코어가 어떻게 계산됐는지 전부 보여주거든요.
마치 수학 시험 풀이 과정처럼요." explain을 사용하는 방법은 두 가지입니다. 첫 번째는 검색 쿼리에 explain: true를 추가하는 것입니다.
이렇게 하면 모든 결과 문서에 스코어 계산 과정이 포함됩니다. 두 번째는 explain API를 직접 호출하는 것입니다.
특정 문서 ID를 지정해서 그 문서의 스코어만 분석합니다. 형식은 GET /인덱스명/_explain/문서ID 입니다.
explain 결과는 계층적인 구조로 되어 있습니다. 최상위에는 최종 점수가 있고, 그 아래에 점수가 어떻게 계산됐는지 단계별로 나옵니다.
각 단계에는 value, description, details가 있습니다. 실제 explain 결과를 살펴봅시다.
김개발 씨가 의아해했던 1위 문서를 분석해보니 이유가 드러났습니다. 설명 필드가 매우 짧아서 필드 길이 정규화 효과로 높은 점수를 받았던 것입니다.
Elasticsearch는 기본적으로 짧은 필드에서 검색어가 매칭되면 더 높은 점수를 줍니다. "삼성 스마트폰 추천"이라는 5단어 설명에서 2단어가 매칭되면, 100단어 제목에서 2단어가 매칭되는 것보다 점수가 높습니다.
해당 문서에서 검색어가 차지하는 비중이 더 크니까요. explain 결과에서 자주 보이는 용어들을 알아둡시다.
tf는 Term Frequency로 검색어가 문서에 몇 번 나오는지입니다. idf는 Inverse Document Frequency로 검색어가 얼마나 희귀한지입니다.
fieldNorm은 필드 길이에 따른 정규화 값입니다. boost가 적용됐다면 explain에도 나타납니다.
"boost: 2.0"처럼 명시적으로 표시됩니다. function_score의 각 함수 결과도 확인할 수 있습니다.
explain은 디버깅 도구입니다. 프로덕션에서 모든 검색에 explain: true를 추가하면 성능이 크게 저하됩니다.
개발 환경에서 문제를 분석할 때만 사용하세요. 검색 튜닝 작업을 할 때는 explain 결과를 저장해두는 것이 좋습니다.
변경 전후를 비교하면 어떤 조정이 어떤 효과를 가져왔는지 명확하게 알 수 있습니다. 김개발 씨는 explain을 통해 문제의 원인을 파악했습니다.
설명 필드의 가중치를 낮추고 제목 필드의 가중치를 더 높여서 문제를 해결했습니다. "원인을 알면 해결책도 보이는군요!"
실전 팁
💡 - explain은 디버깅용입니다. 프로덕션 검색에는 사용하지 마세요. 성능이 저하됩니다.
- 튜닝 전후의 explain 결과를 비교하면 변경 효과를 명확히 파악할 수 있습니다.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
CloudFront CDN 완벽 가이드
AWS CloudFront를 활용한 콘텐츠 배포 최적화 방법을 실무 관점에서 다룹니다. 배포 생성부터 캐시 설정, HTTPS 적용까지 단계별로 알아봅니다.