토스ㅣSLASH 23 - 달리는 토스 앱에 React Native 엔진 더하기

Sol·2023년 6월 9일
4

react-native

목록 보기
3/4
post-thumbnail

토스ㅣSLASH 22 - 미친 생산성을 위한 React Native

이전 SLASH 22에서 toss 팀은 React Native를 사용한 토스 글로벌 앱의 사례를 소개했다.
간단하게 발표 내용을 요약하면

  • 당시 글로벌 팀은 FE, iOS, Android 개발자 각 1명 서버 개발자 2명인 적은 인원의 팀
  • 기존 iOS(xCode)의 느린 빌드(배포) 속도와 개발 리소스 부족으로 솔루션이 필요했음
  • 제안된 문제 해결 옵션은 총 두 가지로 모든 Flow를 웹으로 제작, 크로스 플랫폼 프레임워크 사용
  • 크로스 플랫폼 프레임워크 RN/Flutter 두 가지를 비교하고, JS/React 베이스인 RN을 선택한 이유
  • 기존 앱 프로젝트에 RN을 부분적으로 적용 or 완전히 RN으로 다시 작성 비교
  • 코드푸쉬 사용으로 챙긴 이점
  • RN 사용으로 iOS/Android 두 플랫폼 앱의 UI 통일성 유지

자세한 내용은 영상을 보는 것이 더 도움이 된다.


토스ㅣSLASH 23 - 달리는 토스 앱에 React Native 엔진 더하기

조용히 업데이트되고 있는 토스앱

토스의 두 번째, 네 번째 탭인 혜택과 주식 탭은 React Native로 구현돼있다.(토스 앱 설치 후 초기 홈 화면 포함)
해당 서비스들은 원래 Web을 통해 개발되고 있었다.

왜 React Native인가?

토스 프론트엔드 챕터의 중요한 가치

사용자 경험 / 개발자 경험을 중요하게 생각
React Native가 이 두 가지 가치를 합리적인 수준으로 제공할 수 있다는 점에 주목

사용자 경험: Web vs React Native

많은 서비스들을 Web으로 개발 한 이유 -> 빠른 배포 주기와 실험으로 사용자들의 니즈를 파악하기 위함
하지만 성능과 사용자 경험에 대한 문제가 발생
영상처럼 인터넷 속도가 느리거나 불안정한 경우, 사용자는 오랜 로딩 시간을 경험

실제로 데이터 측정을 해보니 100명 중 5명은 3.5초 이상의 로딩을 경험
React Native를 사용하니 거의 대부분의 로딩 시간을 0초 초반으로 유지

네트워크 성능에 많이 의존하는 Web과 달리
이미 파일 시스템에 저장되어 있는 JS 파일을 참조하는 React Native는 네트워크 성능의 영향을 받지 않음

개발자 경험: 서비스 빌드 속도

단순히 JS 파일 하나를 빌드 하는 React Native 특성상 빌드 시간이 짧음
(Next.js에서 웹 서비스를 빌드 하는 시간을 최대로 낮춰도 M1 머신에서 40초가 걸리는 것에 반해 빌드 시간을 75% 이상 감축한 성과)

개발자 경험: SSR 서버 운영 비용

사용자 경험을 지키기 위해 SSR을 도입 -> 인프라 운영 비용 증가, 서버 오류 대응, Kubernetes 운영 비용, 배포 시간 증가
행운 퀴즈 서비스와 같이 트래픽이 변동하는 경우 제때 서버를 증가/감축해야 함

개발자 경험: CDN 운영 비용

반면 React Native의 경우 CDN을 사용해 빌드된 JS 파일을 관리하기만 하면 됨 -> 저렴한 비용, 1초 배포 & 롤백, 운영 비용 0에 가까움

React Native 도입 결정

  1. 빠른 초기 로딩 -> 사용자 경험 개선
  2. 짧은 배포 시간 -> 개발자 경험 개선
  3. 낮은 인프라 운영 비용 -> 플랫폼 엔지니어의 부담 감소

React Native 도입 과정에서 기술적 도전

React Native 개발 환경 도입 후 마주친 기술적 어려움

  1. 서비스별 배포
  2. 빌드 속도 감축
  3. 8개 언어, 3개 플랫폼
  4. 의존성 관리

