프로필사진

Go, Vantage point

가까운 곳을 걷지 않고 서는 먼 곳을 갈 수 없다.


Github | https://github.com/overnew/

Blog | https://everenew.tistory.com/





티스토리 뷰

반응형

 

 

 

이 글은 데이터 중심 애플리케이션 설계 서적(마틴 클레프만 저)를 읽고 정리한 내용입니다.

 

데이터 중심 애플리케이션 설계 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

 

 

 

 

데이터 복제는 다음과 같은 이유로 필요하다.

1. 지리적으로 가깝게 복제본을 위치시켜 네트워크 지연시간을 줄인다.

2. 장애에 대처한다.

3. 읽기 질의를 제공하는 장비수를 확장해 처리량을 늘린다.

 

데이터 량이 작거나 복제 대상의 데이터가 앞으로도 변경되지 않는다면, 복제는 너무나 쉽다.

하지만 거대한 데이터이고 변경이 일어난다면?

 

 

최근은 데이터 량이 급증하면서 단일 노드로 구성된 데이터베이스에서 분산 데이터베이스로 넘어가고 있다. 

노드 간 변경을 복제하기 위한 분산 데이터베이스의 세 가지 대표 알고리즘인 단일 리더, 다중 리더, 리더 없는 복제의 구성과 트레이드오프를 살펴보자.

 

 

 

 

단일 리더 복제

 

복사본을 저장하는 각 노드를 복제 서버(replica)라고 한다.

원본 데이터베이스의 모든 쓰기는 모든 레플리카에서도 처리돼야 동일한 데이터를 유지할 수 있다.

 

가장 일반적인 해결책은 리더 기반(마스터-슬레이브) 복제이다.

노드 중 하나가 리더의 역할을 하여 클라이언트의 쓰기 요청을 받는다.

리더는 이를 먼저 로컬 저장소에 기록하고, 팔로워(슬레이브)라고 불리는 다른 레플리카에게 전송한다.

이를 전송받은 레플리카들이 동일한 순서로 쓰기를 진행한다.

 

레플리카들은 쓰기 요청은 받을 수 없지만, 읽기 요청은 받을 수 있기 때문에 병렬적으로 읽기 처리량이 늘어난다.

 

이러한 단일 리더 로직이 RDB인 PostgraSQL MySQL, 일부 비관계 DB인 몽고 DB, 에스프레소에 내장된다.

거기에다 카프카와 같은 고가용성 큐에서도 사용된다.

 

여기서 생각해보아야 하는 것은 동기화의 문제이다.

 

 

리더는 쓰기 요청을 동기식 팔로워에게 전달하고, 확인이 끝나야 user에게 응답을 보낸다.

비동기식 팔로워에게는 전달 후 응답을 기다리지 않기 때문에, user 서비스의  지연과는 관계가 없다.

리더가 다운되더라도 동기식 팔로워에서 최신 데이터를  사용할 수 있다.

 

 

하지만 간단한 해결책인 만큼 문제가 있다.

문제로 인해 동기식 팔로워가 응답이 없다면 user의 요청이 처리될 수 없다.

 

따라서 모든 팔로워가 동기식인 상황은 비현실적이다.

(현실적으로 동기식 팔로워는 보통 단 하나만 둔다고 한다.)

 

 

 

반 동기식은 동기식 팔로워가 다운되면, 비동기 팔로워 하나를 최신 복사본이 있게 만들어 동기식 팔로워가 되게 한다.

보통은 완전 비동기식으로 구현되어, 리더가 다운됐을 때 쓰이지 않은 데이터는 날아간다.

팔로워가 많은 환경이라면, 많은 노드가 비동기식 복제일 수밖에 없다.

 

 

리더의 장애

팔로워들의 장애는 트랜잭션의 로그를 찾아 그 시점 이후로의 변경 로그를 리더로부터 받아오면 해결이 가능하다.

하지만 리더의 장애는 까다롭다.

팔로워 하나를 리더로 승격하고 모든 팔로워가 새로운 리더로부터 데이터를 받도록 재설정해야 한다.

이때 발생할 수 있는 문제가 워낙 많기 때문에 현업에서는 수동적인 장애 복구가 선호된다고 한다.

 

 

 

읽기 확장 아키텍처

