Operation/System Architecture

대규모 서비스 - 샤딩을 처리하는 솔루션 정리! (+ DB Shard Proxy)

JaeHoney 2023. 6. 23. 19:50
반응형

이전에 Java Spring에서 샤딩을 처리하지 못하던 이슈를 해결한 경험이 있다.

그때 당시는 해당 기술이 샤딩(Sharding)인지도 몰랐다. 참고로 당시 0년차였다..

 

추가로 당시는 대단한 일을 한 것처럼 느껴졌지만 지금은 샤딩을 처리하는 다양한 기술들을 알게 되었다.

  • Naver D2, 우아한형제들 기술블로그, 카카오 뱅크 발표, 요기요 기술 블로그, ...

그래서 이전에 구현한 코드를 어떻게 개선할 수 있을 지 생각해보기 위해 포스팅을 하게 되었다.

샤딩을 처리하는 방법

DB 서버 1대로 트래픽이나 저장량이 감당이 안될 때, Local cache나 Global cache를 동원하기도 한다. 그리고 Master-slave 패턴을 도입해서 읽기 작업의 경우 여러 대의 Slave로 구성하기도 한다.

 

하지만 총 저장 용량이 많거나 부하가 클 때는 여러 개의 DB서버로 분리해야 하고 이를 샤딩 (Sharding)이라고 한다.

 

샤딩은 크게 아래의 솔루션으로 나눌 수 있다.

  1. 앱 서버에서 처리하는 방법
  2. DB 단위에서 처리하는 방법
  3. 플랫폼(추가 인프라) 단위로 처리하는 방법

1. DB 서버에서 자체적으로 처리하는 방법

대표적으로 Spider, nStore, MongoDB, MySQL Fabric 등의 기술이 있다.

 

Spider를 예로 들면 MySQL의 스토리지 엔진 중 하나이다. Spider를 사용하면 디비 서버의 환경 또는 구조를 변경하지 않고도 샤딩이 가능하다.

 

Spider는 MySQL의 파티셔닝 기능을 사용해서 각 파티션마다 다른 DB서버로 데이터를 저장할 수 있게 하는 구조이다.

 

다른 DB 서버와 분산 트랜잭션, 조인 등도 할 수 있다는 장점이 있다.

 

아래의 단점들도 있다.

  • SPIDER 테이블을 Drop해도 데이터 노드의 데이터는 사라지지 않는다. (링크만 제거 된다.)
  • TRUNCATE 명령문을 사용하면 모든 MySQL Server의 데이터를 지워버린다.
  • 파티션을 전제로 한다.
  • 전체 텍스트 검색과 R-Tree 인덱스를 지원하지 않는다.
참고: http://file.pmang.kr/images/noc/img/pdf/noc_tr2_se9_choi.pdf

2. 앱 서버 단위에서 처리하는 방법

DB 서버에서 자체적으로 처리하면 DBMS에 종속적이라는 단점이 있다.

 

추가로, 기존의 DB 서버를 마이그레이션 하는 데에도 큰 비용이 소요된다.

  • 이때 앱 서버에서 처리하는 방법을 고려할 수 있었다.

방법으로는 대표적으로 Hibernate Shards, Shardingsphere-jdbc 등이 있다.

 

샤딩 스피어는 아파치에서 제공하는 오픈 소스로 현재에도 Fork 6.1k, Starred 17.5k로 검증된 라이브러리라고 볼 수 있다.

 

샤딩 스피어를 사용하면 앱 서버에서 샤딩 전략(Strategy)를 만들면 해당 전략에 따라 DB 서버를 매핑해서 처리해준다.

  • 높은 효과에 비해 사용이 쉬운 편이다.

아래는 Shardingsphere-jdbc의 아키텍처이다.

아래 레퍼런스를 보면 카카오 뱅크 다니시는 분의 샤딩 스피어를 활용한 이야기가 자세히 적혀있다!

참고: https://www.sosconhistory.net/soscon2019/content/data/session/Day%202_1430_2.pdf

3. 플랫폼 단위에서 처리하는 방법

이전에 샤딩은 애플리케이션 서버 레벨에서 구현하는 경우가 많았다.최근에는 이를 플랫폼 차원에서 제공하려는 시도가 늘고 있다.

 

별도 인프라를 통해 Middle tier에서 샤딩을 해결할 수 있다면 DB나 앱 서버에서 해당 책임을 분리할 수 있으므로 큰 장점이 있다..!

 

대표적으로는 Shardingsphere-proxy, Spock proxy(MySQL Proxy 기반), Gizzard(Twitter에서 만든 분산 관리 라이브러리), Cubrid shard등이 있다.

 

아래는 Shardingsphere-proxy의 아키텍처이다.

해당 프록시를 활용하면 앱 서버 단에서 모든 질의는 DB 서버가 아닌 해당 플랫폼을 통해 이루어진다. 아래는 야놀자 주문 서비스에서 ShardingSphere-proxy를 도입한 것을 정리한 내용이다.

ShardingSphere-proxy로 향하는 단일 포인트의 경우 Proxy 서버를 분산해서 해결할 수 있다.

 

Gizzard

Gizzard는 네트워크 서비스 미들웨어로 네트워크를 통해 데이터 저장소에 복제하도록 디자인되어있다. 그래서 데이터 저장소를 MySQL, SQL Server, Redis 등 어떠한 솔루션을 선택해도 큰 문제가 없다.

 

