Journal

Performance / engineering note

A window, not a copy

How the inspector reads gigabytes of on-device storage without freezing your app — or our dashboard.12 min read · NativeScope engineering

The problem

The trap every inspector falls into

The obvious design serializes every key, row and value, then ships the copy to a desktop dashboard. It looks fine against twenty keys. Against a real database, the app stalls while stringifying and the browser chokes while parsing a payload it never needed.

A faster serializer, compression or a heavier grid cannot fix that shape of problem. The cost is still proportional to the size of the data. You cannot optimize your way out of moving a gigabyte you never needed to move.

The thesis

One rule: the window, not the copy

The values stay where they live. The interface slides a window over them, and the rendered row count remains tied to the viewport. Key inventory is the explicit exception: AsyncStorage and MMKV expose all names through getAllKeys(), so NativeScope cannot make that provider operation constant-time. It keeps the expensive value reads, transfer and DOM work bounded after enumeration.

THE TRAP · COPY EVERYTHINGon-device store · GBserialize all of itfrozen · O(dataset)THE RULE · SLIDE A WINDOWwindow = visible slice onlyjust the slice60fps · O(visible)
Fig. 1 Same store, two philosophies. Copying scales with the data; a window scales with the screen.

The contract

The budgets we refuse to break

A principle you cannot measure is just a slogan. The hard byte, preview and page limits are covered by CI tests. Scheduling and rendering behavior are exercised by the scale runtime and browser QA rather than presented as synthetic millisecond guarantees.

BudgetLimitWhy it exists
Single WebSocket message≤ 256 KBMeasured in UTF-8 bytes and covered by budget tests.
Key-value preview≤ 64 KBA complete value moves to the cancelable stream lane.
SQLite cell preview≤ 4 KBThe grid never receives a full oversized cell by accident.
Value reads per key page≤ 500Names may be enumerated by the provider; values stay paged.
Dashboard DOMO(viewport)Virtualization bounds mounted rows as loaded pages grow.
Runtime schedulingcooperativeValue batches yield to the app event loop between slices.

The architecture

The path a byte takes

Three actors sit between a byte on the device and a pixel on screen. The runtime reads one requested page and truncates values to bounded previews. A local bridge relays bounded frames. The Studio virtualizes the viewport and avoids rebuilding off-screen SQLite rows.

YOUR APPLOCAL BRIDGESTUDIOgetAllKeys — namespaged read · yieldstruncate → previewpaged values · ≤8ms slicesWS≤256KBvirtualized DOMstale-request guardsbounded history60fps · O(viewport)previews + metadatastream lane · full values & exportdisk
Fig. 2 Two lanes. Previews ride ordinary messages; full values and exports use bounded streams.

On the device

Read little, yield often

Key names are fetched first. One page is read in small batches and each value becomes a preview before crossing the wire. Between batches, NativeScope returns the JavaScript thread to the event loop, keeping taps, animations and network callbacks alive.

Pagination uses keyset cursors instead of OFFSET, so page one and page one hundred thousand have the same shape of cost. Search runs next to the data and returns only matches, instead of transferring a store to filter it elsewhere.

The transport

Stream, never dump

Large values use a separate lane: bounded chunks with a beginning, an end and a running checksum. Streams are cancelable, and Chromium exports can flow directly to disk. Browsers without the File System Access API use a clearly signaled buffered fallback.

DEVICE200 MB valueread a sliceat a timebegin012· · ·nend ✓ sumeach chunk ≤ 64 KB · yield between readscancel — stop the device mid-readSTUDIOrender if smallsave to diskbuffer fallback elsewhere
Fig. 3 Bounded chunks, cancelable, checksum-verified. Supported browsers write directly to a file.

The dashboard

A viewport over millions

A million-key list still mounts roughly thirty recycled DOM nodes. Large JSON collections use the same virtual-row discipline, while node counting stops at a fixed diagnostic cap. Live SQLite refreshes re-query the visible window, not everything the user has ever scrolled through. Fluidity on both ends is one rule applied twice.

THE APP · JS THREAD✕ one blocking readframes dropped · app janks✓ short slices + yields≤8ms each · your frames (▲) run in the gapsTHE DASHBOARD · DOM1,000,000 rows≈ 30 nodesactually rendered
Fig. 4 The same rule, twice: slices free the app thread; a viewport keeps the dashboard DOM small.

In the tables

Two grids, one window

The same discipline reaches the two places where NativeScope shows data in bulk. A million-row SQLite table and a JSON array with tens of thousands of items both render as a small set of recycled rows. Global key search and SQLite search run next to the data, so matches return without transferring the underlying values or rows first. Once a JSON value is explicitly loaded, its visual filter runs locally over that value.

Live refresh follows the same rule. When data changes, NativeScope re-queries only the visible rows by keyset cursor. The cost stays proportional to the viewport, not to everything the user has already scrolled through.

SQLITE ROW GRID1,000,000 rows≈ 30 nodeson change:re-query onlythe visible rowskeyset — row 10Mcosts like row 1JSON VALUE VIEWERarray · 50,000 items≈ 30 nodesvirtualized:one scroll,no page buttonssame window,other datathe live refresh is a window too — O(viewport), never O(loaded)
Fig. 5 Both grids are windows, and so is live refresh: a change re-queries only what is on screen.

The guarantee

100% of the data, always

A preview is a display choice, never a limit on access. It carries the true size and an explicit truncation marker. The complete value is always available on demand or as a streamed file, verified against accidental corruption in transit.

We stopped copying data to show it and started opening a window onto data that stays where it is. The budgets, cursors, streams and virtualization are what keep that window honest at the scale of gigabytes.