HomeJournalThis post

DuckDB-Wasm vs SQLite OPFS for Local Analytics

Compare DuckDB-Wasm and SQLite OPFS on the same local analytics workload across ingestion, queries, writes, persistence, concurrency, recovery, and delivery.

JP
JP Casabianca
UI/UX designer and full-stack engineer · Bogotá

DuckDB-Wasm vs SQLite OPFS is a choice between two capable local databases whose strengths appear only after the same analytics workload is measured end to end.

This comparison holds data and questions constant while testing ingestion, columnar scans, transactional updates, persistence, workers, interruption recovery, bundle weight, and export.

DuckDB-Wasm vs SQLite OPFS needs one workload

The comparison begins by freezing a complete local session: same files, same device class, same browser state, same questions, same mutations, and the same recovery drill. A single query benchmark cannot price startup, ingest, persistence, bundle transfer, or the time a user spends waiting for their first useful answer.

Freeze representative files, tables, query questions, local edits, device cohorts, and offline expectations before installing either engine. A benchmark built from synthetic aggregates can miss the startup, parsing, mutation, and recovery behavior that users actually encounter.

A matched analytics workload becomes the first exhibit in the browser benchmark. It records dataset hashes, schemas, row counts, queries, mutations, devices, cold state, and success thresholds so the data product owner can challenge how the claim was formed. In the browser benchmark, browser analytics database is the first evidence the data product owner requests from DuckDB-Wasm vs SQLite OPFS. The session is the unit of comparison because that is what the user experiences.

Separate analytical scans from app writes

DuckDB-Wasm earns attention when the product is shaped around analytical scans, joins, aggregates, and columnar formats such as Parquet. Measure worker startup, module load, file access, ingest path, query latency, and peak memory together.

A fast warm scan may still arrive too late for a small one-off exploration. Let DuckDB-Wasm reframe the DuckDB-Wasm vs SQLite OPFS claim before the data product owner accepts the browser benchmark's answer. Columnar speed matters most when the surrounding delivery path lets users reach it quickly.

Classify wide aggregations, joins, window functions, point lookups, row edits, constraint checks, and transaction batches separately. DuckDB-Wasm's analytical architecture and SQLite's embedded transactional model should be judged on the work each product session actually contains.

Ask the system to include cold scan, warm repeat, point edit, bulk import, and mixed read-write phases, on purpose. The data product owner treats one dashboard query stands in for the complete session as a failed browser benchmark branch.

Engine behavior is tested against DuckDB-Wasm overview, DuckDB-Wasm data ingestion, and SQLite Wasm persistence. DuckDB documents its Wasm client and ingestion paths, while SQLite documents persistence options and OPFS tradeoffs. Neither project's benchmark can select an engine for a product with different scans, mutations, devices, and recovery expectations.

Measure ingestion as user-visible time

SQLite OPFS fits a different center of gravity: durable row-oriented state, indexed lookups, transactions, incremental edits, and offline application recovery. Measure commit latency, contention, checkpointing, file growth, and reopen behavior. Analytical queries are possible, but they should be judged against the actual schema and session rather than a generic reputation.

Use the outcome of compare CSV, Parquet, existing database, and interrupted import where supported to interrogate an ingestion waterfall. The data product owner revises the browser benchmark wherever those two records disagree.

Time download or file selection, worker startup, engine initialization, parsing, registration, table creation, indexing, and the first useful result. Parquet or Arrow registration may suit DuckDB-Wasm, while a durable SQLite database may avoid repeated ingestion after the first session. DuckDB-Wasm vs SQLite OPFS becomes observable when the browser benchmark gives SQLite Wasm a test the data product owner can repeat. Transactional strength becomes visible through edits and recovery, not through a feature checklist.

One workload enters two local enginesMatched files and questions branch into a columnar analytical path and a transactional persistent path before whole-session evidence rejoins.FreezeDuckDBSQLiteDecide
  • Freeze: Match the workload
  • DuckDB: Measure scans
  • SQLite: Measure writes
  • Decide: Price the session
