정합성은 코드가 아니라 구조에서 결정된다 (feat. Uber)
- 개발
- 2025. 12. 30. 20:37
카드에는 크게 두 가지 결제 방식이 있습니다. 체크 카드와 신용 카드입니다. 결제 과정은 서로 다르지만, 사용자가 기대하는 결과는 같습니다. 같은 10,000원짜리 상품을 구매했다면, 체크 카드로 결제했든 신용 카드로 결제했든 결제가 '성공했다'는 최종 상태는 동일해야 합니다.
결제 방식이 다르다는 사실은 사용자에게 중요한 정보가 아닙니다. 중요한 것은 성공 여부와 그 결과가 일관되게 유지되는가입니다. 이처럼 서로 다른 처리 과정을 거치더라도 하나의 동일한 결과로 수렴해야 하는 문제를 시스템에서는 정합성(consistency) 이라고 부릅니다. 이 정합성 문제를 다루는 가장 단순한 접근은 클라이언트가 서버에 상태를 반복적으로 요청하는 HTTP 요청 기반의 폴링 방식입니다. 현재 상태를 계속 확인함으로써 결과가 맞는지 스스로 검증하는 방식입니다. 하지만 시스템의 규모가 커지고, 실시간성이 요구되며, 동시에 처리해야 할 요청이 폭증하기 시작하면 이 단순한 접근은 점점 한계를 드러내게 됩니다.
이 글에서는 결제 시스템의 정합성 문제를 출발점으로, 대규모 실시간 서비스를 운영하는 Uber의 사례를 통해 정합성이 분산 시스템에서 어떻게 깨지고, 또 어떤 구조적 선택을 통해 다뤄지는지를 살펴봅니다.
우버는 어떻게 정합성을 해결했을까? 그리고 어떤 정합성일까?
Uber에는 크게 두 가지 서비스가 존재합니다. 운전자를 위한 서비스와 탑승자(손님)를 위한 서비스입니다. Uber는 이 두 사용자 간의 매칭 결과를 여러 서비스에 걸쳐 저장하고 관리합니다. 하지만 서비스가 거대해지면서, 동일한 매칭 결과가 시스템마다 다르게 인식되는 정합성 문제가 발생하기 시작했습니다. Uber는 이러한 문제를 해결하기 위해 아키텍처를 한 번에 바꾸기보다, 총 세 단계에 걸쳐 점진적으로 구조를 개선해 나갔습니다.
1세대
초기의 Uber는 서비스 규모가 크지 않았습니다. 그로 인해 아키텍처 또한 비교적 단순한 형태를 유지하고 있었습니다. 핵심 서비스는 크게 두 가지였습니다. 탑승자와 운전자를 매칭하는 디스패치 서비스, 그리고 사용자의 이용 기록을 저장하는 API 서비스입니다.
디스패치 서비스는누가 누구와 매칭될지를 결정하는 역할을 맡았고, API 서비스는 디스패치가 만든 매칭 결과를 저장하며 탑승 이력과 운행 이력을 관리했습니다. 매칭 결과가 생성되고 저장되는 흐름이 단순했기 때문에 이 시기에는 정합성 문제가 크게 발생하지 않았습니다.
디스패치 서비스: 탑승자와 운전자를 매칭하는 결정을 담당하는 핵심 서비스

아키텍처를 대략적으로 그리면 위와 같은 그림으로 표현할 수 있습니다. (실제로는 훨씬 더 복잡했겠지만요.)
이 구조에서 중요한 점은 사실을 만들어내는 주체가 하나뿐이었다는 것입니다. 매칭 결과는 디스패치 서비스에서 한 번만 결정되었고,
다른 서비스들은 그 결과를 그대로 받아들이는 구조였습니다. 이러한 구조가 가능했던 이유는 서비스 수가 적었고 트래픽 또한 제한적이었기 때문입니다. 그 결과, 이 시기에는 정합성 문제가 크게 드러나지 않았습니다.
2세대
2세대에 접어들면서 Uber의 서비스 규모는 빠르게 커지기 시작했습니다. 이에 따라 디스패치 서비스가 처리해야 하는 책임 또한 점점 늘어났습니다. 기존의 디스패치 서비스는 탑승자와 운전자의 매칭을 결정하는 동시에, 그 결과를 API 서비스로 전달하는 역할까지 함께 수행하고 있었습니다. 하지만 서비스 수와 트래픽이 증가하면서 이 구조는 점점 병목이 되기 시작했습니다. 특히 디스패치 서비스에서 생성된 매칭 결과를 여러 API 서비스로 전달하는 과정이 지연되기 시작했고, 이는 전체 서비스 응답 속도에 직접적인 영향을 주었습니다.
이 문제를 해결하기 위해 우버는 디스패치 서비스가 모든 책임을 떠안는 구조에서 벗어나, 기능 단위로 서비스를 분리하기로 결정했습니다.
서비스를 분리함으로써 각 서비스가 담당하는 역할을 명확히 하고, 데이터 전달 속도와 확장성을 개선하고자 한 것입니다.

