80만명이 사용하는 서비스에서 무중단 마이그레이션 하기

아테나스랩
아테나스랩 팀블로그
8 min readJan 17, 2024

안녕하세요. 아테나스랩 서버 개발자 Jake입니다.

몇 달 전 오늘학교에서 커뮤니티 사용자 수가 급격하게 늘어나 매일 수천개의 게시글, 수만개의 댓글/대댓글이 등록되는 주요기능으로 성장하면서 유지보수 및 개선을 함에 있어서 기존 구조의 한계를 느꼈고 이를 해결하기 위해 대대적으로 커뮤니티의 구조를 개선하였습니다. 이 과정에서 저희 오늘학교 개발챕터에서 했던 고민들과 경험들을 공유해보려 합니다.

오늘학교 캐릭터

왜 구조개선을 하게 됐는가?

오늘학교는 대학생들의 에브리타임, 직장인들의 블라인드, 학부모들의 맘카페와 같은 학생들을 위한 커뮤니티를 제공합니다. 최초의 기획, 개발 단계에서는 현재의 사용량을 예측하지 못하고 개발속도에 집중해서 각각의 기능들을 잘게 나누고 확장가능성도 고려하지 않은 채 개발을 했습니다. 이에 따라 여러 문제가 발견되었습니다.

  1. 의존성이 높은 여러기능들로 인해 단순한 기능개발이나 유지보수에 신경써야할 것들이 너무 늘어나서 기능개발 및 유지보수에 시간소요가 점점 늘어났습니다.
  2. 게시판 구조가 하드코딩 되어있어 실제 DB구조와 게시판 구조가 상이해서 게시판을 추가,제거 및 수정하기가 어려웠습니다.

그래서 유지보수 및 기능개선을 좀 더 쉽게 하고 확장성 높은 구조로 변경하기 위해 작은 기능들로 흩어져 있던 커뮤니티 관련 기능들을 하나로 묶어 새로운 큰 기능을 만드는 작업을 구상하였습니다.

마이그레이션 전략

우선 기존구조에서 완전히 탈피하기 위해 기존구조를 고려하지않고 새롭게 커뮤니티 테이블 구조 및 로직을 만들었고 그 결과 API와 테이블 구조가 모두 변경된 새로운 V2가 만들어졌습니다. V2 테이블구조나 로직들은 꽤나 만족스러운 결과물이 나왔고 이제 마이그레이션만 남겨두고 있었습니다.

이 프로젝트의 성패를 결정할 중요한 작업이기 때문에 여러번의 논의를 거쳤고 그 끝에 몇 가지 조건 및 방향을 설정할 수 있었습니다.

💡 우선 기존 구조에서 완전히 탈피하기 위하여 기존 코드를 리팩토링하는 수준이 아니라 처음부터 모든 테이블 구조와 API를 새롭게 만들었습니다. 여기에서부터는 반복을 줄이기 위하여 기존의 구조에서 작성된 코드와 API를 “V1”, 새로운 구조에서 구현된 코드와 API를 “V2”로 명명하여 아래의 내용들을 이어가도록 하겠습니다.

새롭게 구현된 V2의 테이블구조나 로직들은 꽤나 만족스러운 결과물이 나왔고 마이그레이션만 남겨두고 있었습니다. 마이그레이션은 마지막 단계의 작업이기는 하지만 이 전략이 결국 최종적으로 프로젝트의 성패를 결정할 수 있는 중요한 작업이었기 때문에 팀 내 구성원 분들과 여러 번의 논의를 거쳤고 그 끝에 몇 가지 조건 및 방향을 설정할 수 있었습니다.

  1. 서버를 내리지 않고 무중단으로 이루어져야 한다.
    - 서비스 중단 후 마이그레이션을 할 경우 데이터의 양을 생각해봤을 때 수 시간이 소요될 작업이라 사용자 경험측면에서 좋지 않다.

2. V1, V2를 동시에 사용할 수 있어야 한다.

  • V2 API를 사용하는 앱버전 배포가 된다 해도 기존 버전 사용자들은 V1 API를 사용한다.

3. 2번의 상태가 꽤 긴 시간 동안 유지될 수 있다.

  • 강제업데이트를 지양하기 때문에 V1 API를 사용하는 유저들의 비율이 현저히 적어지기 전까진 두 버전 모두 사용 가능해야 한다.

위 3가지 조건을 만족하기 위해서 기존V1에 연결된 테이블과 새롭게 만들어진V2에 연결된 테이블이 항상 같은 상태를 유지해야 한다는 요구사항이 생겼습니다.

마이그레이션 전략

이 프로젝트를 진행하면서 정신적으로 가장 힘들었던 시기가 마이그레이션 전략을 구상하는 시기였습니다. 매일매일 찾아보고 혼자 생각해보고 팀원분들께 피드백 받고 전략을 전면수정하면서 좌절도 많이하고 실패도 많이 했었지만, 그만큼 생각을 많이하고 좀 더 도전적인 마음가짐을 가지게 된 계기가 됐던 시기입니다.

요구사항 대응

1. 무중단 마이그레이션이 필수적일까?

우선 가장 간단하고 직관적인 마이그레이션 전략으로 맨 처음 구상했던 방식은 서비스 점검 공지를 한 후 예상시간을 추정 후 그 만큼의 가용시간을 확보해서 서비스를 잠시 중단한 후에, 데이터 마이그레이션을 하고 다시 서비스를 재개하는 방식입니다.

