이 글은 NHN Forward 2019에서 본 것을 정리한 글입니다. 원본 영상은 https://www.youtube.com/watch?v=8Eb_n7JA1yA 이니 참고해주세요.
샤딩, Sharding
은 영어 의미로 조각을 의미한다. 즉 샤딩은, 어떤 큰 한가지를 나눈 것을 의미한다고 할 수 있다.
데이터베이스를 생각해보자. 우리는 데이터베이스에 다양하고 많은 데이터를 저장한다. 만약 데이터베이스의 사용자가 많아지고 쿼리가 복잡해지면, 데이터베이스가 하나만 있어서는 모든 것을 처리하기엔 힘들다. 마치 사람에게 여러가지 일을 한번에 하라는 것과 비슷할것이다. 만약 내가 굉장히 바쁘다면, 내 몸이 여러개였다면 좋을텐데... 하는 생각을 하게 된다. 이것이 샤딩과 유사하다.
모던 데이터베이스시스템은 이런 분산 DB
를 고려한 시스템도 존재한다. 그리고 우리에게 편한 DB는 RDB
이다. 사용하기 편하고, 여러 기능들이 있으며, 유저 커뮤니티가 크고 검증되었다는 장점이 있다.
하지만 만약 사용하고있는 RDB가 분산환경을 고려하지 않고 설계된 DB라면, 새로운 환경을 맞아 변화를 꾀하려 할 것이다. 상대적으로 풍부한 기능을 사용하면서 데이터 확장을 꾀할 수 있는 방법은 RDBMS를 샤딩하여 사용하는 것이다.
샤딩
은 물리적으로 다른 데이터베이스에 데이터를 수평 분할 방식으로 분산 저장하고 조회하는 방법을 말한다. 만약 여러 게임이용자가 다른 플랫폼, 퍼블리셔를 가질 때, 각각의 플랫폼이나 퍼블리셔마다 분산하여 저장하는 방법을 꾀할 수 있다.
여러 대의 서버가 있고, 여러 대의 DB가 있다. 서버와 DB가 1:1 맵핑이 되지 않지만 모든 서버는 빠른 시간 내에 DB를 조회할 수 있어야 한다.
문제는 그 수가 많다는 것이다.
서버의 수: 200
DB의 수: 30
단순 산수로 각 MySQL서버마다 60000개의 커넥션이 필요하다. DB에 절대적으로 과부하가 걸릴 것이다. 그렇다면 미들웨어를 놓아서 서버와 DB 사이에 라우팅 기능을 하면 어떨까?
요구사항이 이렇다고 하자. 그렇다면 만약 미들웨어로 프록시를 둔다고 생각해보자. 프록시와 요구사항을 만족하려면 아래와 같이 정리할 수 있다.
3가지 후보가 있었다.
당연히 ProxySQL
을 선택한다. 하지만 자료 정리가 잘 되어있지 않음
이란 단점도 존재한다.
그렇다면 정리를 해보자. 최초의 요구사항과 미들웨어 요구사항을 한번에 정리해본다.
에는 MySQL 네이티브 프로토콜, 쿼리 기반 라우팅, 쿼리 재사용
ProxySQL은 가능하다.
GPL 오픈소스 라이선스를 가진다.
즉, ProxySQL
을 미들웨어로 선택하여 아키텍처 구조를 변화시킨다.
3가지 그룹으로 나뉜다.
가상 머신 N개 안에 각각 게임 서버와 ProxySQL을 둔다. 이는 1차적으로 커넥션 수를 줄이기 위함이다. 다음 그룹으로 가기 전에 로드밸런서 가 존재한다.
프록시 내에 ProxySQL을 클러스터로 삽입한다. 단, 아직 신뢰성이 낮아(성능을 보지 못해) 기존에 사용하던 DB Service node를 OR로 배치하여 혼용한다.
DB와 유저를 실제 DB로 라우팅하는 그룹을 두어 RouteDB와 Redis(RouteDB를 위한 Cache DB)를 둔다.
여기서 ProxySQL의 역할은 다음과 같다.
참고) 쿼리 룰: 어느 유저와 맺은 커넥션인지에 따라 다른 DB로 쿼리를 보내는 룰을 사용한다.
이제 스키마 샤딩으로 인해 유저 수에 따라 유연하게 대처할 수 있다. 유저 수가 증가하면 서버를 나누고 감소하면 합친다. 즉, 논리 DB는 1개지만 상황에 따라 물리 DB를 1~N개로 나눌 수 있다. 한 대의 MySQL에 10개의 스키마를 생성하고 10대의 MySQL서버로 서비스를 한다면 총 100개의 스키마로 샤딩을 하게된다. 관리는 복잡하지만 부하를 그만큼 줄일 수 있다.
유저의 데이터 입장에서 자신의 고유한 정보를 고유 스키마에 저장해야한다. 이 때, '모든' 유저와 스키마 사이에 맵핑이 필요한데, 이를 3번 그룹에서 봤던 'RouteDB'에서 관리하는 구조를 가진다. 그리고 이를 1차적 부하를 막기위한 Redis를 두었다.
그렇다면 DB를 조회하기 위해 다음과 같은 플로우가 존재한다.
동접자가 200만명이라 하였다. 과연 2개의 서버로 그것이 해결이 될까? 당연히 병목현상이 발생할 것이다. 병목은 곧 서비스 장애를 말한다. 또한 쿼리를 여러번 날려야 하는 불편함과 이의 코드 복잡성이 존재한다. 만약 RouteDB와 Redis를 구조에서 없앨 수 있다면 성능상 개선은 물론 신뢰성도 확보할 수 있다.
RouteDB를 fade-out하기 위해 구조를 다시 바꾼다. 단, 원래 사용하려고 했던 기능, 즉 특정 유저는 특정 스키마를 접근해야 한다는 것인데, 이를 다음과 같이 해결하려 한다.
UniqueID
라 한다.f(x)
이다.N
을 사용한다. 단 이 N은 게임 특성과 볼륨에 맞춰 넉넉하게 사용해야한다.즉 UserDB index를 다음의 식으로 표현하여 발급한다.
UserDB Index = f(UniqueID) % N
그렇다면 유저는 RouteDB에 쿼리를 날리지 않아도 자신의 스키마를 찾을 수 있다. 그렇다면 Redis도 필요없다.
이번엔 3단계지만, 더 간단한 3단계로 나눈다.
게임서버 - 프록시SQL - DB
동작 구조는 다음과 같다.
훨씬 간단해졌다.
이름 | CPU | RAM | 서버 수 | OS | VM | Etc |
---|---|---|---|---|---|---|
게임서버 | 8 | 16 | 100~150 | CentOS | O | Tardis / TCP |
프록시 - 유저 | 8 | 16 | 20 | CentOS | O | 최근 Scale-out |
Mysql - 유저 | 8 | 16 | 10 Sets(total 40) | CentOS | X | - |
처음 문제가 되었던 MYSQL Connection 부문에도 많은 성능 향상을 일으켰다. Proxy SQL의 부하가 가도 DB에는 부하가 가지 않게 구조를 개선했다.
추가로 쿼리 캐싱
과 쿼리 재사용
등을 사용하여 커넥션 양을 더 줄였다. 최종 실제 가용 커넥션은 3,000개지만 평소 사용량은 10% 미만이다.
여기서는 MySQL일 경우를 고려하여 진행하였다. 만약 다른 RDBMS로 서비스를 진행 중 다음과 같은 문제를 맞닥트렸다면 다른 해결방안을 모색해야 할 것이다.
그게 아니라면 해당 RDBMS에서 MySQL로 마이그레이션 해야하는데, 이는 다음 포스트에 개발자와 DBA를 위한 마이그레이션이라는 포스트로 정리하여 올려보도록 한다.
예고: Oracle to MySQL(or Opensource DB) migration for SQL developer and DBA
https://www.youtube.com/watch?v=8Eb_n7JA1yA&feature=youtu.be
https://d2.naver.com/helloworld/14822
https://tech.kakao.com/2016/07/01/adt-mysql-shard-rebalancing/
완벽하게 이해한것은아니지만.. 좋은글인거같아요 감사합니다!!!