리더 기반 복제는 모든 쓰기가 리더라는 단일 노드를 거쳐야만 하지만, 읽기 작업은 모든 노드가 가능하다.

따라서 쓰기의 비율이 아주 적고 읽기가 대부분이라면, 팔로워를 늘려서 읽기 요청을 분산시킬 수 있다.

이러한 아키텍처는 읽기 확장(read-scaling)이라고 한다.

 

물론 팔로워가 많기 때문에 비동기식 복제에만 적합한 아키텍처이다.

하지만 아래의 그림처럼 노드들이 비동기 팔로워라면 읽어온 데이터가 최신 데이터가 아닐 수 있다.

 

 

노드마다 읽기 질의의 결과가 다르더라도, 쓰기가 잠시 멈춘다면 최종적으로는 리더와 모든 데이터가 동일할 것이다.

이때까지 걸리는 시간을 복제 지연이라고 한다.

 

 

복제 지연 문제 해결 방법

복제 지연은 서비스에서 심각한 문제가 될 수 있다.

이에 대한 해결 전략들은 많은 애플리케이션이 사용하는 방법으로, user가 제출한 데이터를 user가 재로딩 시에 볼 수 있게 하는 것이다. (이러한 방식은 읽기 요청이 많고, 쓰기 요청이 적을 때 적합하다.)

이러한 쓰기 후 읽기 일관성은 어떻게 구현되야 할까?

 

수정한 내용을 읽을 때는 리더에서만 읽는 경우를 생각해 보자.

예를 들어 프로필 편집은 소유자만 편집할 수 있으므로, 자신의 프로필은 리더에서 읽고 다른 유저의 프로필은 팔로워에서 읽을 수 있다.

 

만약 대부분의 데이터가 수정 가능한 애플리케이션은 리더에서 대부분의 데이터를 읽어야 하므로, 읽기 확장의 의미가 없어진다. 

따라서 갱신 후 특정 시간 동안 (예를 들면 1분 정도)은 리더에서 읽고, 특정 시간 후에도 복제 지연이 발생하는 팔로워에 대해서는 읽기를 금지한다.

 

혹은 클라이언트의 최근 쓰기 타임스탬프를  기억해 두고, 읽으려는 팔로워 서버가 해당 타임스탬프까지 복제할 때까지 질의 대기한다.

 

 

 

카카오톡을 여러 장치에 설치해 써보면, 서로 간의 데이터 반영이 상당히 느린 경험이 있을 것이다.

만약 여러 디바이스로 동시 접근이 가능한 서비스라면, 더욱 어려워진다.

태블릿은 wifi 그리고 스마트폰은 모바일 데이터를 사용한다면 동일한 데이터 센터의 복제 서버에 라우팅 된다는 보장이 없다.

이런 경우는 서로 간에 동작하는 코드도 서로 접근할 수 없으므로 타임스탬프로 관리할 수 없다.

 

 

단조 읽기(monotonic read)

 

여러 복제 서버가 존재한다면 최신 서버에서 읽었다가 복제 지연이 발생하고 있는 서버에서 읽게 되면, 시간이 역행하는 현상을 발견할 수 있다.

단조 읽기는 이를 방지한다. 따라서 새로운 데이터를 읽은 후에는 항상 동일한 복제 서버에 수행되게 한다.

 

 

일관된 순서로 읽기

 

메신저 서비스에서 먼저 보낸 메시지에 심한 복제 지연이 걸려, 다른 사람이 늦게 보낸 메시지가 먼저 도착한다면 혼란스러울 것이다.

이런 현상을 방지하기 위해 일관된 순서로 읽기 보장이 필요하다.

쓰기가 일련의 순서대로 진행되었다면, 읽기도 그 순서대로 진행되어야 한다. 특히 분산 데이터베이스는 파티션마다 독립적으로 동작하기 때문에 이런 문제가 발생할 수 있다.

따라서 인과가 있는 쓰기는 같은 파티션에만 기록되게 할 수 있다. 하지만 실시간 서비스에서는 효율적이지 않을 수 있다.

 

애플리케이션 코드에서 이 모든 것을 신경 쓰는 것은 쉽지 않다.

따라서 데이터베이스에서 제공하는 트랜잭션이라는 강력한 보장을 사용할 수 있다.

그러나, 분산 데이터베이스로의 전환에서 트랜잭션 기능은 너무 cost가 높아 많은 시스템이 포기했다.

 

 

 

 

