FaaS와 비동기 처리로 브라우저의 부담 줄여주기
결론: 평균 30.0651초 -> 0.988초로 약 30배 속도 개선됨
1. 랭킹 페이지에서 순위권 유저 프로필 사진 보여주기
매니에서 프로모션을 위해 유저들간의 순위를 매기고 보여주는 랭킹 페이지를 구현하고 있었다.
예를 들어 친구 초대를 많이 한 사람, 이번 달 가장 많이 문제를 푼 사람 등 순위를 메겨 선물을 주는 프로모션을 진행할 수도 있기 때문이다.
한번에 1등 부터 100등까지 유저의 순위를 보여주도록 기획했는데, 로그인한 유저의 순위까지 보여주려면 100 ~ 101개의 유저 정보를 한번에 보여줄 수 있어야 했다.
문제는 순위별 유저들의 정보를 보여줄 때, 유저들의 프로필 사진을 전부 보여주어야 한다는 점이였다.
프로필 사진을 보여주는 것이 왜 문제가 되었었냐면, 사진과 같이 용량이 큰 데이터는 유저들의 정보를 저장해 두는 Document형 NoSQL인 Firebase RDB에 보관하지 않고, 따로 존재하는 저장소인 firestorage에 저장하기 때문이다.
따라서 DB에도 접근해 회원들의 정보를 가져온 다음, storage에 또 접근해서 데이터를 가져와야 했다.
내가 선택한 방식은 순위 한 줄 한 줄의 컴포넌트들이 알아서 각자 storage에서 비동기적으로 사진을 불러오는 방식을 선택했다. 현재 사용중인 firebase storage는 기존에 사용하던 다른 데이터들과 달리, 전체 데이터를 받아온 다음 js 객체처럼 다루는 것이 불가능했다. 단지 특정 파일이 호스팅된 URL을 받아올 수 있었다.
무슨 말이냐면, 유저들의 사진 데이터가 저장된 저장소 전체를 가져온 다음, 필요한 데이터만 가져오는 것이 불가능했다.
그리고 조금 뒤에 코드를 보며 설명하겠지만, 정확한 데이터의 path를 지정해 주고, 따로 요청을 보내야 이미지 URL을 받아올 수 있었다. 그래서 유저들 전체의 프로필 사진 URL을 전부 가져온 다음, 서버에서 각각 처리하는 것이 불가능했다.
결국 하나 하나 데이터의 경로를 짚어줘야 했다.
1. 유저의 순위 정보를 가져온다.
2. 100위권 유저들의 정보를 이용해 각자의 프로필 사진을 가져온다
이 순서로 로직이 진행되어야 했는데, 여러번의 외부 I/O 호출이 예상되었다.
2. 너무 느린 기존 Photo URL 로직
또 하나의 문제는 프로젝트에서 Photo URL을 가져오는 방식이였다.
사실 우리 프로젝트에선 프로필 사진을 가져올 일이 별로 없었다.
마이 페이지와 메인 페이지에서만 로그인한 유저의 프로필 사진을 볼 수 있었기 때문에,
간단한 로직으로 사진 정보를 가져오고 있었다.
뒤에서 자세히 말하겠지만, 이미 만들어져 있는 이 코드를 이용해 100명의 유저 데이터를 가져오는 실험을 해 보았다.
순수하게 프로필 사진만 가져오는데도, 프로필 사진 로딩에 평균 30초, 가장 늦게 로딩된 유저 프로필 사진은 43초가 걸렸다.
즉, 위 로직은 너무나도 느리다!
일단 storage에서 데이터들의 reference를 가져온다. 이후 가져온 래퍼런스에서 Download URL 데이터를 바로 얻을 수 있으면 좋으나, firebase에서 허용해주지 않는다. (도큐먼트에는 나와있지 않으나 stackoverflow에 따르면 각 데이터의 권한 인증 문제로 추측된다고 한다.)
대신 Download URL을 비동기적으로 API를 통해 가져온다!
사실 테스트 과정에서 너무나도 느려서 검색해보니, 스택 오버플로우 유저들의 말에 따르면, 매번 비동기적으로 이미지 URL 데이터를 요청해야 한다는 답변을 볼 수 있었다.
설마 매번 리퀘스트를 날리겠어.. 하고 코드를 열어보니 사실이였다.
getDownloadUrl의 구현이다. 진짜로 매번 리퀘스트를 작성하고 있다.
(정말 firebase를 빨리 벗어나고 싶다.)
위 코드는 유저 한명 한명 마다 storage 래퍼런스에 있는 모든 데이터에 대해 getDownloadUrl을 수행한 다음
첫 데이터 만을 가져오고 있다. (첫 데이터가 유저 프로필 사진이다.)
그럼 유저 마다 storage에 프로필 사진을 포함한 N개의 정보를 가지고 있다고 가정해 볼때
래퍼런스를 가져오는 리퀘스트 1회에 N회의 리퀘스트가 추가로 발생한다. 프로필 사진을 한번 가져올 때 마다 이런다.
문제는 100명의 데이터를 가져와야 하는 상황.
페이지를 열 때마다 최소 수백회의 리퀘스트가 날아간다.
이러니까 느릴 수 밖에 없다.
코드 자체가 잘못된건 아니다, 단지 이제까지 여러명의 storage 안의 데이터를 가져올 필요가 없었던 것이다. 우리 프로젝트는 굉장히 짧은 시간안에 빠르게 프로젝트를 진행해야 했기 떄문에, 초기에 작성된 로직은 대부분 React 프로젝트에서 수행되었다. 내가 합류하기 직전에 Google Cloud APIs라는 FaaS를 사용하게 되었으나, 기능 개발이 우선이기에 위에서 보았던 코드와 같이 여러 로직들이 브라우저 단에서 수행중이였다.
3. FaaS에게 연산 맡기기 + 각 컴포넌트가 비동기로 수행 테스트
상황이 상황인 만큼 일단 유저 전체의 데이터를 받아본 다음 전체 화면을 랜더링하겠다는 생각은 버렸다.
그리고, 해당 페이지를 사용하는 유저가 정말로 필요한 정보가 무엇인지 생각해 보았는데
"랭킹"페이지를 방문하는 유저들이 궁금한 것은 순위 정보이지, N위의 유저가 어떤 프로필 사진을 가졌는지가 아니다.
그래서 순위를 나타내는 순위 정보가 먼저 랜더링 되도록 했다.
그리고 순위 정보를 가진 각 컴포넌트, 각 Row들이 각자의 프로필 사진 정보를 가져오게 했다.
그리고 각 Row가 사진 데이터를 가져오는 동안엔 기본 프로필 사진이 보이게 해뒀다.
1. 백엔드에서 (FaaS) 순위 정보를 집계한다.
2. 데이터를 받아와 화면에 유저 순위와 데이터를 보여준다.
3. 각 컴포넌트가 직접 자신의 프로필 사진을 가져오게 한다.
이렇게 진행했는데, 프로필 사진의 로딩이 너무 느린 문제를 발견하게 되었다.
이에, 캐시를 전부 삭제한 다음 소요 시간을 테스트 해보았다.
100명의 가상 회원을 만들어 두고, 각 단계별로 소요 시간을 확인했다.
그랬더니, 3번 과정에서 시간이 오래 걸리는 것을 알 수 있었다.
100명의 유저 프로필을 모두 가져오는 데에 평균 30.0651초가 걸렸다. 가장 빠른 것 조차 1.509초만에 처리 되었고, 가장 느린 것은 43.657초가 소요되었다. 동영상이 없는데 마치 동기적으로 작동하듯 위에서 부터 아주 천천히 사진들이 하나 하나 로딩 되었다. 가장 먼저 보이는 1~20위권은 2 ~ 3초 안에 로딩 되었지만, 이정도도 느린 것은 사실이다.
이래서 기존의 Photo URL을 가져오는 코드를 의심하게 되었던 것이다.
이후 같은 일을 하는 함수를 백앤드에서 새로 만들어 주었다.
구글 클라우드에 직접 접근해서 해당 uid가 가진 프로필 사진의 URL을 가져온다.
클라이언트가 아닌 node js 백엔드에서 접근할 때는 리퀘스트 한번만에 URL을 가져올 수 있어서 시너지가 좋았다.
정말 별거 없는 짧은 코드지만, 입문자 입장에서 이 몇줄로 성능이 개선되었다는 점이 즐거웠다.
이제 각 컴포넌트가 새로운 API를 통해 프로필 사진을 가져오게 했다.
캐시를 지우고 테스트 해본 결과 평균적으로 0.988초가 소요되었다.
이는 약 30.430배의 차이다 (최소 0.572초, 최대 1.659초)
이번 경험을 통해 작은 변화로도 큰 개선을 할 수 있음을 배웠다.
조금 느린 코드를 조금 낫게 만든 정도였다. 하지만 그 조금이 100건이 쌓이니 큰 차이가 되었다.
조금만 공부를 해보신 분들껜 사실 이 과정 전부가 별거 아닌 일이겠지만, 입문자 입장에서 너무 즐거운 경험이였다.
그리고 사용자 입장에서 뭐가 중요한지를 고민하게 해주었다.
만약에 순위 정보와 프로필 사진을 꼭 동시에 봐야 하는 상황이였다면, 다른 방법을 고려해야 했을 것이다.
브라우저는 화면을 랜더링 하는 데에만 집중하게 하고, 나머지 부담스러운 로직은 백앤드가 수행할 수 있도록 해주는 것이 정말 중요하다는 것을 다시금 느껴볼 수 있었던 좋은 경험이였다.
이 이야기를 친구에게 하니, 당연히 로직을 백엔드에 두었어야지? 라고 했다.
그 말이 맞다. 하지만 매니에서 일하면서 프로젝트가 항상 최고의 선택만 하면서 진행될 수는 없다는 것을 배웠다.
좀 더 빨리 사이트를 구축하고, 테스트 해보기 위해선 어쩔 수 없는 일이였을 것이다.
재미있는 경험이였다.
프로필 사진 로딩은 평균 30.0651초에서 -> 0.988초로 개선되었다.
빨리 출시 되고, 프로모션도 진행 되어서 랭킹 페이지가 쓰일 일이 있었으면 좋겠다.
'🔥 Projects 🔥' 카테고리의 다른 글
[충격] S3 배포시 '이것' 설정 안 하자... 개인정보 '술술' (0) | 2023.06.02 |
---|---|
[Nginx] Don't try this at home - "IF" is Evil (0) | 2023.04.17 |
[Security] @WithSecurityContext를 이용해 커스텀 UserDetails SecurityContext Test 코드 작성하기 (0) | 2023.04.17 |
[AWS] 하나의 EC2 인스턴스에 client, server 프로젝트 전부 배포하기! - React + Spring boot (0) | 2022.08.31 |