들어가며 링크 복사

안녕하세요. 렌터카개발파트에서 서버 개발을 담당하고 있는 해리입니다. 저희 파트는 '일단기렌터카'(최단 하루부터 최장 30일까지 대여 가능)와 '카셰어링'(분 단위로 이용 가능) 두 가지 서비스의 서버 개발을 맡고 있습니다. 이번 포스팅에서는 '카셰어링' 서비스를 설계하고 개발하는 과정에서 직면한 이슈들, 이를 해결하기 위한 고민들, 그리고 최종적으로 어떻게 구성하여 론칭했는지 설명하겠습니다.

'카셰어링' 서비스는 2024년 1월에 오픈한 채널링 기반 서비스입니다. 채널링 기반 서비스란 여러 벤더사(외부 공급 업체)들이 하나의 플랫폼(카카오 T)을 통해 서비스를 제공하는 것을 의미합니다. 이러한 서비스의 특징은 벤더사와의 통신 및 데이터 정합성 문제가 서비스 품질과 밀접하게 연관된다는 점입니다. 따라서 프로젝트 초기부터 프로토콜 흐름, API 설계 등에 대해 벤더사와 긴밀하게 소통해 왔습니다. 이 과정에서 고민한 내용들과 문제 해결을 위해 선택한 돌파구를 공유하고자 합니다.

요구 사항 및 해결 방안에 대한 고민 링크 복사

고민 목표
벤더사 트래픽에 대한 부담 조회 트래픽 감축
카카오 T 외부 장애로 인한 서비스 장애 외부 결합 느슨하게

벤더사 측에서 가장 우려한 점은 카카오 T를 통해 발생하는 트래픽 부담이었습니다. 기존에 자체 앱으로 서비스를 제공하던 벤더사들은 갑작스러운 트래픽 증가가 자사 서비스에 장애를 일으킬 수 있다고 언급했습니다. 이에 따라 서비스 특성을 고려하여, 트래픽의 주된 비중이 조회일 것으로 예상하고 조회 요청에 대한 부담을 줄이는 것을 목표로 삼았습니다.

저희 측에서 가장 우려했던 점은 벤더사 장애로 인한 서비스 장애였습니다. 여러 벤더사와 협력하여 서비스를 제공하는 상황에서, 특정 벤더사의 장애가 전체 렌터카 서비스의 중단으로 이어지는 것을 막아야 했습니다. 따라서 벤더사별 장애를 격리하고 벤더사와의 결합을 느슨하게 하여, 벤더사의 장애가 우리 서비스로 확산되지 않도록 하는 설계를 목표로 삼았습니다.

해결 전략 링크 복사

트래픽 부담은 이벤트 기반 데이터 관리로 링크 복사

가장 먼저 계획한 전략은 벤더사의 일부 데이터를 우리 측에 상시 보관하는 것이었습니다. 이 방식은 앞서 언급한 모든 요구사항을 해결하는 데 기여할 수 있었습니다.


이 전략으로 서비스 플로우에서 불필요한 조회 요청을 줄여 벤더사의 트래픽 부담을 크게 낮출 수 있습니다. 동시에 과도한 요청으로 인한 벤더사 장애도 예방할 수 있습니다. 게다가 서비스에 필요한 일정 수준의 데이터를 우리가 보관하기 때문에, 벤더사에 장애가 생겨도 카카오 T 사용자는 예약하기 전까진 이를 알지 못합니다.

하지만 유사한 상황에서 흔히 사용되는 '스케줄링 업데이트' 방식만으로는 충분한 대응이 어려웠습니다. 차량 상태나 예약 여부와 같은 실시간 데이터가 서비스에 필요했기 때문입니다.

이러한 문제를 해결하기 위해 채택한 개념이 바로 'CQRS'를 통한 '이벤트 기반 데이터 관리'입니다.

