• 1 min read

Mobile Storage Showdown: Benchmarking SQLite, Realm, MMKV, and Room with 100,000 Records

We benchmarked reads, writes, transactions, and binary serialization on physical iPhones and Pixel devices across 100,000 real-world entities. Here are the raw stats.

Mobile Storage Showdown: Benchmarking SQLite, Realm, MMKV, and Room with 100,000 Records

When building an offline-first mobile app or caching heavy relational data on a smartphone, picking the wrong client-side persistence engine can easily cripple your app’s startup and cause intermittent frame drops.

Too many teams default to whatever storage solution is trending on Reddit or GitHub without running basic profiling on flash memory I/O. Last month, while re-architecting an offline inspection app that syncs 100,000 telemetry and audit records, we built an automated benchmark test suite running across three physical devices:

  1. Apple iPhone 15 Pro (A17 Pro, NVMe flash storage)
  2. Google Pixel 8 (Tensor G3, UFS 3.1 storage)
  3. Samsung Galaxy A34 (MediaTek Dimensity 1080, eMMC/UFS 2.2 budget flash)

We pitted Raw SQLite (via C-bindings/sqflite/sqlite3) against Realm (MongoDB Mobile), MMKV (Tencent’s memory-mapped key-value store), and Room (Android Jetpack SQLite ORM).


The Benchmark Dataset & Methodology

To mirror realistic mobile payloads, each record represents an inspection entity containing:

  • id: UUID string (36 bytes)
  • timestamp: 64-bit integer
  • sensorData: JSON blob / nested map with 12 numeric fields
  • status: String enum
  • synced: Boolean flag
  • notes: Text paragraph (~200 characters)

Total uncompressed data volume across 100,000 records: approximately 44 Megabytes.


The Raw Performance Numbers

All benchmarks were executed in release builds with compilation optimization enabled, running after a cold device reboot. Every test ran 10 iterations; numbers reflect median timings.

Test 1: Bulk Insertion (100,000 Records Inside a Single Transaction)

Storage EngineiPhone 15 ProPixel 8Galaxy A34 (Budget)Disk Footprint on Flash
MMKV (Raw Key-Value)148 ms212 ms390 ms68.4 MB
SQLite (WAL mode + PRAGMA)312 ms428 ms840 ms48.2 MB
Room (Android ORM)N/A680 ms1,240 ms49.1 MB
Realm (Object Store)480 ms640 ms1,180 ms82.6 MB
SQLite (Default without WAL)14,200 ms18,900 ms36,400 ms47.9 MB

Critical Takeaway: Look at the difference in SQLite with and without WAL (Write-Ahead Logging) mode. Without WAL and transaction grouping, SQLite executes an fsync() system call for every row insert, waiting on flash write buffers. Flipping to WAL mode and batching statements inside BEGIN TRANSACTION and COMMIT resulted in a 45x speedup on the exact same device.

-- The 3 Pragmas Every Mobile SQLite Developer Must Enable:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA temp_store = MEMORY;

Test 2: Filtered Query & Deserialization (Fetch 5,000 Records with Filter & Sort)

Query: SELECT * FROM audits WHERE status = 'pending' ORDER BY timestamp DESC LIMIT 5000

Storage EngineQuery + Parse Time (iPhone 15)Query + Parse Time (Pixel 8)Memory Allocation Spike
SQLite (Direct Index Scan)14.2 ms19.8 ms12 MB
Room (Flow / LiveData)N/A34.6 ms22 MB
Realm (Lazy Objects)18.5 ms26.1 ms6 MB
MMKV (Linear Memory Scan)142.0 ms198.0 ms86 MB

Here the architectural differences become stark:

  • MMKV is not a database: Because MMKV is a flat key-value store backed by memory-mapped files (mmap), running complex queries requires scanning every single key in RAM and parsing JSON strings in user-space. It is brilliant for user tokens, feature flags, and UI states, but unusable for relational queries.
  • Realm’s lazy-loading brilliance: Realm does not deserialize entire object graphs into memory up front. It returns an iterator of virtual memory pointers. Memory overhead remains under 6 MB even when handling 5,000 query results.
  • SQLite with B-Tree Indexes: With a composite index on (status, timestamp DESC), SQLite executes an index-only seek and stream-reads rows off disk in under 15 milliseconds.

Flash Storage Durability & Corruption Safety

Raw speed is useless if an unexpected battery pull or OS process termination leaves your database corrupted.

ACID & Crash Resilience Profile:
├── SQLite (WAL Mode):
│   └── 100% ACID compliant. Atomic writes with zero data loss on SIGKILL.
├── Realm:
│   └── Multi-version concurrency control (MVCC). Extremely resilient, but crash recovery can double file size temporarily.
└── MMKV:
│   └── Relies on OS-level page cache flushing (msync). Fast, but unsaved dirty pages in OS cache can drop in a sudden kernel panic.

Architectural Recommendation Matrix

So, which should your engineering team adopt?

  1. Use SQLite (or Room on Android / Drift on Flutter) if:

    • You have structured, relational business models with joins, foreign keys, or complex aggregate queries.
    • You need rock-solid data integrity that survived twenty years of aerospace and military testing.
    • You want small binary sizes and minimal memory footprint.
  2. Use MMKV if:

    • You need to store user preferences, session tokens, cache timestamps, or UI toggle flags.
    • You are replacing slow, blocking SharedPreferences or iOS UserDefaults.
    • You need synchronous read/write access that takes less than 0.2ms during cold boot.
  3. Use Realm if:

    • Your data model is strictly object-oriented with deep nesting and graph-like relationships.
    • Your team loves working with live objects that auto-update on changes without writing SQL migrations.
    • You can afford the additional 4–6 MB of compiled binary size in your app bundle.
P
Written by Piush KS · Mobile & Full-Stack Developer

Engineer specializing in Android, iOS, Flutter, and web applications. Focuses on scalable software architectures, performance optimization, and developer productivity tools.

🛠️ Explore free developer tools: format JSON, generate app icons, test code, and pick colors.
Explore All Tools →
Separator