디스크 기반 Skip list 를 사용한 대용량 실시간 랭킹WoW 랭킹 서비스 wowz.kr 를 사례로
윤석주 (nori)
모빌팩토리
윤석주 ( @noricube )
- 2012: 서울
- Zoo Invasion
- 퍼즐 주주
- Project VM 개발중
발표자 소개
서비스 컨셉
wowz.kr
- 와우 경매장의 데이터를 수집하여 통계를 보여주는 서비스
- 가격 , 가격의 추세
- 와우 인벤에서 좋은 반응 ( 우측 배너 링크 )
WoW 의 각종 통계로
랭킹을 알려주는 사이트를 만들자 !
MMORPG?
여러가지 요소 중 경쟁이 핵심
와우의 경우 ?
게임 시스템 내에서 제공
http://kr.battle.net/wow/ko/pvp/leaderboards/rbg
와우의 경우 ?
유저들이 만든 사이트
http://www.wowprogress.com/
문제는
앞에 두개의 예는 모두 하드코어 ( 레이드 , PvP) 유저들이 초점
- 라이트 한 유저들도 경쟁을 즐길 수 있는 방법이 있을까 ?
- 레이드 , PvP 외에 다른 요소로 경쟁을 즐길 수 있을까 ?
와우의 통계 기능
흥미로운 통계들
- 낚은 물고기
- 경매장에서 획득한 골드
- 사용한 생명석
- 익사 횟수
- ….
통계 기능 활용
와우의 통계 기능을 활용해서 경쟁 하도록 하면 어떨까 ?
- 경매장에서 골드 많이 획득한 순위
- 낚시로 많이 낚은 순위
- 제일 많이 죽은 (?) 순위
서비스 구현
API 활용
마침 WoW 에서는 캐릭터 정보 ( 통계 ) API 제공
- 1 시간에 36000 번까지 호출 가능
- WoW 에서 로그아웃 후 즉시 갱신됨
Þ 한 캐릭터 갱신에 100ms 이내로 하면 제일 좋음
연산 예상
서버당 3 만 ~7 만 정도의 만렙 캐릭터
연산 예상
- 전 서버를 합산하면 40 만개 정도의 캐릭터
- 통계로 얻을 수 있는 수치는 대략 1000 개
- 서버 별 , 전 서버 랭킹을 각각 따로 보여주고 싶음
Þ 각각 40 만의 캐릭터 정보를 가진 2000 개의 셋의 랭킹 계산
공간 예상
- 캐릭터당 12byte 를 소모 한다고 가정
- Uid 4byte
- Score 8byte
- 12byte * 40 만개 ( 캐릭터 ) * 2000( 랭킹셋 ) = 약 9GB
Þ 메모리에 올리기엔 커서 디스크 자료 구조 사용
알고리즘 선택
- Binary Heap 으로 구현
- 디스크 기반에서 추가 / 삭제 / 검색이 무난한 속도
- 추상화 하기가 쉬움
왜죠 ?
너무 느리다
Heap 은 랭킹을 구할 때 마다 Heapsort 를 수행하는것
- O(Nlog(N)) 의 시간 복잡도 소요
- 2000 개의 랭킹을 갱신하는데 3 분 이상 소요
Redis 는 뭘 쓰지 ?
Indexed Skip List
- 1989 년에 나온 최신 (!) 자료 구조
- 검색 / 추가 / 삭제 / 랭킹 O(log(N))
어떻게 ??
* http://www.slideshare.net/jongwookkim/skip-list
Skip List
- Linked list 에 추가 포인터가 있는 자료 구조
- 모든 요소는 정렬 되어 있다
head
11
22
30
42
51
65
73
NIL
NIL
NIL
* http://www.slideshare.net/jongwookkim/skip-list
Skip List
- 일반적인 linked list
- 검색하려면 모든 노드를 탐색 해야 함
head
11
22
30
42
51
65
71
80
NIL
* http://www.slideshare.net/jongwookkim/skip-list
Skip List
2 개 마다 추가 포인터를 둔다면
[N/2]+1(N 은 리스트의 크기 ) 만 탐색하면 됨
head
11
22
30
42
51
65
71
80
NIL
NIL
예를들어 71 을 찾는다면
* http://www.slideshare.net/jongwookkim/skip-list
Skip List
2 개 마다 1 개의 추가 포인터 , 4 번째 요소마다 2 개의 추가 포인터
[N/4]+2 만 탐색하면 됨
head
11
22
30
42
51
65
71
NIL
NIL
NIL
* http://www.slideshare.net/jongwookkim/skip-list
Skip List
- 2i 번째 노드는 2i 개의 포인터를 가지도록 하면 ?
- O(log(N)) 개만 탐색
* http://www.slideshare.net/jongwookkim/skip-list
Skip list
이러한 구조를 계속 유지한다면
- 검색은 매우 빠르다
- 삽입 / 삭제가 너무 느리다
* http://www.slideshare.net/jongwookkim/skip-list
Skip list
추가 할때 포인터를 확률적으로 결정한다면 ?
- 노드 중 1/2 은 포인터 1 개
- 노드 중 ¼ 은 포인터 2 개
- 노드 중 1/2i 은 포인터 i 개를 가짐
* http://www.slideshare.net/jongwookkim/skip-list
Skip list
- 포인터를 랜덤 하게 가지므로 구조를 유지할 필요 없음
- 하지만 높은 확률로 O(log(N)) 시간에 동작
* http://www.slideshare.net/jongwookkim/skip-list
Skip List insert
- 연관되는 포인터를 저장하면서 탐색
- 추가할때 구조 변경 없이 포인터만 바꿔주면 해결
head
12
22
34
40
55
60
70
NIL
NIL
NIL
55 를 추가한다면
Skip List delete
- 연관되는 포인터를 저장하면서 탐색
- 추가와 마찬가지로 구조 변경 없이 포인터만 바꿔주면 해결
head
12
22
34
40
55
60
70
NIL
NIL
NIL
55 를 삭제한다면
Skip List rank
- 포인터마다 skip 하는 길이를 기록
- 검색 후 값을 더하면 순위
head
12
22
34
40
55
60
70
NIL
NIL
NIL4
2
1 1
2 2
1 1 1 1 1
60 의 순위는 ?
4+2 = 6
Skip List 를 디스크에
- 포인터를 파일 offset 으로 대체
- 추가 시에 파일 끝에 요소 추가
- 삭제 시에 삭제한 offset 보관 , 추가시 재사용
head 1 2 3
head [4] [6] [NIL] 1 [6]
2 [9] [NIL] 3 [NIL] [NIL]
Skip List 를 디스크에
- Memory Mapped file 사용
- 포인터로 접근 가능해서 편리함
- 우선 고정 크기로
- 만들기 쉽고 SSD 용량은 상대적으로 저렴함
Heap 과 비교하면
- 오버헤드 제외 하고 디스크 기반에서 매우 유리
- 디스크는 메모리대비 가격도 저렴
Heap Skip List
추가 / 삭제 상황에 따라부모 노드와 swap
포인터만 교체
랭킹 구하는 시간 O(Nlog(N)) O(log(N))
오버헤드 X 요소 만큼 추가 포인터
실제로 구현 해보니
- 한 캐릭터 업데이트에 50ms 정도 소요
- Xeon E3-1231v3, SSD 기준
- 원하던 100ms 안에 랭킹 갱신이 가능
- SSD 용량 80GB 소요
- 가변 크기를 사용한다면 많이 줄일 수 있는 부분
결과
- 구현은 완료 !
- 현재 사이트 제작 + 다듬는 중 6 월말 오픈 예정
- 기대해주세요 !
감사합니다
Reference
• Skip Lists: A Probabilistic Alternative to Balanced Trees
• http://epaperpress.com/sortsearch/download/skiplist.pdf
• 스킵 리스트 소개 자료 ( 김종욱님 )
• http://www.slideshare.net/jongwookkim/skip-list