서비스별 배포

혜택, 주식, 홈, 포인트 등 4개의 탭이 각각 별도로 빌드/배포되어야 함 -> React Native에서 마이크로 프론트엔드 구현이 필요

더 빠르게 실험하기

토스의 'Experiment Over Opinion'이라고 하는 조직 원칙이 존재
실제로 토스에서 대부분의 서비스는 가설 수립, 디자인, 개발, 분석, 모니터링까지의 시간이 몇 시간에서 일주일 정도가 소요됨

독자적인 사일로 문화

토스가 운영하는 제품은 상당히 복잡하고 많음 내부적으로 100개가 넘음
모든 서비스들이 다 함께 배포되면 하나의 서비스가 다른 서비스에 영향을 줄 수 있는 가능성이 생김
그럴 경우 작은 수정사항이어도 대부분의 제품에 대해 문제가 없는지 검증하는 프로세스가 필요해짐 -> 일하는 속도가 느려짐
반대로 한 서비스의 수정사항이 그 서비스에만 영향을 준다면 -> 테스트, 변경 사항 검증이 단순해짐

토스 One App

토스는 One App으로 토스뿐만 아니라 토스증권, 토스뱅크와 같이 다른 법인의 개발자들이 하나의 앱을 만듬
각각 법인이 개발한 제품이 독립적으로 배포되어야 일하는 속도를 유지하면서도 앱을 안정적으로 운영할 수 있음

매일 배포 횟수

서비스별 배포 개발 환경 덕분에 Web과 React Native 합쳐서 하루에 대고객 40회 개발 환경 100회 이상 배포
React Native 서비스는 모두 별도의 빌드, 배포 프로세스를 운영하도록 설계

앱 크기 제한

하지만 서비스별로 빌드와 배포를 수행하는 것은 생각보다 많은 기술적인 어려움을 가져오게 됨
일반적인 React Native 서비스의 JS 파일 크기는 3MB 정도
아무 기술적 조치가 없다면 1개 서비스가 늘어날 때마다 앱 용량이 3MB씩 늘어남
iOS/Android에서 셀룰러 네트워크로 내려받을 수 있는 앱 용량의 크기에 제한이 있음 -> 3MB 생각보다 매우 큰 크기

특히 토스는 100개가 넘는 서비스를 운영하고 있어 물리적으로 제한 용량 안에 모든 서비스를 담을 수가 없음

Under the hood: 파일 분리하기

이를 보안하기 위해 각각의 서비스의 JS 파일을 Shared와 Service로 나눔
Shared: React, React Native 등 모두가 공유하고 있는 라이브러리
Service: 그 외 다른 부분을 포함

한 서비스를 로드할 떄 두 개의 JS 파일을 순서대로 불러오는 방식을 채택
Bundler의 설정을 정교하게 조정하여 이를 구현

Shared 파일은 __toss_require__라고 하는 특별한 코드 공유 함수를 노출하고
Service 파일은 이를 기반으로 라이브러리를 import 하도록 설정함

자세한 내용은 toss.tech 아티클에서 확인 가능

서비스별 배포: 약한 React Native 생태계

점진적 도입을 지원한다는 얘기와 달리 생각보다 React Native 생태계는 점진적 도입을 뒷받침하고 있지 않음
Code Push 라이브러리를 자체적으로 구현해야 함
유명 라이브러리조차 1개의 React Native View를 사용한다고 짜여져 잇는 등 문제가 많아
직접 패치나 PR을 보내야 하는 경우가 있음

서비스 빌드 속도

React Native 개발 환경은 페이스북이 개발한 Metro 번들러를 사용 -> 느린 속도, 비일관적인 빌드
1분 안에 빌드가 끝나길 기대했지만 Metro로는 어려움
캐시가 잘못돼서 Metro 명령어를 실행하는 컴퓨터에 따라서 빌드 결과가 잘못되는 경우가 있음

React Native 소스코드를 분석하여 esbuild로 번들러를 교체 -> 빠른 속도, 일관적인 빌드

혜택 탭의 경우 Metro 빌드 2분 / esbuild 빌드 10초 -> 90% 이상의 시간 감축, 배포 시작부터 종료까지 모든 과정을 20초 안에 끝냄

8개 언어, 3개 플랫폼

