RDB -> NoSQL로의 마이그레이션 도전기

RDB -> NoSQL로의 마이그레이션 도전기

Tech

요청서 도메인의 구조적 한계

숨고에는 매일 수만 건의 요청서가 쏟아집니다. 이 요청서는 고객이 겪고 있는 문제를 고수가 해결하기까지 이어지는 모든 경로의 시작점이자 숨고 매출과 매칭 로직 전반을 떠받치는 핵심 데이터입니다.

그런데 이 중요한 도메인이 오랫동안 MariaDB 기반의 복잡한 관계형 스키마 위에서 운영되고 있었습니다. 요청서 한 건을 완전한 구조로 조회하기 위해서는 최소 7~10개 테이블을 조인해야 했고, 기능 하나를 추가할 때마다 새로운 컬럼과 테이블이 늘어나며 스키마는 점점 비대해졌습니다.

그로 인해 조인의 깊이는 계속 깊어졌고 쿼리는 무거워졌으며 스키마 수정과 마이그레이션은 새벽 배포 없이는 시도조차 어렵게 변했습니다. 결국 요청서 도메인은 “새로운 기능을 빠르게 실험하고 발전시키기 어려운 구조적 병목”으로 자리 잡았습니다.

숨고가 더 빠르게 진화하기 위해서는 이 노후화된 구조를 그대로 유지하는 것이 오히려 더 큰 리스크가 되었습니다. 이제 요청서 도메인은 RDB의 한계를 벗어나 서비스 로직 중심으로 유연하게 확장될 수 있는 NoSQL 구조로 재설계될 필요가 명확해졌습니다.

SELECT r.*, oi.*, p.*, c.*, pay.*, saddr.*, ot.*, t.*, d.* FROM request AS r JOIN order_items AS oi ON r.id = oi.order_id JOIN products AS p ON oi.product_id = p.id JOIN customers AS c ON r.customer_id = c.id JOIN payments AS pay ON r.id = pay.order_id JOIN shipping_addresses AS saddr ON o.shipping_address_id = saddr.id JOIN order_tags AS ot ON r.id = ot.order_id JOIN tags AS t ON ot.tag_id = t.id LEFT JOIN deliveries AS d ON r.id = d.order_id였;

너무 많은 양의 join을 필요로 했던 로직들

이 구조는 다음과 같은 한계를 요약하면 아래와 같습니다.

스키마 변경 난이도: 요청서는 숨고의 핵심 도메인인 만큼 기능 추가가 빈번하게 일어납니다. 새로운 기능이 추가될 때마다 컬럼을 추가하거나 별도 테이블을 생성하고 FK와 조인을 새로 작성해야 했습니다. 이 과정에서 잦은 서비스 점검과 새벽 배포가 발생했으며 개발 속도에 영향을 미쳤습니다.

성능 저하: 조인 연산이 많은 복합 쿼리에서 응답 속도가 느려졌습니다.

서비스 복잡성 증가: 테이블 수가 점점 늘어나면서 복잡도가 상승하고 각 테이블이 어떤 역할로 쓰이는지 이해하기 어려워졌습니다.

이 문제를 해결하기 위해 요청서 도메인을 MongoDB 기반의 NoSQL 구조로 전환하기로 결정했습니다. 더불어, 서비스 운영에 영향이 없도록 정교하고 동시에 매끄럽게 전환하는 작업이 필요했습니다.

RDB → NoSQL로의 전환

Step 1. 전환 목표 및 방향

전환의 가장 큰 목표는 데이터 구조 단순화와 개발 생산성 향상이었습니다. MySQL 기반에서는 스키마를 수정하거나 테이블을 추가할 때마다 많은 코드 수정이 필요했고, 배포 주기도 느려졌습니다.

MongoDB 전환을 통해 다음을 달성하고자 했습니다.

  • 파편화 되어있던 DB 테이블 정보를 갈무리하여 가독성 증가
  • 조인 제거로 쿼리 복잡도 감소
  • 스키마 유연화로 새로운 기능 추가 시 속도 개선
  • 데이터 조회 속도 개선

Step 2. 스키마 설계 전략

대체 이미지

스키마 설계에 앞서 운영 복잡도를 낮추기 위해 한가지 절대적인 원칙을 고수하고자 했습니다. “최대한 적은 컬렉션으로 관리하자.”

다양한 스키마 디자인 패턴들을 접목하여 아래처럼 2개의 콜렉션으로 정리되었고 인덱스 또한 현재 도메인 로직을 고려하여 최대한 적은 양의 인덱스만 유지했습니다.

  • 요청서 Collection은 변경 가능성이 거의 없는 필드를 중심으로 구성하였습니다 (요청자 정보, 서비스 ID, 위치 정보, 생성 시각, 견적 제한 등)
  • 요청서 메타데이터 Collection 은 빈번하게 업데이트되는 필드를 별도로 분리하였습니다 (견적 개수, 매칭 상태, 업데이트 타임스탬프 등)

인덱스 전략

  • 복합 인덱스: (status, service_id, created_at)
  • 지리 인덱스: location.geojson_data2dsphere 인덱스 적용
  • 기간 조회용 인덱스: started_at, ended_at
  • 참조 및 추적용 인덱스: request_id, user_id

Step 3. 마이그레이션 절차

