grep

Engineering

매달 수 백만 건의 여행을 책임지는 NOL 주문 이야기

NOL Tech Blog야놀자

2025년 7월 25일

원문에서 보기 ↗

이 이미지는 생성형 AI를 활용해 제작하였습니다.

놀(NOL)라운 주문 아키텍처

여행을 계획하며 설레는 마음으로 NOL 앱으로 숙소를 찾고, 틈새의 여가 시간을 책임질 레저를 알아본 뒤, ‘결제하기’ 버튼을 누르는 순간, 놀(NOL)라운 주문 여정은 시작됩니다. 이 글에서는 ‘결제하기’ 버튼을 누른 순간부터 결제를 마치고 알림톡을 받기까지 NOL 주문의 백스테이지에서는 어떤 일이 벌어지고 있는지 소개합니다.

놀(NOL)라운 여정의 시작, 주문에서부터

NOL 서비스의 뒷단에는 쿠폰, 포인트, 회원, 메시징, 결제, 상품 등 다양한 마이크로서비스가 복잡하면서도 유기적으로 연결되어 있습니다. 이 가운데 주문 서비스의 역할은 무엇일까요?

가성비 여행을 위한 소중한 쿠폰, 후기를 작성하고 받았던 포인트, 간편결제 앱을 통한 결제, 다른 사람이 내 정보를 훔쳐 가지 못하게 하기 위한 인증, 예약이 완료되면 당연히 받아야 할 것 같은 알림톡까지. 주문 서비스는 이 모든 서비스를 아울러 하나로 연결하는 오케스트레이션 서비스로서 안정적인 주문 과정을 담당하는 서비스입니다.

자, 이제 이 복잡한 과정을 어떻게 풀어내고 있는지 다양한 관점별로 차근차근 설명해 보겠습니다.

🛒 통합 장바구니 — 모든 놀거리를 한 번에

커머스주문개발팀이 책임지고 있는 상품 카테고리는 크게 5가지로 나뉩니다.

  1. 국내숙박(모텔, 호텔, 펜션, 게스트하우스)
  2. 레저
  3. 기차
  4. 버스
  5. 해외숙박

각 카테고리 상품을 매번 결제하는 사용자 경험은 단순한 불편함을 넘어 분명 이탈로 이어질 것입니다.

NOL 서비스는 ‘통합 장바구니’ 체계를 도입해 다른 카테고리의 상품이라도 장바구니에 담아서 한 번에 주문할 수 있는 구조로 되어 있습니다. 현재 NOL 서비스에서 주문할 수 있는 상품을 간단히 도식화해 보면 다음과 같습니다. (단, 해외숙박 카테고리는 현재 통합 장바구니에 담을 수 없으므로 이 이후로는 다루지 않겠습니다.)

NOL 서비스 상품 구조

특히 기차 카테고리 상품은 묶음 할인이 존재하기 때문에 장바구니가 필수입니다. 이렇게 장바구니에 담은 다양한 상품은 ‘결제하기’ 버튼을 눌러 통합 장바구니 주문(이하 통합 주문) 서비스로 전달됩니다. 이 순간부터 주문 여정은 시작됩니다. (사실 상품 상세 페이지에서 바로 주문을 시작할 때에도 단일 상품을 주문하는 것처럼 통합 주문 서비스로 전달되고 있답니다)

통합 주문 서비스는 다양한 카테고리 상품을 추상화된 하나의 ‘상품’으로 다루기 위해 복잡한 구조로 설계되어 있습니다. 이 서비스를 쉽게 이해하기 위해서는 주문 프로세스를 알아야 하는데요. 아래와 같이 크게 세 단계로 나눠볼 수 있습니다.

<주문 프로세스>

주문 프로세스 간소화 시퀀스

  1. 주문 준비(1~3): 기본적인 사용자 인증, 포인트 잔액, 재고 등을 검증한 뒤, 주문 정보를 적재하는 단계입니다. 각 카테고리 주문 서비스를 상품별로 병렬로 호출하여 모든 상품이 주문 준비를 완료할 수 있게 합니다.
  2. 사용자 결제(4): 결제 서비스의 모듈을 활용하여 사용자가 결제 인증을 진행하는 단계입니다. 주문 정보를 결제 서비스로 전달하는 것이 중요하며, 이 과정에서는 실 결제가 발생하지 않습니다.
  3. 주문 완료(5~7): 모든 준비가 끝난 뒤, 쿠폰 사용 확정, 포인트 차감, 결제 승인, 재고 차감 등 주문을 처리하는 단계입니다. 통합 주문 서비스에서는 각 카테고리 주문 서비스를 호출하여 주문을 완료합니다.