다중 리더 복제

 

지금까지 리더 복제를 살펴보면서 든 의문은 리더가 하나인데 부하를 버틸 수 있을까?

거기에 네트워크 장애도 있다면?

 

즉, 단 하나의 리더는 부족할 수 있고, 다중 리더로 확장할 필요가 있다.

물론 리더끼리의 쓰기 처리도 다른 리더에게도 전달해야 하므로, 각 리더는 동시에 다른 리더의 팔로워가 된다.

 

단일 리더에서 여러 데이터 센터로 팔로워들이 분산되어 있다면, 모든 쓰기가 일단 리더로 라우팅 돼야 하므로 네트워크 지연이 복제 지연을 발생시킨다.

하지만 다중 리더에서는 데이터 센터마다 하나의 리더를 두어 처리 후에 다른 리더로 데이터를 복제시킬 수 있다.

이는 하나의 리더가 다운되었을 때 회복하는 데에도 유리하다.

 

일부 데이터베이스들이 다중 리더 설정을 제공하기도 한다.

하지만 서로의 리더에서 동시에 쓰기가 실행될 경우의 충돌 해소, 자동 증가 키 등과 같이 문제 소지가 많아서 다중 리더는 지양해야 하는 영역으로 간주된다.

예를 들어 A리더에서는 1,2의 순서로 쓰기가 될 수 있지만, B리더에서는 2,1 순서로 쓰기가 될 수 있다.

이때 어느 리더가  원본 데이터인지 알 수가 없게 된다.

 

사실 모든 사용자의 디바이스가 로컬에서는 다중 리더로서의 역할을 할 수 있다.

온라인 서비스가 일시적으로 오프라인으로 전환되더라도 쓰기 요청은 다시 온라인이 되었을 때 서버와 데이터 동기화가 진행되어야 하기 때문이다.

 

다중 리더에 의한 문제는 여전히 가끔씩 데이터가 맞지 않는 캘린더 서비스에서도 확인할 수 있다.

 

그렇다면  구글에서 여러 사람이 동시에 편집하는 스프레드시트는 어떻게 구현될까?

이는 데이터베이스 복제 문제라고 생각하지 않을 수 있지만, 오프라인 상태에서의 편집이 온라인으로 변경될 때 문서에 반영되어야 한다.

이때 Helen이 작업한 내용이 Liam이 작업한 내용과 겹친다면 데이터 충돌이 일어난다.

다중 리더에서는 이를 회피하기 위해 같은 작업을 하는 유저들은 같은 단일 리더에서만 쓰기 작업을 하도록 할 수 있다.

단일 리더라면 중복 쓰기를 애초에 방지하거나, 뒤늦게 반영한 user에게 트랜잭션 rollback을 일으킬 수 있다.

이는 Streamlit의 동시성 제어 라이브러리의 room 개념과 비슷한 듯하다.

 

사실 스프레드 시트와 같이 다중 리더 복제 문제는 애플리케이션 코드를 통해 충돌을 해소시킬 수 있다.

https://en.wikipedia.org/wiki/Operational_transformation

 

Operational transformation - Wikipedia

From Wikipedia, the free encyclopedia Operational transformation (OT) is a technology for supporting a range of collaboration functionalities in advanced collaborative software systems. OT was originally invented for consistency maintenance and concurrency

en.wikipedia.org

Operational Transformation 알고리즘으로 수정하는 텍스트에 대한 정보를 이용하여 서로 간에 문서 lock 없이도 충돌이 해소된다.

 

문자 편집이 아닌 다른 종류의 서비스에서는 여전히 충돌 해소는 더욱 힘들다. 따라서 다중 리더 복제는 철저한 테스트를 통해 구현돼야 한다.

 

 

 

 

리더 없이 복제할 수 있을까?

 

리더 기반 데이터 베이스는 관계형 데이터베이스와 함께 성장했다.

하지만 원시의 데이터베이스는 쓰기 순서 보장과 같은 기능은 없었다.

 

Amazon의 Dynamo DB는 리더가 없이, 클라이언트가 직접 DB에 쓰기 작업을 요청할 수 있다.

이러한 데이터베이스를 Dynamo 스타일이라고 한다.

 

Dynamo 스타일의 장점은 복제 노드가 다운되더라도 데이터베이스들이 여전히 사용가능 하다는 것이다.