CQRS는 명령과 조회의 책임을 분리하는 전략입니다. 이는 읽기와 쓰기 모델이 다른 경우에 관심사 분리가 가능해 유연한 대응을 할 수 있게 해주는 전략으로, 우리의 현재 상황에 적절하다고 판단했습니다.

이벤트 기반 구조를 설계하면서 우리가 설정한 몇 가지 핵심 규칙이 있습니다.

1. 절대 조회 측에서 직접 ‘Read DB’의 데이터 변경을 시도하지 않을 것 링크 복사

비즈니스 로직상 KM(카카오모빌리티) 측에서 시작된 플로우가 조회 DB의 업데이트로 이어지는 경우가 있습니다. 이런 상황에서도 반드시 쓰기(Write) 대상(벤더사)에서 발행하는 이벤트를 기반으로만 읽기(Read) 데이터의 정보를 갱신한다는 원칙을 따릅니다.

2. 읽기 데이터 모델링 간소화 링크 복사

‘Read DB’에서 필요 이상의 정보를 담으면 변경 이벤트 대상이 과도하게 많아져 벤더사의 이벤트 발행 부담이 커질 수 있습니다. 이는 또한 구현의 복잡도를 높이게 됩니다.

이를 방지하기 위해 읽기 데이터 모델은 서비스 노출에 필요한 최소한의 데이터만을 포함해야 합니다.

3. 모든 이벤트 정보는 반드시 발행 시점의 타임스탬프 첨부 링크 복사

이벤트는 서로 독립적으로 발행되기 때문에 발행 순서와 수신 순서가 다른 상황이 발생할 수 있습니다. 이런 경우에 대응하기 위해 모든 이벤트에 발행 시점(Write DB 기준 시점)의 타임스탬프를 기록했습니다. 그리고 이벤트의 종류와 상황에 따라 각기 다른 방식으로 대응했습니다.

4. 이벤트 처리 로직은 반드시 멱등성 보장 링크 복사

마지막으로 구현의 단순화를 위해 채택한 메시지 전략은 ‘at-least-once’입니다. 이 전략은 동일한 이벤트가 중복(retry)으로 수신되는 상황을 피할 수 없습니다. 따라서 이벤트를 수신하는 로직은 반드시 멱등성을 보장하도록 설계해야 합니다.

여기까지가 이벤트 구조 설계 및 구현에 앞서 기본 틀로 삼은 규칙들입니다.

이에 더해, ‘네트워크 등의 장애’ 혹은 ‘데이터 불일치’ 등 예기치 못한 상황에 대비하여 주기적으로 실행되는 조회/업데이트 배치 데이터 보정 작업을 추가했습니다. 이를 통해 장애 상황에 임시 대응하고, ‘이벤트 누락’과 동기화 불일치’를 감지합니다. 또한, 발생한 로그를 분석하여 원인을 파악하고 대응함으로써 데이터 동기화의 완성도를 높였습니다.

느슨한 결합은 비동기 처리로 링크 복사

다음은 벤더사 장애로부터 조금 더 멀어지기 위해 비즈니스 로직에도 벤더사 장애 격리 로직을 녹였던 부분입니다.

일시적인 실패 후 후속 조치가 가능한 API의 경우, 비동기 처리와 후속 대응 로직을 통해 장애를 격리할 수 있습니다.

예를 들어, 사용자가 차량 이용 후 반납하는 상황에서 벤더사 장애로 반납이 계속 실패한다면, 사용자에게 불필요한 과금이 발생하고 이로 인한 CS 대응 리소스까지 소모됩니다. 이런 상황에 대비해 저희 파트는 벤더사와의 통신이 실패해도 반납 가능 여부에 대한 검증만 완료되면 즉시 반납을 승인합니다. 또한, 실패한 요청에 대해서는 별도 컴포넌트를 통해 일정 시간 후 재발송하는 방식으로 후속 대응하고 있습니다.

물론 모든 API에서 비동기 처리를 하는 것은 불가능합니다. 예를 들어, 예약을 시작하는 차량 선점 로직에서는 최종 확인을 벤더사에서 해야 하므로 반드시 동기로 처리해야만 합니다.

