개요
분산 프로세스 간 올바른 거래의 어려움
Alice와 Bob이 둘다 만원을 가지고 있고 Alice가 Bob에게 천원을 이체한다고 하자. 그러면 Alice는 구천원, Bob은 만천원을 가지는 결과를 예상하고 싶다.
- Alice 입장에서
- 비동기적 통신으로 인해 Bob의 은행에서 이체 메시지를 확실히 받을 수 있는 보장이 없다.
- Bob의 은행이 해당 메시지를 아직 받지 않은 상황에서 Alice가 잔고를 업데이트한다면 Alice만 손해를 입을 것이다.
- Alice가 안전하게 잔고를 갱신하기 위해서는 Bob으로부터 이체를 받았다는 메시지 수신이 필요하다.
- Bob 입장에서
- Bob이 Alice로부터 이체 메시지를 받았다. Bob은 이대로 자신의 잔고를 늘려도 될까? -> X
- Bob이 알리려는 내용이 Alice에게 정확하게 전달되지 않으면 Alice는 잔고를 갱신하지 않고 Bob의 잔고만 증가하게 된다.
안전성과 라이브니스
분산 컴퓨팅 시스템에게 요구하는 두 가지 기본 요건
- 안전성(Safety) - 잘못된 결과가 일어나서는 안 되는 원칙
- 라이브니스(Liveness) - 원하는 결과가 반드시 어느 시점에는 실현되어야 하는 원칙
안전성과 라이브니스 동시 충족 문제
- 안전성에만 초점을 맞춘다면 서로 메시지를 반복적으로 주고 받다가 결국 라이브니스 요구사항을 충족하지 못하게 된다.
- 안전성과 라이브니스를 모두 추구하다가 교착 상태에 빠질 수 있다.
- 컴퓨터 네트워크가 비동기적 통신 문제를 완전하게 해결할 수 있는 상황이 아니라면 양자 합의 방식을 통해 안전성과 라이브니스를 모두 충족하면서 수행하는 것은 매우 어렵다.
2단계 커밋 프로토콜
트랜잭션: 분산 컴퓨팅 참여 프로세스들 간의 거래 행위
- 트랜잭션이 안전하게 수행되려면 해당 트랜잭션은 ‘원자성(Atomicity)’을 가져야 한다.
- 트랜잭션이 원자성이 있다고 하는 것은 트랜잭션에 참여하는 컴퓨팅 주체들이 모두 해야 할 일을 하거나, 아예 시작도 하지 않는다는 것
원자성을 보장하면서 트랜잭션이 수행될 수 있도록 하는 ‘2단계 커밋 프로토콜(Two-Phase Commit)’
- 트랜잭션을 중계하는 코디네이터 TC(Transaction Coordinator)
- 예: Alice의 이체 요청을 받아와 Alice와 Bob의 은행에 전달하여 트랜잭션이 안전하게 수행되도록 함
스트로우맨 프로토콜
예:
a. Alice가 천원을 Bob의 S 은행 계좌에 이체할 것을 TC에게 요청한다. (Go! 메시지)
b. 요청을 전달받은 TC는 K 은행에 Alice 계좌에서 천원을 출금하고, S 은행에는 Bob의 계좌에서 천원을 입금하라고 동시에 전달한다.
c. TC는 Alice에게 요청을 잘 전달했다고 회신한다.
중앙의 중계자가 존재해도 스트로우맨 프로토콜이 잘 작동하지 않는 경우들
- 만약 Alice가 천원도 없었다면
- 만약 Bob의 계좌가 없었다면 → Alice 천원 잃음
- (가정) Alice의 K 은행이 TC로부터 요청을 받기 전에 예기치 않은 정전 발생으로 시스템이 중단되었다.
- TC는 일단 명령을 내린 후에 바로 Alice에게 요청을 잘 전달했다고 회신
- 하지만 K은행이 시스템이 중단되어 전달을 받지 못했다면 TC의 회신이 Alice에게 전달되지 않음
- TC와 Bob의 S 은행 사이의 네트워크가 중단되었을 때 Alice의 계좌에서만 출금 발생
- TC의 동작 중 중단
- Alice의 계좌에서는 출금되고 Bob의 계좌에는 입금되지 않은 상황 발생
장애가 일어날 수 있다는 것을 전제하고 ‘장애에 내성(Fault-tolerant)’을 가진 분산 컴퓨팅 시스템을 구축하여 안전성이 위반되는 일 자체를 막는 것이 더욱 현명한 방안이다.
원자적 커밋 프로토콜
장애가 발생하더라도 안전하게 트랜잭션을 수행할 수 있도록 스트로우맨 프로토콜의 문제 개선
a. Alice가 TC에게 K 은행에서 S 계좌로 이체할 명령을 내린다.
b. TC가 Alice의 명령을 받으면, 두 은행에게 준비하라는 명령을 동시에 내린다.
c. 각 은행은 준비 명령에 대해서 예(Yes) 또는 아니오(No)로 TC에게 회신한다.
TC는 두 은행으로부터 받은 회신들을 바탕으로 이행을 확약해야 할지, 아니면 명령을 취소해야 할지 결정해야 한다.
- 두 은행에서 모두 ‘예’라고 했다면 TC는 이행하라면 메시지를 두 은행에 보낸다. + Alice에게 이체 요청을 잘 처리했다고 회신
- 만약에 한 은행이라도 ‘아니오’라고 회신했다면, TC는 명령을 취소한다는 메시지를 보낸다. + Alice에게 요청 처리가 실패했다는 메시지를 보낸다.
안전성 보장을 위한 조치
TC가 Commit 메시지를 보낸 직후 TC가 중단된 경우
문제
- TC가 중단되면 Alice에게 이체를 처리했는지 안 했는지에 대한 회신을 할 수 없고,
- TC가 중단된 상태에서 정상 상태로 돌아왔을 때, TC가 Commit 메시지를 보냈는지에 대한 여부를 기억하지 못하고 있다면 사용자에게 이체 처리 결과를 알려줄 수도 없다.
- TC의 Commit 메시지가 정상적으로 K 은행과 S 은행에 도달해서 각 은행들이 출금과 입금을 진행했는데, 막상 TC는 자신이 Commit 메시지를 보냈지만 이제 요청 처리 상태를 모르게 되었다.
⇒ TC가 Commit 메시지를 보낸 사실을 기억하지 못하는 상황
해결
→ 항상 중단될 것ㅇ르 대비하여 TC는 Commit 메시지를 보내기 바로 직전에 Commit 메시지를 보낼 것이라는 ‘기록(log)’을 디스크에 저장한다.
⇒ TC가 중단되고 다시 재구동했을 때, 디스크의 기록을 찾아 Commit 메시지를 보냈다고 기억할 수 있고, Alice에게 이체 명령 처리 상태를 온전하게 보내줄 수 있다.
K 은행과 S 은행 모두 준비 메시지에 대해서 ‘예’라고 답했지만, 메시지를 보낸 즉시 둘 중의 한 은행이 중단된 경우
문제
S 은행은 메시지를 수신하자마자 TC가 보낸 Commit 메시지를 전달받지 못하고, 시간이 지나 다시 S 은행이 정상적인 상태로 돌아왔지만 S 은행은 ‘예’라는 메시지를 보낸 기억이 없으며, 중단된 동안 TC가 보낸 Commit 메시지도 받지 못한 상황
해결
K 은행과 S 은행도 메시지를 보내기에 앞서 디스크에 해당 메시지를 보낸다는 저장을 먼저 진행
- S은행에서 디스크에 저장한 기록을 보고 TC에게 ‘예’라고 회신했는데, Commit 사실 메시지를 받은 적이 없다는 것을 확인했다면
- TC에게 ‘예’라는 메시지를 재전송하고 다시 Commit 메시지를 전달받아 자신이 해야 할 출금 작업을 이행할 수 있도록 해야 한다.
명령 또는 회신 메시지들을 디스크에 기록하려는 찰나에 중단 발생하는 경우
- 만약 TC가 중단된 상태에서 정상으로 돌아와 기록을 확인했을 때, Commit 메시지를 보냈다는 기록이 없었다면 그대로 이체 명령을 취소한다.
- 마찬가지로 은행들 역시 중단 상태에서 정상으로 돌아와 기록을 확인했을 때, ‘예’라는 메시지를 보냈다는 기록을 가지고 있지 않다면, 각자 이체를 위해서 해야 할 작업들을 그대로 취소한다.
라이브니스 보장을 위한 조치
<상황1>
K 은행이 ‘예’ 메시지를 보낸다는 것을 기록할 찰나에 중단되어 버렸고, S 은행은 준비 명령을 받은 후 TC에게 ‘예’라고 답했다.
- TC 입장에서는 K 은행 자체가 문제가 있어 답변하지 않는 건지, 단순하게 네트워크 자원에 문제가 있어서 답변이 오지 않는 것인지 알기 어렵다.
- 아무런 행동 없이 기다려야 한다는 것은 해야 할 일이 결국은 일어나야 한다는 조건, 라이브니스를 위반하게 되는 것이다.
해결
- 메시지가 정해진 시간 내에 오지 않으면 당초의 명령을 취소하고, Alice에게 다시 이체 요청을 해야 한다고 알린다. (처음부터 모든 과정을 반복해야 하므로 시간 낭비)
<상황2>
K와 S 은행 모두 ‘예’라고 답했으며 디스크 기록까지 완료, 하지만 TC로부터 응답 X
- 다른 은행이 동의했는지에 대한 여부를 모르기 때문에 Commit 메시지 없이 독단적으로 작업을 이행할 수 없다.
- 두 은행은 TC의 후속 명령 없이는 마냥 기다려야 한다.
→ 안전성을 보장하지만 라이브니스 위반
해결 - 두 은행이 서로의 상태를 확인하는 과정을 추가
K은행은 S은행에게 직접 상황을 물어보기로 한다.
- (경우1) S 은행이 무응답 → K 은행은 TC로부터의 후속 명령을 기다리기
- (경우 2) S 은행이 TC로부터 Commit 또는 취소 명령을 받았다고 응답한다. → K 은행은 자신도 그 명령을 받은 것으로 간주
- TC에서 S 은행 사이의 네트워크는 문제없었고, TC와 K 은행 사이의 네트워크에 문제가 있었다고 판단
- (경우 3) S 은행이 K 은행에게 아직 TC에게 답변하지 않았다거나, 아예 ‘아니오’를 회신했다고 말한다. → K 은행은 더 이상 기다릴 것 없이 자신이 할 일을 취소해버리면 된다.
- TC가 프로세스를 재시작하는 것이 훨씬 빠르다.
- (경우 4) S 은행이 K 은행에게 TC에게 ‘예’라고 회신했다고 말한다. → K과 S은행은 TC로부터 후속 명령을 계속 기다리면 된다.
⇒ 두 은행 간에 상황 메시지 확인 절차를 통해 더 빠른 이행 확약 여부를 결정하는 프로토콜 종료 및 재시작의 기준을 프로토콜에 더하여 라이브니스 문제를 해소
2단계 프로토콜 수행 예시
2단계 커밋 프로토콜(Two-Phase Commit Protocol) 혹은 2PC 프로토콜
: 원자적 커밋 프로토콜에 선제적 메시지 기록을 통한 메시지 재전송과 프로토콜 종료 또는 재시작의 조건을 더해서 이행 확약의 안전성과 라이브니스를 보장하는 프로토콜
알고리즘
- 클라이언트가 TC(Transaction Coordinator)에게 트랜잭션 명령 메시지를 보낸다.
- TC가 트랜잭션에 참여하는 모든 프로세스들에게 트랜잭션을 수행할 준비를 하라는 메시지를 보낸다.
- 준비하라는 메시지를 받은 각 프로세스는 TC에게 ‘예’ 또는 ‘아니오’라고 답변한다.
- TC의 결정
- 만약 모든 프로세스들이 ‘예’라고 답했다면 TC는 모든 프로세스에게 명령 이행을 확약하라는 메시지를 보낸다.
- 만약에 어느 한 프로세스라도 ‘아니오’라고 답한다면 클라이언트의 이행을 취소한다는 메시지를 보낸다.
(예) S 은행이 ‘예’라고 메시지를 TC에게 보내기 전에 장애가 발생했지만, K 은행은 ‘예’라고 메시지를 이미 보냈고 TC도 해당 메시지를 받았다.
- ‘예’라고 전달한 기록이 없다면 TC가 Commit 메시지를 전송했을리가 없을 것이므로 문의할 필요가 없다.
- TC는 실제로 제시간 안에 S 은행으로부터 아무런 응답을 받지 못했으므로 취소 메시지를 보낸 상황
- S은행이 중단되었다가 정상 상태로 돌아왔을 때, ‘예’ 메시지에 대한 기록이 없다면 바로 준비 상황을 취소하는 것으로 받아들이면 된다.
(예) S 은행이 ‘예’라고 답변한 후에 중단되었다.
‘예’라고 회신한 기록이 있기 때문에 K 은행의 회신에 따라서 TC는 어떤 결정을 내렸을 것이다.
- TC가 Commit 메시지나 취소 메시지를 보낼 시점에서 S 은행이 회복됐다면, 해당 후속 메시지가 그림과 같이 S 은행에 도착할 것이다.
- 만약 기다렸는데 오지 않으면 상대 은행인 K 은행에게 지금까지 어떤 결정이 있었는지 물어보면 된다.
(예) S 은행이 ‘예’ 메시지를 보낸 후에 중단되었고, 해당 회신 메시지도 네트워크 장애로 인하여 TC에 전달되지 않았다.
- ‘예’ 메시지가 TC에 도달하지 않았고, TC는 기다리다가 실제 취소 메시지를 보냈다.
- 이후 S 은행이 다시 정상적으로 작동하여 ‘예’ 메시지를 보냈었다는 것을 알았지만, 아직 어떤 결정이 난지는 알 수 없다.
- 이때 ‘예’ 메시지를 다시 TC에 보내서 어떤 결정이 났는지를 물어 알아볼 수 있다.
(예) TC에게 문의했을 때 응답 X
- TC가 회복할 때까지 기다리지 않고, S 은행이 K 은행에게 직접 문의하여 TC의 후속 명령이 무엇이었는지 질문하고 그에 맞게 자신이 할 일을 결정하면 된다.
도서 - 분산컴퓨팅 2장