AsyncStorage with Auto Backup vs MMKV with self-managed keys when restored data must be verifiable
0 reputation · 18 Jun 2020, 00:39 UTC
0 reputation · 18 Jun 2020, 00:39 UTC
I'm designing local persistence for a React Native app (0.73-era, Android and iOS) where a reinstall or device-to-device restore must never silently resurrect stale state. The data is user preferences plus a small offline cache, and after any restore the app needs to prove the recovered records are intact and current before trusting them.
The trade-off I can't resolve: AsyncStorage is convenient but is swept into Android Auto Backup and iOS container restores by default, so old values can reappear on a new device with no signal that they were restored rather than freshly written. MMKV (or Keychain-backed storage) gives me control over backup exclusion and encryption, but if the encryption key lives in the platform keystore, restored files may be undecryptable on a new device — which is arguably worse than stale data.
My current plan is to store a schema version, record count, and checksum alongside the data and validate on cold start, but that detects corruption, not provenance.
Which approach do you pick when restored data must be verifiable: backup-inclusive AsyncStorage with a provenance marker, or backup-excluded MMKV with re-sync on fresh installs? Is there any reliable cross-platform way to distinguish "restored from backup" from "fresh install" at first launch? And should restored auth tokens ever be trusted, or always re-validated?
29275 reputation · 18 Jun 2020, 01:49 UTC
Pick backup-excluded storage with re-sync on fresh installs, and treat your checksum/version marker as an integrity check, not a provenance check. The reason is that neither AsyncStorage nor MMKV can tell you where bytes came from — platform backups restore bytes, not meaning. Since you must implement provenance at the application layer anyway, the restore path stops being an asset and becomes a liability: it is the only path that can silently resurrect stale state. Excluding your data from backup removes that path entirely, and "fresh install" and "restored device" collapse into the same, already-tested code path: re-authenticate, re-sync.
Your instinct to store a schema version, record count, and checksum is right for detecting corruption and version skew. But provenance — "was this written by this install, for this user, on this device?" — requires an anchor that does not travel with the backup. Anything stored next to the payload (device ID, install timestamp, user ID) gets restored along with it and proves nothing. The only reliable anchors are:
Notice both anchors work independently of whether the payload sits in AsyncStorage or MMKV. That's the core finding: the storage engine is not the decision that determines verifiability.
Because backup-inclusive storage gives you a second, worse failure mode with no compensating benefit. If your server-epoch check is mandatory anyway, a successful restore saves you one re-sync of preferences and a small cache — cheap data — while a failed-to-detect restore is exactly the silent-stale-state bug you're designing against. Also note Android Auto Backup has a per-app data cap (historically ~25 MB) and restores can be silently skipped, so you can't even rely on restore being complete. Asymmetry of risk says: opt out.
android:allowBackup="false" or use fullBackupContent/dataExtractionRules to exclude your storage files; on iOS, mark the relevant files/directories to be excluded from backup.Do a real end-to-end test: back up a debug build, restore to a second device or emulator, and log what your validation layer reports on first launch. Confirm the backup exclusion actually took effect (inspect the restored sandbox), and add a production metric for "restored data failed validation" so you know how often the recovery path fires. If you later decide you want seamless restore for non-sensitive preferences, you can layer it back on top of the epoch check — but build the verified path first.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.