통합 주문 서비스에서 가장 복잡한 부분은 카테고리별로 독자적인 상품 구조로 되어 있다는 점입니다. 이 문제는 공통으로 처리할 수 있는 세부 내용을 추상화하여 해결할 수 있습니다. 그래서 각 카테고리 주문 서비스는 모두 동일한 인터페이스를 구현하고 있습니다.

국내숙박, 레저, 기차 주문 서비스 모두 ‘주문 준비’, ‘주문 승인’, ‘주문 롤백’과 같은 공통 API를 제공하고, 응답 형식과 오류 처리 방식도 통일해 처리하고 있습니다. 통합 주문 서비스 입장에서는 카테고리와 상관없이 동일한 프로세스로 호출하고, 처리하며, 카테고리 주문 서비스에서는 각 도메인의 비즈니스 로직(재고 관리, 취소 수수료 계산, 외부 업체 연동 등)을 독립적으로 처리할 수 있습니다. 다만 카테고리별로 필요한 상품 정보나 도메인 특화 데이터는 그 형태가 다르므로 주문 준비 요청에서는 카테고리별로 파라미터를 다르게 전달하기도 합니다.

통합 주문, 카테고리 주문 호출 구조

🔍 주문 준비 — 꼼꼼한 사전조사

주문 프로세스 중 주문 준비 단계를 더 깊이 알아보겠습니다. 이 단계에서는 각 상품의 재고와 상태를 검증하고, 주문과 상품 관련 정보를 적재합니다. 국내숙박 주문 서비스를 예로 들면, 사용자가 구매하고자 하는 숙소, 입실일, 객실 정보를 기준으로 국내숙박 상품 서비스를 호출하여 재고가 존재하는지, 또 사용자가 숙소를 탐색할 때 노출된 가격이 유지되고 있는지 확인합니다.

이후 소개할 쿠폰과 포인트 검증도 이 단계에서 진행됩니다. 쿠폰의 경우 각 카테고리 주문 서비스에서 복잡한 사용 조건을 검증하고, 포인트의 경우 사용하려는 금액만큼 잔액이 충분한지 확인합니다. 단, 이 단계에서는 실제 차감이 아닌 사용이 가능한지 아닌지만 검증합니다.

통합 주문은 여러 개의 상품을 동시에 주문하기 때문에 모든 상품의 주문 준비가 마무리되어야 다음 단계로 진행할 수 있습니다. 만약 하나의 카테고리라도 재고 부족이나 가격 변동, 쿠폰 사용 조건 미충족 등의 이유로 주문 준비에 실패한다면 전체 주문도 함께 실패 처리합니다. 이렇게 하여 일부 상품만 주문되는 혼란스러운 상황을 사전에 방지합니다.

카테고리 주문 포함 주문 준비 시퀀스

통합 주문 서비스에서 흥미로운 부분 중 하나는 주문번호 채번입니다. 통합 주문 서비스는 수평 확장이 가능하도록 설계되어 있고, 트래픽에 따라 자동으로 스케일인/아웃되고 있습니다. 이러한 환경 속에서 동시에 요청이 들어오더라도 요청마다 고유한 주문번호를 채번해야 한다는 문제가 발생합니다.

가장 손쉽게 생각할 수 있는 해결책은 데이터베이스 Sequential ID를 사용하는 것이지만, 이는 데이터베이스에 강하게 결합하게 되며, 새로운 행을 추가해야지만 주문번호 채번이 가능하고, 주문번호를 생성한 뒤에 또 행을 수정하는 코드를 작성해야 한다는 번거로움이 있습니다.

통합 주문 서비스에서는 Redis 분산 카운터를 활용하여 시퀀스 값을 생성한 뒤, 날짜와 내부 고객 고유번호를 조합하여 고유한 주문번호를 생성합니다.

주문번호 채번 의사 코드

