아키텍쳐

[강의 정리] Distributed Systems 5: Replication

hayz 2023. 3. 29. 16:23

# 강의 영상

https://www.youtube.com/watch?v=mBUCF1WGI_I&list=PLeKd45zvjcDFUEv_ohr_HdUFe97RItdiB&index=15 


정리

이제 우리는 "Replication" 문제에 대해서 이야기할 때가 되었습니다. "replication" 은 "replica" 라고도 하는, 동일한 데이터를 여러 노드에 복사해 저장하는 방식을 의미합니다. Replication 은 분산 시스템, 분산 파일 스토리지 등에 거의 기본 특징으로 자리잡고 있는데요, 우리는 이 replica 매커니즘을 이용해서 "fault tolerance" 를 만들어 내기도 합니다. 즉, 하나의 노드가 망가지면, 동일한 데이터가 저장된 노드에서 그 역할을 대신 수행하며 장애를 대응하는 것이죠. 

 

* RAID(Redundant Array of Independent Disks) 와 비교해 보면, 

    - RAID 는 하나의 컨트롤러를 보유하며, 각 노드는 서로 독립적으로 동작합니다. 서로 노드간의 통신은 이루어지지 않아요.

    - Replication 은 별도의 컨트롤러 없이 서로가 서로에게 복제를 하며 통신을 하게 됩니다. 

 

Manipulating remote state

단순 변경되지 않는 데이터를 복제하는건 간단하지만 데이터가 "변경" 될 때를 주의해서 다뤄야 합니다. replication에 대해서 깊게 들어가기 전에, 분산 시스템에서 데이터 변경이 일어나는지 알아보고 갑시다.

만약 게시글에 좋아요를 누를 수 있는 SNS 서버를 만든다고 할 때, 우리는 누구든 게시글을 올리고 서로 좋아요 버튼을 통해 좋아요를 누를 수 있습니다. 그리고 이 "좋아요" 의 갯수는 어딘가에 저장되어 보관 되고 있죠. 그런데 만약 좋아요를 누른 요청 혹은 서버에서오는 응답 둘 중 하나가 네트워크 상에서 손실이 되었다면 어떻게 될까요? 일반적으로, 재요청을 보내겠죠. 그런데 이 때 조심하지 않으면, 좋아요가 여러번 반영된 현상이 나타날 수 있습니다. (위 그림 처럼) 응답이 제대로 오지 않았지만, 실제로 데이터베이스에 반영은 되었기 때문에 사용자가 의도하지 않은 상태가 될 수 있습니다. 위 예시를 보면 좋아요 숫자 하나에 그렇게 큰 의미가 없어보일 수도 있겠지만, 위 문제가 은행 잔고에도 똑같이 적용된다면 어떨까요? 생각보다 큰 문제가 발생하게 됩니다. 

 

이러한 중복 반영의 문제를 해결하기 위한 방법중에 하나로 "deduplicate request" 가 있습니다. 근데, crash-recovery 모델에서는 이러한 요청들을 저장해두기 때문에 요청이 중복되었다는것을 알기 위해서는 실제로 crash 가 발생해야 하는 문제가 있습니다. 

그렇기 때문에, 대안으로 우리는 이러한 요청의 중복을 확인하기 보다 요청을 "idempotent" (멱등) 하게 만드는 방식이 있습니다. 

"idempotence" 우리 말로 멱등성은 위와 같은 특징이 있습니다. 멱등성이 아닌 경우는, 매 요청을 할 때 마다 값이 변하게 되지만, 멱등성이 있는 경우에는 요청을 몇번을 날려도 같은 결과를 가지게 됩니다. 위에서 지켜본 "좋아요" 수 증가 로직은 멱등성이 지켜지지 않았다고 볼 수 있습니다. 이런 멱등성을 가지는 경우 재 시도 했을 때 훨씬 안전한 특징을 가지고 있습니다. 

 

