실제 앱 적용 사례를 통한 Firebase SDK와 RESTful API 성능비교
Grain 앱은 필름 카메라를 위한 SNS 앱 이기 때문에 실시간으로 많은 양의 데이터를 송수신 해야하고 심지어 이미지 데이터도 사용해야하기 때문에 이 부분에 대한 성능 최적화가 가장큰 핵심 과제였다. 그래서 우리가 고민했던 여러가지중 데이터 통신 수단으로 Firebase SDK와 RESTful API 중 어떤 방법을 써야 더 좋을지에 대한 비교와 고민의 과정을 기록하고자 한다. 우선 절대적 기준에서 우리의 측정치와 방법이 좋다고는 할 수 없지만 우리의 상황에서 문제를 해결하기 위해 어떤식의 합리적 선택을 했는지에 대한 기록이다.
우선 우리의 프로젝트 시작 멤버는 6명의 iOS 개발자로 이루어져 있었기 때문에 서버구축을 할 여력이 되지 않았다. 그래서 자연스럽게 선택지는 AWS Amplify와 Google Firebase 두 개로 좁혀졌다. 그리고 무료 플랜의 여유가 조금 더 있어서 개발과정에서 테스트가 용이할 것같은 Google Firebase를 선택했다. 그 과정에서 Firebase와 통신을 위한 도구로 RESTful API를 쓰느냐 Firebase SDK 를 쓰느냐의 선택의 기로에 서게 되었고 사실 별 생각 없이 어느 하나를 골랐어도 큰 문제가 되지 않을 선택의 기로이긴 했지만 우리는 돈이 없는 학생들이었고 최소한의 리소스로 최대한의 결과를 이끌어 내기 위해 사소한 부분도 놓치지 않고 분석하고 고민하여 결정했다.
데이터 통신 관련 해결해야 했던 이슈
우리는 데이터 양이 많고, 적은 비용으로 많은 데이터 송수신이 필요하며, 이미지 데이터가 많고 이미지 캐싱이 필요했기 때문에 다음과 같은 이슈를 해결해야 했다.
- 데이터 최적화:
- 데이터 압축: 가능한 경우 데이터를 압축하여 전송한다. 특히 이미지 데이터의 경우, 압축된 형식(예: JPEG, WebP)을 사용하여 저장하고 전송한다.
- 데이터 페이징: 한 번에 모든 데이터를 가져오는 대신, 필요한 만큼의 데이터만 가져오는 페이징 기법을 사용한다.
- 이미지 최적화:
- 적절한 해상도: 사용자의 디바이스 및 화면 크기에 맞는 해상도의 이미지를 제공한다.(사진이 중요한 앱이기 때문에 손실이 크지 않은 선에서 적절한 2배수 이미지를 제공해야 했다.)
- Lazy Loading: 이미지가 화면에 표시될 때만 이미지를 로드하는 기법을 사용하여 초기 로딩 시간을 줄인다.
- 캐싱 전략:
- 로컬 캐싱: 이미지나 자주 사용되는 데이터는 로컬에 캐시하여 재요청을 줄인다. 이를 위해
NSCache
나 이미지 캐싱 라이브러리(예: Kingfisher, SDWebImage)를 사용할 수 있다. - 서버 캐싱: Firebase Storage나 Firestore의 내부 캐싱 기능을 활용하여 데이터의 재요청을 최소화한다.
- 로컬 캐싱: 이미지나 자주 사용되는 데이터는 로컬에 캐시하여 재요청을 줄인다. 이를 위해
- 비용 최적화:
- Firebase 비용 최적화: Firestore의 경우, 데이터 읽기/쓰기/삭제에 따라 비용이 발생하므로, 요청을 최적화하여 비용을 줄인다. 또한, Firebase Storage의 경우 데이터 저장 및 네트워크 대역폭에 따라 비용이 발생하므로, 이미지 최적화 및 캐싱 전략을 통해 비용을 줄인다.
- CDN 사용: 이미지나 정적 콘텐츠를 전송할 때 CDN(Content Delivery Network)을 사용하여 전송 속도를 높이고 서버의 부하를 줄인다.
- 백엔드 최적화
- 데이터베이스 인덱싱: Firestore에서 데이터 검색 속도를 높이기 위해 적절한 인덱싱 전략을 사용한다.
- 백엔드 로직 최적화: 서버 측 로직을 최적화하여 불필요한 연산을 줄이고 응답 시간을 빠르게 한다.(우리의 경우는 서버 자체의 로직이라기 보단 CRUD관련한 로직에 해당했음)
우리는 데이터 통신 관련해서 위와 같은 과제들을 해결해야 했다. 그래서 우선적으로 우리의 두 가지 선택지를 비교 분석했다.
RESTful API VS Firebase SDK
RESTful API:
장점:
- 유연성: RESTful API는 일반적으로 어떤 플랫폼이나 언어에서도 사용할 수 있습니다. 따라서 다양한 클라이언트에서 동일한 API를 사용할 수 있다.
- 표준화: RESTful API는 HTTP 프로토콜을 기반으로 하므로, 표준화된 방식으로 데이터를 요청하고 응답받을 수 있다.
단점:
- 오버헤드: 각 요청에는 HTTP 헤더와 메타데이터가 포함되므로, 데이터 전송에 약간의 오버헤드가 발생할 수 있다.
- 폴링: 실시간 업데이트를 위해서는 주기적으로 데이터를 폴링해야 할 수 있다.
Firebase SDK:
장점:
- 실시간 업데이트: Firebase SDK는 데이터의 변화를 실시간으로 감지하고 업데이트를 자동으로 받아올 수 있다. (리스너 기능)
- 최적화된 프로토콜: Firebase SDK는 효율적인 데이터 전송을 위해 최적화된 프로토콜을 사용합니다. 따라서 데이터 전송에 필요한 오버헤드가 줄어든다.
- 내장 캐싱: Firebase SDK는 내부적으로 데이터 캐싱을 지원합니다. 이로 인해 네트워크 연결이 끊어진 경우에도 앱이 일부 데이터에 액세스할 수 있다.
단점:
- 플랫폼 제한: Firebase SDK는 지원되는 플랫폼에서만 사용할 수 있다.
- 비용: 많은 양의 데이터를 실시간으로 동기화하면 비용이 증가할 수 있다.
사실 단순한 비교로 생각하면 Firebase SDK의 압승이라고 할 수 있다. 하지만 우리의 개발 환경과 리소스를 고민하지 않을 수 없었다. 결론부터 말하자면 우리는 RESTful API를 선택했는데 그 이유를 설명하고자 한다.
왜 RESTful API?
우리가 측정할 수 있는 유의미한 비교 방법은 메모리 사용 효율을 비교해보는 것이었고 간단하게 정리하면 아래와 같다.
-
SDK의 추가 부하: Firebase와 같은 서비스를 사용할 때 해당 서비스의 SDK를 추가해야 합니다. 이 SDK는 다양한 기능과 서비스를 제공하기 위해 추가적인 메모리를 사용할 수 있습니다. 반면 RESTfulAPI의 구동 기반인 URLSession은 iOS의 기본 라이브러리에 포함되어 있으므로 추가적인 메모리 부하가 적다.
-
캐싱 메커니즘: Firebase SDK는 내부적으로 데이터를 캐시하기 위한 자체 메커니즘을 가질 수 있습니다. 이로 인해 메모리 사용량이 증가할 수 있고 RESTfulAPI를 사용할 때는 개발자가 직접 캐싱 전략을 선택하고 구현할 수 있으므로, 필요에 따라 메모리 사용량을 최적화할 수 있다.
-
데이터 동기화: Firebase는 실시간 데이터베이스와 같은 기능을 제공하며, 이를 위해 백그라운드에서 데이터 동기화 작업을 수행할 수 있는데 이런 작업은 물론 데이터 실시간 데이터 통신에 유리한 점도 있지만 데이터 송수신량을 늘리고 메모리 사용량도 증대하는 역할을 할 수 있다.
-
커스텀 구현: RESTfulAPI를 사용하면 네트워킹 작업을 더 세밀하게 제어할 수 있습니다. 따라서 특정 작업에 대한 메모리 최적화를 수행하기가 더 쉽다.
-
이미지 처리: 이미지 처리에 관한 로직이나 라이브러리의 선택에 따라 메모리 사용량이 크게 달라질 수 있다. RESTfulAPI만을 사용하여 이미지를 처리하는 경우와 Firebase SDK와 함께 이미지를 처리하는 경우의 로직이나 라이브러리 선택에 따라 결과가 달라질 수 있는데 이 부분은 실제로 성능 개선에 큰 영향을 미치는 부분이었다.
그 외에도 Firebase SDK는 플랫폼이 제한적이다. 물론 한동안 firebase 를 사용할 것이고 구글이 망할 일은 아마 없겠지만 NoSQL구조를 가졌기 때문에 이점도 있지만 그 한계점도 명확했기 때문에 다른 DB 혹은 플랫폼의 확장성을 고려하지 않을 수 없었다.
다음은 조금 창피하지만 우리 개발 인원들의 맥북 성능이 조금 부족했던 탓에 Firebase SDK 패키지를 앱에 추가하는 것보다 iOS 기본 제공하는 기술인 URLSession 기반의 RESTfulAPI 의 사용이 압도적으로 쾌적한 개발환경을 선사했다. 하지만 단순히 빌드 속도 차이만 났을 뿐 당초 예상했던 SDK 추가 부하 측면에서는 미세하게 차이는 났지만 그렇다고 의미 있는 수준의 차이는 아니었다.
Firebase SDK
**RESTful API
위에 보는 사진은 두가지 방법을 사용 했을때 빌드 속도차이인데 무려 열 배나 차이나는 볼 수 있다. 맥북의 성능에 따라 더 큰 차이가 났기 때문에 개발 편의성 측면에서 무시할 수 없는 부분이었다.
Firebase SDK + AsyncImage
RESTful + AsyncImage
그냥 두 가지 방법을 단독으로 사용했을때는 큰 차이가 없었지만 이미지 캐싱을 추가 하니까 약 30%의 차이를 보이는 것을 볼 수 있다. 물론 여러가지 요소들을 고려해야하고 이 값이 절대적인 앱의 성능의 수치를 나타내는 것은 아니지만 같은 코드 같은 환경에서 단순히 네트워킹 방식의 변화로 이같은 차이가 나타난다는 것은 흥미로운 지점이다. 정확한 원인분석은 더 해봐야 알 수 있겠지만 예상에는 Firebase SDK는 내부적으록 자동 캐싱하는 메커니즘을 포함하고 있기 때문에 이중으로 캐싱하는 구조로 작동하여 메모리를 조금더 사용하는 것 같다. 그렇다면 왜 굳이 추가적인 캐싱 라이브러리를 사용했냐고 묻는다면 우리는 이미지를 많이 사용하고 이미지 캐싱과 그 활용이 중요한데 Firebase SDK의 경우는 임시적 캐싱을 하고 Cloud FireStore 에 대한 캐싱과 활용은 제공하지만 FireStorage에 관한 캐싱은 특별히 제공하지 않는지 찾을 수 없었다.
그리고 메모리 측면에서 뿐만 아니라 RESTful API 를 사용하여 CRUD를 우리의 의도에 맞게 커스텀 할 수 있었기 때문에 실제 동일 작업에 관해 데이터 요청에 대한 트래픽이 50% 정도 감소한 것 또한 확인할 수 있었다.
결론
실제로 위에서 제시된 성능 차이는 모든 상황에서 일관적으로 나타나는 것은 아닐 수 있다. 그러나 이러한 비교와 분석 과정 자체가 중요하다고 생각한다. 개발 과정에서 “그냥 썼던 것”이나 “편리한 라이브러리를 선택하자”는 단순한 접근 방식보다는, 선택한 기술이나 도구에 대한 깊은 이해와 그에 따른 합리적인 결정이 필요하다는 것을 깨달았다.
이러한 과정을 통해 우리 팀은 단순히 기술을 사용하는 것이 아니라, 그 기술의 장단점을 파악하고, 주어진 상황과 요구사항에 가장 적합한 선택을 하는 능력을 키웠다. 이는 단순한 개발 능력을 넘어서, 문제 해결 능력과 기술적 판단력을 향상시키는 중요한 경험이었다.
결론적으로, 개발은 단순한 코드 작성이 아닌, 주어진 문제와 상황에 대한 깊은 이해와 그에 따른 최적의 해결 방안을 찾아내는 과정이다. 이번 프로젝트를 통해 우리는 그러한 과정의 중요성을 다시 한번 깨닫게 되었으며, 이를 바탕으로 더 나은 개발자로 성장할 수 있을 것이라 확신한다.
댓글남기기