7년째 돌아가는 서비스, 어떻게 새로 짜야할까요?

7년째 돌아가는 서비스, 어떻게 새로 짜야할까요?

살기 위한 엔지니어링, 그 너머로 나아가려면

몇 년 동안 하나만 개발했지만 서비스의 이해도가 자연스레 높아지지는 않는 것 같다.
7년째 돌아가는 서비스, 어떻게 새로 짜면 좋을까.

무엇을 만들고 있나요?

Airbridge는 마케팅 성과를 측정, 분석하여 데이터 기반 의사결정을 돕는 서비스다. 수집, 정제, 제공이라는 데이터 흐름에서 내가 속한 프론트엔드 팀은 대시보드(Analytics Web App)를 만들고 있다.

마케팅(광고)는 정말 복잡하면서도 많은 데이터가 오가는 도메인 중 하나다. 참고로 React가 처음 만들어진 곳도 페이스북에서 가장 복잡한 곳으로 손꼽는 ‘광고 대시보드’를 만드는 조직이다. 도메인 특성, 그리고 마케터 등 실제 고객의 업무 흐름과 연결되며 Airbridge의 프론트엔드는 난이도가 조금은 높은 일을 하고 있다.

대량의 클라이언트 및 서버의 상태를 순차성, 공간 효율성, 엔티티 등을 고려하며 관리한다. 데이터의 양이 많고 크기 때문에 렌더링 최적화 기법은 필수, 안정적으로 차트 등의 시각화를 제공하기 위한 고민도 필요하다.

여러 기능 역시 유기적으로 연결되어있다. 예를 들어, 트래킹 링크 목록에서 한 번의 클릭으로 특정 링크의 성과 리포트를 뽑아준다. 리포트 내용은 설정값, CSV 등으로 추출할 수 있다. 데이터를 보며 테이블 셀에서 필터를 바로 걸거나, 차트를 이미지로 복사해 Slack 등에 바로 공유할 수 있다.

Airbridge 변천사 Airbridge 변천사

처음부터 그랬던 것은 아니지만

물론 처음부터 지금의 복잡성과 기능을 가진 것은 아니었다.
 2017년 우연한 계기로 합류해 처음 Airbridge의 대시보드를 마주했을 때 난감했던 기억이 난다. 물론 디자이너 → 프론트엔드 개발자로 전향한 첫 회사라 불안한 것도 있었지만, 눈에 띄는 기술적 문제들이 좀 있었다.

컴포넌트 분리가 거의 되어있지 않았다. React 0.12일 때 짜여진 createClass()도 있었다. 전역 상태 관리는 Reflux(Flux향 멀티 스토어) 절반에 Redux 절반. 그리고 Redux Thunk에는 273줄짜리 비동기 데이터 호출 로직이 붙어있었다.

처음에는 눈앞에 보이는 기술적 과제를 해결하는 것이 나의 일의 전부라고 생각했다. 사수님을 따라 컨테이너에서 컴포넌트, 상수 등을 떼어내고, Reflux를 걷어내고, Webpack 설정을 개선하고, 회사 공용 Jenkins에서 CI를 분리하고...

하지만 시간이 흘러 제품이 성장하고 함께하는 팀원들이 많아지며, 전혀 생각해보지 않았던 차원의 기술적 과제들이 날아오기 시작했다. 예를 들어...

  • 기존의 서버 상태 로직에 순차성이 보장되지 않는 것을 알았다면?

  • 데이터량이 급격하게 늘어나 대시보드 접속 속도나 렌더링이 느려진다면?

  • 백엔드와의 타임존 일치를 위해 Intl 모듈 없이 타임존을 구현해야 한다면?

  • 갑자기 일주일 안으로 다국어 환경을 구성해야 한다면?

  • 컴포넌트의 prop 네이밍이 제각기라 실수하는 비율이 늘어난다면?

문제를 만날 때마다 팀에서 함께 논의하여 해결해왔다. POC, 라이브러리 교체, 때로는 의도적으로 문제 해결을 유예하며 더 나은 방법을 찾았다.

