우리 FISA 백엔드 세미나를 준비하며 어떤 주제로 발표를 할지에 대한 고민이 많았던 것 같다.
나의 최근 관심사는 '시스템 아키텍처'였기 때문에 처음에는 '가상 면접 사례로 배우는 대규모 시스템 기초' 도서를 바탕으로 DAU(일간 활성 사용자)에 따른 아키텍처의 확장을 주제로 발표하려 했지만, 백엔드 주제와는 약간 떨어져있다고 생각했고, 코드 등 실습으로 된 내용을 보여주기엔 부족하다고 생각하였다.
그래서 아키텍처 확장이라는 키워드는 가져가지만, 데이터베이스만 중점으로 하여 '아키텍처 확장에 따른 분산 데이터베이스'를 주제로 선정하였다.
서비스의 사용자가 증가함에 따라 스케일아웃을 통한 서버의 증설로 사용자들을 수용할 수 있지만, 서버가 점점 늘어남에 따라 수많은 사용자의 요청이 발생할 경우, 데이터베이스에도 병목이 생기게된다.
데이터베이스 하나로 여러 대의 서버의 읽기/쓰기 작업을 처리할 경우, 특히 쓰기 작업의 경우 시간이 상대적으로 오래걸리기 때문에 병목이 발생할 수 있고, 이는 사용자의 이탈로 이어질 수 있다.
이뿐만이 아니라 과도한 트래픽이 몰릴 경우, 데이터베이스는 '단일 장애 지점'이 될 수 있고, 이러한 문제를 해결하기 위해선 데이터베이스의 확장이 불가피하다.

막연히 확장한다고 생각하면, 'DB의 수를 늘려서 부하 분산을 진행하면 될것이다'라고 생각할 수 있지만, 이 경우엔 DB마다 요청에 따른 데이터가 달라지게 되고, 데이터 정합성에 문제가 생기게 된다. 또한, 데이터베이스간 동기화를 진행하더라도, 동기화 과정에서 데이터 정합성이 일그러지게 된다.
이러한 문제를 해결할 수 있는 방법으로 Replication을 이용하여 데이터베이스를 복제하고, Master/Slave DB를 각각 지정하여 Master DB에서는 쓰기를 담당하고, Slave DB에서는 읽기만 담당하도록 한다.
Why?
상대적으로 오랜 시간이 소요되는 쓰기 작업을 Master DB에서 담당하고, 해당 DB를 복제한 여러 Slave DB들은 읽기 작업만 수행하여 병목을 해소한다. 또한 대부분의 서비스에서는 읽기 요청이 쓰기 요청보다 훨씬 많기 때문에 여러 Slave DB를 통해 읽기 요청을 빠르게 처리할 수 있기 때문이다.
Replication을 도입할 경우, Master/Slave의 역할 분리를 통한 부하 분산과 더불어 장애 극복에도 용이하다. 하나의 DB가 다운될 경우, Slave들의 경우 다른 Slave로 요청을 보내도록 하여 장애를 극복할 수 있으며, 만약 Master DB가 다운되어 쓰기 작업을 수행하지 못할 경우에는, 미리 지정해놓은 Slave DB를 Master DB로 승격시켜 Master DB가 복구되기 전까지 Master DB의 역할을 수행할 수 있다.
MySQL 기준 Master와 Slave간 데이터 정합성을 위한 데이터 복제 방법은 아래와 같다.
GTID (Global Transaction identifier)
원본 서버(소스)에서 커밋된 각 트랜잭션과 관련하여 생성되고 연결되는 고유 식별자
GTID는 모든 서버에서고유한 값으로 존재하며, Master에서 커밋되는 트랜잭션과 Slave에서 복제되는 복제 트랜잭션을 구분하는데 이용된다.
GTID는 클라이언트의 트랜잭션이 바이너리 로그 (Binary Log)에 기록되는 경우에 생성되어 고유 값이 할당된다
Replication을 적용하였더라도, 서비스가 흥행하여 사용자가 점점 많아질 경우, Slave를 무한정 확장하는 것이 아닌, Cache를 사용하여 DB의 부담을 덜어주자.
이렇게 서버와 DB간의 Redis와 같은 캐시를 두어 반복되는 동일한 요청에 대해서는 Cache를 통해 빠르게 응답할 수 있도록 하여, DB까지 요청이 전달되는 것이 아닌 캐시가 처리하도록 하여 부하를 줄여 줄 수 있다.
마지막으로 소개할 분산 데이터베이스 적용 방법은 Sharding이다.
Sharding
샤딩은 대규모 데이터를 관리하기 위해 데이터를 작게 분할하고, 이를 여러 서버에 분산하여 저장 및 처리하는 기술이다. 데이터베이스를 수평적으로 분할하여 부하를 분산하는 방식이다.
위의 사진과 같이 데이터베이스를 스케일 아웃하여 증설하고, 각 샤드별로 Master/Slave DB를 두어 부하를 분산할 수 있다. Shard를 나누는 방식에는 여러가지가 존재하지만, 간단하게 두 가지만 소개해보려 한다.

모듈러 샤딩은 모듈러 연산을 통해 데이터의 PK의 모듈러 연산 값을 이용해 어떤 샤드에 해당 데이터를 저장할지 정하는 방식이다.

레인지 샤딩은 PK의 범위를 기준으로 DB를 특정하는 방식이다.
이렇듯 샤딩을 통해 데이터를 나누고, 해당 데이터를 각 샤드에서 읽기/쓰기하는 방식으로 부하를 분산할 수 있지만, 샤딩은 고려해야할 부분이 많다.

유명 인사 문제라고도 불리는 Hot Spot 문제는 특정 샤드에 트래픽이 몰리는 데이터들이 여러개 존재하는 경우 해당 샤드에 부하가 쏠리게 되는 문제이다. 모듈러 샤드나 레인지 샤드를 적용할 경우 해당 데이터들을 처리할 수 있는 방법이 없다.
따라서 샤딩을 적용할 때에는 샤드를 어떻게 분배할지에 대해 많은 고려가 필요하다.
사용자 수가 증가함에 따라 데이터베이스에 가해지는 부하를 어떻게 분산할 것인지에 대해 여러 방법들을 살펴보았는데, 분산 데이터베이스를 적용한다면 부하를 분산하여 안정적인 서비스를 운영할 수 있다는 이점은 존재하지만, 해당 방법을 적용하는데에 초기 설정, 장애 복구, 정합성 유지 등과 같이 고려해야 할 점이 늘어난다.
이러한 트레이드 오프가 있지만, 안정적인 서비스 운영을 위해 필수적인 요소이므로 견고한 설계와 꾸준한 모니터링 및 관리 체계를 통해 적절한 시기에 도입하는 것이 운영 비용 및 안정적인 대응에 중요한 요점이라 생각된다.