하지만 해당 작업을 진행하기 직전에 서비스 중단 후 마이그레이션 한 경험으로 봤을 때 사용자 경험 측면에서 좋지 않을 것 같고 데이터의 양이 직전 작업보다 훨씬 많아서 시간적인 측면에서도 좋은 전략은 아니라고 판단했습니다.

2. 어떻게 두 테이블을 항상 같은 상태를 만들 수 있을까?

그래서 서비스를 중단하지 않는 다른 전략을 구상해보기 시작했는데, 맨 처음 막연하게 생각했던 전략은 조회 요청은 V1, V2 둘다 각자의 테이블로 보내도록 하고, 데이터에 변동이 생기는 생성, 삭제, 수정 요청은 V1, V2 둘다 V1에 연결된 테이블로 보내고 해당 변동 사항에 대해 V2에 연결된 테이블에도 반영해주는 방식을 생각했습니다.

데이터 생성, 삭제, 수정 요청
데이터 조회 요청

V1, V2가 서로 다른 DB에 연결된 버전이었다면 최적의 방법이 될수 있었겠지만, 같은 DB에 있는 서로 다른 Table 이라는 점을 생각해봤을때 그리고 구현 조건에서 ‘이 체계가 꽤 긴 시간동안 유지될 수 있다’ 라는 항목을 고려했을 때, 모든 생성, 삭제, 수정요청에 대해 불필요하게 쿼리가 2배로 발생하게 되는 문제가 있었습니다.

3. 테이블 2개를 계속 유지할 필요가 있을까?

몇번의 논의를 거친 후에 테이블을 기존 V1과 새로운 V2를 둘 다 유지하는 것보다 하나로 통합 관리하는게 나을 것 같다는 판단하에 새로운 전략을 구성했습니다.

데이터 생성, 삭제, 수정 요청
데이터 조회 요청

모든 생성, 삭제, 수정 요청 및 조회 요청을 V2 테이블로 보내도록 하고 V1 API의 경우 기존 로직을 V2 테이블 구조에 맞춰 전처리 해주는 로직을 중간에 삽입해서 제대로 데이터를 반영할수 있도록 했습니다.

전처리 해주는 converter 로직을 개발하는데 소요가 조금 들긴 하지만 기회비용을 생각해봤을 때 추후 관리나 DB부하에 있어서도 훨씬 이득이라고 판단했습니다.

데이터 마이그레이션

저는 무중단 마이그레이션에 있어서 가장 중요한 핵심 포인트는 마이그레이션 도중 변경되는 데이터들을 어떻게 동기화 해주는가라고 생각합니다.

동기화 방법으로는 여러가지가 있지만 CDC도입은 아직 준비가 전혀 안되어 있어서 저희에게는 꽤나 부담스러운 선택지였습니다. 여러가지 방법을 찾아보다가 그나마 가장 간단하게 사용가능한 SQS를 사용하기로 했습니다.

  • 우선 V1 API에서 V1테이블에 저장한 값을 json으로 encoding해서 SQS Queue에 쌓습니다.
  • 그 후에 별도의 로직을 통해 기존 V1 테이블에서 V2 테이블로 데이터마이그레이션을 진행합니다.
  • 모든 데이터 마이그레이션이 종료된 후 SQS Queue에 쌓여있던 변동사항들을 순차적으로 V2 테이블에 반영시켜줍니다.
  • 이 작업 중간에 위에서 언급했던 converter로직을 추가해줍니다.

모든 작업이 끝나고 나면 V1테이블은 더이상 사용되지않고 두 버전 모두 V2테이블을 사용하게 됩니다. V1테이블은 이 작업이 모두 끝나는 순간 제거가능하게 되고 V1 API는 모니터링을 통해 더이상 요청이 들어오지 않는다고 판단되면 제거할 수 있게 됩니다.

기대효과 및 결과

작업의 맨 첫 시작은 개발챕터 내부의 욕심에서 시작되었으나 결과적으론 나머지 챕터들은 물론 개발챕터 내부에서도 개발소요를 줄일 수 있는 좋은 작업이 되었던 것 같습니다.

  • 커뮤니티가 기존 억지로 짜여진 구조에서 벗어나 좀 더 확장가능성 있는 구조를 가지게 되면서 기능 개발함에 있어서 배포에 대한 의존성 및 개발소요를 줄이고 어드민페이지에서 수정가능하도록 변경되었습니다.
  • 기존에 뿔뿔이 흩어져있던 기능들을 하나로 모아줌으로써 좀 더 컴팩트한 구조를 갖게 됨으로써 기능 개선이나 유지 보수를 하는데 있어서 훨씬 수월해졌습니다.

글을 마치며

점점 서비스가 성장해나감에 따라서 기존구조들의 한계를 느끼고 개선욕구를 느끼는 경우가 많아지는 것 같습니다. 그 첫번째 작업이었던 커뮤니티였는데, 꽤나 성공적으로 마무리 된 것 같아서 기분이 매우 좋았습니다. 다만 초반에 설계나 전략을 세우는 데 있어서 경험이 부족해서 시행착오들을 겪어야 했던것이 좀 아쉬웠습니다. 그래도 그러한 시행착오를 통해서 좀 더 배울 수 있었던 것 같아서 한편으로는 나쁘지 않았던 것 같기도 합니다.

긴 글 읽어주셔서 감사드리고 다음 번에 또 다른 문제해결 과정에 대해 공유해 드리겠습니다!!

감사합니다! ( •̀ ω •́ )y

--

--