React Nativesms 코어로 C++, iOS Objective-C, Android Java 그리고 Javscript와 Flow를 사용
토스는 iOS Swift, Android Kotlin, 그리고 Typescript를 사용
현실적으로 한 팀이 모든 언어와 플랫폼에 대해서 전문가이기는 어려움

토스에 있는 숙련된 iOS/Android 개발자들의 도움을 받기로 함

React Native Test Page

구현하고자 한느 기능의 최소 테스트 페이지를 만들어서 iOS, Android, JS를 다루는 FE 개발자가 협업함
이후 만들어진 테스트 페이지는 코드의 리팩토링 후에도 올바르게 동작하는지 꾸준히 테스트하는데 사용됨

의존성 관리 Node Resolution Algorithm

React Native는 'node_modules'라고 하는 디렉터리로 대표되는 Node.js 표준 알고리즘에 따라서 의존성을 설치함
해당 설치 방법은 문제가 많음 -> 느린 설치 속도, 정합성을 보장할 수 없음, Phantom Dependencies
표면상으로 쉽게 드러나는 문제는 아니지만, 이상한 문제를 유발시켜 디버깅을 어렵게 만듬

의존성 관리 Phantom Dependencies

예를 들어 모노레포 루트에서 React Native CLI 사용을 위해 'react-native' 패키지를 설치한 상황
모노레포에 포함된 모든 패키지에서는 의존성으로 'react-native'를 명시하지 않아도 사용할 수 있음
Node.js 표준 알고리즘에 따라 상위 폴더의 node_modules까지 순회하면서 의존성을 찾기 때문
루트 패키지의 의존성을 수정할 경우 하위 패키지 모두를 깨트릴 수 있는 가능성이 있음

자세한 내용은 toss.tech 아티클에서 확인 가능

의존성 관리 Yarn Plug'n'Play

토스에서는 이를 Yarn Plug'n'Play(PnP) 도입으로 해결
토스 프론트엔드 챕터는 의존성 설치의 성능과 엄격함을 위해서 PnP 설치 전략을 적극 사용하고 있음
하지만 React Native 개발 환경 생태계는 Node.js 표준 설치 방식에 크게 의존적임
Yarn 사이트에 따르면 악명 높다. 'Notorious'라고 표현하기도 함

토스는 JavaScript 레포지토리와 iOS/Android 레포ㅓ지토리가 분리되어 있다는 장점을 바탕으로
여러 설정 변경을 거쳐 PnP 환경을 도입하는 데에 성공함

의존성 설치 속도

토스가 이전에 React Native 레포지토리에서 사용하던 PNPM은 node_modules를 만들기 위해 42초가 걸리는 반면
Yarn PnP 도입 및 최적화 시에는 6초로 설치 시간을 단축함

자세한 내용은 토스ㅣSLASH 22 - 잃어버린 개발자의 시간을 찾아서: 매일 하루를 아끼는 DevOps 이야기에서 확인

엄격한 의존성 관리

의존성 설치가 엄격해져 이상한 오류를 미리 방지할 수 있음 -> node_modules가 꼬이지 않음, Phantom Dependencies 문제 없음
가끔 발생하는 디버깅 시간을 크게 줄일 수 있음

요약

  1. 서비스별 배포: Shared + Service 파일 분리로 해결
  2. 빌드 속도 감축: Metro로 인한 느린 빌드 속도를 esbuild로 바꾸어 해결
  3. 8개 언어, 3개 플랫폼: React Native Test Page를 만들어 협업
  4. 의존성 관리: Yarn Plug'n'Play 도입으로 깨지기 쉬운 의존성 설치를 해결

앞으로의 계획

Isomorphic Package

토스에서는 Web과 React Native의 개발 환경을 거의 동일하게 맞추는 것을 목표로 하고 있음
토스 대부분의 라이브러리를 Web, React Native, Server 모두에서 사용할 수 있도록
'Isomorphic Package'를 만들려고 함
서비스를 개발하는 사람이 어떤 환경인지 신경 쓰지 않고
쓰던 대로 코드를 쓰면 기대하는 대로 코드가 동작하는 것

profile
야호

1개의 댓글

comment-user-thumbnail
2023년 12월 18일

왐마.. 쩌네요 토스

답글 달기