최근 회사들의 업무를 살펴보면 모놀리식 구조에서 MSA로 전환하는 사례가 많습니다.
그만큼 MSA의 확장성과 편한 유지보수가 입증되어
많은 모놀리식 프로젝트들이 MSA로 전환하는 동향을 보이고 있는데요.
저 또한 2번의 스타트업 경험 중 2번 모두 모놀리식에서 MSA로 전환하는 업무를 경험했었습니다.
근데 MSA로 전환하면서 몇가지 까다로운 것들이 생기는데 그 중 하나가 서버 간 데이터 동기화였습니다
MSA의 일반적인 구조는 각 Feature로 분리된 서버가 자신의 DB를 독립적으로 가지는 것이기 때문에
만약 한 서버에서 다른 서버의 데이터가 필요한 경우 데이터 동기화를 시켜주어야 합니다.
실제로 제가 진행했던 프로젝트 중 캘린더나 기관 통합 검색을 구현할 때 이런 상황이 있었는데요.
대표적으로 캘린더 기능을 기준으로 설명드리면
캘린더 기능의 Requirements가 마이페이지에서 ( 수업 시작, 종료 ), ( 출석 시작, 종료 ), ( 과제 시작, 종료 ) 등 각 유저와 관련된 일정들을 보여줘야 하는 기능이었습니다.
위 기능 구현을 위해 독립적인 캘린더 서버를 구현해야 했는데
캘린더의 일정을 만들기 위해 필요한 데이터(ex. 수업, 과목, 출석 등과 같은 데이터)가 모두 다른 서버 DB에 있는 상황이었습니다.
그 데이터들을 캘린더 서버로 가져와서 일정 테이블의 스키마에 맞게 변형시켜야 하는 것이 까다로운 이슈였습니다.
아래 본론부터 이 이슈를 해결하기 위해 어떤 고민들이 있었고 어떻게 캘린더 기능을 구현했었는지에 대한 경험을 공유하려 합니다.
분산된 시스템에서 데이터를 동기화하는 방법은 매우 다양한 방법이 있습니다.
최근에 NHN Forward 22 발표 중 분산 시스템에서 데이터를 전달하는 방법에 대한 영상을 봤는데
정말 내용이 좋고 설명을 잘해주시더라구요...
제가 캘린더 기능 구현을 위해 데이터 동기화에 대해 Survey 할 때는 못봤던 자료라서
일찍이 보지 못했음에 아쉬움이 남습니다. ( 영상 링크는 Reference에 추가해놓겠습니다. )
본격적으로 제 경험을 말씀드리면
캘린더 기능 설계를 할 때는 아래 3가지와 같은 선택지를 고민했습니다.
1. Redis Queue를 이용
처음에 생각했던 방법은 원본 데이터의 변경( 생성, 수정, 삭제)이 일어나면 해당 이벤트에 대한 ID를 생성하고 이를 Redis Queue에 넣어 순차적으로 동기화하는 방식을 생각했습니다.
저희 서버의 모든 데이터들에는 AWS의 ARN(Amazon Resource Number)에 영감을 받아 만든 각 데이터가 어떤 데이터인지 구분할 수 있는 고유한 키가 있었습니다.
그래서 변형이 일어날 때 이 키를 이용해 이벤트 ID를 만들고 Redis Queue에 넣은 후 비동기 테스크가 일정 주기마다 돌면서 Queue에서 이벤트 ID를 꺼내 어떤 데이터에 변경이 일어났는지 알아낸 다음 ERN으로 최근 데이터를 가져와 캘린더 서버에 Upsert하는 방식을 고안했습니다.
설명이 조금 복잡해지는데 요약하면 아래와 같습니다.
1. 원본 데이터의 변경이 일어남
2. 변경에 대한 이벤트 ID를 생성 -> 이때 ERN이라는 리소스에 대한 고유한 키값 사용
3. 생성한 이벤트 ID를 Redis Queue에 넣음
4. 비동기 서버가 일정 주기마다 Redis Queue에서 이벤트 ID를 꺼냄
5. 이벤트 ID로 DB에서 최신 데이터를 불러와 캘린더 서버 DB에 Upsert
이 플로우만 본다면 굉장히 직관적인 방법이라 생각하고 검토를 받았는데
이 구조를 사용하지 못하는 큰 이슈가 하나 있었습니다.
원본 데이터의 변형을 감지하려면 감지가 필요한 리소스의 모든 Post, Patch, Delete 엔드포인트에 위 플로우에 대한 코드를 각각 추가해야 한다는 점입니다.
데이터 동기화가 필요한 리소스가 굉장히 많았기에 이걸 하나하나 추가하기엔 굉장한 공수가 들었고
만일 추가가 된다 하더라도 나중에 관리하기가 매우 까다롭기 때문에 결국 위 방식은 보류하고 다른 방식을 고려하게 됩니다.
2. CDC
1번 설계가 무산되고 더 많이 Survey 해보았고 CDC라는 방식을 찾아 다시 설계하였습니다.
CDC란 Change Data Chapter의 약자로 DB 변경 사항을 자동으로 캡쳐해 저장해놓는 디자인 패턴입니다.
즉, 원본 데이터가 변경되면 이 변경 사항을 캡쳐해 저장하는 기술이 CDC이고 이를 Kafka와 같은 메시지 브로커를 사용해 다른 서버, 저의 경우에는 캘린더 서버에 동기화 시키는 방식이었습니다.
정말 일반적으로 사용되는 분산 시스템에서 데이터 동기화 방식이고 성능과 에러 처리 또한 보장되어 설계적으로는 빈틈이 없는 구조였습니다.
하지만 부득이하게도 당시 저희 백엔드 팀에서 CDC를 도입할 수 없는 모종의 이슈가 있었어서 이 설계를 사용할 수 없게 되었고 결국 다른 방법을 고민하게 됩니다.
3. 자체 비동기 서버를 이용한 데이터 동기화
이제는 구글링해서 찾을 수 있는 데이터 동기화 방식들은 모두 사용할 수 없었고
아예 새로운 구조를 만들어 데이터를 동기화 시켜야하는 매우 챌린징한 프로젝트가 되었는데요.
특정 프로젝트를 설계할 때 직관적인 쉬운 구조로 만드는 걸 가장 중요하게 생각했었고 이를 만족시키는 새로운 구조를 정말 많이 고민하였습니다.
최종적으로 설계한 구조는 기존에 있던 자체 비동기 서버를 이용해 데이터 동기화를 구현하는 것이었는데요.
큰 플로우를 설명드리면 아래와 같습니다.
1. 데이터 동기화가 필요한 모든 테이블에 생성이나 수정 시점을 저장하는 updated 필드를 만듬
2. 비동기 서버는 history에서 마지막으로 동기화 된 시점 이후의 데이터들을 불러온다.
3. 불러온 데이터를 캘린더 서버에 Upsert 시킨다.
4. 동기화가 완료되면 가장 updated 필드를 역순 정렬해 가장 첫번째 updated를 history에 저장한다.
위 과정은 모두 비동기적으로 동작하기 때문에 DB Connection을 제외하면 성능적으로 걱정하는 부분이 없었고 비동기 서버 한 곳에서 관리되기 때문에 직관적인 구조였는데요.
반면에 비동기적으로 동작하기 때문에 이슈 트래킹도 굉장히 까다로울 뿐더러 데이터가 동기화되려면 최대 3분이 걸리는 경우도 있었습니다.
캘린더 기능의 요구 사항이 실시간성이 보장되지 않아도 괜찮아서 위 설계대로 개발이 진행되었고
우여곡절 끝에 현재는 배포되어 정상적으로 제 역할을 수행하고 있습니다.
지금까지 진행했던 프로젝트 중 가장 챌린징한 프로젝트라서 기억에 많이 남는데요.
사실 실제로 구현할 때는 외래키로 연결되어 있는 원본 데이터들을 비정규화해서 동기화 시킨 후 또 캘린더 일정에 맞게 정규화를 하는 이슈도 있었고 초기 데이터들을 비동기 서버가 옮기면 너무 오래 걸리기 때문에 한 번에 미리 옮겨놓아야 하는 이슈도 있었고 정말 해결해야 할 이슈가 많았습니다.
그래도 고생해서 개발한 기능이 운영 서버에서 제대로 동작하는 걸 보니 신기하기도 했고 언제 무슨 이슈가 터질 지 몰라 두려웠습니다 ( 다행히 큰 이슈는 없었습니다 ㅎㅎ )
만약에 CDC와 Kafka를 사용하면 어땠을 지 궁금해서 최근에 이 구조를 사용한 개인 프로젝트를 하나 진행하고 있는데 실제로 데이터 동기화까지 구현해보니 정말 커스텀 할 수 있는 옵션들도 많고 좋았습니다.
그래서 다음 포스팅은 어떻게 CDC로 변경 데이터를 캡쳐하고 Kafka로 데이터 동기화를 시키는지에 대한 주제로 돌아오겠습니다.
항상 잘 보고 있습니다 :)