카테고리 주문에서도 통합 주문번호와 동일한 문제가 발생합니다. 이때는 통합 주문 서비스에서 위와 유사한 방법으로 주문번호를 상품별로 생성하고, 카테고리 주문 서비스로 전달하여 각 주문번호의 고유성을 보장합니다.

🎟️ 쿠폰/포인트 — 할인 받고 가성비 챙기기

가성비 넘치는 여행을 위해서는 할인을 빼놓을 수 없습니다. NOL에서 사용할 수 있는 할인 수단은 크게 쿠폰과 포인트로 나뉩니다. 쿠폰과 포인트 모두 각각 비즈니스 로직이 복잡하면서도 커머스에서 중요한 축을 담당하고 있기 때문에 별도 팀에서 마이크로서비스 형태로 운영하며, API를 제공하고 있습니다.

기본적으로 할인 수단은 주문 준비 단계에서 검증을 진행하고, 주문 완료 단계에서 실제 사용 처리됩니다.

쿠폰 먼저 자세히 알아보도록 하겠습니다. 쿠폰은 당일에만 사용할 수 있거나, 특정한 숙소에만 사용이 가능하다는 등 각 카테고리에 밀접하게 종속된 복잡한 사용 조건을 가지고 있습니다. 또, 일부 프로모션에서는 희소성을 위해 쿠폰 개수를 제한하기도 합니다.

그러므로 쿠폰은 통합 주문 서비스에서 처리하지 않고, 각 카테고리 주문 서비스에서 쿠폰 서비스를 호출하여 복잡한 사용 조건을 검증하고, 재고 차감을 진행하며 쿠폰 사용을 준비합니다. 이 결과로 쿠폰 토큰을 받아 적재한 뒤, 최종적으로 주문 완료 단계에서 이 쿠폰 토큰을 사용해 사용 처리를 진행하게 됩니다.

포인트는 쿠폰보다 비교적 단순합니다. 모든 포인트 처리는 통합 주문 서비스가 담당하는데요, 주문 준비 단계에서는 포인트 서비스를 호출하여 포인트 잔액이 부족하지 않은지 확인하고, 주문 완료 단계에서 잔액을 차감합니다. 현재는 복잡한 내부 포인트를 하나로 통합한 NOL 포인트, 그리고 타사와 협업하여 NOL 서비스에서 사용할 수 있는 외부 포인트를 다루고 있습니다.

쿠폰 및 포인트 호출 시퀀스

할인 수단 처리에서 중요한 것은 1. 결제 순서, 2. 실패 대응, 3. 결제 금액 안분입니다.

첫 번째로, 결제 순서부터 알아보겠습니다. 결제 순서는 각 결제/할인 수단의 시스템 복잡도가 높은 순서대로 처리하는 것이 원칙입니다. 통합 주문 서비스에서 포인트 잔액을 검증하거나 사용할 때 비교적 불안정한 외부 연동 포인트부터 처리를 시작하여 실패 상황에서 최소한의 처리를 진행할 수 있도록 합니다.

두 번째로, 실패 대응입니다. 할인 수단 처리는 차례대로 진행되지만, 이 중 하나라도 실패하는 경우 이전 처리를 취소해야 합니다. 결제와 할인은 여러 서비스를 유기적으로 연결하는 과정이기 때문에, 통합 주문 서비스에서 적절한 대응을 통해 데이터 일관성을 보장합니다. 구체적인 구현은 이후 트랜잭션 관리를 소개하며 자세히 설명하겠습니다.

마지막으로 할인 수단 처리에서 가장 복잡한 결제 금액 안분입니다. NOL 주문에서는 장바구니에 여러 상품을 담고 통합 결제를 진행한 뒤, 일부 상품을 취소할 수 있습니다. 특히, 국내 숙박의 경우 연박 결제 후 단박 취소 등의 상황이 발생할 수 있습니다. 단순하게 1/N을 고민해 볼 수 있지만, 특정 상황에서는 1원 단위로 떨어지지 않거나 처리 순서를 고민해야 하는 경우가 발생합니다. 주문 서비스에서는 각 결제수단별로 내부 정책에 따라 금액을 안분하여 처리하는 형태로 이 문제를 풀고 있습니다.

구체적인 처리 방법은 정책상 공개가 불가하여 사례를 소개해 드리겠습니다.