2023년 지금은 그때에 비해 많은 것이 변했다. 코드 규모가 4~5배 커졌고, 컴포넌트와 컨테이너 등은 목적에 따라 분리되었다. 타입스크립트가 도입되고, pnpm이나 Vite, Tanstack Query 등 새로운 도구를 적극 차용했다. 디자이너는 직접 코드를 수정하여 변경 사항을 푸시하고, 번역은 구글 시트에 Apps Script로 Git의 Branch를 구현하여 병렬로 작업 및 배포할 수 있게 되었다.

판을 엎는 이유

여기서 한 가지 의문이 들 수 있다. 7년간 다양하고 다채로운 변화를 잘 해쳐왔다면 굳이 서비스를 새로 짜지 않아도 되는 것 아닌가. 엔지니어링 매니저로 일을 하면서 이와 관련해 몇 가지 고민을 하게 되었다.

하나, PMF에서 GTM의 단계로 나아가려면?

대부분의 제품은 PMF(Product-Market Fit) 단계를 넘지 못하고 망하거나 갈아엎어진다. 그렇기에 ‘살기 위한 엔지니어링’을 하기로 마음을 먹었다. 테스트나 기술적 고도화보다는 빠르게 만들고 빠르게 고치는 것에 집중했다. 시니어도 없고 제품의 성공도 담보할 수 없지만, 이 고개를 넘어 GTM(Go-to-Market)으로 갈 수 있다면 좋은 분을 모셔 해결을 부탁드릴 수 있을 거라는 막연한 믿음이 있었다.

6년이 지나 GTM의 초기 단계로 접어들며, 지금처럼 개발 및 배포 속도는 유지하면서 기능의 안정성은 높일 필요가 있었다. 하지만 개발부터 배포까지의 사이클을 측정했을 때 상대적으로 정체중이라고 판단했다.

둘, 시간 + 비용 + 컨텍스트 재분배를 고려하면?

상대적으로 낡은 기술을 쓰거나 잘 관리되지 않는 부분을 작업할 때에는 같은 기능을 만들더라도 더욱 시간을 들여야만 했다. 다른 기술에서 제공해주는 것을 직접 구현하는 것도 있었고, 다른 곳에 충돌을 일으키지 않는지 점검하고 QA하는 시간도 오래 걸렸다.

SKAdNetwork 지원처럼 기능 자체가 복잡하거나, 그냥 날려 짜서 코드가 복잡한 곳도 있었다. 이는 작업자가 남긴 로그나 주석을 찾아보고 직접 물어보며 구조를 파악하는 데에 시간이 걸렸다.

사람이 많아지고 제품이 복잡해지며 이런 경우가 늘어나기 시작했다. 기능을 명시적으로 분리/배포하고, 그 과정에서 오래 있는 사람이 컨텍스트를 나눠준다면 문제 해결에 도움이 될 것으로 생각했다.

셋, 프론트엔드 생태계의 변화와 재작성 비용의 감소세

최근 1~2년간 프론트엔드 생태계에서 몇 가지 변화를 느낄 수 있었다. Tailwind, Zag 등 ‘Framework Agnostic(탈 프레임워크화)’이 떠오르기 시작했다. React는 Server Component 개념을 도입하며 ‘React를 더욱 잘 쓰기 위한 방법 중 하나’로 소개하기 시작했다.

모든 변화에 항상 반응하고 흔들려서는 안되지만, 기존처럼 클라이언트에서 모두 렌더링하는 형태에서 서버를 함께 사용해 렌더링 및 연산 비용, 상태 관리 등을 줄이는 흐름으로 변화하는 것은 중요한 신호라고 생각했다.

변화의 흐름을 유연하게 받아들이기 위해서는 적절한 그릇이 필요하다. Airbridge의 코드 수명을 대략적으로 7~8년 정도로 생각한다면, 올해에서 내년 정도가 그릇을 바꿔끼기에 적합하다고 생각했다.

선택지와 사례 분석