이제 두개 이상의 노드가 존재하는 replicas에 대해서 하나의 상황을 살펴보겠습니다. 

아래 그림의 위, 아래 두 상황이 있습니다. 첫번째는 클라이언트가 각 노드에 x 를 추가 하고 삭제하는 과정인데, 마지막 B 노드의 x 를 삭제하는 remove(x) 과정 중 장애가 발생하였습니다. 두번째는 클라이언트가 A, B 노드에 add(x) 를 실행하는 과정에서  A 에게 전해진 요청이 누락되는 상황이 발생했습니다.  여기서 살펴볼 것은 두가지의 상황이 각기 다른 상황이었지만, 결론적으로는 x 는 A에 없고, B 에는 있다는 상황은 똑같습니다. 

중요한 것은, 위 두 상황이 다르게 일어났지만 결과는 똑같기 때문에, replicas 는 두 상황의 차이점을 구분하지 못한다는 것입니다. 

위 문제를 해결 하기 위해 두가지의 방법이 있습니다. 첫번째, 매 연산에 Logical timestamp 를 부여 하고, 해당 timestamp를 별도의 데이터베이스에 저장하고, 두번째로, 삭제 요청이 왔을때 실제로 지우는 것이 아니라 tombstone 이라고 하는 deleted 라는 하나의 마크를 표시 하는 방식으로 두는 것입니다. 아래 그림은 timestamp 를 적용하고 tombstone 방식을 적용하였을때의 예시입니다.

 

대부분의 replicated 시스템에서는, 변경을 감지하고 상태를 맞춰주는 일을 합니다. (anti-entropy라고 불리는) 이는 복제된 시스템 간의 데이터 일관성을 맞춰주기 위한 목적입니다. 위 우리가 설정해둔 tombstone 덕 분에 우리는 데이터가 삭제되었는지를 파악할 수 있습니다. 그리고 timestamp 덕분에 어떤 버전이 최신이고 어떤 것이 예전 것인지 확인 할 수 있습니다. anti-entropy 프로세스는 새로운 버전에 대해서 적용 시키고, 이전 버전에 대한 것은 누락시킵니다. 

 

이런 상황도 가능합니다. 요청에 timestamp 를 붙이는 경우에 서로 다른 client 가 각각 같은 key값을 수정 하려 하고, 서로 다른 노드에 엇갈린 순서로 도착하였다고 했을 때, 각 노드는 도착한 시간에 대해 의존하지 않고, 각각의 timestamp 값에 의존해야 합니다. 

자세하게 보면 위 방식은 우리가 사용하는 timestamp 종류에 따라 나뉠 수 있습니다. 가장 마지막에 들어온 값만 설정하는 경우 Last Writer Wins (LWW)전략으로 t2 에 해당 하는 값만 반영되고 t1 에 해당하는건 누락될 수 있습니다. (Lamport Clock 등) 하지만, Multi-value register 를 사용하면 두 값 모두 들어온 순서에 따라 저장시키는 방식도 있습니다. (Vector clock 등) 하지만, 이 방식은 많은 양을 저장하고 있어야 하기 때문에 비용이 비싸진다는 단점이 있습니다. 

 

이번 장을 통해 분산 시스템중 복제 시스템을 다룰 때 생각해봐야할 문제 들과, 그 문제들을 해결하는 방식에 대해서 살펴보았습니다. 아래는 우리가 더 생각해 볼 수 있는 주제로 한번 쯤 생각해보면 좋을것 같습니다. 

 

Apache Cassandra 는 위에서 우리가 논의한 비슷한 복제 방식을 사용하는 유명한 분산 데이터베이스 입니다.  하지만, 우리가 얘기했던 logical timestamp 를 사용 하는 대신, physical timestamp를 사용하고 있습니다. 그리고 그 이유에 대해서 이 링크에서 이야기 하고 있는데요, 한번 살펴보면 좋을것 같습니다. -> https://www.datastax.com/blog/why-cassandra-doesnt-need-vector-clocks