리더 기반 복제에서는 리더 노드가 다운되면, 장애 복구까지 지연이 발생한다.

하지만 리더가 없는 복제에서는 세 가지 노드에서 하나의 노드가  다운되어 쓰기 요청에 대해 응답이 없더라도, 두 가지 노드에서 응답을 받으면 문제없이 쓰기가 진행되었다고 판단한다.

이후 특정 노드에서 누락된 쓰기 요청에 대해 질의할 때, 하나의 복제 노드가 아닌 여러 노드에 병렬 읽기를 요청하여 버전 숫자를 체크하여 어떤 값이 최신 데이터인지 파악할 수 있다.

 

누락된 데이터는 읽기 시에 감지하여 추가되거나, 백그라운드에서 지속적으로 쓰기 누락이 있는지 확인하여 복제본을 최식화 할 수 있다.

 

여기서 드는 의문점은 병렬 읽기의 cost이다.

이를 최적화하기 위해서는

[최소 쓰기 성공 수 + 최소 읽기 노드 수 > 모든 노드 수] 라는 정족수 공식이 만족되어야 한다.

이 파라미터는 Dynamo 스타일의 DB에서 설정할 수 있다.

 

만약 쓰기보다 읽기가 훨씬 많이 일어난다면, 모든 노드에 쓰기를 성공해야 하도록 설정하면 항상 하나의 노드에서 읽어와도 항상 최신본이 된다.

하지만 단 한 개의 노드라도 다운이 된다면, 모든 쓰기 요청이 중단된다.

물론 노드가 다운되는 일은 드물지만 데이터가 유실되는 최악의 시나리오에 대비해야 한다.

 

위의 공식을 만족하더라도 하나의 노드에 쓰기 작업이 동시에 들어오거나(쓰기 순서 보장이 힘듬), 쓰기/읽기 요청이 동시에 들어오는 경우와 같이 예외적인 케이스에서 항상 올바른 데이터를 넘겨주지 않을 수 있다.

 

리더 기반 복제는 리더가 항상 최신 데이터이므로 복제 노드들이 얼마나 뒤처졌는지 알 수 있지만, Dynamo 스타일은 그마저도 모니터링하기가 힘들다.

 

심지어 쓰기 순서 보장이 없다면, 같은 Key의 값이 다른 두 유저에게 의해 덮어 써질 수 있다.

유저 A에게 네트워크 상으로 가까운 노드는 A가 전달한 value로 값을 쓰고, 이후 네트워크 상으로 더 거리가 먼 유저 B가 전달한 value로 최종적으로 값이 덮어쓰어질 수 있다.

반대로 유저 B에게 네트워크 상으로 가까운 노드는 B가 전달한 value로 값을 쓰고, 이후 네트워크 상으로 더 거리가 먼 유저 A가 전달한 value로 최종적으로 값이 덮어쓰어질 수 있다.

 

두 가지 노드에게 해당 Key로 값을 요청하면 서로 다른 값을 전달할 것이다. 심지어 어떤 값이 정답인지 조차 알 수 없다.

이러한 쓰기 충돌을 어떻게 해야 할까?

 

이럴 때는 사실 각 노드는 어떤 클라이언트의 요청이 먼저 왔는지 조차 알 수 없다.

따라서 타임스탬프를 부여하고 가장 최신인 쓰기만 유지하고 나머지는 무시하는 최종 쓰기 승리라는 알고리즘을 선택할 수 있다.

클라이언트는 자신의 쓰기에 대해 성공적으로 응답을 받을 수 있지만, 막상 읽어보면 다른 값일 수 있다.

 

그렇다면 값이 증가되기 전에 읽어가는 것처럼, 동시에 일어났지만 인과성이 있는 데이터에 대해서는 어떻게 해야 할까?

특히 수량이 정해진 쇼핑몰의 선착순 구매와 같은 작업에서는 크리티컬 한 문제일 것이다.

 

이는 위에서 언급한 쓰기 충돌처럼 데이터 손실이 있을지도 모르지만 알고리즘으로 최선을 다해 극복해야 한다.

 

 

 

 

이처럼 데이터 복사본을 분산 저장하는 것에는 심혈을 기울여야 한다.

 

 

 

반응형
댓글
반응형
인기글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함