2025년 토스 메이커스 엔지니어링 데이 참석 후기

tossfrontendengineering

토스 프론트앤드 세션 듣고 느낀 점

첫번째 들었던 세션.

두번째 들었던 세션

1개의 nextjs 으로 이루어져 있었음. 이후 1개의 app 을 micro app 1, micro app2, micro app3 와 같은 방식으로 구분했음.

이때 런타임 통합을 사용하기로 함. 빌드 시점에 하기 보다는 실행 시점으로 전략을 잡음.

네이티브 ESM 에 집중함. 모듈을 가져올때 import hello from https://example.com/hello.js 와 같이 사용할수 있음.

그런데 이 경우 import 모듈 이름이 너무 불편함. 이 때 script importmpa 을 이용하면

와 같이 사용할수 있음. 이 경우 별도로 페더레이션 없이 순수한 표준 기술로 사용할수 있음.

그리고 적절한 마이크로 앱을 불러오기 위해 각 페이지에서 어떤 마이크로앱을 참조해야 하는지 알아야 함. 이를 해결하기 위해 라우팅테이블을 이용했음.

page1 -> micro app 1 page2 -> micro app 1 page3 -> micro app 2 page4 -> micro app 2

마이크로앱에서는 특정 컴포넌트를 렌더링하고 호스트(컨테이너) 에서는 그 마이크로 앱을 불러오는 방식으로 함 (전부다 리액트를 렌더링함)

토스테서는 page1 -> page3 등으로 이동할때 사용하는 라우팅 기술을 별도로 만들어서 사용하고 있다고 함. 여기서는 구체적으로 설명해주지는 않았음.

이 환경을 구축해둘 경우

각 팀마다 별도로 ESM 번들을 만들수 있게 되었음.

유저가 특정 페이지에 도착하면 html 을 렌더링하는데 라우팅테이블과 임포트맵을 script 형태로 만들어 유저에게 전달된다.

라우팅테이블 -> 어떤 서비스 임포트맵 -> 어떤 자바스크립트 리소스

이와같이 설계하면 각 개별 번들이 동적으로 조립되어 1개 페이지를 리턴할수 있음.

그럼 이제 각 팀별로 어떻게 개별 번들하게 될까.

각 특성 서비스에서 번들을 만든 뒤 이 번들을 만든 뒤 그걸 임포트맵에 새로 등록해주는 방식으로 가능하다. 이 경우 카나리 배포(점진적 배포) 가 가능하다.

즉 이 원리를 이용해 실제 prod 가 아닌 프리뷰 모드를 배포할수 있어서 동작 및 디자인 검증을 할수 있는 환경이 자연스럽게 만들어지게 된다.

게다가 이걸 각 브랜치별로 서브도메인을 자동으로 만들어주고 임포트맵도 그 브랜치에 맞게 수정되기에 이 주소만 알면 누구나 사내에서 접근해 테스트가 가능하다.

그리고 임포트맵의 경우 http 주소도 쓸수 있게에 특정 서비스 개발시 localhost 등으로 접근해 형상을 최대한 맞춰서 작업할수 있다.

추가로 각 서비스별로 공통 로직도 각 서비스별로 독립되어 있다보니 코드 중복이 발생할수 있음. 하지만 센트리와 같은 외부 라이브러리 툴의 경우 각각 사용하게 되면 번들 크기를 불필요하게 늘어나게 할수 있음.

그래서 여기에 shared 모듈 개념을 도입해 이것도 별도로 서비스로 만들어 각 서비스에서 참조할수 있도록 함. 즉 기존 구조를 바꾸지 않고 쉐어드라이는 서비스를 만들어 이 서비스에 모두 의존하도록 만들었다고 함.

하지만 역시.. 브라우저 호환성은 어쩔수 없다. 이 문제를 해결하기위해 systemjs 를 사용해 failback 로직을 적용하여 동적으로 그릴수 있도록 하였음.

세번째 세션 모두가 평등하게 빠른 서비스 만들기 - 토스뱅크

프론트는 각 디바이스/인터넷 환경마다 다른 성능을 경험하고 같은 유저라 하더라도 상황에 따라 다른 성능을 경험하게 된다. 그래서 어떤 유저는 아무리 개발팀에서 노력해도 상황에 따라 느린 서비스를 체험할수밖에 없다.

