Firebase Storage의 편리함 속에 숨겨진 Latency
개발 중인 프로덕트에서 이미지 로딩 속도가 눈에 띄게 느리다는 것을 발견했다. 사실 처음 원격 파일 저장소를 활용할 때도 "왜 이렇게 로딩 속도가 느릴까" 생각은 하고 있었지만 지금껏 미뤄오다 드디어 해결하고자 한다. (연구에 따르면 지연시간이 1초 증가할 때마다 이탈률이 무려 10% 증가한다고...!)
네트워크 탭을 열어 프로파일링을 해보니, 이미지 한 장을 불러오는 데 2초가 넘는 시간이 걸리고 있었다. 사용자 경험(UX) 측면에서 2초의 대기 시간은 이탈을 유발하기에 충분한 수치다.
우리는 백엔드 인프라로 Firebase Storage를 사용하고 있었다. 초기 개발 속도를 끌어올리기에는 훌륭한 선택지였지만, 서비스가 고도화됨에 따라 예상치 못한 병목이 발생한 것이다. 문제를 해결하기 위해 Firebase가 이미지를 서빙하는 근본적인 방식을 들여다보기로 했다.
원인 분석과 기술적 해결 과정
1. Problem: Firebase Security Rules와 인증 오버헤드
Firebase Storage는 사실 Google Cloud Platform(GCP)의 Cloud Storage(GCS) 위에 얹혀진 추상화(Abstraction) 계층이다. 우리가 흔히 사용하는 Firebase Storage의 URL(firebasestorage.googleapis.com/...)을 통해 이미지를 요청하면, 이 요청은 곧바로 GCS로 가지 않는다.
그 사이에는 'Firebase 애플리케이션 서버'라는 중간자가 존재한다. 이 서버는 요청이 들어올 때마다 Firebase Security Rules(보안 규칙)를 검사하고, 인증 토큰(Token)의 유효성을 검증한다. 즉, 단순한 정적 리소스를 하나 가져오는 데에도 무거운 로직이 개입하고 있었던 것이다.
(아래 테스트를 통해 이 인증 과정에서만 약 700ms ~ 800ms에 달하는 오버헤드가 발생하고 있다는 것을 확인했다.)
2. Solution: 추상화 계층 벗겨내기 (GCP Bucket 직접 접근)
서비스 특성상 콘텐츠 썸네일과 같은 리소스는 굳이 엄격한 인증 과정을 거칠 필요가 없는 공개(Public) 데이터다. 어차피 서비스에 접속하면 누구나 볼 수 있으니까. 그렇다면 굳이 무거운 Firebase의 인증 레이어를 거칠 이유가 없다.
해결책은 간단했다. Firebase URL 대신, 내부적으로 연결되어 있는 GCP Cloud Storage의 Bucket URL을 직접 호출하여 정적 파일을 서빙하도록 아키텍처를 변경하는 것이다.
변경 전 (Firebase URL):
https://firebasestorage.googleapis.com/v0/b/[프로젝트_ID].appspot.com/o/[파일_경로]?alt=media&token=[토큰]변경 후 (GCP Bucket URL):
https://storage.googleapis.com/[프로젝트_ID].appspot.com/[파일_경로]
이렇게 URL 구조를 변경함으로써, Firebase의 인증 미들웨어를 완전히 우회하고 GCS로부터 이미지를 직접(Direct) 다운로드할 수 있게 만들었다.
아래와 같이 GCP에 접속하여 Firebase Storage에 해당하는 Bucket을 Public로 바꿔주기만 하면 GCP Bucket URL로 쉽게 접근이 가능하다.
3. 성능 개선 결과
URL 교체 후 네트워크 성능을 다시 측정해 본 결과, 극적인 성능 향상을 확인할 수 있었다.
초기 로딩 (HTTP 200 OK): 2.03s에서 1.39s로 단축 (약 30% 성능 향상)
Firebase URL (HTTP 200)
캐싱된 이미지 로딩 (HTTP 304 Not Modified): 895ms에서 16ms로 단축 (약 98% 성능 향상)
Firebase URL (HTTP 304)
특히 주목할 만한 부분은 캐싱된 이미지를 불러올 때의 속도다. 기존 Firebase URL을 사용할 때는 브라우저 캐시가 존재하더라도(304 상태 코드), 리소스의 변경 여부와 권한을 확인하기 위해 매번 Firebase 인증 서버를 다녀와야 했기 때문에 895ms나 소요되었다. 하지만 GCP Bucket으로 직접 접근하자 브라우저와 저장소 간의 직접적인 캐시 통신이 가능해져 단 16ms 만에 로딩이 완료되었다.
BaaS의 이면, 그리고 엔지니어의 시각
이번 최적화 과정을 통해 BaaS(Backend as a Service)가 제공하는 편리함의 이면을 깨달을 수 있었다. Firebase는 개발자에게 놀라운 생산성을 제공하지만, 그 추상화된 계층 아래에서 어떤 일이 일어나고 있는지(low-level) 이해하지 못하면 병목 현상을 마주했을 때 속수무책일 수밖에 없다.
700ms의 네트워크 낭비를 줄이는 것은 단순히 숫자 상의 개선이 아니다. 리소스의 성격(Public vs Private)을 파악하고, 불필요한 인증 과정을 과감히 생략하는 아키텍처의 재설계였다.
추천글