Firebase Realtime Database는 Google이 2012년에 모바일 및 웹 애플리케이션을 위해 출시한 클라우드 JSON 실시간 데이터베이스입니다. 모든 데이터는 하나의 큰 JSON 트리에 저장되고 WebSocket 연결을 통해 접속된 클라이언트 간에 실시간으로 동기화됩니다. 공식 문서에 따르면 Firebase, 2025, Realtime Database는 최대 200,000개의 동시 연결과 최대 1,000개의 동시 쓰기를 초당 지원합니다. 이 데이터베이스는 서버 인프라가 필요없으며 iOS, Android, Web 및 서버 플랫폼을 위한 SDK를 제공합니다.
주요 포인트
Firebase Realtime Database는 접속된 모든 클라이언트 간에 실시간으로 데이터를 저장하고 동기화하는 클라우드 NoSQL 데이터베이스입니다. 2012년에 Firebase로 출시되어 모바일 개발자를 위한 첫 번째 클라우드 실시간 데이터베이스가 되었습니다. 데이터는 JSON 형식으로 표현되고 계층적인 트리로 조직화되며, 각 노드는 고유한 경로를 가집니다.
Realtime Database의 주요 가치는 내장된 동기화 기능입니다. 어떤 장치에서 애플리케이션이 데이터를 변경하면, 다른 모든 클라이언트가 지속적인 연결을 통해 즟시간에 업데이트를 받습니다. 이를 통해 개발자는 클라이언트 간 데이터 전송을 위한 자체 동기화 메커니즘, WebSocket 서버 또는 REST API를 구현할 필요가 없습니다.
데이터베이스는 모든 주요 플랫폼을 위한 SDK를 제공합니다: Android (Java, Kotlin), iOS (Swift, Objective-C), Web (JavaScript) 그리고 Admin SDK를 통한 서버 환경. Google에 따르면 Realtime Database는 전 세계적으로 150만 개 이상의 활성 Firebase 프로젝트에서 사용됩니다. 더 현대적인 Firestore가 등장했을지라도 Realtime Database는 간단한 데이터 구조를 가진 프로젝트에서 인기 있는 선택으로 남아 있습니다.
관계형 데이터베이스와 달리, Realtime Database는 테이블과 행을 사용하지 않습니다. 모든 데이터는 중첩된 JavaScript 객체와 같은 단일 JSON 트리입니다. 예를 들어, 사용자와 메시지를 저장하려면 users/userId/name과 messages/messageId/text의 계층 구조가 만들어집니다. 트리의 각 경로는 문자열이며, 이 경로를 통해 데이터에 직접 액세스할 수 있습니다.
{
"users": {
"user1": {
"name": "이반 페트로프",
"email": "ivan@example.com"
},
"user2": {
"name": "마리야 소콜로바",
"email": "maria@example.com"
}
},
"messages": {
"-Nabc123": {
"text": "안녕하세요!",
"userId": "user1"
}
}
}
중요한 특징은 깊은 중첩이 성능에 영향을 미친다는 것입니다. 애플리케이션이 특정 경로에서 데이터를 읽으면 해당 경로의 모든 하위 노드가 로드됩니다. 따라서 데이터 구조를 가능한 평혼하게 설계하고 3-4 레벨 이상의 깊은 중첩을 피하는 것이 권장됩니다. 이 문제를 해결하기 위해 데이터 비정규화(denormalization)가 사용됩니다.
Realtime Database와 Firestore는 종존 Google의 두 가지 클라우드 실시간 데이터베이스로 비교됩니다. 둘 사이에서 선택은 프로젝트의 구체적인 요구 사항, 쿼리 복잡성, 필요한 일관성 및 예상 부하에 따라 달라집니다. 각 데이터베이스의 강점을 이해하면 올바른 아키텍처 결정을 내릴 수 있습니다.
Realtime Database의 주요 장점은 낮은 동기화 지연 시간입니다. 모든 데이터가 추가 추상화 계층 없이 단일 JSON 트리에 저장되므로 Firestore보다 동기화가 덮 빵니다. 업데이트 전달 속도가 중요한 애플리케이션(채팅, 온라인 게임, 협업 편집 시스템)에서는 Realtime Database가 더 적합할 수 있습니다.
Realtime Database는 간단한 데이터 구조와 높은 업데이트 빈도를 가진 시나리오에 더 적합합니다. 일반적인 예로는 채팅, 실시간 좋아요, 타이핑 지표기, 사용자 존재 상태 등입니다. 또한 가격이 작업 수가 아닌 데이터 용량에 기반하므로 프로토타입과 예산이 제한된 프로젝트에 좋은 선택입니다.
반면, 복잡한 쿼리(여러 필드 필터링, 정렬, 집계)가 필요한 애플리케이션에서는 Firestore가 더욱 강력한 기능을 제공합니다. Realtime Database는 하나의 매개 변수로만 필터링을 지원하고 여러 필드를 동시에 정렬할 수 없습니다. 프로젝트가 클라이언트 측에서 복잡한 데이터 분석을 계획한다면 Firestore가 더 실용적인 선택입니다.
Realtime Database는 양방향 데이터 동기화를 위해 지속적인 WebSocket 연결을 사용합니다. 클라이언트가 특정 경로에서 setValue 또는 updateChildren을 호출하면 열린 채널을 통해 Firebase 서버로 데이터가 전송됩니다. 서버는 변경사항을 적용하고 밀리초 내에 구독한 모든 클라이언트에 업데이트를 배포합니다. 각 연결은 고유한 세션 키로 식별됩니다.
구독 메커니즘은 listener를 통해 작동합니다. 개발자는 특정 노드의 변경에 구독하거나(addListenerForSingleValueEvent) 지속적인 업데이트를 받을 수 있습니다(addValueEventListener). 데이터가 변경될 때마다 지정된 경로의 완전한 데이터 스냅샷과 함께 onDataChange 콜백이 트리거됩니다. 이는 변경된 문서만 수신되는 Firestore와 달립니다.
Realtime Database는 디스크 캐시를 통해 Android 및 iOS에서 오프라인 모드를 지원합니다. SDK는 로컬 데이터 사본을 유지하고 네트워크가 없을 때도 쓰기 작업을 계속 처리합니다. 연결이 복구되면 모든 적립된 변경 사항이 서버로 전송됩니다. 충돌 해결에는 마지막 쓰기 승리 전략이 사용되지만 개발자는 ServerValue.TIMESTAMP를 통해 충돌 해결을 위한 사용자 정의 로직을 구현할 수 있습니다.
val database = FirebaseDatabase.getInstance()
val myRef = database.getReference("messages")
// 데이터 쓰기
myRef.push().setValue(
hashMapOf(
"text" to "새 메시지",
"timestamp" to ServerValue.TIMESTAMP
)
)
// 지속적 업데이트로 읽기
myRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val data = snapshot.getValue()
Log.d("TAG", "데이터: $data")
}
override fun onCancelled(error: DatabaseError) {
Log.w("TAG", "오류: ${error.message}")
}
})
트래픽과 성능을 최적화하기 위해 특정 하위 노드의 변경을 추적할 때 value listener 대신 child listeners를 사용하는 것이 권장됩니다. ChildEventListener는 하위 요소의 추가, 수정, 삭제 및 이동에 대한 별도의 콜백을 제공하여 UI 업데이트를 더 정확하게 제어하고 데이터 변경 값마다 모든 리스트 항목을 다시 그리지 않도록 합니다.
Realtime Database는 데이터 액세스 제어를 위해 선언적인 규칙 언어를 사용합니다. 규칙은 JSON 트리의 각 경로에서 누가 데이터를 읽고 쓸 수 있는지 설명합니다. 그들은 각 요청 전에 Firebase 서버에서 확인되며 권한 부여를 위한 서버 측 로직이 필요없습니다. 규칙은 유연한 액세스 구성을 위한 변수, 내장 객체 및 함수를 지원합니다.
기본적으로 모든 사용자에 대해 데이터베이스 액세스가 거부됩니다. 개발자는 트리의 다양한 레벨에서 ".read" 및 ".write" 규칙을 사용하여 접근적으로 액세스를 개방합니다. 조건은 auth 변수를 통한 인증, 요청 유형(읽기/쓰기) 및 data 객체를 통한 기존 데이터를 확인할 수 있습니다. 또한 규칙은 newData 객체를 통한 쓰여진 데이터의 검증을 지원합니다.
{
"rules": {
"users": {
"$uid": {
// 소유자만이 데이터를 읽을 수 있습니다
".read": "$uid === auth.uid",
// 소유자만이 쓸 수 있습니다
".write": "$uid === auth.uid",
// 쓸 때 필드 검증
".validate": "newData.hasChildren(['name', 'email'])"
}
},
"messages": {
// 인증된 사용자는 누구나 읽을 수 있습니다
".read": "auth !== null",
// 인증된 사용자만이 쓸 수 있습니다
".write": "auth !== null",
".indexOn": ["timestamp"]
}
}
}
규칙은 ".indexOn" 지시문을 통한 데이터 인덱스도 지원합니다. 이것이 없으면 orderByChild 정렬 쿼리가 거부되거나 비효율적으로 실행됩니다. 인덱스는 특정 필드에 대한 정렬이 수행되는 각 경로에 대해 지정됩니다. 규칙은 캐스케이드되며, 깊은 규칙이 부모 규칙을 도시하고, 어떤 레벨에서 액세스가 정의되지 않으면 부모 규칙에 따라 허용 또는 거부되어집니다.
Realtime Database는 5가지 데이터 유형을 지원합니다: String, Number, Boolean, Map(객체) 그리고 List(배열). 중첩 깊이는 32 레벨로 제한되며 단일 노드의 최대 크기는 256 MB를 초과하지 않아야 합니다. 효율적인 데이터베이스 작업을 위해 평혼한 데이터 구조를 설계하고 대량 데이터를 로드하는 깊은 쿼리를 피하기 위해 비정규화를 사용하는 것이 권장됩니다.
사용자 상태(온라인/오프라인)를 위한 Android 애플리케이션에 Realtime Database를 통합하는 실용적인 예를 살펴보습시다. 애플리케이션은 실시간으로 업데이트되는 현재 상태와 함께 사용자 목록을 표시합니다. 사용자 식별을 위한 Firebase Authentication과 비동기 작업을 위한 코루틴이 사용됩니다.
시작하려면 앱 모듈의 build.gradle 파일에 firebase-database-ktx 의존성을 추가하세요. 라이브러리 버전은 Firebase BoM을 통해 관리되어 모든 컴포넌트의 호환성이 보장됩니다. 의존성을 추가한 후에는 Application 클래스에서 또는 ViewModel에서 는키 초기화를 통해 Firebase를 초기화해야 합니다.
dependencies {
implementation platform("com.google.firebase:firebase-bom:33.0.0")
implementation "com.google.firebase:firebase-database-ktx"
implementation "com.google.firebase:firebase-auth-ktx"
}
설정 후, 사용자를 관리하기 위한 리포지토리가 만들어집니다. 각 사용자는 name, email 및 status 필드를 가진 /users/{uid} 트리의 노드로 표시됩니다. 상태 추적을 위해 클라이언트 연결이 끞어졌을 때 자동으로 쓰기 작업을 수행하는 Firebase의 특별 메커니즘인 onDisconnect가 사용됩니다. 이를 통해 클라이언트 측의 추가 코드 없이 앱을 닫거나 네트워크를 잃으면 사용자 상태가 "offline"으로 변경됩니다.
class PresenceRepository {
private val database = FirebaseDatabase.getInstance()
private val auth = FirebaseAuth.getInstance()
private val presenceRef = database
.getReference("presence")
fun trackPresence() {
val uid = auth.currentUser?.uid ?: return
val userRef = presenceRef.child(uid)
userRef.onDisconnect().setValue("offline")
userRef.setValue("online")
}
fun getPresenceStream(): Flow<Map<String, String>> =
presenceRef.snapshotFlow()
.map { snapshot ->
(snapshot.value as? Map<*, *>)
?.mapKeys { it.key.toString() }
?.mapValues { it.value.toString() }
?: emptyMap()
}
}
예제의 핵심 요소는 onDisconnect입니다. 이 메커니즘을 통해 클라이언트 연결이 끞어졌을 때 서버에서 실행될 쓰기 작업을 설정할 수 있습니다. 익경우 사용자가 연결 해제되면 앱 종료 이벤트를 처리할 필요 없이 상태가 자동으로 "offline"으로 설정됩니다. 앱이 비정상 종료되도 Firebase가 onDisconnect 작업을 실행하여 다른 사용자가 올바른 상태를 볼 수 있습니다.
자주 묻는 질문
Realtime Database는 단일 JSON 트리에 데이터를 저장하고 더 낮은 동기화 지연을 제공합니다. Firestore는 문서 컬렉션을 사용하고 복잡한 쿼리와 강한 일관성을 지원합니다. Realtime Database는 간단한 채팅과 상태 관리에 더 좋고 Firestore는 복잡한 데이터 구조와 분석이 필요한 애플리케이션에 더 좋습니다.
단일 Realtime Database 노드의 최대 크기는 256 MB입니다. 중첩 깊이는 32 레벨로 제한됩니다. 둘의 Firebase 프로젝트에서 여러 개의 Realtime Database 인스턴스를 만들 수 있습니다(Spark 요금제에서 최도5개, Blaze 요금제에서 최도100개).
Realtime Database는 Firebase Authentication과 통합됩니다. 보안 규칙에서는 인증된 사용자의 uid를 포함한 auth 변수가 제공됩니다. 개발자는 데이터 소유자의 uid를 확인하여 JSON 트리의 개별 노드 레벨에서 액세스를 제한할 수 있습니다. 익명 및 비인증 사용자는 auth = null입니다.
네, Realtime Database는 runTransaction 메소드를 통해 트랜잭션을 지원합니다. 트랜잭션은 단일 노드에 대한 읽기-수정-쓰기 작업의 원자성을 보장합니다. 동시 변경이 발생하면 트랜잭션이 현재 데이터로 재시도됩니다. 카운터, 평가 및 데이터 일관성이 중요한 상황에 유용합니다.
네, Realtime Database는 Android 및 iOS에서 오프라인 모드를 지원합니다. SDK는 데이터를 로컬로 캐시하고 네트워크 없이도 쓰기 작업을 계속 처리합니다. 연결이 복구되면 모든 적립 변경 사항이 서버와 동기화됩니다. 오프라인 모드를 사용하려면 원하는 노드에서 keepSynced(true) 메소드를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.