기차 5만 원, 숙소 6만 원(3만 원 X 2박)을 결제하려 합니다. 때마침 기차 상품은 1만 원 묶음 할인이 적용되고, 숙소도 땡처리 쿠폰을 찾아 2만 원이 할인되었으며, 이전 여행에서 후기를 작성하고 받은 3만 포인트를 사용하여 총 5만 원을 결제한 상황입니다. 하지만 피치 못할 사정으로 인해 연박 중 1박을 취소하여 3만 원을 환불받아야 하는 상황이 발생했습니다. 이 상황에서 데이터를 어떤 구조로 적재하고, 어떤 방법으로 처리하는 것이 가장 유연할까요?

💳 결제 — 놀러갈 결심을 시작하는 순간

NOL 서비스에서는 관심사에 따라 주문 서비스와 결제 서비스를 분리하여 운영하고 있습니다. 주문 서비스는 회원, 상품, 할인, 결제 등 여러 서비스를 호출하며 주문-결제 전 과정을 조율하는 서비스라면, 결제 서비스는 각 결제수단 별 PG사 연동 인터페이스 통합을 책임지고 있습니다. NOL 결제 서비스는 ‘NOL(야놀자)에서 결제 서비스를 안정적으로 운영하는 방법’에서 자세히 다룹니다.

NOL(야놀자)에서 결제 서비스를 안정적으로 운영하는 방법

IT 서비스를 운영할 때 가장 중요한 모듈은 무엇이라고 할 수 있을까요? 많은 핵심 컴포넌트들이 있지만, 결제 서비스를 그 중에 하나로 넣는데에 이견을 표하시는 분은 없을 거라고 생각합니다.medium.com

모든 결제 서비스 연동은 통합 주문 서비스에서 책임을 갖습니다. 주문 프로세스 단계별로 나누어 보자면 다음과 같습니다.

  1. 주문 준비 단계: 금액, 상품명 등 주문 정보와 함께 결제 서비스에 결제 준비를 요청합니다. 응답으로 받은 결제 파라미터는 결제 서비스와 느슨하게 결합하기 위해 JSON String 형태로 전달합니다.
  2. 사용자 결제 단계: 결제 준비 후 전달받은 파라미터를 결제 프론트 서비스로 넘깁니다. 여기서 사용자는 PG 결제 화면에서 인증을 진행합니다. 결제 인증이 완료되면 결제 프론트 서비스는 통합 주문 서비스를 호출합니다.
  3. 주문 완료 단계: 결제 서비스로 최종 결제 승인을 요청합니다. 이 시점에서 실제 결제가 진행되며, 결제 실패 시 전체 주문이 롤백됩니다.

주문-결제 프로세스 시퀀스

단, 결제 과정에서는 한 가지 예외가 존재합니다. 바로 할인수단으로 전액을 결제하는 경우인데요. 사용자 결제는 PG 결제를 위해 사용자에게 결제 인증을 요청하는 단계입니다. 그러므로 할인수단으로 전액을 결제할 때는 사용자 결제 과정을 건너뛰고 주문 준비-주문 완료 과정을 한 번에 처리하도록 구현되어 있습니다.

주문(전액 할인수단) 프로세스 시퀀스

주문과 결제 서비스는 얼핏 보면 비슷한 책임을 갖는다고 생각할 수 있습니다. 하지만 두 서비스는 전혀 다른 관심사를 갖고 있습니다. 혼동하실 수 있을 것 같아 조금 더 구체적인 사례를 두 가지 들어보겠습니다.

[사례 1: 오류 메시지 처리] PG 결제 이후 응답하는 다양한 오류 메시지들이 존재합니다. 이는 결제 도메인의 관심사입니다. 주문 서비스 입장에서는 회원, 상품, 쿠폰, 포인트 서비스처럼 하나의 외부 서비스 호출 오류로 취급하며, 사용자에게 보이는 메시지 또한 이를 종합하여 주문 서비스에서 정의합니다.

[사례 2: 연동 PG 정보] NOL 결제 서비스는 한 가지 결제수단(카드, 휴대폰, 가상계좌 등)이라도 PG 장애 전파를 방지하기 위해 N개의 PG가 연동되어 있기도 합니다. 각 주문 시점에 연동한 PG 정보는 결제 서비스의 관심사입니다. 하지만 사용자가 어떤 결제수단을 선택했는지는 두 서비스 모두 중요하게 바라보기 때문에 주문과 결제 서비스 모두 정보를 적재합니다.

