Loading
2022. 5. 28. 18:50 - lazykuna

분산 시스템에서 메시지를 중복으로 받을 수밖에 없는 이유

분산 시스템에서는 기존 단일 시스템에서의 컴퓨팅 환경과 다르게 동작하는 특성을 종종 보고 놀랄 때가 있습니다. 메시징 시스템도 그러한 요소중 하나입니다.

정말 중복으로 올 수는 있는 건가요?

애당초 스펙에서 at least once를 스펙으로 내세우고 있습니다. 분산시스템 큐로 널리 알려진 SQS에서도 “At-least-once”를 내세우고 있습니다.

그렇다면 어떤 경우에 메시지가 중복으로 올 수 있을까요? 아마 아래와 같이 메시지를 전달받고 수행 도중 문제가 발생했을 경우일 것입니다.

  • 데이터를 제대로 받지 못한 경우. 혹은, ACK를 보내지 못했을 경우. (네트워크 패킷 유실 등)
  • 데이터를 받았지만, 회신을 보내지 못함 (혹은 늦게 전달됨). (어플리케이션의 문제, 회선 문제 등)

그렇다면 또다른 의문이 들 수 있습니다. 그냥 메시지를 한 번만 보내면 안 될까…? 당연히 안 되겠죠! 메시지를 아예 받지 못한 경우에는 정합성 문제가 발생하게 되고, 추적도 어려울 것입니다.

이게 문제가 될까요?

물론 클라이언트가 정상적으로 처리하지 못해서 메시지를 두번 받게 되는 것은 정상적인 행위입니다. 애당초 그러라고 이렇게 만들어 진 것이니까요.

하지만 서버가 만약 어떠한 연유로 ACK를 받지 못했다면, 클라이언트는 중복된 메시지를 받게 될 것입니다. 이를 그대로 처리하려고 하면 당연히 문제가 발생하게 될 것입니다.

이를 해결하기 위한 방법

중복된 메시지를 받게 되는 것은 클라이언트이므로, 클라이언트가 이를 처리할 수 있어야 합니다.

1. 가장 마지막으로 받은 메시지보다 작은 timestamp의 메시지를 무시

가능하다면 사용할 수 있는 방법이나, 순서가 뒤죽박죽이면 쓸 수 없습니다.

이를테면, 서버에서 메시지를 A - B - C 순서대로 보냈다고 가정할 시, 클라이언트가 아래와 같이 수신했다고 하면: A - C - B - A'

“B”의 메시지가 무시되는 문제가 발생합니다. 왜냐하면 timestamp가 더 큰 C보다 B가 늦게 도착했기 때문에 이미 받은 중복 메시지인 것으로 착각하기 때문입니다.

2. 메시지 ID를 캐시

기존에 받았던 메시지 ID를 캐시해 놓고 있다가, 메시지를 새로 받게되면 해당 ID가 이미 존재하는지 검사하여 중복을 방지하는 방식입니다.

단 이건 캐시 window가 너무 길면 메모리 소요량이 기하급수적으로 증가하기 때문에, 적당 수준의 window를 할당/계산해두어야 합니다.

예) 메시지가 초당 1000개가 오고, 메시지 ID의 크기는 32bit(4byte)에, 약 1분까지의 retry에 대해서 중복전송을 방지하는게 목표라면 1000 * 4 * 60 = 240kb의 캐시를 설정할 수 있음.

참고