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)でデータを保存します。各エンティティは1つのファイルにマッピングされ、レコードは固定オフセットで順次保存されます。これにより、ファイル全体をスキャンせずに個別のオブジェクトを読み書きできます。
ObjectBoxでの作業にSQLは必要ありません。クエリはコード内のfluent APIを通じて構築されます。コンパイラはヘルパークラスMyObjectBoxとすべてのCRUD操作をカプセル化する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レコード) | 〜5 ms | 〜180 ms | ×36倍高速 |
| 読み取り(1000レコード) | 〜3 ms | 〜70 ms | ×23倍高速 |
| 更新(1000レコード) | 〜6 ms | 〜150 ms | ×25倍高速 |
| インデックス検索 | 〜0.1 ms | 〜2 ms | ×20倍高速 |
| DBサイズ(1000オブジェクト) | 〜28 KB | 〜112 KB | 4分の1 |
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クラスが作成されます。以下は、TODOリストアプリの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はエンティティ間のリレーションシップ(1対1、1対多、多対多)をサポートしています。リレーションシップは@Relationアノテーションを介して定義されます。SQLiteとは異なり、ObjectBoxは外部キーを使用せず、リレーションシップはフラットファイル内のIDリストを通じて実装されるため、オブジェクトグラフのナビゲーションが高速化されます。
@Backlinkリレーションシップにより、子エンティティから個別の参照フィールドなしで親エンティティに移動できます。ObjectBoxは自動的にバックリンクを維持するため、データの重複と2つのテーブル間の同期が不要になります。
ObjectBox Query Builderを使用すると、条件のチェーン(equal、notEqual、greater、less、in、contains、startsWith)を構築できます。すべての条件はand/orで組み合わせることができます。結果は並べ替え、制限、ListまたはLazyList(大規模データセット用)として取得できます。
ObjectBoxは自動的に@Idフィールドをインデックス化します。カスタムインデックスの場合は、頻繁にクエリされるフィールドに@Indexアノテーションを使用します。インデックスはequalおよびinクエリを高速化しますが、挿入は遅くするため、検索されないフィールドはインデックス化しないでください。ObjectBoxは複数のフィールドを同時にフィルタリングするための複合インデックスをサポートしています。複合インデックスはリストフィルターに特に便利です。たとえば、isCompletedフィールドとpriorityフィールドに複合インデックスがあると、優先度の高い未完了タスクの検索が数十倍高速になります。
ObjectBox Syncは、デバイス間でのリアルタイムデータ同期のための独自技術です。WebSocketベースのプロトコルを使用し、last-writer-winsによる競合解決を行います。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アプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。