저사양 기기와 네트워크 변수를 최대한 제거할수 있도록 해야 함. 토스에서는 이 중 네트워크 변수를 최대한 제거해보는것으로 전략을 잡았음.

앱 -> 웹뷰 -> 서버에서 html 응답 -> 브라우저 렌더링 동작 -> api 통신 등 -> 완료

이 때 js 가 먼저 실행되어야 하지만 실제 화면이 렌더링 된 이후에 지연 처리해 체감되는 속도를 빠르게 하였음.

여기서 js,css 는 화면을 그릴때 속도에 영향을 미치기에 크리티컬 리소스라고 정의할수 있음. 게다가 네트워크를 통해 이 데이터를 가져오게 됨.

반면 네이티브에서는 이런 크리티컬 리소스를 대부분 로컬에 보유하고 있어서 여기까지는 네트워크와 상관없이 동일한 경험을 할수 있게 보장한다.

그럼 결국 크리티컬 리소스를 불러오는 시간을 0에 가깝게 가자!

이미 이런 크리티컬 리소스들은 CDN 을 사용하고 캐싱을 사용하지만 결국 리소스를 네트워크에서 가져오는것은 동일하다. 그리고 브라우저 캐싱의 경우 로컬에 보관되어 있고 리소스 변경이 없고 유효시간 이내라면 로컬 데이터를 바로 사용할수 있다.

하지만 의도적으로 캐싱을 사용하면 안되는 경우가 있다. 리소스들이 변경되거나 버전이 변경되었을 때이다. 그리고 html 은 캐싱하게 될 경우 변경사항이 적용되지 않기에 html 의 경우 캐싱하면 안된다.

하지만 결국 이후 클라이언트사이드에서 API 통신해서 데이터를 보여줘야 하고. 크리티컬 리소스가 변경된다면 캐싱 데이터가 무효화되고 토스는 배포가 빈번하게 일어나므로 캐싱 유효 시간이 길게 갈수가 없다.

그래서 탠스택쿼리에서 쓰는 전략을 도입했다고 함. (SWR)

v1 의 렌더링을 거친후 v2 가 있다면 미리 캐싱해두고 쓸수 있지만 이 전략은 제어하기 어렵고 잘못된 화면을 보여줄 가능성이 있음.

그래서 이후 preload 방식을 도입해보기로 함. 서비스워커와 네이티브중 선택해야 했고 이 팀에서는 네이티브를 선택해 네이티브 자원을 통해 미리 리소스를 불러오고 이를 웹뷰에 적용하는 방식으로 고민함.

다만 이 전략을 모두 사용하진 않았고 유입이 많거나, 첫 진입 페이지 중 첫 페이지에서 프리로드할수 있게 정의하고 크리티컬 리소스를 파악하기 시작했음.

만약 이 구조를 가져간다고 할 경우 프리로드 동작을 할 때 CDN에 너무 지나치게 요청이 갈수 있기에 비용 최적화고 고민해야 함. 이를 위해 e-tag 를 사용하고 이 키값을 이용해 요청수를 제어할수 있었음.

이렇게 테스트했음에도 불구하고 이 작업을 폐기한 이유는 "너무나 잦은 배포" 이다. 아무리 e-tag 로 분류한다 해도 잦은 배포로 인해 cdn 요청이 너무 많게 되어서 비용대비 효용이 적었다고 함.

네번째 세션

로딩속도 개선 SSR 잘 사용하기.

통장 서비스 페이지

html 을 서버에서 미리 그릴수 있음. 만약 잘 사용하지 못하면 JS 를 클라이언트에서 처리하느라 그만큼 지연을 볼수 있음. 하지만 서버사이드에서 이 로직을 미리 처리해 그려서 내려주면 훨씬 빠르게 쓸수 있음.

개발 초기나 성장하는 시기에는 특별히 분류하지 않지만 이제 각 블록 단위로 SSR 을 쓸지 CSR 을 쓸지 고민하고 결정하는 시기를 거쳤음.