🎉 주문 완료 — 놀라운 주문 여정의 클라이맥스

주문 프로세스 중 가장 핵심 단계인 주문 완료 처리에 대해 알아보겠습니다. 주문 완료는 지금까지 소개했던 모든 내용을 종합하는 단계입니다. 통합 주문 서비스는 분산 시스템 환경에서 데이터 일관성과 장애 복구를 보장하기 위해 주문 완료를 세 단계로 나누어 처리합니다.

첫 번째 단계는 결제 및 할인수단 승인입니다. 할인 수단에서 설명했듯이 안정성이 상대적으로 낮다고 판단되는 순서대로 처리하는 것이 원칙입니다. 다만, 결제는 가장 중요하기 때문에 결과적으로 PG 결제 승인을 먼저 진행한 뒤, 이후 안정성이 낮은 외부 연동 포인트부터 내부 포인트, 쿠폰 순서대로 처리하여 모든 금전적 거래를 완료합니다.

두 번째 단계는 주문 승인 단계입니다. 통합 주문 서비스는 각 카테고리 주문 서비스에 병렬로 주문 승인을 요청합니다. 이 시점에서 각 카테고리 주문 서비스는 재고 차감, 외부 주문 생성 등 핵심 비즈니스 로직을 실행합니다. 병렬 처리를 통해 전체 처리 시간을 최소화하면서도, 모든 카테고리로부터 성공/실패 응답을 수집하여 다음 단계 진행 여부를 결정합니다. 만약 이 단계에서 한 개의 상품이라도 실패한다면 모든 카테고리에 롤백을 요청하고 결제 및 할인수단을 취소해야 합니다.

세 번째 단계는 주문 완료 전파입니다. 모든 카테고리의 주문 승인이 성공적으로 완료되었다면, 각 카테고리에 최종 주문 완료를 전파합니다. 이 단계는 각 카테고리 주문 서비스가 주문 완료를 최종적으로 인지하고 필요한 후처리 작업을 수행할 수 있도록 합니다. 이 시점에는 이미 금전적 거래와 상품 확보가 모두 완료된 상태이므로, 일부 카테고리의 완료 처리가 실패하더라도 통합 주문의 상태는 변하지 않습니다.

이러한 3단계 접근 방식을 통해 NOL 주문 시스템은 복잡한 분산 환경에서도 안정적인 주문 처리를 보장하면서, 장애 상황에서의 데이터 일관성, 그리고 사용자 경험을 모두 고려한 견고한 아키텍처를 구현하고 있습니다.

카테고리 주문 포함 주문 완료 시퀀스

🔄 트랜잭션 관리 — 안정성 확보하기

주문 완료 단계는 복잡하지만, 안정적으로 처리되어야 합니다. NOL 주문 시스템은 Saga 패턴의 컨셉을 빌려 오케스트레이터 역할로서 단계별로 무엇을 실행할지, 실패했을 때 어떻게 롤백할지 미리 정의하고, 이를 순차적으로 처리합니다.

예를 들어 할인수단 처리 단계에서 외부 포인트와 내부 포인트 차감은 성공했는데 쿠폰 적용에서 실패한다면, 통합 주문 서비스는 자동으로 앞서 처리한 과정을 역순으로 취소합니다. 주문 승인 과정에서도 마찬가지입니다. 숙박과 레저 주문은 성공했는데 기차 주문에서 실패한다면, 이미 승인한 결제와 할인수단을 모두 취소하고 성공한 숙박, 레저 주문도 롤백 처리합니다. 이런 식으로 어느 단계에서 실패하더라도 사용자에게는 “전체 주문이 실패했다”라는 일관된 결과를 보여줄 수 있습니다.

