ObjectBox는 모바일 및 엣지 디바이스에 최적화된 고성능 NoSQL 데이터베이스입니다. SQLite와 달리 SQL 계층 없이 자체 플랫 파일 저장 형식을 사용합니다. ObjectBox Benchmarks, 2024에 따르면, ObjectBox는 Android 디바이스에서 SQLite보다 10~100배 빠르게 삽입 및 읽기 작업을 수행하므로 고부하 모바일 애플리케이션에 가장 적합한 선택입니다.
핵심 요점
ObjectBox는 독일 회사 ObjectBox.io에서 개발한 인메모리 NoSQL 데이터베이스입니다. 기존 관계형 데이터베이스와 달리 ObjectBox는 자체 SLOT(Super Lightweight Object Tree) 형식을 사용하여 객체를 플랫 파일에 직접 저장합니다. 이는 ORM 및 SQL 구문 분석의 오버헤드를 제거합니다. SLOT 형식은 메모리가 제한된 디바이스에 최적화되어 있으며, 데이터는 자동으로 조각 모음되고 데이터베이스 크기는 동일한 레코드 수의 동등한 SQLite 데이터베이스보다 평균 4~6배 작습니다.
ObjectBox는 Android, iOS, Flutter 등 모든 주요 모바일 플랫폼을 지원합니다. Android의 경우 ObjectBox Gradle Plugin을 통해 코드 생성을 사용할 수 있으며, 어노테이션이 달린 엔터티를 처리하고 데이터 작업을 위한 Box 클래스를 생성합니다. Flutter의 경우 Dart 래퍼가 있는 네이티브 플러그인이 사용됩니다. 모바일 플랫폼 외에도 ObjectBox는 Linux, macOS, Windows, WebAssembly에서 실행되므로 모바일 앱과 서버 동기화 간에 데이터 모델을 재사용할 수 있습니다.
ObjectBox 연구(2023)에 따르면 이 데이터베이스는 Google Play에서 5,000개 이상의 애플리케이션에 사용되며, 1,000만 명 이상의 사용자를 보유한 앱도 포함됩니다. 주요 사용 사례: 데이터 캐싱, 오프라인 작동, 시계열 저장(IoT)입니다. ObjectBox는 특히 지속적인 인터넷 연결 없이 디바이스에서 센서 판독값을 빠르게 기록하고 여러 디바이스에서 분석 데이터를 수집해야 하는 건강 및 피트니스 추적 앱에서 인기가 높습니다.
ObjectBox는 Android 플랫폼을 위한 Realm 및 SQLite의 대안으로 등장했습니다. 첫 번째 안정 버전은 2018년에 출시되었습니다. Realm과의 주요 차이점은 ObjectBox에 GPL 제한이 없고(Apache 2.0 사용) 모바일 디바이스부터 Linux 서버까지 더 많은 플랫폼을 지원한다는 것입니다.
ObjectBox는 자동 Box 클래스 생성, 빠른 검색을 위한 내장 인덱스, 롤백 지원 트랜잭션, RxJava 및 Kotlin Flow를 통한 반응형 쿼리, ObjectBox Sync(독점 프로토콜)를 통한 디바이스 간 동기화를 제공합니다. Firebase Realtime Database와 달리 ObjectBox Sync는 중앙 서버 없이 피어투피어 모델로 작동하여 지연 시간과 인프라 비용을 줄입니다.
ObjectBox는 자체 바이너리 형식인 .mdb(ObjectBox Data File)에 데이터를 저장합니다. 각 엔터티는 하나의 파일에 매핑되며 레코드는 고정 오프셋으로 순차적으로 저장됩니다. 이를 통해 전체 파일을 스캔하지 않고도 개별 객체를 읽고 쓸 수 있습니다.
ObjectBox 작업에 SQL은 필요하지 않습니다. 쿼리는 코드에서 fluent API를 통해 빌드됩니다. 컴파일러는 모든 CRUD 작업을 캡슐화하는 도우미 클래스 MyObjectBox와 Boxes를 생성합니다. 데이터 모델은 어노테이션 @Entity, @Id, @Index 및 @Relation을 사용하여 설명됩니다.
// Connecting ObjectBox in Android
class App: Application() {
lateinit var boxStore: BoxStore
override fun onCreate() {
super.onCreate()
boxStore = MyObjectBox.builder()
.androidContext(this)
.build()
}
}
공식 벤치마크에 따르면 ObjectBox는 특히 배치 삽입 및 대량 읽기에서 SQLite를 크게 능가합니다. 그러나 SQLite는 널리 보급되어 있고 내장 SQL 지원으로 인해 모바일 애플리케이션의 사실상 표준으로 남아 있습니다.
| 작업 | ObjectBox | SQLite | 차이 |
|---|---|---|---|
| 삽입(1000개 레코드) | ~5ms | ~180ms | ×36배 빠름 |
| 읽기(1000개 레코드) | ~3ms | ~70ms | ×23배 빠름 |
| 업데이트(1000개 레코드) | ~6ms | ~150ms | ×25배 빠름 |
| 인덱스 검색 | ~0.1ms | ~2ms | ×20배 빠름 |
| DB 크기(1000개 객체) | ~28KB | ~112KB | 4배 작음 |
ObjectBox는 복잡한 JOIN 쿼리, 집계 함수(그룹화가 있는 SUM, COUNT) 또는 기존 SQL 스키마와의 통합이 필요한 시나리오에서 SQLite보다 뒤떨어집니다. 프로젝트가 이미 Room ORM과 함께 SQLite를 사용하는 경우 ObjectBox로 마이그레이션하려면 모든 데이터 액세스 로직을 다시 작성해야 합니다. ObjectBox는 사용자 지정 SQL 쿼리도 지원하지 않으며 fluent API만 지원합니다. 그러나 일반적인 CRUD 작업 및 캐싱의 경우 ObjectBox는 최대 100배의 성능 향상을 제공하며, 이는 다양한 OS 버전의 Android 디바이스에서 공식 벤치마크를 통해 확인되었습니다.
ObjectBox는 어노테이션을 사용하여 엔터티를 정의합니다. 컴파일 후 put, get, remove 및 query 메서드가 있는 Box 클래스가 생성됩니다. 다음은 할 일 목록 앱의 Task 모델에 대한 일반적인 작업입니다.
각 엔터티는 @Entity로 어노테이션됩니다. @Id가 있는 필드는 기본 키가 되고 @Index는 해당 필드의 검색 속도를 높입니다. ObjectBox는 값이 0일 때 자동 ID 생성을 지원합니다.
@Entity
data class Task(
@Id var id: Long = 0,
@Index var title: String = "",
var isCompleted: Boolean = false,
var priority: Int = 0
)
Box는 엔터티 작업을 위한 기본 클래스입니다. put() 메서드는 객체를 저장하거나 업데이트하고, get()은 ID로 읽고, remove()는 삭제합니다. 모든 기본 작업은 동기식이며 호출 스레드에서 실행됩니다.
val taskBox: Box<Task> = boxStore.boxFor(Task::class.java)
// Create
val task = Task(title = "Buy products", priority = 3)
val newId = taskBox.put(task)
// Read
val savedTask = taskBox.get(newId)
// Update
savedTask.isCompleted = true
taskBox.put(savedTask)
// Delete
taskBox.remove(newId)
ObjectBox는 .subscribe()를 통한 반응형 쿼리를 지원합니다. 구독자는 쿼리 조건과 일치하는 데이터가 변경될 때 알림을 받습니다. 이는 항목이 추가되거나 제거될 때 목록을 자동으로 업데이트하는 UI에 편리합니다.
val query = taskBox.query()
.equal(Task_.isCompleted, false)
.orderDesc(Task_.priority)
.build()
query.subscribe { tasks ->
// tasks — List, 자동으로 업데이트됨
updateUi(tasks)
}
ObjectBox는 엔터티 간의 관계(일대일, 일대다, 다대다)를 지원합니다. 관계는 @Relation 어노테이션을 통해 정의됩니다. SQLite와 달리 ObjectBox는 외래 키를 사용하지 않습니다. 관계는 플랫 파일의 ID 목록을 통해 구현되어 객체 그래프 탐색 속도를 높입니다.
@Backlink 관계를 사용하면 별도의 참조 필드 없이 하위 엔터티에서 상위 엔터티로 이동할 수 있습니다. ObjectBox는 백링크를 자동으로 유지 관리하여 데이터 중복과 두 테이블 간의 동기화를 제거합니다.
ObjectBox Query Builder를 사용하면 조건 체인(equal, notEqual, greater, less, in, contains, startsWith)을 빌드할 수 있습니다. 모든 조건은 and/or을 통해 결합할 수 있습니다. 결과는 정렬, 제한 및 List 또는 LazyList(대규모 데이터 세트의 경우)로 검색할 수 있습니다.
ObjectBox는 자동으로 @Id 필드를 인덱싱합니다. 사용자 지정 인덱스의 경우 자주 쿼리되는 필드에 @Index 어노테이션을 사용합니다. 인덱스는 equal 및 in 쿼리의 속도를 높이지만 삽입 속도는 느려지므로 검색되지 않는 필드는 인덱싱하지 마십시오. ObjectBox는 여러 필드를 동시에 필터링하기 위한 복합 인덱스를 지원합니다. 복합 인덱스는 목록 필터에 특히 유용합니다. 예를 들어 isCompleted 및 priority 필드에 복합 인덱스가 있으면 우선순위가 높은 미완료 작업 검색이 수십 배 더 빠릅니다.
ObjectBox Sync는 디바이스 간 실시간 데이터 동기화를 위한 독점 기술입니다. last-writer-wins 방식의 충돌 해결과 함께 WebSocket 기반 프로토콜을 사용합니다. Sync는 오프라인 동기화가 필요한 앱(메모, 작업 목록, 여러 센서의 IoT 데이터)에 적합합니다. 단, 프로덕션 환경에서는 라이선스 구매가 필요합니다.
// Query with multiple conditions
val highPriorityIncomplete = taskBox.query()
.greater(Task_.priority, 5)
.equal(Task_.isCompleted, false)
.build()
.find()
// Substring search
val searchResults = taskBox.query()
.contains(Task_.title, "product", StringOrder.CASE_INSENSITIVE)
.build()
.find()
자주 묻는 질문
ObjectBox는 Apache 2.0 라이선스로 배포되며 제한 없이 상업적 사용이 가능합니다. ObjectBox Sync 기능(크로스 플랫폼 동기화)은 독점적이며 프로덕션 사용을 위해 라이선스 구매가 필요합니다.
BoxStore는 스레드로부터 안전합니다. 여러 스레드가 동시에 데이터를 읽을 수 있습니다. 쓰기는 Box 수준에서 잠깁니다. 복잡한 트랜잭션의 경우 boxStore.runInTx()를 사용하여 작업 그룹의 원자성을 보장합니다.
네, ObjectBox는 버전 3.0부터 Kotlin Multiplatform(KMP)을 지원합니다. Android, iOS, JVM, Native 대상을 사용할 수 있습니다. KMP의 경우 expect/actual을 지원하는 별도의 Gradle 플러그인 objectbox-kotlin이 사용됩니다.
ObjectBox는 앱 디렉토리의 .mdb 파일에 데이터를 저장합니다. 백업하려면 BoxStore.copy()를 통해 전체 ObjectBox 디렉토리를 복사하거나 boxStore.close()를 호출한 후 수동으로 파일을 복사합니다. 라이브 파일은 복사할 수 없습니다. 데이터가 손상됩니다.
Room은 SQLite 위의 ORM으로 SQL 쿼리가 필요하고 객체 매핑에 오버헤드가 있습니다. ObjectBox는 SQL 없는 NoSQL 데이터베이스로, 한 자릿수 더 빠르지만 복잡한 JOIN을 지원하지 않으며 관계형 데이터베이스가 아닙니다. 선택은 작업에 따라 다릅니다. 관계형 데이터에는 Room, 빠른 객체 저장에는 ObjectBox가 적합합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.