크게 두 부분으로 나뉜다.
질의 | 빈도 |
---|---|
twitch | 1 |
2 |
위와 같이 사용자가 검색한 단어를 빈도수와 함께 ‘빈도 테이블’에 저장한다.
빈도 테이블을 이용해 인기 top5 검색어를 계산한다.
만약, 사용자가 ‘tw’ 를 검색창에 입력한 상태라면, ‘tw’로 시작하는 단어 중, 인기 top5 검색어를 반환하면 된다.
SELECT * FROM frequency_table
WHERE query Like 'prefix%'
ORDER BY frequency DESC
LIMIT 5
하지만 이렇게 되면, 데이터가 많아지기 떄문에 DB에 병목 현상이 발생할 수 있다.
위의 관계형 데이터베이스를 이용해, 가장 인기 TOP5 다섯 개 질의문을 골라내는 방안은 효율적이지 않아, 트라이 자료구조
를 이용해 구현해보려고 한다.
트라이는 문자열들을 간략하게 저장할 수 있는 자료 구조이다.
위와 같이 문자와 빈도수를 노드 안에 저장해보자
따라서, 가장 많이 사용된 질의어 k개를 찾는 해당 알고리즘의 시간복잡도
O(p) + O(c) + O(clogc)
최악의 경우 트리 전체를 탐색해야 할 수도 있다.
이를 방지하기 위한 2가지 방안이 있다.
사용자가 검색어를 엄청 길게 입력하는 일은 거의 없다. 따라서 접두어의 길이를 약 50으로 생각하고 제한을 둔다면,
해당 접두어를 표현하는 노드를 탐색할 때, O(p) → O(1)로 바뀔 수 있다.
말 그대로, 노드마다 인기 검색어를 캐시해 두는 방법이다.
노드마다, 위처럼 인기 검색어를 저장해 두기 때문에 장단점은 존재한다.
살펴본 위 2가지 방안을 적용한다면,
따라서, 가장 인기있는 k개의 검색어를 찾는 알고리즘 자체의 시간복잡도는 O(1)로 나타낼 수 있다.
규모 확장이 쉬운 데이터 수집 서비스를 위해서라면, 데이터가 어디에서 오고, 어떻게 이용되는 지 알아야 한다.
예를 들어, 트위터 같은 실시간 애플리케이션이라면, 검색어를 항상 신선하게 유지해야 하고, 만약 구글과 같은 애플리케이션이라면, 그렇게까지 실시간일 필요는 없다.
지금까지 봤던 설계안은 2가지 문제점이 있는데, 이걸 개선해보자.
위를 반영하여 개선된 설계안
query | time |
---|---|
tree | 2023-10-01 22:01:01 |
try | 2023-10-01 22:01:05 |
위와 같이 검색창에 입력된 질의와 time 정보 데이터를 로그 데이터로 수집한다.
로그는 당연히 새로운 데이터가 추가될 뿐, 수정, 삭제 등의 기능이 제공되지 않는다. 또한 인덱스도 필요없다.
위에서 살펴봤던 로그는 양이 엄청나고, 데이터 형식이 제각각일 경우가 많다.
따라서 로그 취합하는 서버를 두어, 아래와 같이 빈도수와 해당 날짜(일별)를 이용해 데이터를 취합한다.
query | time | frequency |
---|---|---|
tree | 2023-10-01 | 12000 |
tree | 2023-10-08 | 15000 |
toy | 2023-10-01 | 850 |
toy | 2023-10-09 | 6256 |
작업 서버는 비동기적 작업을 실행하는 서버 집합이다. 트라이 자료 구조를 만들어, 데이터베이스에 저장한다.
분산 캐시 시스템으로, 트라이 데이터를 메모리에 유지하여 읽기 연산 성능을 높이는 역할
매주, 트라이 데이터베이스의 스냅샷 갱신
트라이 데이터베이스는 당연히 지속성 저장소이다.
관계형 데이터베이스는 아까 사용 안한다고 했으니, 어떤 선택지가 있을까?
문서 저장소: 새 트라이를 매주 만들어, 직렬화하여 DB에 저장할 수 있다.
ex) MongoDB
키-값 저장소: 트라이에 보관된 모든 접두어를 key, 각 트라이 노드에 보관된 모든 데이터를 value로 저장
한 번 개략적 설계안을 개선한 후, 질의 서비스가 어떻게 동작하는 지 살펴보자.
질의 서비스는 번개와 같이 빨라야 하기 때문에, 또 다음과 같은 최적화 방안을 생각해보자
AJAX 요청
브라우저는 보통 AJAX 요청을 보내, 자동완성된 검색어 목록을 가져온다.
장점: 요청을 보내고 받기 위한, 페이지 새로고침 필요 X
브라우저 캐싱
자동완성 검색어 제안 결과는 짧은 시간 안에 자주 바뀌지 않는다.
따라서, 제안했던 검색어 리스트를 브라우저 캐시에 넣어두면, 다음 질의의 결과를 바로 브라우저 캐시에서 가져올 수 있다.
ex) 구글은 제안된 검색어를 한 시간 동안 캐시해둔다.
데이터 샘플링
대규모 시스템일 경우, 모든 질의 결과를 로깅하도록 해놓으면, CPU 자원과 저장공간을 엄청나게 소진
N개 요청 중 1개만 로깅.
폭력성, 짙은 혐오성과 관련된 질의어는 트라이 구조에 저장은 하되, 추천 검색어로는 들어가면 안된다.
위와 같이 캐시에서 추천 검색어를 조회하기 전, 필터 계층을 두어 부적절한 질의어를 걸러내자.
데이터베이스에서 물리적으로 삭제하는 건, 매주 트라이 구조를 업데이트 할 때, 비동기적으로 진행하면 된다.
❓ 하지만, 왜 애초에 저장할 때 걸러내지 않고, 조회할 때 필터를 거치는 건지 궁금하니 추후에 찾아보도록 해야겠다.
자동완성된 검색어를 사용자에게 추천하는 기능은 끝냈다.
이제 트라이 구조가 한 서버에 넣기에 너무 방대하게 커질 경우를 대비해보자.
일단 영어만 지원해도 되기 때문에, 첫 글자를 기준으로 한 샤딩을 고려해보자
⇒ 이를 해결하기 위해 과거 질의 데이터를 분석해, 샤딩
검색어 대응 샤드 관리자
어느 저장소 서버에 어떤 검색어가 저장되는 지에 대한 정보를 관리하는 역할
s
로 시작하는 검색어의 양이 u, v, w, x, y, z
로 시작하는 검색어를 전부 합친 것과 비슷하다면, 각각 샤드 하나씩 두어도 충분하다.
다국어 지원이 가능하다면?
비영어권 국가의 언어 지원은 트라이에 유니코드 데이터를 저장
국가별로 인기 검색어 순위가 다르다면?
국가별로 다른 트라이 사용, 트라이를 CDN에 저장하여 응답속도를 높이는 방법도 고려해볼만 하다
실시간으로 변하는 검색어의 추이 반영을 하고 싶다면?
현 설계안은 실시간 검색어를 지원하기에 적합하지 않다.
만약 실시간 검색어를 지원하고 싶다면?