통합 주문 서비스에서는 ‘상태 기록 → 순차 실행 → 선택적 보상’ 패턴을 통해 복잡한 분산 트랜잭션을 안정적으로 관리하고 있습니다. 데이터베이스에 거래 이력과 카테고리 주문 테이블 레코드를 생성하고, 각 처리 이후 상태를 변경하는 방식으로 이를 구현합니다. 특히 거래 이력의 경우 정상 이력을 생성해 결제 승인을 처리하고, 실패 시에는 정상 거래 이력을 참고해 보상(롤백) 거래 이력을 추가로 생성하여 롤백 처리를 수행합니다. 이를 통해 포인트, 쿠폰, PG 결제 등 각 결제 수단별 처리 상태를 명확하게 관리할 수 있습니다.

하지만 서비스 전면 장애 등의 상황이라면 롤백 처리 과정에서도 오류가 발생할 수 있습니다. 이 경우에는 경고 로그만 남기고 다음 처리를 계속 진행하여, 일부 환불 실패가 전체 보상 프로세스를 중단시키지 않도록 구현되어 있습니다. 이러한 방식으로 주문 DB를 DLQ로 활용해 실패 상태로 기록된 카테고리 주문과 거래 이력을 조회하여 배치잡 등으로 재처리를 가능케 합니다.

트랜잭션 관리 의사 코드

📨 주문 전파 — 알림톡 받고 소문내기

주문이 완료되면 예약 완료 알림톡을 발송해 예약이 정상적으로 완료되었다는 것을 알려야 합니다.

알림톡 발송 서비스는 순수한 알림톡 발송 책임만 갖고 있고, 통합 주문 서비스에서 특정 조건에 맞는(예: 체크인을 본인이 하는지) 알림톡 템플릿을 골라 발송을 요청합니다. 알림톡 서비스는 발송 요청 시 데이터를 적재한 다음 바로 성공 응답을 주기 때문에 주문 완료 후 동기적으로 처리합니다.

또, 주문 정보는 프로모션, 정산, 회원 등급 등 다양한 도메인에서 중요한 정보로 사용되기 때문에 주문 정보를 전파해야 합니다.

각 서비스에서 필요한 정보를 요청하고 매번 API를 만들어주는 것은 많은 리소스를 필요로 하며, 심각한 경우 예측하지 못한 대량 요청으로 인해 부하가 발생하여 사용자 주문이 실패할 가능성이 있습니다. 그러므로 내부 Kafka 클러스터를 활용해 이벤트 기반 아키텍처로 주문 정보를 전파합니다.

내부 Kafka 클러스터에 이벤트를 발행하고, 각 서비스에서 이벤트를 구독하여 필요한 정보만 골라 사용할 수 있습니다. 이 이벤트는 가능한 범용적으로 사용할 수 있도록 구체적인 주문 정보를 담기 때문에 각 카테고리 주문 서비스에서 주문 완료 이후 상태가 변경될 때마다 이벤트를 발행하도록 구성하였습니다.

주문 서비스 이벤트 전파 다이어그램

주문이 완료됐다는 사실을 알리고 나면 주문 프로세스는 끝납니다. 이제 주문 내역 페이지에서 주문 상세 정보를 확인할 수 있고, 다음 여정을 계획할 수 있게 됩니다.

마무리

매달 수백만 건의 주문을 처리하는 NOL 주문 시스템에 대해 폭넓게 살펴보았습니다. 주문 서비스는 사용자와 다양한 백엔드 서비스들 사이의 복잡한 상호작용을 조율하는 핵심 역할을 담당하기 때문에 안정성과 확장성, 그리고 사용자 경험을 모두 고려한 세심한 설계가 필요합니다.

특히 다양한 카테고리의 상품을 하나의 주문으로 통합하면서도 각각의 특성을 살리고, 복잡한 주문 정책을 안정적으로 처리하며, 장애 상황에서도 데이터 일관성을 보장하는 것은 끊임없는 개선과 고민을 요구합니다.

이 글에서 소개한 것 외에도 주문 시스템에는 묶음 할인, 대기 예약, 취소 수수료, 관리자 시스템, 모니터링 인프라, 개인정보 관리 등 분량과 보안상 담을 수 없는 깊은 고민이 담겨 있습니다. 함께 고민하며 놀(NOL)라운 주문 서비스에 기여하고 싶다면 커머스플랫폼팀에 합류해 보시는 건 어떠신가요?

누구나 마음 편히 놀 수 있는 세상을 함께 만들어갈 동료를 찾고 있어요 :)

지금 채용 중인 포지션 보러가기 >