분산 시스템에서 ‘정확히 한 번’은 없다
분산 시스템에서 ‘정확히 한 번’은 없다
단일 프로세스 안에서 코드를 실행할 때는 “이 작업은 한 번만 실행된다”는 가정을 크게 의심하지 않는다. 함수가 호출되면 실행되고, 결과가 반환된다. 하지만 시스템이 여러 서비스로 나뉘고, 네트워크를 통해 통신하는 순간 이 가정은 더 이상 성립하지 않는다. 분산 시스템에서는 “정확히 한 번(exactly-once)”이라는 개념이 생각보다 훨씬 어렵고, 사실상 특별한 조건이 아니면 보장하기 힘들다.
그 이유는 단순하다. 네트워크는 신뢰할 수 없기 때문이다. 요청이 성공했는지, 실패했는지, 혹은 응답이 단순히 늦어지고 있는 것인지 클라이언트는 정확히 알 수 없다. 이 상황에서 가장 안전한 선택은 재시도다. 하지만 재시도를 하는 순간, 동일한 작업이 여러 번 실행될 가능성이 생긴다.
이 지점에서 등장하는 개념이 at-least-once와 at-most-once다.
at-least-once는 “최소 한 번은 실행된다”는 보장이다. 메시지가 중복 처리될 수는 있지만, 유실되지는 않는다. 반대로 at-most-once는 “최대 한 번만 실행된다”는 의미다. 중복은 없지만, 메시지가 유실될 수 있다. 두 방식 모두 장단점이 있고, 어떤 것을 선택하느냐는 시스템의 요구사항에 따라 달라진다.
실무에서 메시지 큐나 이벤트 기반 시스템을 다루다 보면 이 차이를 직접 체감하게 된다. 예를 들어 결제 처리나 주문 생성 같은 중요한 이벤트에서는 메시지 유실이 더 치명적이기 때문에, 보통 at-least-once 방식을 선택한다. 하지만 이 경우 동일한 이벤트가 두 번 처리될 수 있기 때문에, 시스템은 이를 감당할 수 있도록 설계되어야 한다.
여기서 핵심이 되는 개념이 바로 idempotency(멱등성)이다. 같은 요청이 여러 번 들어와도 결과가 한 번 실행된 것과 동일하게 유지되도록 만드는 성질이다. 예를 들어 “주문 생성” API가 멱등성을 가진다면, 동일한 요청이 두 번 들어오더라도 주문은 하나만 생성되어야 한다. 이를 위해 요청에 고유한 식별자를 부여하거나, 이미 처리된 작업인지 확인하는 로직을 추가하는 방식이 사용된다.
이 과정을 제대로 고려하지 않으면, 분산 시스템에서는 다양한 문제가 발생한다. 중복 결제, 중복 데이터 생성, 상태 불일치 같은 이슈들이 대표적이다. 특히 이벤트 기반 구조에서는 하나의 이벤트가 여러 소비자에게 전달되기 때문에, 각 소비자가 동일한 이벤트를 어떻게 처리할지에 대한 명확한 기준이 필요하다.
흥미로운 점은 많은 시스템이 “정확히 한 번”을 목표로 시작하지만, 결국은 “중복을 허용하되 안전하게 처리하는 방향”으로 설계가 바뀐다는 것이다. 완벽한 전달을 보장하려는 시도는 복잡도를 크게 증가시키고, 그에 비해 얻는 이점이 제한적인 경우가 많기 때문이다. 반대로 멱등성을 기반으로 설계하면, 재시도와 중복을 자연스럽게 흡수할 수 있다.
또한 장애 상황에서는 이 차이가 더욱 중요해진다. 소비자가 메시지를 처리하던 중에 실패했을 때, 해당 메시지를 다시 처리할지, 아니면 버릴지에 대한 정책이 필요하다. 이때 멱등성이 보장되어 있다면 재처리를 선택할 수 있고, 시스템은 더 안정적으로 동작한다.
결국 분산 시스템에서는 “정확히 한 번 실행된다”는 가정 대신, “여러 번 실행될 수 있다”는 전제를 받아들이는 것이 더 현실적이다. 그리고 그 위에서 중복을 어떻게 다룰 것인지, 데이터의 일관성을 어떻게 유지할 것인지 설계하는 것이 핵심이 된다.
그래서 중요한 질문은 이것이다.
이 작업이 두 번 실행되면 어떤 일이 벌어질까.
그 질문에 안전하게 답할 수 있을 때, 비로소 분산 시스템은 안정적으로 동작하기 시작한다.