
게스트하우스 예약 시스템을 개발하면서, 여기어때, 야놀자와 같은 서비스의 검색 기능을 참고해 사용자 친화적인 검색 기능을 구현하고자 했습니다.
사용자가 검색어를 입력한 뒤 숙소를 탐색할 수 있도록 아래 요소를 기준으로 검색 기능을 기획했습니다.
검색어에 대한 데이터는 MongoDB에 행정구역, 관광지 등의 데이터를 수집하여 위도, 경도와 함께 저장해두었습니다
일반적인 숙소 플랫폼은 아래와 같은 정렬 기준을 제공합니다:
이 중 추천 순은 단순 정렬이 아닌 점수화 기반의 계산을 필요로 합니다.
이 중 ‘추천 순’은 다음과 같은 가중치를 기반으로 점수화했습니다
| 항목 | 가중치 |
|---|---|
| 거리 유사도 | 40% |
| 평점 | 35% |
| 리뷰 수 | 25% |
| 좋아요 수 | 10% |
모두 정규화된 값으로 계산하여 공정성을 유지하고, 리뷰 수와 좋아요 수는 상한 개수를 정하여 규모에 영향을 줄였습니다.
검색어와 게스트하우스 이름의 유사도를 Levenshtein 알고리즘을 통해 구현하려 했으나, SQL만을 이용하여 구현하기에는 페이징 처리에 어려움이 있었고,
이를 해결하기 위해 Elasticsearch를 도입하는 방법도 존재하였지만, 현재 Workway와 같이 작은 서비스에서 도입하기에는 너무 무거운 기술이라고 생각되어 이름에 대한 유사도는 제외하기로 결정했습니다.
NumberExpression<Double> recommendScore = Expressions.numberTemplate(
Double.class,
"(" +
"(GREATEST(0, 1 - cast(ST_Distance_Sphere(point({0}, {1}), point({2}, {3})) as double) / 10000) * 0.3) + " +
"(IFNULL(avg({4}), 0) / 5 * 0.3) + " +
"(LEAST(COUNT(DISTINCT {5}), 100) / 100 * 0.2) + " +
"(LEAST(COUNT(DISTINCT {6}), 100) / 100 * 0.2)" +
")",
게스트하우스lng, 게스트하우스lat,
키워드lng, 키워드lat,
리뷰점수,
리뷰,
좋아요
);
데이터에 대한 페이징 처리가 필요했기때문에, SQL레벨에서 점수 계산을 해야했고,
거리를 계산하는 MySQL의 ST_Distance_Sphere 함수를 통해 해결할 수 있었습니다.
추천 정렬은 단순 조건문보다 훨씬 복잡하지만,
사용자 경험에 가장 큰 차별점을 만드는 요소이기도 합니다.
QueryDSL의 Expressions.numberTemplate()를 통해 유연하게 구현할 수 있었고,
DB 부하와 비즈니스 정확도 사이의 균형을 잡는 것이 핵심이었습니다.