전환은 RDB를 진실의 원천으로 유지하면서 MongoDB로 동기화하는 형태로 진행했습니다.

  1. 이중 쓰기 적용
    기존 RDB 기반 로직에 MongoDB의 insert, update, delete 로직을 추가하여, 모든 변경이 두 데이터베이스에 동시에 반영되도록 했습니다.

  2. 데이터 마이그레이션
    과거 데이터를 한 번에 옮기는 스크립트를 작성해 4천만 건 이상 데이터를 이전했습니다. 요청서가 마감되면 더 이상 업데이트되지 않기 때문에, 데이터 동결 구간을 이용해 안정적으로 이관할 수 있었습니다.

  3. 인덱스 지연 생성
    마이그레이션 속도 문제를 피하기 위해, 대량 insert 완료 후 인덱스를 생성했습니다.

  4. 점진적 읽기 전환
    마이그레이션이 완료된 뒤 일부 API의 read 로직을 MongoDB로 전환하기 시작했습니다. 현재 신규 요청서 관련 API는 MongoDB를 완전히 사용하며 일부 레거시 API를 제외하고는 모두 MongoDB로 전환 완료되었습니다.

Step 4. 데이터 참조 구조

MongoDB의 _id를 완전히 도입하는 과정에서 한 가지 문제가 있었습니다. 클라이언트 단에서 여전히 요청서 id 기반의 엔드포인트를 사용 중이었기 때문에, 서로 다른 데이터베이스 간 참조가 필요했습니다.

이를 해결하기 위해 다음처럼 양방향 참조 구조를 도입했다.

MongoDB 문서에 request_id를 포함해, 기존 시스템과의 조회 호환 유지

MySQL 테이블(request_meta)에도 request2_id를 추가해 MongoDB _id와 동기화

이로써 구 시스템과 신 시스템 간의 데이터 조회 일관성을 보장했습니다.

Step 5. 문제 해결 사례

1. ID 이중 관리로 인한 혼선

MongoDB _id와 MySQL request_id를 함께 운영하는 과정에서 일부 API가 서로 다른 기준으로 조회해 데이터가 불일치하는 문제가 있었습니다. → 해결: MySQL에 request2_id 필드를 추가하여 _id를 저장하고, 서버단에서 모든 요청을 일시적으로 두 키 모두 검색하도록 조정했습니다.

2. 날짜 포맷 불일치

MySQL은 로컬 타임존, MongoDB는 UTC를 사용하여 ended_at 기준 쿼리 결과가 어긋났습니다.
해결: 마이그레이션 스크립트에서 UTC로 변환 후 저장하고, 애플리케이션 레벨에서는 UTC를 표준으로 처리하도록 변경했습니다.

적용 후 개선된 점

MongoDB 전환 후 다음과 같은 변화를 체감하였습니다.

  • 요청서 관련 주요 API의 평균 응답 속도가 약 90% 개선
대체 이미지
개선 전
대체 이미지
개선 후
  • 기존 6~7개 테이블 조인 로직이 단일 컬렉션 조회로 단순화
  • 스키마 변경 시 DB 마이그레이션이 필요하지 않아 기능 추가 속도 향상
  • DB 구조 단순화로 도메인 난이도가 감소하여 협업 능률 향상

향후 개선점

이번 전환은 숨고 서비스에서 시도한 첫 번째 대규모 NoSQL 마이그레이션 프로젝트였습니다. 의존성을 최소화하고 직접 스크립트 기반으로 진행했었기에 여전히 고도화해야 할 여지는 다음과 같습니다.

RDB를 통해 호출 중인 모든 API 데이터를 MongoDB 로 호출할 수 있도록 작업 필요

1 MongoDB Atlas 모니터링 지표 기반 자동 알람 강화

2 RDB 테이블 제거 시나리오 구성 및 실행

(1) RDB를 직접 조회하거나 갱신하는 API를 전수 조사

(2) 비즈니스 로직 의존도가 낮은 조회 API부터 MongoDB 기반으로 전환

(3)이중 쓰기 해제 및 RDB 동결

(4) RDB 테이블 제거

마치며

RDB 기반의 견고한 스키마는 초기 서비스나 데이터 구조가 안정적인 단계에서는 큰 장점이 됩니다. 그러나 숨고처럼 도메인이 빠르게 확장되고, 다양한 기능이 반복적으로 추가·변경되는 환경에서는 스키마 변경 자체가 병목이 되기 시작합니다.

이번 전환을 통해 요청서 도메인은 더 이상 테이블 구조와 조인 복잡도에 발목 잡히지 않는 형태로 재정비되었습니다. 이제는 서비스 로직의 목적에 맞춰 데이터를 구성하고, 필요한 필드를 유연하게 확장하며, 운영·개발 양쪽에서 부담 없이 기능을 발전시킬 수 있는 토대가 마련되었습니다.

아직 일부 레거시 API가 RDB를 통과하고 있지만, 이 역시 점진적으로 MongoDB 기반으로 흡수될 예정입니다. 더 나은 모니터링, 자동화된 알람 체계, 불필요한 RDB 테이블 정리 등 후속 과제까지 완료된다면, 숨고의 요청서 도메인은 완전한 NoSQL 기반의 고성능·고확장성 구조로 자리 잡게 될 것입니다.

이 전환은 단순한 데이터베이스 교체가 아니라, 숨고 서비스가 앞으로 더 빠르게 진화하기 위한 구조적 기반을 만드는 과정이었습니다. 향후 신규 기능 개발 속도와 서비스 안정성은 이번 변화의 가장 확실한 성과로 자리할 것입니다.

  • #backend
  • #go
  • #java
  • #msa
  • #node.js
  • #nosql
  • #python
  • #rdb
  • #soomgo
  • #tech
Jade Han
Jade HanBackend Engineer

모두의 더 나은 삶을 위해
함께 변화를 만들어갈 동료를 기다립니다

채용중인 공고 보기