안녕하세요. B2B 물류 스쿼드의 백엔드 개발자, 시나브로우입니다.
저희 B2B 물류 스쿼드에서는 올리브영의 오프라인 발주와 물류 시스템을 담당하고 있습니다.
오프라인 개발 환경에서의 주요 도전 과제는 모놀로식 기반의 레거시 시스템을 신규 플랫폼으로 전환하는 것입니다.
프론트, 백엔드 시스템을 분리하고 AWS 기반으로 백오피스 신규 플랫폼을 구축했습니다.
스트랭글러 패턴(Strangler Pattern)으로 2022년 12월부터 점진적으로 오픈하여 2023년 3월에 모든 발주 시스템을 신규 플랫폼으로 이관했습니다.
최적화 과정
플랫폼을 새로 오픈한 이후로, 개발 요청이 급증하게 되었습니다. 매주 새로운 화면을 추가하며 사용하는 프론트 라이브러리의 수도 계속해서 증가했습니다.
이로 인해 프론트 성능의 저하 문제에 직면하게 되었습니다.
브라우저에서 로드되는 용량의 증가로 인해 프론트의 SPA(Single Page Application) 특성상 단점들이 점점 두드러지기 시작했습니다.
SPA 의 특성상 모든 어플리케이션 파일을 번들링하여 한 번에 로드하기 때문에 초기 구동 속도가 느려지는 문제가 발생했습니다.
어플리케이션의 규모가 최적화가 필요한 수준까지 커졌다는 사실에 뿌듯함을 느끼며, 즉시 최적화 작업에 착수했습니다.
최적화를 위한 주요 단계는 다음과 같습니다.
- 큰 용량의 라이브러리를 대상으로 코드 스플리팅 및 Webpack을 사용한 번들링 분리 저장.
- CSS 파일의 최적화 번들링, Font 압축.
- Nginx를 통한 gzip 압축 적용.
- Nginx를 사용한 정적 파일 서빙 및 정적 파일 캐싱.
'Performance-Analyser' 로 측정한 최적화 결과 지표는 다음과 같습니다.
Performance-Analyser 로 분석한 Front Performance Indicators
-
DOM Content Loading: HTML이 완전히 로드되고 파싱되었을 때의 시점
-
Total(onload event): 모든 리소스(스타일시트, 이미지, 스크립트 등)가 로드 되었을 때의 시점
최적화를 진행하기 전, 웹 페이지의 로딩 시간은 약 1.6초에서 2.5초 사이였습니다.
하지만, 효과적인 최적화 작업을 거쳐 로딩 시간을 0.7초로 줄였고, 추가적인 캐싱 작업을 통해 더욱 빠른 0.2초의 로딩 시간을 달성할 수 있었습니다.
Font 압축
최적화 과정 중에, 의외로 폰트 압축 부분에서 가장 난관이었습니다.
보안 이슈로 인해 CDN 도입이 어려운 상황에서는 폰트를 어플리케이션 내부에 직접 가져와서 사용해야 합니다.
여기서 큰 문제점은 폰트의 용량이 크다는 것입니다.
예를 들면, NotoSans 폰트만 해도 3.8MB에 달하여 웹 페이지의 로딩 시간에 큰 영향을 미쳤습니다.
폰트 용량을 줄이기 위해 .otf, .ttf 확장자를 .woff, .woff2로 변환해봤지만, 폰트가 깨지거나 정상적으로 import 되지 않았습니다.
대안으로 'file-loader' 플로그인을 사용하여, 폰트가 배포되는 패키지를 수동으로 지정하며 분리하여 번들링했습니다.
참고로, 용량이 큰 다른 라이브러리들은 코드 스플리팅을 통해 간단히 분리 및 번들링이 가능합니다.
폰트를 분리하여 번들링을 적용하면 nginx 의 gzip 압축과 캐싱도 적용할 수 있습니다.
결과적으로 NotoSans 폰트가 3.8 MB에서 72 B로 엄청나게 압축되었습니다.
폰트 이외에 모든 리소스들도 압축 및 캐싱을 적용하여 제공하고 있으나, 폰트의 압축률이 가장 높았습니다.
브라우저 API 데이터 캐싱
최적화 일환으로 브라우저 캐시도 사용하고 있습니다.
브라우저 API 데이터 캐싱은 API 데이터를 클라이언트 브라우저 Local storage 에 저장하는 방식을 말합니다.
저희 스쿼드에서는 axios 와 손쉽게 사용할 수 있는 'axios-cache-interceptor' 라이브러리를 사용하고 있습니다.
'axios-cache-interceptor' 는 axios 인터셉터를 제공하여, 브라우저 캐시를 적용합니다.
axios 인터셉터는 전달된 요청 값이 브라우저의 Local storage에 이미 저장되어 있다면, 해당 Local storage에 저장된 응답 데이터를 반환합니다.
만약 Local storage에 요청 값이 저장되어 있지 않다면, axios 어댑터를 통해 API를 호출합니다.
API 호출하고 클라이언트에 응답 데이터를 반환한 후, 그 응답 데이터를 Local storage에 저장합니다.
조금 복잡해 보일 수 있지만, Local storage 를 Redis 처럼 이용한다고 생각하면 이해하기 쉽습니다.
브라우저 캐싱을 활용함으로써 다음과 같은 장점을 얻을 수 있었습니다.
- SPA에서 반복된 API 호출의 빈도를 줄여 초기 로딩 시간을 단축
- 캐싱된 API 호출 시간 단축 및 DOM 컨텐츠 로딩 속도 개선
- Local storage 사용으로 브라우저 간 데이터 공유 가능
브라우저 API 캐싱 이후 최초 로딩 시 호출되는 API 수가 왼쪽에서 오른쪽으로 감소했습니다.
저희 스쿼드는 브라우저의 API 데이터 캐싱과 함께 Vuex 를 사용하여, 공통 코드와 같은 정적 데이터를 더욱 효율적으로 저장 및 관리하고 있습니다.
Vuex 로 데이터를 관리하여 SPA의 각 컴포넌트에서 데이터를 효율적으로 재사용하고 있습니다.
또한, 새 창을 열 때마다 API를 새로 호출하는 대신 Local storage에 저장된 데이터를 불러와 Vuex Store 에 저장합니다.
그리고 각 컴포넌트는 Vuex Store 의 데이터를 이용하여 필요한 정보를 표시합니다.
이 처럼 올리브영 B2B 물류 스쿼드에서는 SPA의 단점을 줄이면서 동시에 공통 API와 같은 중복된 API 호출에 따른 네트워크 통신을 최소화하여 DB Connection 횟수도 감소시키고 있습니다.
끝으로
지금까지 올리브영 B2B 물류 스쿼드의 백오피스 프론트엔드 성능 개선 작업을 소개하였습니다.
저희 스쿼드가 개발하는 어플리케이션은 고객 대상의 온라인 서비스는 아니지만,
항상 내부 사용자의 편의와 만족도를 위해 최적의 서비스를 제공하기 위해 노력하고 있습니다.
성능 개선 작업은 계속 이어질 예정입니다. 오늘은 여기까지 소개하겠습니다!
신기술에 관심 많으시고 저희 스쿼드와 함께 오프라인 서비스를 개선하고 싶은 분이라면, 채용공고를 확인해주세요!
더 발전된 모습으로 다시 만나 뵙겠습니다. 감사합니다.