Figure 1: The same product trace, not engine reputation, determines the choice.

Define persistence and authority

Decide whether local data is a cache, an offline authority, a user-owned artifact, or a projection of server truth. OPFS persistence, access-handle requirements, browser eviction, export, and reset behavior need product copy and recovery paths regardless of engine.

The data product owner cannot accept a local data authority map without dataset, source of truth, storage path, durability expectation, eviction response, export, and deletion. In the browser benchmark, that omission counts as missing evidence rather than polish.

Ingestion often decides before SQL does. DuckDB can query supported columnar data with little reshaping, while SQLite may require an import that buys later transactional behavior.

Record parse, copy, schema creation, and index time separately so a fast final query does not hide an expensive first-use conversion. This browser benchmark section turns OPFS persistence into the data product owner's counterexample for DuckDB-Wasm vs SQLite OPFS. Count conversion time as part of the first question the product can answer.

NeedDuckDB-WasmSQLite OPFSMeasure
Parquet scanNative fitImportFirst answer
Row updatesPossibleNative fitCommit
PersistenceDesign itOPFSRecovery
BundleMeasureMeasureCold load
Figure 2: Strengths become decisions only after delivery and recovery are included.

Keep database work inside owned workers

The next DuckDB-Wasm vs SQLite OPFS move belongs to the data product owner once browser analytics database changes the browser benchmark's result. Local persistence earns trust only after an interrupted write and a clean reopen.

Persistence is a product feature, not a storage checkbox. Close the tab mid-write, reopen after a browser restart, encounter a quota boundary, migrate a schema, and recover from a corrupted or incompatible file. OPFS behavior, worker ownership, and backup strategy determine whether local data feels dependable.

Run engine operations in a dedicated worker architecture that serializes or coordinates access according to the chosen library and OPFS mode. Expose a small message protocol with cancellation and result limits instead of sharing mutable engine handles across UI components.

Push the edge by attempting to issue concurrent queries, close a tab, restart a worker, and open a second tab. The browser benchmark fails when multiple tabs or components race one opaque connection, leaving the next correction with the data product owner.

Measure memory and result transfer

Columnar scans, Wasm heaps, imported buffers, SQLite pages, result materialization, and structured cloning can all inflate memory beyond database-file size. Track peak heap and transferred bytes while paging or aggregating results rather than returning every row to the UI.

Memory and concurrency become visible under a realistic session. Scan a large file while charts request related aggregates, or edit records while a background summary runs. Observe copies between main thread and worker, result materialization, lock waits, and cancellation. The best isolated engine can still create a poor interactive schedule.

First reproduce return small aggregate, paged detail, large group set, and accidental full table; then write the mismatch into a memory-and-transfer profile. This order gives the browser benchmark an inspectable debugging trail for the data product owner. Question DuckDB-Wasm vs SQLite OPFS through DuckDB-Wasm; the browser benchmark should expose the assumption the data product owner must own. Interactive contention can reverse a decision made from isolated query timings.

Rehearse interruption and schema change

Kill the worker during import, transaction, checkpoint, query, and export, then verify the next session's integrity and message. Apply representative schema migrations and engine upgrades to copied user data before claiming durable offline behavior. At this step, the data product owner reads SQLite Wasm as an explicit DuckDB-Wasm vs SQLite OPFS choice recorded by the browser benchmark. Bundle and startup costs belong beside database latency in a browser product.

Preserve failure point, committed state, database check, reopen result, migration path, user recovery, and residue alongside a local recovery corpus. The browser benchmark is auditable only when the data product owner can read that pair without private context.

Delivery cost includes Wasm modules, workers, helper libraries, and cold compilation. Measure compressed transfer, cache reuse, startup on a mid-range phone, and impact on the application's existing JavaScript. An analytical engine used once per month should not silently dominate every user's first navigation.

  1. 1Freeze

    Match the workload

  2. 2DuckDB

    Measure scans

  3. 3SQLite

    Measure writes

  4. 4Decide

    Price the session

