Faster, synchronous local key-value storage through JSI
AsyncStorage wraps every read and write in an asynchronous bridge call, while MMKV, built on C++ and memory-mapped files, answers synchronously through JSI. This article compares both in speed, API design and encryption, and walks through a practical strategy for migrating existing AsyncStorage data to MMKV.
Table of Contents
- 1. How AsyncStorage works: asynchronous bridge calls per access
- 2. How MMKV works: synchronous, C++-based and backed by mmap
- 3. Benchmark comparison: read and write times for small values
- 4. API comparison: from AsyncStorage promises to MMKV synchronous calls
- 5. Migrating existing AsyncStorage data to MMKV
- 6. MMKV's encryption option
- 7. Multi-instance and multi-process support
- 8. Limits of MMKV: large data volumes and complex queries
- 9. Adding it to a project: installation and New Architecture compatibility
- 10. Summary
- 11. FAQ
1. How AsyncStorage works: asynchronous bridge calls per access
AsyncStorage stores data differently depending on the platform, traditionally in a SQLite database on Android, in a collection of property list files on iOS, and every single access, whether reading or writing, runs through an asynchronous call handled in the past through the classic bridge and today through a TurboModule. Even reading a single small string produces a full asynchronous round trip with promise resolution.
For occasional access, for example saving an auth token once at login, this overhead is unproblematic. But once an app frequently reads or writes many small values, for example form state, UI preferences or feature flags checked on every render, the asynchronous nature of AsyncStorage adds up to noticeable latency and a growing number of concurrent promises.
2. How MMKV works: synchronous, C++-based and backed by mmap
MMKV was originally developed by Tencent for WeChat and is built on a C++ core library that maps data directly into process memory through memory-mapped files, instead of opening, reading or writing a file on every access. Through JSI, MMKV is addressed directly and synchronously from JavaScript, with no promise, bridge serialization or thread switch at all, which makes a fundamental difference for small, frequent accesses.
Since MMKV operates on the same native level operating systems use for efficient file access themselves, the detour through a separate database engine like SQLite disappears entirely. Changes are written incrementally into the underlying file, without every single write having to re-serialize the entire file.
3. Benchmark comparison: read and write times for small values
In publicly available benchmarks, including those published by the MMKV library itself, small values like individual strings or numbers show a speed difference in the range of roughly ten to thirty times in favor of MMKV over AsyncStorage, especially for many consecutive individual accesses. The difference grows more pronounced the smaller the individual values and the more frequent the accesses.
For very large, rare write operations, for example storing a multi-megabyte JSON blob once, the difference narrows somewhat, since raw data volume matters more here than per-call protocol overhead. For the typical use case of small, frequent key-value access, though, MMKV stays clearly ahead in every realistic scenario.
4. API comparison: from AsyncStorage promises to MMKV synchronous calls
The API difference shows up directly in the code: AsyncStorage requires await or a .then() chain for every access, while MMKV provides synchronous methods that return a value immediately, without the calling function itself needing to be asynchronous. This especially simplifies code that wants to read values while rendering, which with a purely asynchronous API is only possible through workarounds like an initial loading state.
The example below compares both APIs directly for the same use case, saving and reading an auth token.
// AsyncStorage: every access is asynchronous
import AsyncStorage from "@react-native-async-storage/async-storage";
async function saveToken(token: string) {
await AsyncStorage.setItem("auth_token", token);
}
async function loadToken(): Promise<string | null> {
return AsyncStorage.getItem("auth_token");
}
// MMKV: synchronous access without a promise
import { MMKV } from "react-native-mmkv";
const storage = new MMKV();
function saveTokenSync(token: string) {
storage.set("auth_token", token);
}
function loadTokenSync(): string | undefined {
return storage.getString("auth_token");
}
5. Migrating existing AsyncStorage data to MMKV
A migration is usually implemented in practice as a one-time step at app startup: all existing keys are determined via AsyncStorage.getAllKeys(), the corresponding values read through multiGet, and then written synchronously into a new MMKV instance. After a successful migration, a dedicated flag property marks the process as complete so it does not repeat on future app starts.
It matters to make the migration idempotent and to not let error cases like a single failed value abort the entire migration, otherwise users could end up with data partly migrated and partly still sitting in AsyncStorage. A proven pattern is to not delete AsyncStorage immediately after migration, but only after several successful app starts, keeping a way back in case something goes wrong.
6. MMKV's encryption option
MMKV supports built-in encryption, activated when creating an instance through an encryption key, transparently encrypting all data with AES before it is written to the underlying file. For sensitive data like session tokens or personal user information this is a noticeable advantage over AsyncStorage, which ships with no encryption at all by default and relies on additional libraries for that.
The encryption key itself should not sit in plain text in the code, but be managed through a secure secure-storage mechanism like the iOS keychain or the Android keystore, so the encryption is not undermined by an easily readable, hardcoded key. MMKV itself does not manage this key, that remains the app's responsibility.
7. Multi-instance and multi-process support
Unlike a single global AsyncStorage instance, MMKV allows multiple independent, named instances within the same app, which works well for cleanly separating different data categories like user preferences, cache data and auth information, without worrying about key name collisions. Each instance can also get its own encryption configuration.
MMKV also supports multi-process access, which becomes relevant as soon as an app has extensions like an iOS share extension or an Android widget process that need access to the same data as the main process. AsyncStorage offers no built-in mechanism for that and would require its own inter-process communication.
8. Limits of MMKV: large data volumes and complex queries
MMKV is designed as a pure key-value store and offers no way to run structured queries like filtering, sorting or joining multiple records, the way a relational database would. For use cases like a searchable list of thousands of records with complex filter criteria, MMKV is therefore the wrong choice, SQLite or a specialized solution like WatermelonDB fits better here.
For very large individual values too, for example caching entire API responses several megabytes in size, MMKV loses part of its speed advantage, since the mmap-based approach shines mainly with many small, frequent accesses. MMKV remains, at its core, a replacement for AsyncStorage, not a full database for complex, relational requirements.
9. Adding it to a project: installation and New Architecture compatibility
Installing react-native-mmkv happens through the usual package manager, followed by a native build step, since MMKV is built on native C++ code and not shipped as pure JavaScript. On iOS a pod install is required, on Android autolinking picks it up automatically as long as the project configuration is current.
Current versions of react-native-mmkv are explicitly built for the New Architecture with JSI and do not work in pure legacy mode without the New Architecture enabled, since the synchronous API sits directly on top of JSI HostObjects. Projects still running entirely on the old architecture either need to fall back to an older MMKV version or switch to the New Architecture first before adopting MMKV.
| Criterion | AsyncStorage | MMKV | Practical meaning |
|---|---|---|---|
| Access type | Asynchronous, promise-based | Synchronous, direct return value | MMKV works for reads during rendering |
| Speed for small values | Comparatively slow | Roughly 10 to 30 times faster | Clearly noticeable for frequent access |
| Encryption | Not built in, needs an extra library | Built-in AES encryption per instance | MMKV saves an extra dependency |
| Multiple instances | One global instance | Several named, independent instances | Clean separation of data categories with MMKV |
| Fit for large data volumes | Limited, meant for small values | Also limited, no SQLite replacement | Neither fits complex queries well |
Mironsoft
React Native app development and Magento integration
A mobile app for the Magento shop that actually runs smoothly?
We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.
App Concept
Plan the architecture and feature scope of a Magento-connected app together.
Magento API Integration
Cleanly connect product catalog, cart, and checkout to the shop API.
Store Publishing
Guide the App Store and Google Play release process without pitfalls.
10. Summary
MMKV vs. AsyncStorage at a Glance
AsyncStorage
An asynchronous key-value store over a TurboModule, solid for occasional access, but overhead adds up with frequency.
MMKV
A synchronous, C++ and mmap-based store over JSI, noticeably faster for many small accesses.
Migration
AsyncStorage data can be read once at app startup and moved into a new MMKV instance.
Limits
MMKV remains a key-value store without query capabilities, SQLite stays the right choice for complex data models.