1. 캐시 처리
어떤 요청이 온다. → Api 서버에서 요청을 받아서 DB에 쿼리를 날리고 DTO를 만들어서 응답한다.
와 같은 흐름이 있다라고 했을 때, 동일한 파라미터로 동일한 엔드포인트를 요청하는 경우에는 그 응답을 cache처리해서 DB 질의를 하지 않고 응답을 돌려주는 방식이 있다.
API 서버가 여러대인 경우?
Redis 등을 사용해서 Cache 전용 서버를 만들어서 API 서버 별로 만든 응답들을 캐시 서버에 저장해 두었다가 돌려준다.
클라이언트 → API 서버 → 캐시 서버 → DB 서버
와 같은 구조로 구성된다.
2. DB 서버 부하 분산
이제는 DB 서버도 뻗는 상황이 왔다고 생각해보자.
고가용성을 보장하기 위해서는 어떤 방법을 사용할 수 있을까?
클러스터링
DB와 DB 서버가 단일인 경우 DB 서버가 다운되면 DB와 연결된 서비스가 전부 다운된다.
이를 대비하여 여러 대의 DB 서버를 둬서 과부하를 방지한다.
이 때 각 서버 간에는 등기화를 해주어 일관성 있는 데이터를 보장하도록 만들어야 한다.
예를 들어 서버 A, B 두 대가 있다고 하면
- A에 쓰기 트랜잭션이 수행되고 커밋된다.
- 실제 디스크에 데이터를 쓰기 전에 다른 노드(B)에 데이터 복제를 요청한다.
- B에서 복제 요청을 수락했다는 신호를 보내고 디스크에 쓰기를 시작한다.
- A에서는 B에서 OK신호를 받으면 실제 디스크에 데이터를 저장한다.
클러스터링의 종류
- Active - Active
- DB 서버가 모두 Active 상태로 운영된다. 이러면 DB 서버가 로드 밸런싱 되는 효과가 있다.
- 대신 둘다 Active이기 때문에 위 구조처럼 하나의 DB를 공유하면 병목현상이 발생할 수 있다는 단점이 있다.
- Active - SatndBy
- 액티브 서버에 문제가 발생한 경우 스탠바이 서버를 액티브로 전환하여 사용한다.
- 이로 인해 병목현상은 해결된다.
- 다만 스탠바이 → 액티브로 전환되는 동안은 서비스를 사용할 수 없게 되고 로드밸런싱이 안 된다는 단점이 있다.
레플리케이션
DB를 마스터와 슬레이브로 나누어서 관리한다.
마스터는 쓰기와 관련된 것만 처리하고 슬레이브에서 읽기만 담당한다.
작동 방식은 이렇다.
- 마스터에 쓰기 트랜잭션이 수행된다.
- 마스터는 데이터를 저장하고 트랜잭션에 대한 로그(BIN LOG)를 파일에 기록한다.
- 슬레이브의 IO Thread는 마스터의 로그 파일을 복사한다.(Replay Log)
- 슬레이브의 SQL Thread는 Repaly Log를 한 줄씩 읽으면서 데이터를 저장한다.
레플리케이션을 사용하면 로드 밸런싱도 되고 마스터가 손상될 시 슬레이브의 데이터를 이용해 복구가 가능하기 때문에 데이터 안정성도 확보된다.
다만 이 방식은 데이터가 비동기 방식으로 동기화되기 때문에 DB 간 데이터 동기화가 완전히 보장되지 않는다는 문제가 있다.
샤딩 (Sharding)
동일한 스키마를 가지고 있는 여러 대의 DB 서버에 데이터를 작은 단위로 분산 저장하는 기법.
샤딩은 데이터를 서로 물리적으로 다른 서버에 데이터를 저장하므로 쿼리 성능이 향상되고 로드밸런싱도 된다. 따라서 scale-out 같은 방법이라고 할 수 있다.
하지만 이렇게 쪼개서 저장하면 데이터를 조인하는 것이 까다롭게 된다.
샤딩의 종류
- Hash sharding
- 샤드 키를 결정하는 데 해시 알고리즘을 이용하는 방식.
- 주로 데이터의 양이 일정 수준으로 유지될 것으로 예상될 때 적용.
- 샤드가 늘어나는 경우 해시 함수가 바뀌어야 해서 이런 상황이 일어나는 경우 데이터의 정합성이 깨지게 되므로 확장성이 좋지 않다.
- Dynamic sharding
- Hash sharding의 문제점을 극복하기 위해 등장.
- locator service를 이용해 샤드 키를 특정 범위 기준으로 분할한다.
- 샤드 키를 추가하기만 해도 되기 때문에 재정렬 비용이 들지 않는다.
실제 적용 사례