이 글은 Martin Kleppmann의 데이터 중심 애플리케이션 설계를 읽고 기억하고자 적는 게시글입니다.
5. 복제
복제는 네트워크로 연결된 여러 장비에 동일한 데이터의 복사본을 유지하는 것입니다. 그렇게 되면 동일한 데이터를 중복으로 저장하게 되고 저장 공간의 사용량이 늘어나게 되는데 왜 복제를 하는걸까요?
이유는 다음과 같습니다.
1. 지리적으로 사용자와 가깝게 데이터를 유지해서 지연 시간을 줄 일 수 있습니다.
2. 시스템의 일부에 장애가 발생해도 지속적으로 동작 할 수 있게 해 가용성을 늘립니다.
3. 읽기 질의를 제공하는 장비의 수를 확장해 읽기 처리량을 늘립니다.
이러한 장점을 위해 복제를 수행합니다. 그런데 아예 변경되지 않는 불변 데이터만을 저장하면 매우 편합니다. 왜냐하면 1번 저장 한 후에 더 이상 데이터가 변경되지 않는다면 그냥 스토리지에 넣고만 있으면 되니까요.
하지만 문제는 삭제 및 갱신이 가능 한 데이터의 복제가 어렵다는 점 입니다.
이러한 노드 간 변경을 복제 하기 위한 세 가지 인기있는 알고리즘인 단일 리더, 다중 리더, 리더 없는 복제를 알아보겠습니다.
3개의 각각의 알고리즘을 알아보기 위해서는 기준이 필요합니다.
복제를 할 때 고려 할 점
복제를 할 때는 몇 가지 고려 할 점이 있습니다.
1. 동기식 복제인지 비동기식 복제인지
2. 잘못 된 복제본을 어떻게 처리하는가?
이 두 부분을 중점으로 각 복제에 대해서 알아보겠습니다.
1. 리더와 팔로워
각 복제는 결국 리더의 유무로 나누어지는 복제 방식입니다. 데이터 복제에서는 데이터베이스의 복사본을 저장하는 각 노드를 복제 서버라고 합니다.
그러면 우리는 모든 복제 서버에 모든 데이터를 어떻게 저장 할 수 있을까요?
가장 일반적인 방법은 리더 기반 복제를 사용하는 것 입니다. 리더 기반 복제는 복제 서버 중 하나를 리더로 지정하고, 클라이언트는 데이터베이스에 쓰기를 할 때 그 요청을 리더에게 보내야 합니다. 그러면 리더는 가장 먼저 로컬 저장소에 새로운 데이터를 기록합니다.
리더 서버의 로컬 저장소에 저장 된 후 데이터 변경에 대한 복제 로그나 변경 스트림의 일부를 다른 복제 서버(팔로워)에 전송합니다.
팔로워는 이러한 정보를 받으면 자신의 로컬 복사본을 갱신합니다.
1-1. 동기식 대 비동기식 복제
복제 시스템의 중요한 세부 사항은 복제가 동기식으로 발생하는지 비동기식으로 발생하는지 잘 파악해야 합니다.
다음의 그림은 책에 있는 그림입니다. 동기식과 비동기식의 동작 방식은 CS를 아는 사람은 잘 알고 있겠지만 다시 한번 알아보면서 어떠한 장단점이 있는지를 알아보겠습니다.
팔로워 1번의 복제 서버는 동기식으로 동작하고, 팔로워 2번의 복제 서버는 비동기식으로 동작하고 있습니다. 그리고 그림을 보면 바로 알 수 있듯 특징또한 명확합니다.
동기식은 리더 서버가 팔로워 서버에 정상적으로 데이터가 복제 될 때까지 기다려야 하지만, 사용자가 읽는 시점에 해당 데이터가 존재함을 확신 할 수 있습니다.
팔로워 서버가 죽거나 네트워크에 문제가 발생하여 응답하지 않는다면, 해당 팔로워에 쓰기 처리가 진행되지 않고 리더 서버는 팔로워 서버가 복구 될 때까지 응답을 기다리며 동작하지 않게 되는 큰 문제도 있습니다.
이러한 부분을 감안해서 현실적인 방법으로 팔로워 중 하나는 동기식으로 동작하도록 하고 나머지 노드는 비동기식으로 동작하는 반비동기식을 적용하여 최소한 두 노드에는 최근 복사본이 있다는 것을 보장 할 수 있지만 자주 사용되지는 않는 것 같습니다. 그만큼 위험하니까요.
반면 비동기식은 리더 서버가 팔로워 서버에 데이터가 제대로 복제 됨을 확인하지 않고 사용자의 명령을 받을 수 있지만, 사용자가 읽는 시점에 해당 데이터가 존재함을 확신 할 수 없습니다.
그럼에도 대부분의 리더 기반 복제는 비동기식을 사용하는 쪽으로 정해지고 있습니다. 서비스 측면에서의 안정성은 몇몇 팔로워에 문제가 생겨도 정상동작하는 비동기가 더 안전하니까요.
1-2. 새로운 팔로워는 어떻게 만들까?
간략하게 정리하면 리더 노드의 스냅숏을 사용하는 방법으로 해결합니다. 새로운 팔로워를 만들면, 팔로워에는 아무런 데이터도 있지 않습니다.
그러면 다음의 과정을 수행하는 것으로 현재 리더 노드와 동일한 데이터를 추적 할 수 있도록 업데이트 됩니다.
- 리더의 데이터베이스 스냅숏을 일정 시점에 가져온다.
- 스냅숏을 새로운 팔로워 노드에 복사
- 팔로워는 리더에게 스냅숏 이후 발생한 모든 데이터의 변경 사항을 요청한다.
- 팔로워는 스냅숏 이후의 모든 데이터 변경사항을 반영한다.
1-3. 노드 중단 처리
하둡에 대한 공부를 하다보면 자주 나오는 고가용성에 대한 이야기이기도 합니다. 노드를 운영하다보면 팔로워 노드에 장애가 발생 할 수 있고, 리더 노드에 장애가 발생 할 수 있습니다.
우리가 다중 노드에 데이터를 복제하는 이유 중 하나인 시스템의 일부에 장애가 발생해도 지속적으로 동작 할 수 있게 해 가용성을 늘리기 위함을 생각하면 어떻게 이러한 문제를 대처 할 수 있는지 알아봐야 할 것 같습니다.
1 ) 팔로워 장애: 따라잡기 복구
위에 적은 새로운 팔로워가 리더 노드를 추적하는 방법처럼 팔로워는 기본적으로 리더 노드의 데이터 변경 로그를 바탕으로 따라잡습니다.
보관 된 로그 중 마지막 트랜잭션을 추적하고, 그 이후의 데이터를 재입력하는 방법으로 해결한다고 합니다.
2 ) 리더 장애: 장애 복구
문제는 이러한 리더 장애입니다. 하둡에서는 버전이 올라가면서 Active, Standby 네임노드라는 개념을 만들거나 아예 로드밸런싱이 가능하도록 네임노드를 설계하는 방안을 만든 것과 같습니다.
기본적인 리더 장애는 하둡 버전 2의 문제 해결 방안처럼 다른 팔로워를 리더로 승격시키는 방안을 사용합니다. 순서로 보면 다음과 같습니다.
- 리더가 장애인지 파악합니다. 보통은 타임아웃(하트비트)를 사용합니다. 왜냐하면 네트워크 문제든 고장이나 정전이든 어떤 문제로 장애가 발생 한 건지 알기 어렵기 때문입니다.
- 새로운 리더를 선택합니다. 일반적으로 새로운 리더에 가장 적합한 후보는 바로 이전에 최신 데이터의 변경사항을 가진 노드를 주로 생각합니다.
- 새로운 리더 사용을 위해 시스템을 재설정합니다. 클라이언트는 이후에 변경 된 리더 노드에 쓰기 요청을 보내야 하기 때문에 시스템에 이전 리더 노드를 팔로워로 만들고 선택 된 팔로워 노드를 리더로 승격시켜야 합니다.
문제는 리더 장애는 잘못 된 복구를 할 가능성이 있습니다.
- 비동기식 복제를 사용하면 새로운 리더는 이전 리더가 실패하기 전의 쓰기를 일부 수신하지 못 할 수 있습니다. 비동기식의 특성 상 데이터를 전송하고 응답을 받지 않기 때문에 이전 리더에 장애가 발생하면, 재전송이나 미전송 문제가 발생 할 수 있을 것 같습니다.
- 특정한 상황에서 두 노드가 모두 자신이 리더라고 생각 할 수 있습니다. 이 경우를 스플릿 브레인이라고 나타내며, 데이터가 유실되거나 오염 될 수 있습니다. 일반적으로는 이 경우 한 노드를 종료하게 된다.
- 리더가 죽었음을 판단하는 타임아웃을 잘 선택해야 한다. 이 기간이 길면, 긴 시간동안 사용자의 요청을 받지 못하는 것이고, 짧으면 불필요한 장애 복구를 하게 될 수 있습니다.
1-4. 복제 로그 구현
1) 구문 기반 복제
가장 간단한 복제 방법으로 구문을 사용해서 복제하는 방법이 있습니다. 리더는 INSERT, UPDATE, DELETE 구문을 팔로워에게 전달하고 팔로워는 그 문장을 그대로 실행하면 동일해 질 것이라고 기대 할 수 있습니다.
그런데 문제가 몇 가지 있습니다.
- NOW()나 RAND()같은 구문은 시점에 따라 달라 질 수 있어서 결과가 달라 질 수 있습니다.
- 자동 증가 컬럼이나 순서가 있는 데이터라면 정확히 같은 순서로 동작해야 합니다.
- 부수 효과를 가진 구문은 완전히 결정적이지 않으면 원하지 않는 결과를 얻을 수 있습니다.
2) 쓰기 전 로그 복제
데이터베이스의 저장소 엔진에 따라 복제하는 방법이 다를 수 있는데, 알아 볼 저장소 엔진은 이전에 공부 한 로그 구조화 저장소 엔진과 B트리를 사용하는 RDB가 있습니다
로그 구조화 저장소 엔진은 쓰기 전용 로그를 사용해 LSM 트리를 만들고 어느 정도의 크기가 되면 SS테이블로 만들어 디스크에 저장하는 방법으로 동작합니다. 그래서 쓰기 전용 로그 자체가 변경사항을 반영하기 때문에 이 로그를 팔로워에 네트워크를 통해 전달하면 복제가 끝납니다.
B트리의 경우에는 쓰기 전 로그(Write-Ahead Log)에 변경 사항을 적고, 고장 난 이후에 일관성 있는 상태로 색인을 복원합니다. 여기서 WAR는 어떤 디스크 블록이 어떤 바이트로 변했는지 상세 정보가 담겨있기 때문에 옮기면 되지만, 문제는 리더 노드와 팔로워 노드의 저장소 소프트웨어 버전을 동일하게 맞춰야 합니다.
3) 로우 기반 로그 복제
로우 자체를 로그로 남기는 방법을 사용 할 수 있습니다.
- 삽입 된 로우의 로그는 모든 컬럼값을 가지고 있습니다.
- 삭제 된 로우의 로그는 해당 로우를 특정 할 수 있는 값( 일반적으로 기본키 없으면 제외한 로우 전체 매핑 )을 저장합니다.
- 갱신 된 로우의 로그는 특정 할 수 있는 값 ( 기본키 )과 모든 컬럼의 새로운 값을 포함합니다.
이러한 로우 기반 로그 복제는 소프트웨어 버전과 형식이 명확하기 때문에 외부 애플리케이션에서 사용하기 편한 부분이 있고, 오프라인 분석과 사용자 정의 색인, 캐시 구축을 위한 데이터 웨어하우스로의 전송 등에 유용합니다.
이러한 방법은 변경 데이터 캡처라고도 합니다.
4) 트리거 기반 복제
트리거 기반 복제는 제대로 이해 한 건지 모르겠지만, 제가 이해 한 내용으로는 어떠한 조건( 예를 들면 새로운 데이터 입력 )이 충족 될 때 애플리케이션(DB)에서 정한 규칙에 따라 복제를 수행하는 것이라고 생각합니다.
예를들면 “A 테이블에 데이터가 입력 될 때 그 데이터를 B에도 추가하는 스토어드 프로시저를 동작시키자” 등의 방법 인 듯 합니다.
1-5. 복제 지연 문제
복제는 노드의 내결함성(고가용성) 뿐만 아니라 확장성( 단일 장비에서 감당하지 못하는 요청을 처리 )과 지연 시간( 사용자에게 지리적으로 가까운 곳에 복제 서버를 위치 시킴 )이 필요합니다.
리더 기반 복제는 모든 쓰기가 리더 노드를 거쳐야 하지만 읽기를 수행 할 때는 분산되어 있는 복제 서버에서 읽어 올 수 있습니다. 그로인해 쓰기의 비중보다 읽기의 비중이 높은 서비스에서는 작업 부하를 효과적으로 분산시킬 수 있게 됩니다.
이러한 읽기 확장 아키텍처는 동기 방식에서는 사용 할 수 없습니다 왜냐하면, 동기 방식으로 복제를 시도하면 네트워크에 무리가 가기 때문입니다. 하지만 비동기식이기 때문에 쓰기가 아직 끝나지 않은 데이터를 읽어오면 최신 데이터를 가져오지 못할 수 있습니다.
이 경우에는 잠시 후 쓰기가 끝난 뒤에 데이터를 가져오는 방식으로 문제는 간단히 해결 되는데, 이러한 특징을 최종적 일관성이라고 합니다.
1 ) 자기가 쓴 내용 읽기
이렇듯 복제에 지연이 걸리게 되면 웃기는 상황이 발생 할 수 있습니다. 바로 내가 방금 쓴 내용을 내가 읽지 못하게 되는 상황이죠. 인스타에 스토리를 올렸는데, 바로 확인해보니 스토리가 없는 상황이라고 할 수 있습니다. 이 경우 최종적으로는 스토리가 정상적으로 등록됨에도 어째선지 만족스럽지 않은 유저 경험이라고 할 수 있을 것 같습니다.
위에서 사용 한 그림을 다시 가져오겠습니다.
이 경우 저자는 다음과 같은 해결방안을 말해줍니다.
- 아예 자신의 게시물(데이터)는 리더 노드에서만 가져오도록 강제하자
- 1번의 경우 읽기 확장 이점이 줄어들 수 있기 때문에 마지막 갱신 시간을 찾아서 특정 시간 동안만 리더 노드 읽기를 강제하자
- 마지막 갱신 시간을 알고 있기 때문에 복제 서버의 데이터가 최신 데이터인지 알 수 있기 때문에 팔로워 노드의 데이터가 최신이 아니면 다른 노드를 찾도록 하자
2 ) 단조 읽기
여기서 말하는 단조 읽기는 한 사용자가 여러 노드에 읽기를 동시에 수행했을 때 서로 다른 결과가 나오는 것을 말합니다.
가장 잘 설명 된 그림을 가져와보면 다음과 같습니다.
사용자 2345는 처음 데이터를 읽었을 때 사용자 1234가 추가 한 데이터를 읽을 수 있었지만, 두 번째로 데이터를 읽었을 때는 보다 이전의 데이터를 읽게 됩니다. 있던 데이터가 갑자기 삭제 된 것처럼 보이게 되는 것 입니다.
이런 혼란을 겪게 된다면 차라리 유저마다 접근 할 팔로워를 선택하게 설계하는 방안을 사용 할 수도 있다고 합니다. 책에서는 사용자의 아이디를 해싱해서 노드를 선택하는 인덱스로 활용하는 방안을 말합니다.
3 ) 일관 된 순서로 읽기
일관적으로 읽어야 하는 데이터 셋은 많습니다. 가장 대표적인게 SNS의 채팅의 경우에는 일관적이지 않으면 안됩니다.
위의 경우를 보면 케이크 부인의 대답이 더욱 빨리 복제되어 관찰자 입장에서 먼저 읽어오게 되는 것을 볼 수 있습니다. 예를 들면 카톡 중 이런 문제가 발생하면 큰 오해를 살 수도 있겠죠.
이런 문제를 해결하기 위해 서로 인관성이 있는 데이터는 같은 파티션에 저장 할 수 있게 강제하는 방법이 있지만, 효율적이지는 않다고 합니다.
복제 지연을 위한 해결책은 없나?
비동기식 복제는 최종적으로는 일관성을 가지지만 복제 지연 자체가 길어질 경우에는 애플리케이션이 어떻게 동작 할 지 생각 할 필요가 있습니다. 사용자의 만족감에 직접적인 영향을 준다면 해결하는게 좋겠죠.
사실 우리가 사용하는 데이터베이스의 대부분은 트랜잭션을 통해 일관성을 강제하는 동작을 수행합니다. 단지 분산 환경으로 설계하면서 트랜잭션의 특징이 유연해지고, 없애기도 한 것 뿐입니다. 그만큼 성능의 차이가 있으니까요.
2. 다중 리더 복제
지금까지의 리더 팔로워 노드로 구성 된 단일 리더 복제에 대해 알아보았습니다. 모든 쓰기는 리더 노드를 통해 이루어지기 때문에 네트워크의 문제와 같은 원인으로 사용자와 리더 노드 간 통신이 안되면 쓰기를 할 수 없게 됩니다.
이런 문제의 해결 방안을 자연스럽게 리더 노드를 여러 개 구성하는 방안으로 이어집니다. 그리고 각 리더 노드는 서로의 팔로워 노드의 역할 또한 수행하게 됩니다.
다중 리더 복제를 사용하는 다양한 상황
다중 데이터 센터 운영
다중 리더 설정에서는 각 데이터센터 마다 리더를 가질 수 있고, 데이터 센터 내에서는 리더 팔로워 복제 방식을 사용하고 데이터센터 간에는 데이터센터의 리더가 다른 데이터센터 리더에게 변경 사항을 복제합니다.
단일 리더와 다중 리더의 특징은
- 단일 리더에서는 모든 쓰기가 리더가 있는 데이터센터까지 이동해야 하지만 다중 리더 설정은 모든 쓰기가 가장 가까이에 있는 데이터센터에서 처리한 후 비동기 방식으로 다른 데이터센터에 전달합니다.
- 단일 리더는 리더가 있는 데이터센터가 고장나면 다른 팔로워 노드를 리더 노드로 승격 시키지만, 다중 리더 방식은 다른 데이터센터가 독립적으로 동작하기 때문에 상관없습니다,
- 다중 리더의 다른 데이터센터와의 통신은 공개 인터넷을 사용해서 보안이 위험하지만, 네트워크 장애 측면에서는 더 안전하다.
- 다중 리더 방식은 쓰기 충돌이 발생 할 수 있다는 점에서 주로 사용되는 방식은 아니다.
오프라인 작업을 하는 클라이언트
여기서 오프라인 작업을 하는 클라이언트에 대한 이야기에는 핵심적으로 선행되는 개념이 있는데 이 부분을 책에서 제대로 안 짚어서 먼저 적습니다.
모든 디바이스는 자체적으로 로컬 데이터베이스가 있고, 네트워크 통신 없이 동기화만 되면 오프라인 작업을 할 수 있는 것을 가정합니다.
저자가 말하고자 하는 내용이 번역 과정에서 모호해진 느낌이 있지만, 간단히 사용자의 디바이스가 하나의 데이터센터이고, 네트워크가 연결 된 상황에서 동기화를 위해 오랜 시간을 연결해야 할 수 있는데 다른 데이터센터와의 네트워크 연결을 신용 할 수 없기에 문제가 될 수 있다. 정도 입니다.
다중 리더에 큰 상관이 있는건지.. 왜 적었는지 모르겠네요.
그냥 클라이언트의 기기 자체를 데이터센터로 생각하고, 다중 리더의 범위를 키워서 어떻게 동작할 수 있는지를 생각해보라는 느낌입니다.
협업 편집
그냥 실시간 협업 편집 기능에서 다중 리더 방식을 사용하면 동시에 쓰기가 이루어져 비동기적으로 변경을 반영 할 시 문제가 될 수 있다. 이러한 부분의 안전성을 위해 락을 거는 방식으로 해결하려고 하면 다중 리더의 장점을 잃는다는 이야기입니다.
쓰기 충돌 다루기
다중 리더 복제에서 가장 큰 문제인 쓰기 충돌을 막는 법을 알아봅니다.
위와 같이 동일한 데이터에 대해서 아직 동기화 되지 않은 데이터센터의 리더에 반영하려고 할 때 생기는 문제가 있을 수 있습니다.
동기 대 비동기 충돌 감지
다중 리더에서는 각 데이터센터의 쓰기를 별도로 할 수 있기 때문에 두 리더가 변경 된 후 비동기적인 방식으로 충돌을 감지하게 됩니다. 이론적으로 동기적으로 이러한 충돌을 감지 혹은 회피 할 수 있지만, 그러면 데이터센터의 독립적인 쓰기가 불가능합니다.
충돌 회피
특정 사용자가 접근하는 데이터센터를 고정하는 방식으로 다른 데이터센터에 중복되는 데이터에 접근하지 않도록 하는 방법이 있습니다.
하지만 그 경우 데이터센터와의 네트워크 문제, 서버 이상으로 다른 데이터센터와 통신하면 깨집니다.
일관된 상태로 수렴
단일 리더는 항상 마지막에 쓰는 데이터가 최종 데이터이지만, 다중 리더는 쓰기 순서가 정해지지 않아 최종값이 무엇인지 명확하지 않습니다. 그래서 한 값으로 수렴하도록 하는 방식으로 충돌을 해소해야 합니다.
예를 들면 타임스탬프를 찍어서 가장 마지막 값이 최종 데이터라고 하는 등의 방법이 있을 것 같습니다.
사용자 정의
자체적으로 충돌을 감지 했을때의 이벤트 핸들링을 하는 듯하다.
다중 리더 복제 토폴로지
리더 노드가 2개 일 때는 서로 간의 복제가 전체 복제가 되지만, 3개 이상의 리더 노드로 구현되면 어떻게 리더 노드간 복제를 할 것인지를 명확히 선택해야 합니다.
크게 다중 리더 복제 토폴로지는 3가지가 있으며 각각의 설명을 적어보면 다음과 같습니다.
- 원형 토폴로지
- 리더 노드가 다른 1개의 리더 노드에 복제하면서 이러한 방식이 전체 리더노드 간 반복됩니다.
- 별 모양 토폴로지
- 중앙의 리더 노드를 하나 두고, 모든 노드는 해당 리더노드에 복제하면서 모든 리더 노드의 변경을 반영 한 중앙 리더 노드가 각 리더 노드에 변경을 반영합니다.
- 전체 연결 토폴로지
- 각 리더 노드는 자신을 제외 한 모든 리더 노드에 변경을 반영합니다.
세 토폴로지는 특징이 있는데, 일단 원형 토폴로지와 별 모양 토폴로지는 단일 장애점이 있습니다. 즉 한 리더 노드가 장애가 발생하면 모든 노드가 동일한 데이터를 가지고 있지 않게 됩니다. 또한 모든 리더 노드에 복제하기 위해서는 1개 이상의 노드를 거쳐야 하는데, 이 과정에서 무한 루프가 발생 할 수 있고 그러한 문제는 태깅을 통해 해결 할 수 있습니다.
이런 문제로 내결함성을 높이기 위해 전체 연결 토폴로지를 사용 할 수 있는데 이 경우에는 일관된 순서로 동작하지 않을 가능성이 있습니다.
이런 문제는 버전 벡터라는 방식을 통해 해결 할 수 있다고하는데, 알게되면 적도록 하겠습니다.