이를 결정하기에 비즈니스적/기술적 측면에 대해 같이 고민했음 이중 거래내역에 집중했는데 서버에서 받은 데이터를 로컬스토리지에 캐싱했고 캐싱이 있을 경우 바로 조회하고 이후 API 를 호출해 업데이트 하는 방식으로 했음. 그래서 토스 앱 쓰다보면 화면 보이고 나서 업데이트되는걸 볼수 있음. (상대적으로 유저가 더 빠르게 느낌)

이후 번들 사이즈를 줄이는 작업을 진행함. 웹팩 번들 애널라이저를 사용해 분석하고 사용/미사용/구버전 등을 정리했음.

API 워터폴 개선

택스텍쿼리 쓰고 있지만 유저 -> 아이템 이렇게 api 요청하는 경우 유저가 끝나야 아이템을 가져올수 있음. 이를 병렬로 바꿨다고 하는데 구체적인 설명은 안해줌. suspense 이용했다고 하는데 관련 사례는 좀 찾아봐야 할듯.

화면 안정성 개선

SSR -> CSR 로 전환되면서 애니메이션이 재생될때 싱크가 맞지 않아 이를 개선하고 이미지의 경우에도 이미지와 텍스트가 다른 타이밍으로 표시되는 문제가 있었음. 그리고 버튼 상태가 api 로드 후 상태가 바뀌면 유저가 아무것도 하지 않아도 상태가 바뀌면서 컴포넌트가 깜빡이는 문제가 있음

또 광고배너가 추가되면서 레이아웃이 시프트되는 문제가 있음.(클라이언트에서 불러오고 캐싱사용 불가)

홈서비스 개선(LCP)

API 를 여기서도 워터폴로 호출하고 있었고 이를 병렬로 수정했으나 http1 에서는 병렬 제한이 있음. 그리고 기존 텅 빈 html 을 서버에서 리턴받고 나머지를 모두 클라이언트에서 렌더링하고 있었음.

이러한 문제를 찾았고 하나씩 수정하기 시작했음.

각각 api 앤드포인트를 많이 요청하기보다 서버에서 어그리게이션으로 묶어서(매시업) 간단하게 요청하는 방식으로 풀었음. 이 때 클라이언트에서도 리패치하는것까지 고려해 함께 협업해 설계했음.

속도가 느린 api 의 경우 어그리게이션에 포함시키지 않고 별도로 개선했음.

그리고 LCP 를 매번 체크하고 슬랙으로 알람이 가도록 해두었음.


100년가는 자바스크립트 SDK (토스페이먼츠)

결제 SDK 를 만들고 공유한 경험에 대해 경험을 나누는 세션임. 다른것보다 특정 가맹점/런타임 환경에서 결제가 제대로 동작하지 않는 문제가 있었음.

토스에서는 global trace id 라는 개념이 있어서 be/fe 를 관통하는 관련 로그를 1개의 키로 볼수 있음. 이 키를 기준으로 시작부터 끝까지 볼수 있기에 시작은 되었지만 끝이 없는 경우 어떠한 장애를 겪었다는것을 알수 있음.

이를 감지하는 모니터링 도구를 직접 만들어서 모니터링에 활용했음. -> 안정성 향상

다양한 가맹점에서 정말 다양한 요구사항이 들어옴.

특정 가맹점에서 요구사항이 들어올때마다 가맹점의 요구사항 코드가 매번 섞일수밖에 없음. 이 요구사항을 코드로 풀어내기 위해 표준 SDK + 커스텀 SDK 를 활용해 해결하였음. 그리고 이걸 기술적으로 해결하기 위해 경계를 긋고 이 맞물리는 부분을 인터페이싱 하도록 했음. -> 확장성 보장

추가로 명확성을 향상하기 위해 인터페이스 자체를 타입으로 명확히 분리하고 jsdoc 을 통해 완벽하게 명시하고 별도 패키지로 분리해 이 인터페이스 코드 자체가 문서가 되도록 분리하고 관리했음.

jsdoc, 독사우르스 등을 통해 이런 함수/타입을 문서로 자동으로 생성되도록 만들어 one way 로 문서를 관리해 명확성을 보장함.

또 이를 zod 스킴으로 체크해 유효성을 아주 명확하게 체크하도록 하였음.