Twitter에서 만든 Gizzard의 경우 특별한 인스턴스 형태는 없는 구조이고, JVM 위에서 실행되며 상당히 효율적이라고 한다.

참고: https://gywn.net/2012/03/gizzard-a-library-for-creating-distributed-datastores/

Nbase-T

Naver에서는 자체 개발한 Nbase-T라는 저장 플랫폼을 사용하고 있다.

 

Nbase는 무중단으로 샤드 데이터베이스를 추가(Scale Out)하면서 트래픽 증가에 대응할 수 있다. Nbase-T는 RDBMS를 기반으로 하고 마이그레이션 및 실시간 데이터 동기화가 용이하다.

참고: https://deview.kr/data/deview/session/attach/네이버_페이_배송_EDA_전환기.pdf

여기부터는 RDB의 샤딩 솔루션을 넘어 추가적인 내용에 대해 다룬다.

MySQL의 한계

DB에서 flags (bigint) 필드만 가지고 비트 연산 쿼리를 수행하는 경우를 생각해보자. MySQL에서는 flags (bigint) 필드만 가지고는 비트 연산 쿼리에서 인덱스를 활용할 수 없다.

 

내부적으로는 특정 범위 내의 한정된 개수를 정해진 순서로 비교해 그 값이 참인 경우 결과를 반환하는 수 밖에 없게 된다.

 

그러므로 범위가 큰 데이터를 대상으로 비트 비교 연산 쿼리를 수행하기보다 정해진 사용자의 데이터 내, 특정할 필드 순서, 제한된 개수만큼을 선택해야 빠르게 결과를 가져올 수 있다.

 

찾고자 하는 조건의 데이터가 처음과 마지막에 흩어져 있는 경우에는 해당 범위 내 전체 데이터를 모두 비교하게 될 것이다. 즉, 최악의 경우 O(n)이기 때문에 적절한 범위 제한을 통해 해당 n을 줄이는 방법 밖에 없다.

 

이러한 한계 때문에 MySQL만으로는 빠른 응답 속도를 보장할 수 없어서 캐시를 도입해야 한다.

Arcus

MySQL이 제공하는 비트 비교 연산을 제공하는 메모리 기반의 캐시는 현재 없다. 심지어 다양한 자료구조를 제공하는 Redis에도 해당 기능은 없다.

 

그래서 Naver에서는 확장 개발한 Memcached cluster인 Arcus에서 256비트의 플래그에 대해 비트 비교 연산 기능을 제공한다.

 

네이버 동영상 플랫폼 팀에서는 아래와 같이 위에서 잠깐 언급했던 Arcus라는 캐시를 도입해서 RDB의 결과를 저장하고 있는 형태를 사용한다.

Arcus에 Object Storage와 RDB의 결과를 캐싱함으로써 처리 속도를 개선한다.

Redis Cluster

추가로 MySQL을 대체할 데이터베이스로 Redis를 선택할 수도 있다.

 

Redis가 주목 받는 이유는 빠른 처리 속도와 검증된 소프트웨어 안정성에 있다. 모든 데이터를 메모리에 상주시켜 처리하고 이벤트 기반의 네트워크 비동기 입출력 처리를 해서 한 Redis 서버는 초당 수만 건 이상의 요청을 처리할 수 있다.

 

네이버에서는 아래의 레퍼런스 처럼 Redis와 Zookeeper 등을 조합해서 Redis cluster를 구성하기도 하고

참고: https://d2.naver.com/helloworld/294797

현재는 nBase-ARC라는 자체적으로 개발한 Redis-Cluster 플랫폼을 사용하는 것으로 보인다. (꼭 한번 보길 권한다!)

이외에도 Memcached, Cassandra 등 좋은 솔루션이 많이 있다.

마무리

해당 부분을 공부하면서 내가 개발했던 Sharding 라이브러리(https://jaehoney.tistory.com/180)의 가장 아쉬운 점은 불편함이다.

 

라이브러리를 개발하면서 필수 요소 중 하나가 편리함이다. 라이브러리를 사용하는 사용자 측에서 별도의 설정을 해야 하거나, 사용 방법을 익혀야 한다면 개선 여지가 있는 것 같다.

  • 내가 개발한 라이브러리는 사용하는 측에서 여러모로 신경을 할애해야 한다.

그때 당시는 지식도 부족했고, 사내의 서버 자원을 할당받아서 전사에 시스템을 적용한다는 것이 꺼려졌었다. (0년차 라서..)

지금 라이브러리를 개편한다면

현재 사용중인 각 애플리케이션 서버에서 처리하는 방식이 아닌 Shard DB Proxy 방식을 사용해서 풀 것 같고,

 

JWT에 샤드 키인 dbIp가 들어있는 부분이 있다. (라이브러리를 사용하는 JavaApplication에서 사용자 요청의 JWT에서 dbIp로 DataSource를 선택하도록 처리가 필요한 부분)

 

해당 부분은 officeId로 dbIp를 조회할 수 있고 office가 많아 봐야 20만개 정도니까 officeId별 dbIp를 Redis나 Memcached 같은 global cache로 관리하고 변경 사항을 이벤트 처리해주면 되었을 것 같다.

 

그러면 라이브러리를 사용하는 측에서는 DB 샤딩에 전혀 신경쓰지 않아도 되고, DB 서버가 추가되는 부분을 Shard DB Proxy 서버에 동기화만 해주면 된다. 👍

참고

반응형