최근까지는 성능 측정 결과를 Room DB에만 저장하고 있었다.
하지만 여러 사용자 디바이스의 결과를 한 곳에 모아서 보고 싶어졌고, 결국 Firebase Firestore를 붙여보기로 했다.
몇 가지 후보가 있었는데, Firestore를 선택한 이유부터 Android 코드까지 정리 라쭈고 ..
Firestore를 선택한 이유
처음에는 Supabase나 MongoDB Atlas 같은 것도 고민했었다.
하지만 이번 목적은 복잡한 통계나 대규모 서버 운영이 아니라,
- 단순한 성능 데이터 업로드
- 관리자용으로 나중에 한 번에 조회
- Android 앱에서 빠르게 붙여서 바로 사용
여기에 초점을 맞췄다.
Firestore를 사용해보니 다음과 같은 장점이 있었다.
1) Android 연동이 가장 간단했다
Auth, Firestore, 초기 설정까지 SDK가 잘 정리되어 있어서 붙이는 데 시간이 거의 안 들었다.
2) 인증 없이 쓰기 허용하기는 위험해서, 익명 로그인이 적당했다
Firestore는 기본적으로 인증되지 않은 사용자의 write를 막는다.
보안 규칙을 풀어버리면 누구나 DB에 쓰기가 가능해져서 절대 추천할 수 없다.
그래서 Firestore를 쓰려면 최소한 익명 로그인 정도는 해두는 게 안전하다.
3) 지금 기록하는 정보가 문서 기반 데이터 구조와 잘 맞았다
(fd 시간, fas 시간, 앱 버전, 디바이스 모델 등) 전부 단순한 key-value 형태여서 Firestore에 적합했다.
Firestore 구조는 이렇게 설계했다
목표가 복잡한 데이터 분석이 아니라 “한 번의 측정 결과 = 한 문서” 형태였기 때문에 아래처럼 구성했다.
performance_results (collection)
└─ {documentId}
├─ userId: "...firebase uid..."
├─ deviceId: "SM-A165N"
├─ osVersion: "34"
├─ appVersion: "1.2.3"
├─ perfTimeMs: 100ms
├─ createdAt: serverTimestamp()
이렇게 저장해두면 나중에 기기별 평균, 앱 버전별 변화 등을 보기도 쉬웠다.
Android에서 Firestore를 쓰기 위해 추가한 의존성
build.gradle.kts에서 BOM 기반으로 Firestore를 추가했다.
dependencies {
implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
implementation("com.google.firebase:firebase-auth")
implementation("com.google.firebase:firebase-firestore")
}
firebase-bom을 쓰면 버전을 하나씩 맞출 필요가 없어서 편했다.
익명 로그인 초기화 코드
Firestore 보안 규칙 때문에 최소한 익명 인증은 필요했다.
앱을 실행할 때 아래처럼 로그인만 해두면 uid가 자동으로 발급된다.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this)
FirebaseAuth.getInstance()
.signInAnonymously()
.addOnSuccessListener {
Log.d("Perf", "anonymous uid = ${it.user?.uid}")
}
}
}
이 과정은 사용자에게 UI가 보이지도 않고 아주 빠르게 끝난다.
Firestore 업로드 코드
측정이 끝나는 시점에 Firestore로 업로드하는 부분은 아래처럼 구성했다.
object PerfUploader {
private val firestore by lazy { FirebaseFirestore.getInstance() }
private val auth by lazy { FirebaseAuth.getInstance() }
fun uploadPerfResult(
fdTimeMs: Long,
fasTimeMs: Long,
subFasTimeMs: Long? = null
) {
val uid = auth.currentUser?.uid
val data = mapOf(
"userId" to uid,
"deviceId" to Build.MODEL,
"osVersion" to Build.VERSION.SDK_INT.toString(),
"appVersion" to BuildConfig.VERSION_NAME,
"prefTimeMs" to prefTimeMs,
"createdAt" to FieldValue.serverTimestamp()
)
firestore.collection("performance_results")
.add(data)
.addOnSuccessListener { doc ->
Log.d("Perf", "uploaded: ${doc.id}")
}
.addOnFailureListener { e ->
Log.e("Perf", "upload failed", e)
}
}
}
실제로 사용했던 위치는 FD/FAS 계산이 모두 끝난 지점이었다.
Firestore 보안 규칙은 이렇게 설정했다
앱에서는 쓰기만 하면 되고, 읽기는 관리자 페이지나 서버에서만 할 예정이기 때문에 다음과 같이 설정했다.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /performance_results/{docId} {
allow create: if request.auth != null; // 익명 포함
allow read: if false; // 클라이언트는 읽지 않음
}
}
}
이렇게 해두면 불필요한 사고를 방지할 수 있다.
마무리
Firestore는 “빠르게 붙여서 간단한 서버 역할을 하게 만들고 싶을 때” 아주 적합했다.
Room을 그대로 유지하면서 Firestore를 추가로 사용해도 충돌이 없고, 구조도 단순해서 도입 부담도 거의 없었다.
이제 성능 로그들이 전부 Firestore에 모이기 시작했기 때문에,
나중에는 관리자 페이지나 BI 도구로 시각화해보는 것도 금방 가능할 것 같다~
필요하면 Firestore 데이터를 기반으로 간단한 관리자 웹 대시보드를 만드는 것도 정리해볼 예정이다!!