1. MFE를 모놀리스로 전환한 이유#
작년 10월 말쯤, 회사 웹 인프라에 큰 변화가 있었습니다.
바로 기존 MFE구조를 하나의 모놀리스(SPA)로 통합했는데요.
페이지별로 각 서버를 나눠 둔 수직적 분할 형태의 MFE 구조였는데
결론부터 얘기하자면 MFE가 저희 서비스 특징과 전혀 어울리지 않았고, 많은 단점에 비해 사소한 장점조차 하나도 없다고 판단하여 전환하였습니다.
입사시기부터 MFE 구조였는데 히스토리를 여쭤보니 당시에는 주요 페이지인 질문상세가 죽어도 다른 페이지는 문제없이 서빙하기 위해서 이러한 구조를 택했다고 하셨습니다. 질문상세에 트래픽이 많이 쏠리는 저희 서비스 특성상 당시의 사정이 이해가 가더군요.
하지만 운영을 하면서 느꼈던 점은 그 다른 페이지들이 대부분 질문상세 페이지로 향하는 진입로 역할을 하는 페이지여서
- 주요 페이지를 제공하는 서버가 죽으면 다른 페이지를 제공하는 서버가 살아있는게 유저에게 크게 의미있나?..싶기도 하고
- RUM을 통해 2주 기준으로 페이지 방문 수를 확인해보니 질문상세가 1000만임에 비해 나머지는 10만 언저리로 미미한 수치였습니다.
이런 이유로 각 페이지별로 서버를 띄우는 것은 낭비라고 생각하였습니다.
또한 보통 MFE하면 언급되는 장점인 독립적 개발/배포 프로세스도 저희 서비스에서는 빛을 보지 못했던게 독립적 개발을 언급할 만큼 팀 규모가 크지 않기도 하고 모든 페이지가 유기적으로 연결되어 있어 여러 페이지를 한번에 배포해야 하는 경우가 대다수였기 때문입니다.
장점이 몇가지가 있다고 한들 (실제로 하나도 없기도 했고) 비용 부담이 과했기에 트레이드오프상 마이크로 구조를 하지 않는 것이 압승인 상황이라고 판단했습니다.
그 외에도 여러 단점이 있었는데 약 1년반동안 MFE로 운영하면서 느꼈던 단점을 세부적으로 정리해보겠습니다.
2. UX 악화#
각 페이지를 제공하는 서버 오리진이 다르기 때문에 뒤로가기나 내부링크 클릭 시 소스를 새로 받아오기에 완전한 새로고침으로 동작합니다.
따라서
- 유저입장에서는 무거운 JS번들을 매번 새로 받아와야 하기에 느리다고 느낄 수 있고
- 무엇보다도 피드 -> 질문상세로 이동 후 뒤로가기를 하면 스크롤 초기화 + 불러왔던 피드 데이터들이 초기화 되어버린다는 참사가 발생합니다.
저희 서비스는 피드 위주 페이지의 비중이 높은 편이기도 하고, 유저층 상당수가 피드를 대충보고 넘어가는게 아니라 보상을 얻기 위해 답변을 달 질문을 찾고자 피드 스크롤을 내리는 빈도가 잦기에 이런 문제는 큰 유저 불편을 초래합니다.
이는 DX 악화와도 연관이 있습니다.
3. DX 악화#
위에서 말씀드린 피드 초기화 문제는 서비스 특성상 용납할 수 없으므로 우회 코드의 추가가 필요합니다. 인프라적인 문제를 소스코드 레벨에서 커버를 해야한다는 것인데요.
즉, 평소에는 잘 사용하지 않는 코드를 사용하여 브라우저와 제 애플리케이션간 싱크를 맞춰야합니다.
저는 리스트 데이터와 스크롤을 복구하기 위해 질문상세로 이동 시 스크롤 위치를 세션 스토리지에 저장해두고 리스트 데이터를 indexed DB에 저장했었습니다.
그리고 질문상세에서 피드로 돌아가면 load 이벤트를 감지하여 indexed DB에 저장한 데이터를 상태에 동기화 + referer를 참고해서 세션 스토리지에 저장해둔 스크롤 위치를 강제로 브라우저에 동기화시킵니다.
다만 의아하실 수 있는데요. BF캐시가 있어서 굳이 복구 처리를 안해도 알아서 피드에서 질문상세 페이지로 접근하던 순간의 스냅샷으로 돌려보내기에 문제없지않냐? 라고 할 수 있지만 피드에서 BF 캐시를 비활성화해야 하는 사유가 있었습니다.
💡 BF 캐시란?
- 사용자가 브라우저에서 '뒤로' 또는 '앞으로' 버튼을 눌러 페이지를 이동할 때, 자바스크립트 힙(Heap)과 DOM 상태를 포함한 페이지 전체의 스냅샷을 메모리에 저장해 두었다가 즉시 불러오는 브라우저 최적화 메커니즘
유저가 질문을 작성하다가 이탈한 경우 임시저장된 질문이 있다는 배너를 피드에 노출하게 되는데
질문작성 페이지에서 이 임시저장 데이터를 초기화하고 뒤로가기 했을 때 배너가 사라져야 함에도 BF 캐시로 인해 이 배너가 사라지지 않고 계속 노출된다는 문제가 있어 기획적인 요청에 따라 BF캐시를 비활성화 해놓은 상태였습니다.
따라서 이전 페이지의 상태를 어딘가에 저장해두는 별도 우회코드가 필요했던 것이었습니다.
또한 질문상세에서 좋아요라도 눌렀다가는 피드와 동기화를 위해 indexed DB에 있는 피드 데이터를 꺼내서 이 질문을 찾고 좋아요 필드를 찾아 업데이트하는 로직까지 추가되어야 하는 등 복잡도가 올라가게 됩니다.
아무래도 indexed DB 컨트롤이나 스크롤 복구 코드는 평소에는 잘 사용하지 않는 코드이다보니 가독성 난이도가 올라가 DX를 해칠 수 밖에 없습니다.
4. 성능 저하#
1) 페이지간 중복된 리소스를 매번 새로 로드#
레이아웃같이 모든 페이지에 동일하게 적용되는 JS 청크를 페이지 이동마다 매번 새로 받아와야 한다는 문제가 있습니다.
다행히도 저희 서비스의 헤더나 푸터는 다시 받아오는게 부담될 정도로 무거운 코드는 있지 않았지만 불필요한 중복 로드임에는 틀림없습니다.
2) 비즈니스 데이터를 매번 새로 요청#
서비스의 주요 비즈니스 데이터로 카테고리라는 데이터가 존재합니다.
피드의 각 아이템 데이터는 카테고리 id를 갖고 있고, 배열 길이 250개에 해당하는 API를 요청하여 이를 매핑하는 형태로 사용하고 있었습니다.
피드 형태의 페이지(메인 피드, 프로필 피드, 단일토픽 피드)를 방문할 때마다 이 긴 배열을 받아오기 위해 API 요청이 발생하게 됩니다.
5. 서버비용 낭비#
AWS ECS 체계에서 6개의 페이지 * 적응형 분기(PC/모바일) = 12개의 서비스가 운영되고 있고 각 서비스 당 적어도 2개의 태스크를 띄우고 있었습니다.
따라서 최소 24개의 태스크가 운영되고 있고 태스크 1개(1vCPU, 2GB Mem)가 월별 41.45달러이므로, 24개라면 연간 11,940 달러가 됩니다.
질문상세쪽 태스크 4개를 제외하고 트래픽이 유의미하지 않은 20개의 태스크가 연간 9,948달러라는 엄청난 낭비를 부르고 있습니다.
- 달러 환율 1450원 기준으로 14,424,600원
최소 기준으로 잡은 비용만 이 정도이고 데이터 트랜스퍼 비용이나 질문상세 서비스에서 스케일 아웃으로 증가하는 태스크 개수 등을 고려하면 +@의 비용도 존재합니다.
다른건 모든 단점을 고사하고 이 비용문제 단 하나만으로도 MFE를 모놀리스로 전환할 이유가 충분합니다.
6. 전환 중 겪었던 문제#
피드 페이지와 질문상세 페이지의 데이터 동기화를 위해 indexed DB로 데이터를 컨트롤 했던 로직을 제거하면서 tanstack query의 캐시를 업데이트하는 코드를 추가해야 하거나
location.href를 useRouter같은 history 기반 api로 바꾸거야 한다는 등 예측 가능했던 이슈를 제외하고
전혀 예상못했던 문제가 있었는데요.
앱의 하단 네비게이션 바는 네이티브 로직으로 만들어졌고 노출 조건으로 웹뷰의 URL의 변화를 체크하면서 특정 페이지에서만 노출하도록 화이트리스팅이 되어 있었는데
SPA 기반 네비게이팅에서는 이벤트가 트리거되지 않는다는 문제가 있었습니다.
즉 기존 location.href의 새로고침 방식의 이동에서는 네이티브의 화이트리스팅 로직이 정상 트리거되었으나 useRouter로 이동시에는 네이티브가 감지를 못해서 하단바를 숨기거나 다시 노출하지 못하는 현상이었습니다.
따라서
- 앱에서 검토했을 때 이 SPA 이동을 감지할 수 있는 방법이 없었기도 했고
- 차후 화이트리스팅된 목록을 수정 해야 한다면 웹에서 컨트롤하는 것이 배포 대응에 더 유리할 것 같아서
웹에서 URL의 path 변경을 감지하여 하단바 on/off를 컨트롤하는 앱프로토콜을 호출하도록 수정하였습니다.
7. 예상치 못했던 추가적인 수확#
언급했던 서버비용 감소, UX/DX 개선 외에도 빌드시간이 크게 감소했다는 수확이 있었습니다.
빌드 대상의 물리적인 숫자가 줄어든거니 당연하다고 볼 수 있는데 실제로 반영하기 전까지 예상하지 못하고 있었네요 ㅎㅎ;
12개의 서비스를 모두 각각 빌드하였을 때를 기준으로 10~12분의 빌드타임이 걸렸었지만
모놀리스 전환 이후로는 2분으로 줄어드는 큰 개선이 있었습니다.
시간을 많이 먹는 타입체크와 린트의 횟수가 1회로 줄어든 점, 번들링도 1회로 줄어든 점이 꽤 커보입니다.
8. MFE는 기술적 고려대상이 아닌 조직적 고려대상#
개인적인 생각을 말해보자면
MFE에 대한 악담(?)만 늘어놓았지만 4~5명 규모의 우리 프론트엔드 조직과 맞지 않았기 때문이지
팀 운영 효율을 중시해야하는 정도의 큰 조직 규모라면 도입해볼 법 하다고 생각합니다.
즉, 우리는 폭발적인 트래픽에 대응하고자 웹 인프라 안정성을 위해 MFE를 도입할거야!같은 기술적인 이유보다는
우리 프론트엔드 챕터의 규모가 커졌으니 작업간 책임 소재도 불분명하고 컨플릭도 많이나는 것 같아. UX나 비용적 단점에도 불구하고 챕터 운영 효율이 더 중요해진듯 해. 따라서 페이지나 도메인별로 팀을 분리하고 각 팀의 범위를 제한하고 책임을 끌어올리기 위해 MFE를 도입해야겠어.
같은 관점으로 접근해야 한다고 봅니다.
팀 운영의 효율적 측면 vs UX/DX 및 비용 등등 단점이 크로스오버되는 지점을 넘어선 조직이어야 MFE가 빛을 발휘할 수 있다고 생각해요
히지만 웬만한 스타트업 프론트엔드 챕터의 규모가 팀의 효율을 논하여 분리해야할 만큼 크진 않을 것이고,
트래픽 폭증으로 인한 구조 고민은 실제로 그 상황이 도래했을 때 고려해봐야지 일어나지도 않은 일에 섣불리 대응했다가는 개발적 피로와 비용 낭비만 초래할 뿐이라고 생각합니다.
트래픽이 치솟는 꿈같은 순간이 다가오면 우선 기존 서버의 스케일 아웃으로 대응하다가 MFE 도입을 고려해도 충분하다고 봐요.
따라서 저는 MFE 도입에 대해 보수적인 접근을 추천합니다.
작은 규모의 팀에서 큰 규모의 팀이 일하는 방식이 힙해보인다고 따라했다가는 뱁새가 황새따라하는 모습이 될 가능성이 크다고 생각합니다.
제 경험에 감정 이입이 되다보니 좀 완강한 표현이 있었을 수 있는데 다른 생각이나 관점이 있으시다면 댓글 부탁드립니다!