하지만 일시적인 실패 이후 후속 조치가 가능한 상황이라면 ‘비동기 처리’ 사용을 최대한 고려했습니다. 이러한 로직 설계를 통해 벤더사 장애로부터 조금 더 멀어지는 데 도움이 되었습니다.

장애 예방은 ‘API 커스텀 요청’으로 링크 복사

마지막으로, 벤더사 API 호출 시 몇 가지 추가 방안으로 벤더사 장애를 예방하고 격리했던 부분을 설명하겠습니다.

첫째, 장애 예방을 위해 API별로 '재시도 횟수'와 '리드 타임아웃'을 수치를 조정했습니다. 물론 벤더사 API의 역할에 따라 재시도(retry)를 하면 안 되는 경우도 있습니다. 하지만 일반적으로 재시도 설정은 시스템상의 에러를 즉시 사용자에게 전달하지 않고 사용자 경험을 향상시킬 수 있기에 꼭 필요한 전략입니다.

추가로, 재시도 설정을 고려할 때 반드시 함께 조정해야 하는 것이 리드 타임아웃(Read timeout)입니다. 리드 타임아웃이 너무 길면 앞단의 클라이언트에서 먼저 타임아웃이 발생할 수 있기 때문입니다.

따라서 재시도와 타임아웃을 설정할 때는 다음을 고려해야 합니다.

  1. 요청 처리에 시간이 많이 필요한 작업이라 한 건을 오래 기다리는 것이 유리한지
  2. 실패 시 빠르게 타임아웃을 발생시켜 재요청하는 것이 좋을지

이러한 점들을 고려하여 API 특성에 맞게 설정하는 것이 중요합니다.

이 과정은 서비스 운영 중에도 지속적인 모니터링과 조정이 필요합니다. 또한, 벤더사와의 끊임없는 소통을 통해 서비스를 계속 개선해야 합니다.

둘째, 장애 격리를 위해 서킷 브레이커(circuit-breaker)를 사용했습니다. 벤더사별로 설정한 서킷 브레이커는 임계치 이상의 요청 실패가 발생하면 장애 상황으로 판단합니다. 그 즉시 일정 시간 동안 요청을 전송하지 않고 실패로 처리합니다.

이 방식은 일시적인 트래픽 과부하로 서킷이 열리면, 대상 벤더사에게 회복할 수 있는 쿨다운 시간을 주어 장애가 지속되는 것을 방지합니다. 실제 운영 중 일부 벤더사에서 장애가 발생했을 때, 해당 벤더사의 서킷이 열려 자원 소비를 막을 수 있었습니다. 이를 통해 전체 렌터카 서비스로 장애가 확산되는 것을 예방할 수 있었습니다.

마치며 링크 복사

이번 포스팅에서는 신규 서비스를 준비하면서 고민했던 부분과 선택한 전략들을 정리해 보았습니다. 구체적인 구현 방식보다는 직면한 상황과 이를 극복하기 위한 방법에 중점을 두었습니다.

모든 상황에 적용할 수 있는 해결책은 존재하지 않습니다. 이 글에서 다루지 못한 예외 상황도 많았습니다. 중요한 것은 비즈니스 상황을 정확히 이해하고 이에 맞는 유연한 대처를 하는 것입니다.

'카셰어링' 서비스 역시 출시된 지 1년이 채 되지 않은 신생 서비스입니다. 지속적인 모니터링을 통해 꾸준히 개선하고 있으며, 이에 따라 앞서 설명한 전략들도 언제든 새롭게 개편될 수 있습니다.

렌터카 그룹은 끊임없이 사용자 경험과 편의성 향상을 위해 노력하고 있습니다. '카셰어링' 서비스뿐만 아니라 '제주/배달 렌터카' 서비스에도 많은 관심 부탁드립니다.

앞으로도 사용자의 불편을 최소화하기 위해 최선을 다하는 서비스가 되겠습니다. 감사합니다.