본문 바로가기
Spring/DB Migrator

[DB Migrator] Spring Boot와 함께하는 DB Migrator 프로젝트

by Taler 2022. 8. 30.

이전에 인턴십을 진행했던 회사에서는 Spring에서 NestJS로의 리팩토링을 담당했었다. 해당 팀에 스프링 개발자가 없어 유지보수에 어려움을 겪던 것이 그 이유. 인턴십 기간 동안 생소한 Typescript를 배우며 결과적으로 총 4개의 API에 대한 리팩토링을 완료했다.

 

포팅을 할 때 테스트코드는 따로 작성하지 않았는데, 때문인지 약간은 빠른 속도로 포팅이 끝나 인턴십 기간이 총 5일정도 남았다. (주말 포함 7일) 새로운 API를 포팅하기엔 너무 부족한 시간이라 DB migration을 한 번 담당해보기로 했다.

 


본격적으로 프로젝트에 대한 설명을 시작해보자.

 

1. 요구사항


당시 Legacy 코드는 NoSQL인 MongoDB를 사용하고 있었다. MongoDB는 검색 등을 빠르게 하기 위해 사용하면 효과적이지만, 레거시 시스템은 MongoDB를 RDB처럼 사용하고 있었다.

 

Mongo를 RDB처럼 사용하면 다음과 같은 단점들이 생겨난다.

1. Join을 사용해 테이블 사이의 관계를 표현하기 어렵다.
2. Mongo 내 Collection 간의 계층 구조를 표현하기 어렵다. 관련된 모든 Collection을 가져와 직접 비교해서 추출해야 됨.
3. 데이터 구조의 변경이 용이하다는 장점이 오히려 단점으로 작용할 수 있다.
    (극단적으로 한 API는 {id, name}으로 저장하고 다른 API에서는 {user_id, user_name}로 저장할 수도 있다...!)

RDB처럼 사용하면 데이터가 쌓일수록 위 문제들이 더욱 부각된다. 마침 레거시 서버의 리팩토링 또한 진행되고 있던 시기였기 때문에 언젠간 해야될 마이그레이션이라면 지금이 적기였다. 이에 따라 필자는 MongoDB에 있는 수십 개의 테이블에 산재된 수백억 건의 데이터를 PostgreSQL (RDB)로 옮기는 작업을 맡았다.

 

이번 DB Migration Task에는 몇 가지 요구사항이 존재했다.

1. 백오피스 서비스이기 때문에 Downtime은 상관 없지만, 최대한 Downtime을 줄여야 합니다.
2. Mongo는 Json이든 list이든 모두 String으로 저장하는데, 이를 Postgres에서 지원하는 Json, List 타입으로 넣어주셔야 합니다.
3. 데이터에 무결성을 확인할 수 있는 과정이 포함되어야 합니다.

Downtime을 최대한 줄여라! 라는 요구사항은 어떻게 해서는 멀티스레딩과 접목시켜라! 라는 이야기로 들렸다. 두 번째 요구사항은 구현 후에 나중에 생각 해보자는 생각을 했었고, 마지막으로 무결성 부분은 몇 번 반복해서 돌리는 것을 생각했었다. 하지만 이 모든 것을 한 번에 해결해주는 것이 바로 ORM, ODM이었다.

 

즉, 멀티스레딩을 지원하는 언어이면서 신뢰할 수 있을 정도의 내구성을 가지고 있는 ORM과 ODM을 가진 언어를 생각해야했고, 어렵지 않게 Java를 떠올렸다. 사실 기존 레거시 코드 또한 Spring으로 작성됐기 때문에 Entity 코드를 단지 복붙하기만 하면 됐다. 당장 시간이 4일정도만 남은 시점에서 다른 선택지는 없다 생각했다.

 

2. 설계


먼저 개괄식 요약

1. Spring을 사용해 MongoDB와 PostgreSQL 각각 연결

2. Spring-data-mongo (ODM), Spring-data-jpa (ORM)을 사용해 각각 Legacy Entity와 Migration Entity 매핑

  1) PostgreSQL의 다양한 Type은 Column type 지정을 통해서 형변환 해주기

  2) Mongo Entity에 포함된 _class, _id 값 삭제하기 (ODM을 사용하면 자동으로 안가져옴)

3. 최대한 빠르게 구현하기 위해 멀티스레딩으로 구현

실제로 정리했던 구현 설계의 일부

4. 무결성 검사는 ORM과 ODM을 신뢰하고 진행하지 않는 방향으로 결정 (시간 부족)

 

전체 프로세스를 간략하게 묘사하자면, ODM이 Mongo의 데이터를 읽어와 Entity 클래스를 생성하면, 해당 Entity 클래스 내부에 있는 convert 메소드를 통해서 JPA entity 클래스를 또다시 생성한다. 이후 ORM은 해당 JPA entity를 저장해서 Postgres에 데이터를 추가하는 것.

 

여기서 중요한 부분은 사진에 나온 어떻게 멀티스레딩으로 구현할 지이다. 결정적으로 Mongo Entity를 읽어오고, JPA Entity로 변환하고, 해당 객체를 DB에 저장하는 것까지 총 3가지 작업이 있는데, 이걸 어떻게 나눌지 고민해봤다.

 

1. 하나의 스레드가 entity를 읽고, 변환해서 저장하는 과정까지 한 번에 진행하는 All rounder 모델
2. reader는 읽어오기만 하고, writer는 변환해서 저장하기만 하는 Reader/Writer 모델

 

원래라면 두 방법 모두에 대해서 구현하고, 어떤 것이 더 성능이 좋은지 확인한 후 해당 모델을 강화시키는 방향으로 개발하는 것이 좋을 것 같았지만, 시간도 없었고 당장 이 민감한 DB 데이터를 가지고 테스트를 한다는 행위 자체가 받아드려지기 힘들었기 때문에 최대한 문제가 덜 발생할 것 같은 All rounder모델로 개발을 시작했다.

 

댓글