Skip to content

Commit 9952f06

Browse files
authored
김현준 13장
1 parent 53b3f6c commit 9952f06

File tree

1 file changed

+142
-0
lines changed
  • week6(11,12,13장)/김현준

1 file changed

+142
-0
lines changed

week6(11,12,13장)/김현준/13장

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
검색창에 단어를 입력하다 보면 입력 중인 글자에 맞는 검색어가 자동으로 완성되어 표시되는 것을 볼 수 있다. 이런 기능을 보통 검색어 자동완성이라 부른다.
2+
3+
### 요구사항
4+
5+
- 빠른 응답 속도: 사용자가 검색어를 입력함에 따라 자동완성 검색어도 충분히 빨리 표시되어야 한다. 페이스북 문서 기준 시스템 응답속도는 100밀초 이내여야 한다.
6+
- 자동완성되어 출력되는 검색어는 사용자가 입력한 단어와 연관된 것이여야 한다.
7+
- 계산 결과는 인기도 등의 순위 모델에 의해 정렬
8+
- 규모 확장성, 고가용성
9+
10+
### 규모 추정
11+
12+
- DAU 천만 명
13+
- 평균적으로 한 사용자는 매일 10건의 검색을 수행
14+
- 질의할 때마다 평균적으로 20바이트의 데이터를 입력한다고 가정
15+
- 문자 인코딩 방법으로는 ASCII → 1문자 = 1바이트
16+
- 질의문으로 평균적으로 4개 단어, 각 단어는 평균적으로 다섯 글자
17+
- 질의 당 평균 20바이트
18+
- 검색창에 글자를 입력할 때마다 클라이언트는 검색어 자동완성 백엔드에 요청을 보냄. 평균적으로 1회 검색당 20건의 요청이 백엔드로 전달.
19+
- 대략 초당 24,000건의 QPS, 최대 QPS 48,000
20+
- 질의 가운데 20% 정도는 신규 검색어라고 가정. 대략 0.4GB(= 천만 * 10 질의/일 * 20자 * 20%)만큼 매일 신규 데이터 추가
21+
22+
## 설계안
23+
24+
### 개략적 설계안
25+
26+
시스템은 두 부분으로 나뉨
27+
28+
- 데이터 수집 서비스: 사용자가 입력한 질의를 실시간으로 수집하는 시스템.
29+
- 질의 서비스: 주어진 질의에 다섯 개의 인기 검색어를 정렬해 내놓는 서비스
30+
31+
질의 빈도 테이블이 있다고 가정하면 SQL 질의문을 아래와 같이 사용
32+
33+
```sql
34+
SELECT * FROM frequency_table
35+
WHERE query Like 'prefix%'
36+
ORDER BY frequency DESC
37+
LIMIT 5;
38+
```
39+
40+
그러나 데이터가 많아지면 DB가 병목이 될 수 있음. (인덱스 효율 X)
41+
42+
### 트라이 자료 구조
43+
44+
RDB는 다섯 개의 인기 검색어를 골라내는 방안으로는 효율적이지 않다. 트라이(접두어 트리라고 한다)를 사용해 해결 할 수 있다.
45+
46+
트라이는 문자열들을 간략하게 저장할 수 있는 자료구조다. 이용 빈도에 따라 정렬된 결과를 내놓기 위해서는 노드에 빈도정보까지 저장해야 한다.
47+
48+
용어 정의 후 알아보자.
49+
50+
- p: 접두어의 길이
51+
- n: 트라이 안에 있는 노드 개수
52+
- c: 주어진 노드의 자식 노드 개수
53+
54+
가장 많이 사용된 질의어 k개는 다음과 같이 찾을 수 있다.
55+
56+
- 해당 접두어를 표현하는 노드를 찾는다. 시간 복잡도는 O(p)이다.
57+
- 해당 노드부터 시작하는 하위 트리를 탐색하여 모든 유효 노드를 찾는다. 유효한 검색 문자열을 구성하는 노드가 유효 노드다. 시간 복잡도는 O(c)이다.
58+
- 유효 노드들을 정렬하여 가장 인기 있는 검색어 k개를 찾는다. 시간 복잡도는 O(c*logc)이다.
59+
60+
이 알고리즘의 최악의 경우에는 k개 결과를 얻으려고 전체 트라이를 다 검색해야 하는 일이 생길 수 있다. 이때 해결법은 다음과 같다.
61+
62+
1. 접두어의 최대 길이 제한
63+
64+
사용자가 검색창에 긴 검색어를 입력하는 일은 거의 없다. 따라서 p값을 작은 정수값이라 가정해도 안전하다. 검색어의 최대 길이를 제한할 수 있다면 접두어 노드를 찾는 단계의 시간 복잡도는 O(p)에서 O(1)로 바뀐다. (최대 탐색 길이가 일정해지기 때문)
65+
66+
2. 각 노드에 인기 검색어를 캐시
67+
68+
각 노드에 k개의 인기 검색어를 저장해 두면 전체 트라이를 검색하는 일 방지. 5~10개 정도의 자동완성 제안을 표시하면 되기에 k는 충분히 작은 값이다. 그러나 각 노드에 질의어를 저장할 공간이 많이 필요하게 된다.
69+
70+
71+
앞의 두 가지 최적회 기법을 적용하면 시간 복잡도는 아래와 같다.
72+
73+
1. 접두어 노드를 찾는 시간 O(1)
74+
2. 최고 인기 검색어 5개를 찾는 질의의 시간 복잡도 O(1)
75+
76+
### 데이터 수집 서비스
77+
78+
사용자가 검색창에 뭔가 타이핑을 할 때마다 실시간으로 데이터를 수정하는 것은 다음과 같은 이유로 실용적이지 않음.
79+
80+
- 매일 수천만 건의 질의가 입력되면 그때마다 트라이를 갱신해야 함.
81+
- 일단 트라이가 만들어지고 나면 인기 검색어는 그다지 자주 바뀌지 않을 것이라 트라이를 자주 갱신할 필요가 없다.
82+
83+
트위터와 같은 서비스는 검색어를 신선하게 유지할 필요가 있지만 구글 검색은 자주 바꿀 필요가 없다.
84+
85+
용례가 달라지더라도 데이터 수집 서비스의 토대인 트라이를 만드는 데 쓰는 데이터는 보통 데이터 분석 서비스나 로깅 서비스로부터 온다.
86+
87+
따라서 이를 바탕으로 재설계한다.
88+
89+
- 데이터 분석 서비스 로그
90+
- 검색창에 입력된 질의에 관한 원본 데이터가 보관되며, 수정은 이루어지지 않고 인덱스도 걸지 않는다.
91+
- 로그 취합 서버
92+
- 양이 엄청나고 데이터 형식이 제각각인 데이터들을 취합하여 소비할 수 있도록 하는 서버.
93+
- 취합 주기는 실시간성의 중요도에 따라 다르게 설정 가능.
94+
- 본문에는 일주일 기준
95+
- 작업 서버
96+
- 주기적으로 비동기적 작업을 실행해 트라이 자료구조를 만들고 트라이 데이터베이스에 저장하는 역할을 담당
97+
- 트라이 캐시
98+
- 분산 캐시 시스템으로 트라이 데이터를 메모리에 유지하여 읽기 연산 성능을 높이는 구실. 매주 트라이 데이터베이스의 스냅샷을 떠서 갱신
99+
- 트라이 데이터베이스
100+
- 지속성 저장소로 사용 가능한 DB는 아래와 같다.
101+
1. 문서 저장서(document store): 주기적으로 트라이를 직렬화해 DB에 저장할 수 있음. 몽고 DB 같은 문서 저장소를 활용하면 편리하게 가능.(유연한 스키마 제공으로 동적 데이터 저장 용이, JSON/BSON 포맷, …)
102+
2. 키-값 저장소: 트라이에 아래 로직을 적용해 해시테이블 형태로 변환 가능.
103+
1. 트라이에 보관된 모든 접두어를 해시 테이블 값으로 변환
104+
2. 각 트라이 노드에 보관된 모든 데이터를 해시 테이블 값으로 변환
105+
- 검색어 삭제
106+
- 위험한 질의어를 자동완성 결과에서 제거 하기 위해 트라이 캐시 앞에 필터 계층을 두고 필터 규척에 따라 검색 결과를 거른다.
107+
108+
### 질의 서비스
109+
110+
새 설계를 바탕으로 아래와 같이 작동한다.
111+
112+
1. 검색 질의가 로드밸런서로 전송된다.
113+
2. 로드밸런서는 해당 질의를 API 서버로 보낸다.
114+
3. API 서버는 트라이 캐시에서 데이터를 가져와 해당 요청에 대한 자동완성 검색어 제안 응답을 구성한다.
115+
4. 데이터가 트라이 캐시에 없는 경우에는 데이터를 데이터베이스에 가져와 캐시에 채운다.
116+
117+
더욱 최적화를 위한 방안은 아래와 같다.
118+
119+
- AJAX 요청: AJAX 요청을 통해 페이지 새로고침을 할 필요가 없어짐.
120+
- 브라우저 캐싱: 제안된 검색어를 브라우저 캐시에 넣어서 해당 캐시에서 바로 가져갈 수 있다. 구글 검색 엔진이 이런 캐시 매커니즘을 사용한다.
121+
- 데이터 샘플링: 모든 질의 결과를 로깅하면 CPU 자원과 저장공간을 엄청나게 소진함. N개의 요청 가운데 1개만 로깅하도록 샘플링
122+
123+
### 저장소 규모 확장
124+
125+
영어만 지원되기 때문에, 첫 글자를 기준으로 샤딩 할 수 있다.
126+
127+
이 방법을 쓰는 경우 알파벳이 26자기 때문에 서버가 26대로 제한된다. 더 늘리고 싶다면 샤딩을 계층적으로 해야한다. (첫 번째 글자는 첫 번째 레벨의 샤딩에, 두 번째 글자는 두 번째 레벨의 샤딩에) 그러나 단어에 시작하는 알파벳의 빈도에 따라 서버에 분배가 되기 때문에 부하가 균등하지 않다.
128+
129+
이 문제를 해결하기 위해서는 과거 질의 데이터 패턴을 분석하여 샤딩하는 방법도 있다. 어느 알파벳이 많이 나왔는지 분석해서 샤드를 분배하는 것이다.
130+
131+
## 추가적으로 생각해볼 점
132+
133+
- 다국어 기능을 지원하도록 트라이에 유니코드 데이터 저장
134+
- 국가별로 다른 검색어 순위를 지원하기 위해 국가별로 다른 트라이를 사용하고 트라이를 CDN에 저장해 응답시간 최소화
135+
- 실시간으로 변하는 검색어 추이를 반영하기. (현재 설계에는 트라이 갱신이 일주일이고 트라이 구성에 많은 시간 소요)
136+
- 샤딩을 통해 작업 대상 데이터를 줄인다.
137+
- 순위 모델을 바꾸어 최근 검색어에 높은 가중치 부여.
138+
- 데이터가 스트림 형태로 와서 한번에 모든 데이터를 동시에 사용할 수 없을 가능성이 있다는 점.
139+
140+
[How We Built Prefixy: A Scalable Prefix Search Service for Powering Autocomplete](https://medium.com/@prefixyteam/how-we-built-prefixy-a-scalable-prefix-search-service-for-powering-autocomplete-c20f98e2eff1)
141+
142+
https://engineering.fb.com/2010/05/17/web/the-life-of-a-typeahead-query/

0 commit comments

Comments
 (0)