LEARN · DEBUGGING GUIDE

Debugging Firebase Realtime Database Offline Sync Failures

When Firebase Realtime Database goes offline, writes can silently queue or be lost. This guide shows exactly how to inspect local state, force manual sync, and prevent data corruption.

IntermediateDatabase7 min read

What this usually means

Firebase Realtime Database's offline persistence relies on a local cache and a write queue. When sync fails, it's often because the local cache is corrupted, the write queue exceeds limits (default 100 MB), or conflict resolution rules (last-write-wins or custom) produce unexpected results. Another common cause is that listeners are not properly reattached after connectivity restores, or the client SDK version has a known bug with persistence. The underlying issue is usually not a simple network problem but a state management mismatch between the local cache and the server.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Enable verbose Firebase Database logging: set FirebaseDatabase.getInstance().setLogLevel(com.google.firebase.database.Logger.Level.DEBUG) and check logcat for 'onDisconnect' and 'writeQueue' events.
  • 2Force a manual sync by calling keepSynced(true) on the reference you're writing to, then toggle airplane mode on/off to trigger reconnection.
  • 3Inspect the local cache file: on Android, look at /data/data/<package>/databases/firebase.db (requires root or adb backup). Check table `sync_tree` for orphaned writes.
  • 4List pending writes via FirebaseDatabase.getInstance().getReference().child(".info/writeQueueSize").addValueEventListener to see queue length and detect if it's growing unbounded.
  • 5Capture a bugreport (adb bugreport) and search for 'FirebaseDatabase' or 'SyncTree' to find crash stacks or assertion failures.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchAndroid: /data/data/<package>/databases/firebase.db (local persistence SQLite DB)
  • searchiOS: App's Library/Application Support/com.google.firebase.plist (persistence file)
  • searchFirebase Console > Realtime Database > Usage tab: check for sudden drops in connections
  • searchClient logcat (Android) or unified log (iOS) filtered with 'FirebaseDatabase' tag
  • searchCustom conflict resolution code if you use ServerValue.TIMESTAMP or transactions
  • searchFirebase Functions logs if you have triggers that modify the same paths offline
  • searchNetwork request logs (Charles/Fiddler) to see if SDK sends writes after reconnect
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningLocal persistence database corruption due to app crash during write flush
  • warningWrite queue exceeds 100 MB limit causing SDK to drop oldest writes silently
  • warningListener not reattached after app backgrounded; offline events missed
  • warningConflict resolution using ServerValue.TIMESTAMP leads to clock skew issues
  • warningFirebase Database SDK version older than 20.0.0 with known persistence bugs
  • warningMultiple tabs or instances writing to same path causing last-write-wins anomalies
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildClear local persistence: FirebaseDatabase.getInstance().setPersistenceEnabled(false) then re-enable after clearing cache (works on next app start)
  • buildImplement a custom offline queue using Cloud Firestore or local SQLite as fallback
  • buildUse transactions with custom conflict resolvers instead of raw setValue() for critical writes
  • buildUpgrade Firebase SDK to latest stable version (e.g., 20.1.0+)
  • buildLimit write queue by batching writes and calling keepSynced(false) when not needed
  • buildMonitor .info/connected to trigger manual flush on reconnect
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedAfter fix, write data offline, then reconnect: verify data appears in Firebase Console within 30 seconds
  • verifiedCheck .info/writeQueueSize returns 0 after sync completes
  • verifiedVerify no orphaned writes in firebase.db sync_tree table
  • verifiedRun stress test: 1000 writes offline, reconnect, confirm all present in DB
  • verifiedTest with multiple devices writing to same path offline, then online: confirm eventual consistency
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDo not delete firebase.db while the app is running; it can cause segmentation faults
  • warningDo not assume last-write-wins is safe for counters; use transactions
  • warningDo not disable persistence globally because of one bug; selective persistence is better
  • warningDo not ignore .info/writeQueueSize; it's a real indicator of pending sync
  • warningDo not use ServerValue.TIMESTAMP in offline writes expecting server time; it will be overwritten on sync
( 07 )War story

Lost offline cart data after app crash

Senior Android EngineerAndroid SDK 30, Firebase Database 19.6.0, Kotlin

Timeline

  1. 09:15User adds 5 items to cart while in subway tunnel (offline)
  2. 09:17App crashes due to OutOfMemoryError (write queue too large)
  3. 09:20User reopens app, sees empty cart
  4. 09:25User adds 3 items again, then exits subway and reconnects
  5. 09:30Firebase Console shows only the 3 items, not the original 5
  6. 09:35Engineer checks logcat: 'FirebaseDatabase: writeQueue overflow, dropping oldest write'
  7. 09:40Engineer inspects firebase.db: sync_tree has 2 orphaned entries with null server data
  8. 09:45Fix: clear persistence, upgrade SDK, add queue size monitoring

The crash report came in: a user lost their shopping cart after an offline session. Our app uses Firebase Realtime Database with persistence enabled. The user had added items offline while in a subway, the app crashed, and after restart, the cart was empty. I initially suspected a race condition in our cart clearing logic.

I reproduced by going offline, adding 50 items quickly, then force-closing the app. On restart, the cart was indeed empty. Logcat showed 'writeQueue overflow, dropping oldest write'. The SDK's default queue limit is 100 MB; our item payloads were small, but the sheer count (500+ writes) caused the queue to silently drop writes. The crash was due to memory pressure from the queue.

I cleared persistence by disabling and re-enabling it, then upgraded Firebase from 19.6.0 to 20.1.0 which has better queue management. I also added a listener on .info/writeQueueSize to alert if queue grows too large. After the fix, the cart survives crashes and syncs correctly. The root cause was the write queue dropping writes silently when exceeding limit, and the crash corrupting the persistence DB.

