Engineering
많은 수의 가격 데이터를 효율적으로 검색 하기 위한 여정: Part-2.색인 축소
서창균Ocean(오션) / 랭킹추천개발팀여기어때
2022년 11월 8일
원문에서 보기 ↗1. 소개
안녕하세요.
여기어때 검색개발팀 오션입니다.
우리는 Part-1에서 실험을 통해 색인 크기가 작을 경우 검색과 색인 성능이 선형으로 좋아진다는 것을 확인하였습니다. 따라서 지속적으로 색인 크기 및 색인 내 문서 크기 축소를 통해 검색과 색인 성능을 향상시키는데 초점을 맞추고 노력하고 있습니다.

색인 크기에 따른 검색 성능
여전히 여기어때 검색데이터 중 가장 큰 비중을 가지는 부분은 가격 데이터 입니다. 가격 데이터는 아래와 같이 구성되어 있고 이미 계산되어 저장되고 있습니다.
가격데이터 수 = 객실 수(숙소 내 객실) ✕ 예약 가능일 ✕ 연속 숙박일 ✕ 가격구분(엘리트가격,할인가)
객실을 약 20만 개라고 가정하고 서비스 내 예약 가능일은 180일, 연속 숙박일은 30일, 가격 구분이 2개일 때 가격 데이터의 수는 최대 약 21억 건입니다.
2,160,000,000(건) = 200,000 ✕ 180 ✕ 30 ✕ 2
우리는 가격 데이터 축소를 위해 여러 가지 방향으로 고민하였습니다.
- 가격 데이터 분리하여 조인하는 방법 → 가설: 색인의 총량은 같거나 더 크지만 각각의 작은 색인을 만들어 조인시켜서 검색해보는 것은 어떨까?
- 검색 후 후처리(es script score 이용)하여 마지막에 가격 계산 → 가설: 365일 및 30박 데이터 전체를 미리 만들어 두니까 색인이 너무 큰 게 아닐까? 오히려 큰 색인에 비해 작은 색인에서 후처리 연산을 통해 검색하는 것이 더 좋은 성능으로 나타나지 않을까?
- 비효율적으로 적재가 되어있는 엘리트 가격 적재 구조 변경 → 엘리트 특별가의 경우 전체 가격에서 15% 정도의 양을 가지고 있으나 데이터 적재 시에는 기존 가격과 동일한 크기의 데이터양을 들고 있다. 이 부분을 조금 더 스마트하게 처리할 방법은 없을까?
위 세 가지 방법 중 검색속도에 영향을 주는 두 번째(script score 이용) 방법을 제외한 첫 번째 방법과 세 번째 방법을 활용하여 POC와 BMT를 진행하였습니다. 결과적으로 성능요구사항에 부합하지않는 첫 번째(조인) 방법을 제외하고 최종적으로 세 번째(엘리트가격 구조변경) 방법으로 적용하였습니다.
2. 방법
아래 그림을 보시면 여기어때에서 가격 데이터를 적재하고 있는 구조를 알 수 있습니다.

AS-IS 가격 적재 구조
prices는 일반 회원 가격, elitePrices는 엘리트 특별 가격입니다. 엘리트 특별가의 경우 엘리트 회원만 볼 수 있는 가격입니다. 기존에는 엘리트 특별가가 없는 객실에도 prices와 elitePrices는 동일한 가격으로 데이터를 가지고 있습니다. 엘리트 회원일 경우에도 엘리트 특별가를 제공하지 않는 객실이거나 일반회원가가 더 싼 가격일 경우 일반회원가를 기준으로 필터링되거나 정렬이 되어야 하기 때문입니다.
우리는 더 효율적인 해결책책을 찾기 위해 고민하였습니다. 그래서 중복을 허용하며 2배로 적재되던 기존의 방법을 엘리트 특별가가 존재하는 만큼만 추가로 적재하는 방법으로 변경하고 객실을 구분하여 적재하는 방법을 통해 해결하였습니다.
객실 데이터를 일반회원용 객실, 엘리트 회원용 객실, 일반회원과 엘리트 회원가가 동일한 객실 등 세 가지로 구분하였습니다.
각각의 객실은 eliteStatus 필드에 상태값은 ELITE, NO_ELITE, BOTH로 구분합니다. ELITE 객실의 경우 ELITE회원만 바라보는 객실이고 NO_ELITE객실은 일반회원이 바라보는 객실입니다. BOTH 객실의 경우 모든 회원이 바라보는 객실입니다.
필드 분리가 아닌 객실 분리 구조로 적재할 경우 몇 개의 제휴점(엘리트 특별가를 가진 객실)의 객실 데이터가 중복될 수 있으나 2배로 적재되던 가격 데이터(elitePrices)의 75%를 축소시킬 수 있습니다.

TO-BE 가격 적재 구조
AS-IS와 TO-BE의 구조 변경에 따른 이해를 돕기위한 조회 SQL예시입니다. (실제로는 Elasticsearch Query로 구성되어 있습니다.)
AS-IS
- 엘리트 회원일 때 가격 정렬
SELECT * FROM ROOM ORDER BY elitePrices.날짜.연박
- 일반회원일 때 가격 정렬
SELECT * FROM ROOM ORDER BY price.날짜.연박
TO-BE
- 엘리트 회원일 때 가격 정렬
SELECT * FROM ROOM WHERE eliteStatus = 'ELITE' OR eliteStatus = 'BOTH' ORDER BY prices.날짜.연박
- 일반회원일 때 가격 정렬
SELECT * FROM ROOM WHERE eliteStatus = 'NO_ELITE' OR eliteStatus = 'BOTH' ORDER BY prices.날짜.연박
3. 테스트
1. 테스트 조건
- 테스트 색인 데이터 예약 가능 일수 : 180일 연박 가능 일수: 30일
- 검색 테스트 조건 검색 쿼리 데이터: 검색 로그 데이터 중 무작위 쿼리 추출 실시간 색인 중 일 때와 하지 않을 때의 차이 확인
2. 성능테스트
- 배치성능
- 총 색인 시간 약 20% 감소
- 색인사이즈는 약 40% 감소

색인 사이즈가 줄어든 양에 비해 총 색인 시간이 비슷한 비율로 줄어들지 않는 이유는 데이터 가공 시간이 기존과 동일하기 때문입니다.
- 검색 성능 1. Throughput 약 70% 향상 2. Latency는 약 43% 감소

성능 측정 시 색인 진행 중일 때와 그렇지 않을 때를 나눠서 측정하였습니다. 색인 사이즈가 줄어듦에 따라 색인 중일 때 AS-IS 대비 더 좋은 성능이 나오는 것을 확인하였습니다.
결론
단순한 아이디어를 통해 좀 더 효율적으로 가격을 적재할 수 있는 구조로 변경할 수 있었고(색인 사이즈는 약 40% 감소, 그로 인한 색인 성능 20% 향상 및 검색성능 70% 상승) 인프라 증설 없이 더 많은 제휴점 및 가격 데이터를 적재할 수 있게 되었습니다.