서비스가 탑승 이력, 운행 이력, 결제, 알림 등으로 늘어나기 시작했습니다. 하지만 서비스가 분리되면서 하나의 매칭 결과가 모든 서비스에
동일하게 반영되지 않는 현상이 발생하게 되었습니다. 여기서 말하는 정합성은 모든 서비스에 동일한 정보가 들어가야 하는것입니다.
3세대
3세대에 들어서면서 우버는 이 정합성 문제를 반드시 해결해야 했습니다. 결국 우버가 선택한 방식은 탈중앙화와 셀프 서비스 구조를 통해
각 서비스가 스스로 상태를 확인하도록 만드는 것이었습니다. 탈중앙화와 셀프 서비스는 과연 무엇일까요? 탈중앙화란 더 이상 중앙에서 모든 데이터를 관리하지 않는 구조를 의미합니다. 지금까지 우리는 정합성을 지키기 위해서는 중앙에서 데이터를 관리해야 한다고 생각해 왔습니다. 그래야 모든 서비스에 같은 정보를 전달할 수 있다고 믿었기 때문입니다.
하지만 우버는 이러한 방식이 오히려 정합성을 깨뜨릴 수 있다는 사실을 마주했고, 그 결과 탈중앙화 구조를 선택하게 되었습니다.
탈중앙화: 상태에 대한 결정을 하나의 중앙에 의존하지 않고 여러 서비스로 분산시키는 구조
셀프 서비스: 각 서비스가 중앙에 의해 상태를 전달받는 대신, 필요할 때 스스로 상태를 조회하고 확인하는 구조

그림만 보면 기준 데이터가 모든 것을 통제하는 중앙 시스템처럼 보일 수 있습니다. 하지만 실제 구조에서는 기준 데이터가 흐름을 제어하지 않습니다. 각 서비스는 독립적으로 동작하며, 정합성이 필요할 때만 기준 데이터를 참조합니다.
즉, 중앙화된 것은 데이터가 아니라 판단의 기준입니다.
그렇다면 기준 데이터는 어떻게 선정할 수 있을까요? 그리고 기준 데이터가 정해지면, 모든 서비스가 이를 계속 확인하는 구조가 되는 걸까요? 기준 데이터는 사실을 최초로 생성한 주체에 의해 정의됩니다. 다만 각 서비스가 이 데이터를 맹신하거나 실시간으로 의존하지는 않습니다. 대신 문제가 발생했을 때, 필요에 따라 기준 데이터를 직접 확인함으로써 시스템 전체의 정합성을 회복합니다.
우버는 매칭이라는 최초 사실을 디스패치에서 한 번만 생성하여 기준 데이터로 저장하고, 각 서비스가 이를 자율적으로 확인하는 구조를 통해 대규모 환경에서도 정합성을 유지했습니다.
마무리
우버는 모든 상태를 실시간으로 맞추려 하지 않았습니다. 대신 이벤트를 통해 상태를 빠르게 확산시키고, 불일치가 발생했을 때만 기준 데이터를 직접 확인해 각 서비스가 스스로 상태를 보정하도록 설계했습니다. 이는 실시간성을 무리하게 보장하기보다, 결국 올바른 상태로 수렴할 수 있는 정합성을 우선한 선택이라 볼 수 있습니다.
여기서 말하는 정합성은 단순히 데이터가 맞다, 틀리다의 문제가 아닙니다. 현재 상태가 올바른지 검증할 수 있는 기준이 존재하는 상태를 의미합니다. 이전에 낙관적 락(Optimistic Lock) 또한 정합성이 맞다고 들었을 때, 저는 쉽게 이해되지 않았습니다. (그땐 그냥 그렇구나 했죠)요청 결과가 매번 달라질 수 있는데, 어떻게 정합성이 맞다고 볼 수 있는지 의문이 들었기 때문입니다. 하지만 다시 생각해보면, 낙관적 락 역시 '락'입니다. 충돌이 발생하면 성공이 아닌 실패라는 명확한 결과를 반환하고, 현재 상태가 유효한지 아닌지를 분명하게 판별할 수 있습니다. 즉, 결과가 성공이든 실패든 상태에 대한 확신을 검증할 수 있기 때문에 낙관적 락 역시 정합성을 만족한다고 볼 수 있다고 생각합니다.
출처
https://blog.bytebytego.com/p/ep-39-accounting-101-in-payment-systems
'개발' 카테고리의 다른 글
| 멀티 모듈 개발기(1) (feat.grale) (0) | 2026.01.02 |
|---|---|
| 스쿼시, 머지, 리베이스 (0) | 2025.12.31 |
| 프록시 서버 알아보기 (0) | 2025.12.26 |
| 멱등성은 응답이 아니라 상태다 (0) | 2025.12.23 |
| 깃 플로우: 브랜치로 읽는 코드의 현재 상태 (0) | 2025.12.20 |