사건의 발단은 곰터뷰에서 우리의 쿼리깎기와, 이전 멘토님의 코멘트중에 있었다.
DB를 더 빠르게 할 수 없을까?
데이터베이스 서버가 꺼졌다면, 우리는 어떻게 대응해야할까? 상태확인만으로 괜찮을까?
당시에는 이걸 그냥 상태확인으로 넘기고, 다른 생각을 해보지 못했었다.
이번 기회에, 이걸 어떻게 해결하는게 좋은지 생각을 하려고 했다.
일단 현 상황부터 확인을 해야헀다.
우리의 DB는 단 하나의 서버로 동작한다.
해당 서버가 터져버리거나, 불의의 사고로 소실되었을 때, 우리는 대처할 수 없다.
그렇다면? 간단하게 데이터베이스를 여려 개 두어서 동기화를 시키거나, DB의 값들을 로컬에 따로 저장했다가, 로컬 스토리지의 데이터를 활용하는 방법 정도가 생각났다.
그런데, 로컬이라는 것도 결국에는 애플리케이션의 수명과 데이터의 수명이 같아지기 때문에, 데이터베이스를 하나 더 만들어야 겠다는 생각이 들었다.
How does Replication look like?
데이터베이스를 Master, Slave로 두 개로 나누어 사용을 하게 된다.
테이블에 변화가 있는 경우에는 Master DB로, 단순 조회의 경우에는 Slave DB로 접근하게 된다.
Master DB를 통해 테이블에 변경이 있을 경우, 해당 변경사항을 Slave DB에 적용시키는 방식으로 동작한다.
그렇다면 Master DB에서 Slave DB로 이동하는 과정은 어떻게 될까?
- Master DB에서 테이블의 데이터 조작이 일어난다. (쓰기/수정/삭제)
- Master DB에서 조작 명령에 대한 트랜잭션들에 대한 로그 파일을 작성한다.
- Slave DB는 Master DB에서 일어난 변경사항을 받아와 Slave DB의 로컬 바이너리 로그에 저장한다.
- 변경사항을 Slave DB의 테이블 데이터에 적용시킨다.
이렇게 4가지 방식으로 진행한다.
이 때 Replication에서 두 가지 생각해야 할 부분이 있다.
- 어떤 방식으로 동작하는지
- 어떤 방식으로 변경사항을 로그를 주고 받을지
How does Replication Work?
Master DB에 쿼리를 보내는 방식은 늘 똑같다.
JPA, QueryDSL, TypeORM, Prisma등등 동일한 방식으로 쿼리를 보낸다.
그렇다면, Master -> Slave로 데이터를 복제시키는 방법은 어떻게 될까?
이를 위해서 우리는 두 개의 쓰레드를 알아야 한다.
mysql이라는 프로세스는 이 때 IO Thread, SQL Thread를 사용하게 된다.
IO, SQL만 싹 빼서 생각해보면, 우리는 이게 너무 익숙하다.
Standard Input/Output(stdio)에 있는 그 IO와, 우리가 데이터베이스를 공부할 때 배우는 그 관계형 데이터베이스의 SQL이다.
IO Thread는 Master에서 로그로 적어둔 변경사항들을 Master에서 바이너리 로그(2진로그)로 Slave에 받아온다.
SQL Thread는 바이너리 로그를 실행시켜주는 쓰레드이다.
아쉬운 것은 두 스레드는 기본적으로는 동기적으로 동작해야한다는 것이다.
비동기적으로 로그 A와 로그 B가 있을 때, A-SQL과 B-SQL이 동시에 같은 데이터를 조작한다면 둘 중 나중에 온 로그가 적용되어야 하지만 그러지 못할 위험이 있다.
How does Replication Make Log?
위에서 계속 나오는 말들이,
바이너리 로그
이거다.
근데, 바이너리 로그가 어떻게 생겼을까?
부스트캠프에서 그룹 프로젝트를 할 때에 멘토님께서는, 로그는 사람들마다/팀마다/요구사항마다 모두 다른 로그를 만들 수 있다고 한다.
그렇다면, DB Replication도 상황에 따라 다른 로그를 남길 수 있지 않을까?
실제로 로그를 남기는 방식은 Statement-Based Replication(SBR), Row-Based Replication(RBR), Mixed Replication로 되어있다.
바로 답을 찾기전에 스스로 생각해보는 시간을 가져보았다.
말 그대로 구문을 기준으로/가로줄(레코드겠지?)기준으로/섞어쓰기 정도가 될 것 같다.
그렇다면 Statement-Based에는 데이터를 조작시킨 일련의 쿼리를 로그로,
Row-Based는 데이터 조작으로 변경된 레코드들을 로그로,
Mixed는 둘중에 효율 좋은 놈으로 담는다는 생각이 들었다.
Statement-Based, Row-Based는 이해가 잘 된다.
Statement-Based로 했을 경우에는, 하나의 트랜잭션으로 20개씩 레코드가 바뀐다면, 효율적일 것이다.
Row-Based로는 20줄의 로그가 발생될 것을 Statement-Based로는 단 한줄에 기록 가능하니까. (장점)
하지만, 만약 쿼리에 timestamp/UUID가 존재한다면, Master와 Slave사이에 괴리가 발생할 수 있다는 문제가 있다. (단점)
Row-Based는 정 반대의 장/단점을 가지고 있다.
timestamp/UUID가 존재하더라도 레코드 자체를 가져가는 것이기 때문에, 데이터의 괴리가 발생하지 않는다. (장점)
하지만, 필연적으로 Statement-Based 이상의 로그가 존재하는, 너무 많은 로그의 문제가 있다. (단점)
이렇게 보았을 때,
Statement-Based가 더 빠르지만 데이터의 괴리가 발생할 수 있다는 것,
Row-Based는 더 느리지만 데이터 일관성을 유지할 수 있다는 것을 알 수 있다.
그렇다면 Mixed는 어떻게 동작할까?
이를 조금 더 공부해봐야겠다는 생각이 들었다.
How does Mixed Replication Work?
Mixed Replication을 할 때 Master DB에서 로그를 작성하는 부분부터, Slave DB가 데이터를 조작하는 부분까지를 모두 정리해보았다.
- Master DB는 SBR과 RBR에 대한 로그를 모두 저장한다.
- Slave DB는 IO Thread에서 SBR과 RBR중에 동적으로 해당 트랜잭션에 대해 가져올 로그를 고른다.
- 쿼리 하나로 여러 레코드가 조작된다면 SBR, 단건이라면 RBR을 가져온다.
- UUID, timestamp와 같이 매 순간 레코드의 데이터가 달라질 수 있는 쿼리라면 RBR을 가져온다.
- SQL Thread는 가져온 로그를 적용시킨다.
이렇게 보면, 바로 이해가 되기도 했다.
그런데, RBR과 SBR의 로그가 해석방식이 다르다면 어떨까라는 생각이 들었다.
만약 RBR이 바이너리로 레코드 기본키가 a인 레코드에 대한 데이터만 딸랑 준다면 어떨까? 라는 생각이 들었다.
근데, 그런 생각할 필요가 없다.
RBR도 쿼리였다. update 쿼리로 데이터를 주고받는 방식이었다.
이제 How 말고, Why를 생각해보자.
사실 DB Replication의 원리적인 부분에 대해서는 '왜?'라는 생각이 (현재로는) 더 나지 않을 정도로 찾아보았다.
그런데, 근원적인 생각을 해야한다.
왜 우리는 DB를 복제해서 데이터 조작/조회를 나눌까?
그리고 DB Replication은 어떤 장점이 있을까?
애플리케이션의 전체적인 속도를 증가시킬 수 있다.
쓰기 전문 DB, 읽기 전문 DB가 분리된다.
데이터베이스에 대한 부하 자체가 줄어들게 된다.
또한 애플리케이션에서 발생하는 select 쿼리에 대한 최적화가 끝났다면, Slave DB만을 이용한 조회는 단일 DB보다 빠르게 동작할 수 있다.
백업 DB로서 존재가치가 있다.
Slave DB를 테스트, 데이터 분석에 이용할 수 있다.
테스트를 하면서 데이터가 조작되더라도, Master DB로 다시 동기화시켜주면 된다.
DB서버 장애, 시스템 유지보수에 어느정도 대응할 수 있다.
현재 우리가 사용하던 Master DB가 장애가 발생했다면 어떻게 될까?
Slave DB를 Master DB로 올리고, Master DB를 Slave로 내리게 한다.
이런 방식으로 시스템 유지보수시에도 Slave DB를 마스터로 승격(?!)시키고 Master DB를 Slave로 돌리면 된다.
사실 DB Replication에서 데이터 조작/데이터 조회를 분리하는 구조는 마치 cqrs와 비슷하다는 생각이 들었다.
확실히 이를 합쳤을 때 성능적으로도, 구조적으로도 더 안전할 수 있다는 생각이 들었다.
NestJS에서 데이터베이스는 TransactionalContext를 연결해 트랜잭션을 운반한다.
그런데, 이런 TransactionalContext를 DB-Application이 아니라, ReadDB-Application-WriteDB로 둔다면 더 효율적일 것이다.
어찌보면 CQRS라는 것도(아직 학습은 안했지만) DB Replication을 통해 생성된 더 효율적인 구조일 수 있다는 생각이 든다.
여기까지 보았을 땐 좋다고 생각했지만 무서운 상황을 그려보았다.
Master, Slave가 모두 불타버리면?!
그러면 데이터가 다 손실되는거다.
기왕 DB 서버를 두 개 두어서 인프라적인 학습을 해본다면,
Master, Slave를 위한 안전빵 서버를 여러 개 두면 어떨까라는 생각이 들었다.
그래서 다음엔 DB 클러스터링도 학습해봐야겠다.
그럼...twenty thousand...🔥
'CS > 데이터베이스' 카테고리의 다른 글
[CS] 데이터베이스 - 키 (0) | 2023.06.09 |
---|---|
[CS] 데이터베이스 - 속성의 타입 (0) | 2023.06.09 |
[CS] 데이터베이스 - 기본 구조 (0) | 2023.06.09 |