Airbridge를 새로 작성한다고 했을 때 앞에서 말한 부분들을 고려하면서 새로운 그릇을 찾고 있다. 지금처럼 SPA를 그대로 제공하되 Module Federation을 보다 적극적으로 사용해 마이크로 프론트엔드 구조를 조기에 차용할 수도 있고. Next.jsRemix 같은 프레임워크를 도입하여 마이크로 프론트엔드 등의 구조는 유예하면서 기능 및 맥락 분리를 선행할 수도 있을 것이다.

프레임워크와 함께 지금까지 사용하던 옵션들도 백지부터 다시 검토하게 된다. 예를 들어 디자인 시스템의 ‘속성에 따른 변화’를 적은 고민과 비용으로 구현할 수 있는 스타일링 방법은 무엇인지. 
Recoil 대신 다른 전역 상태 관리 방법들은 없을지, 혹은 이를 아예 안할 수 있는 방법은 없는지. SPA 구조를 계속 가져간다면 Redux 같은 단일 스토어보다 되려 Zustand 같은 멀티 스토어 개념을 차용해야 할 수도 있다. Recoil도 이 부분을 고려하여 차용했으나 너무 조기에 도입해 많은 팔로업 비용을 치뤄야했다.

최근에는 이러한 고민 중 하나로 Amplitude를 유심히 관찰하고 있다.
Airbridge와 Amplitude는 ‘Analytics’를 다루고 있고, 구조적으로 비슷한 기술적 문제와 한계를 가지고 있다. 그렇기에 Amplitude를 비롯한 여러 서비스들을 조사하며 방법들을 하나씩 모아가고 있다.

Amplitude의 Analytics Dashboard Amplitude의 Analytics Dashboard

참고로 Amplitude 역시 모든 파일이 자바스크립트로 쓰여졌고 Redux에서 모든 비동기 요청을 처리하고 있었다(Redux Query). 이를 타입스크립트로 점진적으로 전환하면서 Module Federation을 사용해 SPA에서도 기능과 맥락을 분리해 서비스를 운영하고 있다.

반대로 고민하지 않는 옵션도 있다. 예를 들어 Fine-Grained Reactivity 개념이 떠오르니 Signal을 씁시다라던가... Solid, Svelte로 갑시다라던가... Rescript를 도입합시다라던가...

이는 해당 개념이 메이저 영역으로 들어올 경우 잠재적인 위기가 될 수 있음에 동의한다. 하지만, 정의한 문제의 효율적 해결을 고민하는 ‘엔지니어’로서의 생각. 그리고 인재의 영입 및 학습, 유지보수 비용 등을 고려하는 ‘엔지니어링 매니저’로서의 생각을 모두 고려했을 때, 지금으로서는 고민을 유예하는 것이 옳다고 판단했다.

도와주세요!

이야기의 마무리는 도움을 요청하는 것으로 마무리하려고 한다. 서비스를 바닥부터 만드는 일은 많이 했지만 서비스를 유지보수하고 새로운 그릇을 짜는 일은 거의 해본 적이 없다. 살아남았기에 할 수 있는 행복한 고민 중 하나가 아닐까.

이번에도 언제나처럼 ‘지금 상황에서의 최선’을 선택하고 싶다. 이를 더욱 잘하려면 비슷한 시행착오를 겪은 분들의 이야기와 경험. 즉 다른 관점이 필요하다고 생각했다.

이 글을 읽는 분들이 Airbridge, 내지는 AB180이라는 회사에 관심을 가지게 하려면 좋은 부분만 추려 이야기하는 것이 좋지 않을까 물을 수 있다. 하지만 꾸민 모습으로는 결국 상상과 현실의 괴리만 불러일으킨다. 현실은 언제나 구질구질하고 아름답게 떨어지지 않는다. 그렇기에 무슨 생각을 하며 어떻게 허우적댔는지 솔직하게 이야기하고 싶었다.

혹시라도 이 긴 글을 읽고 관심이 조금이라도 생겼다면 함께 이야기를 나눠보고 싶다. 우리가 잘못한 부분, 잘하고 있고/잘 할 수 있는 부분을 같이 이야기할 수 있다면 행복할 것 같다.
chanhee@ab180.co