Client Storage

Embed
Preview of the card
<a href="https://watchtowerops.net/docs/instrumentation-client-storage"><img src="https://watchtowerops.net/docs/_api/public/orgs/aa5959cb-1a0b-4a9a-bc9a-913e7ac31cd9/spaces/b7aac181-9916-4805-b253-becc5fd2ca62/pages/7fcef73a-7902-4be8-841d-0f39f5a75dd8/social-card.svg?v=3e7ae33eec8b" alt="Client Storage" width="600" height="315"></a>

<!-- status: beta language: browser, react-native, ios, android, flutter -->

Client storage (browser and mobile embedded stores)

The browser and mobile SDKs time on-device storage and report the operations that are slow or that fail. A slow local query is a common cause of jank that network tracing never shows. Statement text, parameters and error messages are never sent. Each operation is summarised as <operation> <first table> (SELECT users), using the same rules as the Node SDK.

What is sent

  • Slow or failed operation. Browser and mobile RUM send a db event. Flutter sends an OTLP span. The value is the duration in ms. Attributes: db.system.name, db.operation.name, db.query.summary, db.collection.name, db.namespace and error.type (an error class or code, never the message).

  • Thresholds. Async work is reported at or over slowMs (100 ms by default). Synchronous work on the UI thread is reported over one frame (16 ms) and flagged db.main_thread: true. The UI thread is the browser main thread, the iOS or Android main thread, the React Native JS thread, or the Flutter root isolate.

  • Sizes. These are storage events (Flutter: a log record with event.name):

    • wt.embedded.file_size, the store or file size in bytes, by db.system.name and db.namespace

    • wt.embedded.wal_size

    • browser only: wt.embedded.storage.usage, the origin's usage, with storage.quota and storage.persisted. A store that is not persisted can be evicted by the browser.

The datastore entity is {db.system.name, db.namespace, service}.

Per SDK

SDK

Automatic or wrapped

db.system.name

Browser

init({ storage: true }) loads a lazy chunk that patches IndexedDB (requests, transactions, blocked opens, upgrades), Cache Storage and localStorage/sessionStorage, and samples quota, per-store usage and localStorage size every 5 min. import { instrumentSqlJs, instrumentWaSqlite } from "@watchtowerobservability/browser/storage" covers sql.js and wa-sqlite (OPFS). The core bundle does not grow.

indexeddb, cache_storage, localstorage, sessionstorage, opfs, sqlite

React Native

instrumentAsyncStorage, instrumentMMKV, instrumentSQLite (op-sqlite, quick/nitro-sqlite, expo-sqlite), instrumentWatermelon, instrumentRealm, recordStoreSize

asyncstorage, mmkv, sqlite, watermelondb, realm

iOS

Datastore.instrumentSQLite(handle) (GRDB, SQLite.swift, FMDB, via sqlite3_trace_v2), Datastore.instrumentCoreData(), Datastore.measure (SwiftData, Realm), Datastore.recordStoreSize

sqlite, coredata, your choice

Android

WatchtowerSQLiteOpenHelperFactory (Room, SQLDelight, SupportSQLite), Datastore.measure / measureSuspend (DataStore, MMKV, Realm), Datastore.recordStoreSize

sqlite, your choice

Flutter

WatchtowerDb.sql / measure / measureSync (sqflite, drift QueryInterceptor, Hive, Isar), WatchtowerDb.recordStoreSize

your choice

Limits

  • sql.js prepared statements (prepare + step) are not timed.

  • The iOS SQLite hook replaces any sqlite3_trace_v2 callback already set (GRDB db.trace included). Profile events carry no error codes.

  • Browser storage eviction is not detected directly. Use storage.persisted and the trend in quota usage.

  • There are no per-operation counts or throughput: only slow or failed operations are sent.

Updated