Figure 3: Ingest, query, mutate, recover, and deliver form the matched session.

Price delivery and compatibility

Make the adverse case load on cold mobile hardware and on a returning cached desktop session. When the engine bundle is excluded from time-to-first-answer, the data product owner redraws the limit exposed by the browser benchmark.

The decision table should express workload thresholds rather than declare a universal winner. Columnar scans and Parquet may point toward DuckDB-Wasm; frequent transactional edits and app-owned durable state may point toward SQLite OPFS.

Mixed products can isolate responsibilities instead of forcing one engine to impersonate the other. Without a passing OPFS persistence case, the browser benchmark gives the data product owner no complete evidence for DuckDB-Wasm vs SQLite OPFS. A split architecture is valid when ownership between the engines remains explicit.

Record engine assets, worker code, optional extensions, initialization requests, compression, cache policy, cross-origin isolation needs, and supported browsers. An analytical win can still be wrong for a lightweight feature if the delivery tax dominates every short session.

Local-data product choices also appear in SQLite OPFS durable browser data, Yjs local-first editor, analytics briefing layer, and export buttons need data contracts. SQLite durability, local-first collaboration, analytics explanation, and export contracts consume local data for distinct jobs. Keep the database choice scoped to the measured analytics surface rather than making one engine the universal browser store.

Choose from the whole product trace

For release, a local engine decision matrix must agree with a run that can change the workload mix and identify exactly when the recommendation flips. The browser benchmark stays provisional until the data product owner explains any difference. The data product owner closes the DuckDB-Wasm vs SQLite OPFS release only after the browser benchmark demonstrates browser analytics database. Rerunnable evidence keeps the comparison useful as browsers and engines change.

Weight cold start, first result, repeated queries, writes, persistence, memory, recovery, bundle, and operating complexity according to real sessions. Publish raw traces and keep the losing engine available as a re-evaluation baseline rather than flattening the decision to one score.

Archive the benchmark as a runnable product trace with dataset digest, browser and hardware, engine versions, flags, cold and warm state, query set, mutation sequence, recovery result, bundle sizes, memory, and timing distributions. Future upgrades then compete against the same session rather than a remembered demo.

The fixture encodes the workload boundary: columnar Parquet scans choose DuckDB-Wasm and transactional offline writes choose SQLite OPFS.

Runnable artifact — local-analytics-selector.test.mjs

import assert from "node:assert/strict";
const score=(trace,weights)=>weights.scan/trace.scanMs+weights.write/trace.writeMs+weights.recovery/trace.recoveryMs-weights.bundle*trace.bundleMb;
const choose=(bench,weights)=>{const ranked=Object.entries(bench).map(([engine,trace])=>[engine,score(trace,weights)]).sort((a,b)=>b[1]-a[1]);return ranked[0][1]-ranked[1][1]<.002?"measure-both":ranked[0][0]};
const matched={"duckdb-wasm":{scanMs:24,writeMs:180,recoveryMs:92,bundleMb:7.2},"sqlite-opfs":{scanMs:110,writeMs:18,recoveryMs:28,bundleMb:1.4}};
assert.equal(choose(matched,{scan:10,write:1,recovery:1,bundle:.01}),"duckdb-wasm");assert.equal(choose(matched,{scan:1,write:10,recovery:8,bundle:.2}),"sqlite-opfs");
console.log("PASS: local analytics engine follows workload");

Run node local-analytics-selector.test.mjs. Expected receipt: PASS: local analytics engine follows workload.

Choose DuckDB-Wasm for a scan-heavy analytical workload when its columnar execution and data ingestion win the matched trace; choose SQLite OPFS when transactional application state and durable row updates dominate. Reopen the decision as browser storage, engine releases, data shape, or interaction patterns change.