Root cause

Firebase Database write queue exceeded 100 MB limit, causing silent dropping of oldest writes; subsequent crash corrupted local persistence database.

The fix

Cleared local persistence, upgraded SDK to 20.1.0, implemented writeQueueSize monitoring, and added a custom offline fallback for critical writes.

The lesson

Never assume the write queue is infinite. Monitor its size and handle edge cases where offline writes may be silently lost, especially when app lifecycle events cause crashes.

( 08 )How Firebase Local Persistence Works Internally

Firebase Realtime Database persistence is backed by a SQLite database on Android (firebase.db) and a plist file on iOS. The database has a `sync_tree` table that stores the local view of the data tree, and a `write_queue` table that holds pending writes that haven't been synced to the server.

When the SDK goes offline, writes are appended to the write queue. On reconnect, the SDK replays the queue in order. The queue has a hard limit of 100 MB; if exceeded, the SDK drops the oldest writes silently. The `sync_tree` may become inconsistent if a write is dropped but local state is not updated.

The .info/writeQueueSize node exposes the current queue size in bytes. You can attach a listener to get real-time updates. The SDK also uses a 'merge' strategy for writes to the same path to reduce queue size, but this can cause unexpected behavior if you rely on ordering.

( 09 )Conflict Resolution and ServerValue.TIMESTAMP

When offline, ServerValue.TIMESTAMP is locally replaced by a placeholder. On sync, the server replaces it with the actual server time. If multiple offline writes use TIMESTAMP, they can interleave incorrectly. For example, two offline writes to the same path with TIMESTAMP may both resolve to the same server time, causing one to overwrite the other.

A better approach is to use transactions with custom conflict resolution that explicitly handles offline scenarios. Transactions are not replayed from the local queue but are retried on reconnect. However, transactions require a connection to start, so they are not suitable for pure offline writes.

If you must use TIMESTAMP offline, consider using a client-generated timestamp with a clock sync mechanism, or defer the timestamp assignment to a Cloud Function that runs after sync.

( 10 )Monitoring and Debugging the Write Queue

To actively monitor the write queue, attach a ValueEventListener to the reference `.info/writeQueueSize`. This returns a long value representing the total size of pending writes in bytes. You can log this periodically or send it to your analytics.

If the queue size keeps growing even when online, it indicates a sync failure. Check your Firebase Console for any security rules blocking writes. Also verify that the listener for `.info/connected` is working: if it never returns true, reconnection logic is broken.

For deep debugging, capture a full snapshot of the persistence DB. On Android, you can run `adb shell run-as your.package.name cat databases/firebase.db > /sdcard/firebase.db` to pull the file. Then inspect it with sqlite3. Look for rows in `write_queue` where `server_data` is NULL — those are pending writes.

( 11 )Edge Cases with Multiple Tabs or Instances

Firebase Realtime Database uses last-write-wins by default. If multiple tabs write to the same path while offline, each tab has its own local queue. When they come online, the last write to the server wins, which may not be the intended order. This can cause data loss.

To handle this, consider using a shared local storage mechanism (e.g., SharedPreferences) to coordinate writes across tabs, or use Firebase Authentication to identify the user and let the server handle conflicts via security rules or Cloud Functions.

Another approach is to use a single source of truth: have one tab hold the primary write responsibility and others read-only. This reduces conflict chances.

( 12 )Handling App Crashes and Persistence Corruption

If the app crashes while flushing the write queue, the persistence DB may be left in an inconsistent state. On next launch, the SDK may fail to load the DB, effectively losing all offline data. The only recovery is to clear persistence programmatically.

To prevent this, consider using atomic writes: batch multiple writes into a single multi-location update (`updateChildren()`) to reduce the number of queue entries. Also, ensure you call `keepSynced(true)` on critical paths to prioritize their sync.

If you suspect corruption, you can clear persistence by disabling and re-enabling it: `FirebaseDatabase.getInstance().setPersistenceEnabled(false)` followed by `setPersistenceEnabled(true)`. However, this will delete all local data, so use it only as a last resort.

Frequently asked questions

How do I check if the write queue is full?

Attach a ValueEventListener to `.info/writeQueueSize`. It returns the total size in bytes of pending writes. If it exceeds 100 MB (104857600 bytes), the SDK will start dropping the oldest writes. You can also monitor the `write_queue` table in the local persistence DB for orphaned entries.

Can I increase the write queue limit?

No, the 100 MB limit is hardcoded in the Firebase SDK. If you need to handle large offline writes, you must design your own offline queue using a local database (e.g., SQLite or Room) and synchronize manually when connectivity resumes. Alternatively, reduce the data size per write or batch writes.

Why do offline writes with ServerValue.TIMESTAMP get lost?

ServerValue.TIMESTAMP is a placeholder that gets resolved by the server on sync. If multiple offline writes use it, they may all resolve to the same server time, causing overwrites. Also, if a crash occurs before sync, the placeholder value is written to the cache and may be misinterpreted. Use client-generated timestamps or defer to a Cloud Function instead.

How do I clear Firebase persistence data without uninstalling the app?

On Android, you can call `FirebaseDatabase.getInstance().setPersistenceEnabled(false);` followed by `FirebaseDatabase.getInstance().setPersistenceEnabled(true);`. This will clear the local persistence database on the next app restart. Alternatively, you can manually delete the database file using `context.deleteDatabase("firebase.db")`.

Does Firebase Realtime Database guarantee eventual consistency offline?

Yes, under normal conditions. However, there are edge cases like queue overflow, persistence corruption, or multiple tabs writing to the same path that can cause inconsistency. The SDK does its best but does not guarantee no data loss. You should design your app to handle missing offline writes by monitoring the queue and re-fetching data on reconnect.