1년 묵힌 기술 부채 해결기 (feat. TanStack Query)
안녕하세요. 주짓수 앱을 만들고 있는 유서경입니다. 출시한 지 1년 2개월된 앱에 1년 된 기술 부채가 있다면 믿으실 건가요? 놀랍게도 제게 벌어진 일입니다. 드디어 지난주! 이 기술 부채를 해결한 따끈따끈한 이야기를 공유해보려 글을 작성합니다. 제가 이 앱을 운영하며 가장 속이 뻥 뚫리고 그래도 내가 개발자로서 성장했구나를 느낀 시점이었습니다. 이 글은 아래와 같은 분들이 읽기 좋습니다.
Expo managed workflow 환경에서 Firebase를 사용하는 분
TanStack Query 도입기가 궁금한 프론트엔드 개발자 분
기술 부채를 도대체 왜 1년이나 묵히고 있었는지 궁금한 분
1년 동안 서비스의 성장을 가로막았던 기술 부채가 궁금하신 분
1년 동안 기술 부채로 불안에 떨었던 인디 메이커의 머릿속이 궁금한 분
직접적인 코드보다는 문제를 해결한 과정을 소개할 예정이니 가벼운 마음으로 재미있게 읽어주세요.
기술 부채 소개
Post Black Belt는 제가 혼자 기획, 개발 그리고 운영까지 하고 있는 인디 앱입니다. 타 서비스도 마찬가지이겠지만 1인 개발에서 가장 부족한 리소스는 시간입니다. 저의 경우 회사 일과 병행하며 주말이나 여가 시간을 활용했기 때문에 특히 복잡한 이슈를 깊게 생각해볼 시간을 마련하기 힘들었습니다.
기술 부채가 왜 발생했나요?
이 앱은 제가 프론트엔드와 백엔드를 구분하지도 못했던 시절 만든 저의 첫 서비스입니다. 따라서 대부분의 개발 환경, 구조 세팅이 미숙합니다. 처음 이 프로젝트의 목표는 "취업 준비"였기 때문에 기능을 구현하는 것을 우선으로 하고 기술 스택 선정이나 운영은 고려하지 않았습니다. (저 혼자 사용하려 했었습니다 😶)
게다가 제가 프론트엔드 개발자이다보니 (BaaS 임에도 불구하고) 데이터베이스 사용과 관리에 매우 취약했습니다. Post Black Belt는 상당히 독특하게 local DB와 Firestore DB를 모두 사용하고 있는데, 그 이유는 1년 전 땜빵식 해결 방법을 채택했기 때문입니다. 😱
어떤 기술 부채였나요?
처음에는 사용자의 일기를 local SQLite DB에 저장하고 있었고, 앱을 삭제하거나 캐시를 지우게 되면 데이터가 유실되는 이슈가 있었습니다. 사용자가 점점 많아짐에 따라 해당 문제가 크리티컬하게 작용할 것이라 생각하였고 local DB에 일기를 저장하는 동시에 Firestore에도 함께 저장하여 일기를 백업해두도록 하였습니다. 아래 그림을 보면 일기를 생성하고, 수정하고 지우는 로직을 local DB와 Firestore에 함께 수행하고 일기를 읽는 로직은 local DB에서 가져오는 것을 확인할 수 있습니다. local DB를 Firestore로 완전히 대체하지 않은 이유는 SQL DB를 NoSQL DB로 변환하는 데에 꽤 많은 리소스가 필요하고, Firestore의 Read 쿼리가 많이 나와 비용을 지불하고 싶지 않았기 때문입니다.
그러나 여전히 3가지 큰 문제점이 남아있었습니다.
local DB가 여전히 Source of Truth로 작용한다.
이제 사용자가 앱을 삭제해도 데이터가 사라지지는 않습니다. 그러나 여전히 일기의 원본은 사용자 기기 내에 있고, 실제로 Firebase 로그인 핸들링이 제대로 되지 않아 만료되는 이슈가 있었을 때 사용자 기기 내에서만 일기가 저장되는 부차적 이슈가 있었습니다.
Firestore에서 제공하는 자체 캐싱을 사용하지 못하여 Read 쿼리가 많이 발생한다.
Post Black Belt는 Expo 환경으로 개발되었습니다. 제가 처음 앱을 구축했을 때는 Firebase의 React Native용 패키지를 활용하기에 Expo managed 환경의 제약과 버그가 있어 Firebase JS SDK를 선택하였습니다. 그리고 이 경우 지속성 캐시를 활용할 수 없다는 것을 나중에 확인하게 되었습니다....
SQL과 NoSQL이 혼용되어 개발 경험이 매우 좋지 않음
많이들 아시다시피 Firestore는 NoSQL DB입니다. 따라서 local에서는 SQL로 저장해서 쿼리하고 백업은 NoSQL로 구성한다는 것이 개발자로서 꽤나 불편한 경험이었습니다.
왜 기술 부채를 해결하지 않았나요?
위의 문제가 있음에도 제가 이 기술 부채를 해결하지 않았던 이유가 있습니다. (이 글의 제목이 "묵은"이 아니라 "묵힌"인 이유는 의도적으로 해결하지 않았기 때문입니다.)
제가 갖고 있던 가장 합리적인 해결 방법은 local DB를 없애고 Firestore를 Source of Truth로 사용하는 것입니다. 그러나 이 경우 사용자가 많아질수록 Read 쿼리 비용 함께 증가하기 때문에 고려하고 싶지 않았습니다. 사이드 프로젝트는 이미 제 시간이라는 거대한 리소스를 투자하고 있는데 그 이상을 사용하면서 스트레스를 받고 싶지 않았기 때문입니다. 당시에는 사용자보다는 저의 개발 프로젝트라는 인식이 강했기 때문에 정기적인 비용이 지불되면 지속하기 어렵다고 판단하였습니다. (지금은 사용자를 위해 만들고 있습니다. 😇)
왜 1년 만에 기술 부채 해결을 결정했나요?
그럼에도 불구하고 이 기술 부채를 해결하기로 결정한 이유는 두 가지입니다. 첫 번째는 지난 1년 동안 불안함과 찝찝함으로 저를 너무나 괴롭혔기 때문입니다. 더이상 관련 CS가 들어올까 걱정에 떨며 살고 싶지 않았습니다🫠. 두 번째는 이 문제가 서비스의 성장을 막고 있다면 우선순위가 높은 기술 부채이다라는 동료 개발자의 조언 때문이었습니다. 실제로 신규 기능을 기획할 때 이 기술 부채가 꽤 큰 걸림돌이 되고 있었습니다. 아마 제가 느끼기에 너무 거대한 문제여서 부채를 못본 척 하며 미뤘을 수도 있습니다.
여러분은 이런 상황에서 어떤 결정을 하시나요? 정말 하기 싫지만 문제를 해결해야 할 때, 정말 해결할 사람이 나 하나일 때, 이 경우에는 내가 이 프로덕트의 CTO라고 생각하고 막중한 책임감을 부여하면 됩니다. 이게 인디 메이커의 장점이자 단점이랄까요.. ㅎㅎ 믿고 의지할 곳은 나뿐입니다...
기술 부채 해결을 위한 옵션들
처음 기술 부채를 발견했을 때보다 1년이라는 시간이 지났으니, 이제 더 많은 옵션을 고려할 수 있습니다. 다시 정리하자면 아래 문제들을 해결해야 합니다.
(1) local DB를 삭제하고 서버의 DB를 Source of Truth로 사용
(2) Read Query 비용 문제 해결
(3) SQL, NoSQL 중 한 가지만 사용
Supabase로 마이그레이션 해보자
최근 BaaS에서 Firebase의 대안으로 RDB인 Supabase의 인기가 급부상하고 있습니다. 실제로 Supabase에서도 Firebase로부터의 마이그레이션을 잘 지원하고 있으며, 비용도 쿼리 당이 아닌 용량으로 계산되어 (1)부터 (3)의 문제를 모두 해결할 수 있습니다. 이왕 부채를 해결하는 김에 개발 경험이 좋았던 Supabase로 아예 넘어가버리자!라는 원대한 계획을 만들었지만 두 가지 이슈가 있었습니다.
Firebase Auth를 함께 이전하려면 로그인 상태 유지를 위해 서버를 띄워야 하고, 월간 5달러의 비용이 발생함: (2)보다 많은 비용 발생
Firebase를 Supabase로 먼저 대체한 후 (1)을 해결해야 하여 한 번에 너무 많은 문제들을 다뤄야 함: 문제 해결 범위가 너무 방대해짐
Firebase를 JS SDK에서 react-native-firebase로 마이그레이션 해보자
그러면 (2)의 문제를 해결하기 위해 패키지를 변경하는 것은 어떨까요? 이미 서비스 내에서 Firebase를 사용하고 있으므로 상당히 간단해보이며, 추가로 JS SDK를 React Native에 사용하여 제한되었던 Firebase의 타 서비스(GA4, Remote Config 등)을 활용할 수 있게 됩니다. 그러나...
여기도 마찬가지로 Auth 변경으로 로그인이 풀리는 문제가 있습니다. 이 경우 서버를 새로 구성하거나 최악의 경우 모든 사용자에게 재로그인을 받아야 합니다.
큰 문제는 아니지만 JS SDK 코드를 react-native-firebase 인터페이스에 맞게 모두 수정해야 합니다.
서버 상태 관리 툴인 TanStack Query를 도입하자
여기까지 오는데 제 설 연휴와 주말을 모두 써버렸습니다.. 하하.. 하지만 CTO가 여기에서 무너지면 안됩니다.. 이제는 프론트엔드 개발자 베이스다운 해결 방법을 사용해보려 합니다. 서버 상태 관리 툴을 도입하여 직접 캐시를 관리하는 것입니다.
이 방법의 장점은
내가 직접 캐시를 관리하여 비용을 조절할 수 있다.
현재 환경에 TanStack Query Layer만 추가하면 된다.
단점은
처음 사용해보는 라이브러리로 적응 기간이 필요하다.
이미 회사에서 Apollo Client를 사용해보았기 때문에 완전히 어색하지는 않을 것이다.
사용자가 많아질 경우 (2)의 문제가 다시 발생할 수 있다.
아직 발생하지 않은 문제를 먼저 고민할 필요없다. 비용이 많이 발생하면 사용자들이 많아지는 서비스에 기뻐하며 그때의 문제를 또 다시 해결하면 된다.
TanStack Query 간단 사용기
TanStack Query는 FE에서는 상당히 인기가 많은 도구이고, 2023년 React Native 설문조사에서도 Data Fetching 도구로 1위를 수성할만큼 커뮤니티도 개발자 만족도도 좋은 편입니다.
인디 메이커는 대부분의 문제를 혼자(요즘은 GPT 선생님과 함께지만) 해결해야하기 때문에 좋은 커뮤니티를 갖춘 라이브러리는 중요합니다. 저는 공식 문서도 읽기는 했지만 maintainer의 best practice와 실용적인 팁이 담겨있는 TkDodo's Blog에서 많은 도움을 받았습니다. 해당 블로그 내용은 v5 이전의 내용이어서 deprecated된 부분도 있지만 활용 방법만 잘 숙지하면 v5에도 충분히 적용 가능합니다.
또한 React Native 앱을 사용하는 경우 캐시를 AsyncStorage에 저장하여 앱이 종료되었다 다시 켜져도 캐싱을 유지하게 할 수 있습니다. 제가 Firebase JS SDK의 사용하지 못한 이유는 AsyncStorage가 아니라 indexedDB를 활용하기 때문입니다.
간단한 캐싱 예시를 보여드리자면,
위에서는 처음 12월의 일기를 불러올 때 서버에 요청하여(로딩 인디케이터) 데이터를 가져오지만,
12월의 데이터를 재요청할 때는 서버 요청없이(로딩 없음) 캐시에서 바로 데이터를 가져오는 모습을 확인할 수 있습니다.
이 캐시는 사용자가 앱을 종료하였다가 다시 켜도 유지됩니다. 캐시의 상태는 사용자가 CUD를 발생시키면 fresh > stale로 변경되어 서버에 재요청을 발생시키도록 하였습니다. 캐시 상태 관리도 상당히 중요한데요, Post Black Belt는 아직 사용자 개인 일기장으로만 활용되고 있으므로 비용 최소화를 우선적으로 고려하였습니다. 서비스가 성장하여 네트워킹 기능이 생기면 캐싱 정책도 유연하게 변경될 예정입니다.
개인적으로 마음의 짐으로 생각하던 부채를 해결하여 기분이 좋은데요, 치앙마이로 디지털 노마드를 오기 전에 해결해두어 조금 더 가벼운 마음으로 일할 수 있게 되었습니다. 긴 글 읽어주셔서 감사합니다!
주짓수 수련자들을 위한 기술 기록 다이어리
댓글
로그인 후 댓글을 남길 수 있습니다.
와 ! 멋진 경험 공유 감사합니다 :)
"두 번째는 이 문제가 서비스의 성장을 막고 있다면 우선순위가 높은 기술 부채이다라는 동료 개발자의 조언 때문이었습니다." > 너무 좋은 조언이네요 ㅎㅎ
뼈를 때리는 조언이었습니다 ㅎㅎ
와 멋지세요..!!! 진짜 고생 많으셨을 것 같네요아하 tanstack 이 리액트쿼리였군여 저도 비슷한 문제가 생겼을 때 서경님 메이커로그 다시 참고해야겠습니다!! 너무 깔끔하고 좋은 글 써주셔서 많이 배우고 갑니당 감사해요☺️ 치앙마이도 재밌게 조심해서 다녀오세요🙌
좋은 말씀 감사합니다 😊 리액트 쿼리의 이름이 바뀐걸 저도 이번에야 알았어요 ㅎㅎ 제제님도 행복한 3월 보내세요