[ Brave New Geek ] You cannot have exactly once Delivery
분산시스템의 맥락에서, exactly-once message delivery는 존재할 수 없다.
Web browser and Server ? Distributed
Server and database? Distributed
Server and message queue? Distributed
이러한 맥락에서 exactly-once delivery semantics는 있을 수 없다.
분산 시스템은 모든것이 trade-off에 관한 것이다. 이 문제 또한 그와 관련되어있다. 본질적으로 3가지 타입의 delivery semantics가 존재하는데
at-most-once
, at-least-once
, exactly-once
이다. 앞의 2가지는 구현 가능하고 널리 사용되고 있다. 철저하게 말하자면
at-least-once
조차도 불가능하다 말할 수 있음. 기술적으로 말하면 network partitions 이 엄밀히 시간 제약이 있는것이 아니기에 서버의 커넥션이 계속적으로 방해가 된다면 아무것도 전달할 수 없는것임. 그러나 실제적으로 말하면 network partition은 유한하게 지속되기 때문에 at-least-once delivery
는 가능하다고 볼 수 있음왜 exactly-once delivery는 불가능한것인가? 답은
Two Generals thought experiment
또는 Byzantine Generals Problem
에 있다. 또한 우리는
FLP(FischerLynchPaterson) 결과
에 대해서도 고려해야 한다. 이는 실패하는 프로세스(One Faulty Process)의 가능성이 있을 때, 분산 시스템이 어떠한 의사결정에 동의하는 것이 불가능하다는 것을 말한다.내가 너에게 편지를 보냈다고 치자. 그 편지에는 이를 보면 전화해달라는 내용이 적혀있다. 근데 너는 전화를 하지 않는다. 너가 나의 편지를 중요하게 생각하지 않아서 일수도 있고(receiver process에 문제) 편지가 중간에 사라져서(sender가 제대로 못보냈거나, 중간에 소실) 일수도 있다.
분산 시스템에서는 메시지의 전송을 보장하기 위해 해당 메시지를 받았다는 ack를 기다리게 되는데 이러한 모든 것들이 다 잘못될 수가 있다. 메시지가 소실되었는지, ack가 소실이 되었는지, receiver가 crash가 되었는지, receiver가 느린건지, 네트워크가 느린건지, 내가 느린건지 등.
FLP와 Two Generals Problem 는 디자인에서의 복잡성이 아닌 불가능한 결과이다.
메시지가 전송되자마자 acknowledge 가 즉시되고 그 후에 processing이 진행되는 옵션은 sender가 ack 를 받고 그것으로 끝이다. 그러나 receiver가 processing을 하기 전에 혹은 processing 중에 죽어버린다면 데이터는 영구적으로 소멸되는 것임. 이것이
at-most-once delivery
의 상황임.
솔직하게 보면 at-most-once
를 구현하기 위해서는 상황에 따라 이보다 조금더 복잡할 수 있음. task를 processing 하는 worker가 여러개 이거나 work queue가 복제되어 있다면, task 가 ack 되었는데 또 다른 worker에게 한번 더 전송되는 일이 없도록 확신하기 위해서 broker 는 강력한 일관성을 보장해야 함(또는 CAP 이론에서 CP). Apache Kafka는 이러한 coordination을 위해 ZooKeeper를 활용함반면에 우리는 메시지가 처리되고 난 뒤에 acknowledge를 할 수도 있음. 만약 프로세스가 메시지를 받고 ack를 보내기 전에 죽었다면(또는 ack가 보내지지 않았다면) sender는 다시 재전송을 할 것임. 이것이
at-least-once delivery
임. 거기에 더해 메시지를 순차적으로 한 곳이 아닌 다른 곳에 보내고 싶을 때, atomic broadcast가 필요하고 이는 처리량(throughput)에 꽤나 큰 부담이 됨RabbitMQ는 이러한 구절로
at-least-once
전송을 보장함confirm을 활용할 때, 채널이나 연결 실패에서 회복한 producers는 broker 로 부터 acknowledgement 를 받지 못했기에 메시지를 재전송해야함. 이 때 메시지 중복의 가능성이 존재하는데, 왜냐하면 broker가 confirmation을 보냈는데 producer에게 닿지 못했을 수 있기 때문임(network failure로 인한). 그래서 consumer application은 중복제거를 수행하거나 들어오는 메시지에 대해 idempotent 하게 동작되도록 처리를 해야한다.
exactly-once 전송을 실제적으로 가능케 하는 방법은 속이는 것이다. 메시지가 idempotent 하거나(한번 이상 부작용 없이 계속적으로 적용되어도 되는), 중복제거를 통해 idempotency 에대한 요구를 없애거나 하는 방법으로 달성 가능하다. 이상적으로는 우리의 메시지는 순서보장되는 것을 엄격하게 요구하지 않고 commutative(순서에 상관없이 결과가 동일한) 해도 된다. 어떠한 방법을 택하던 간에 design implication 이나 trade-off 가 존재한다.
commutative하고 convergent replicated data types 인 서비스들이 인기가 있다. 그러나 우리는 여전히 메시지의 순서에 관심이 있고, 이는 sequencing, vector clock, partial-ordering 메커니즘을 통해 직접적으로 풀수 있다.
다시 말하지만, exactly-once delivery 같은건 존재하지 않고 우리는 at-least-once나 at-most-once 중에 골라야 한다. 메시지를 idempotent하게 작동되도록 한다거나, 중복제거를 통해서 exactly-once 와 같이 만들수는 있다.
다시한번, 분산시스템을 디자인 할때는 trade-off를 이해하는 것이 중요하다. 비동시성이 넘쳐흐르고, 이는 동시적인 보장되는 행동을 기대할 수 없다는 것을 의미한다. 비동시성의 본질에 대비해 failure와 resiliency 를 고려하여 디자인하라.