duckdb-swift

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

1post · 30d
Nov 40 posts
  1. DuckDB 2.0 as a Mobile Analytics Engine: Embedding In-Process OLAP in Android and iOS Apps --- title: "DuckDB 2.0 LTS: Embedding In-Process OLAP in Android and iOS Apps" published: true description: "DuckDB 2.0 LTS brings production-grade embedded OLAP to Android and iOS — query local Parquet files without a network hop, backed by a stable release window." tags: android, ios, kotlin, architecture canonical_url: https://mvpfactory.co/blog/duckdb-2-mobile-analytics --- ## What we will build Let me show you a pattern I use in every project that does serious on-device analytics. By the end of this tutorial you will have DuckDB 2.0 LTS running inside an Android app via Kotlin and an iOS app via Swift Package Manager — querying local Parquet files with columnar performance that leaves SQLite in the dust on analytical workloads. --- ## Prerequisites - Android: Kotlin, Gradle, familiarity with JDBC - iOS: Swift 5.9+, Xcode 15+, SPM basics - Basic SQL knowledge - Some local Parquet files to query (or willingness to generate them) --- ## Why bother with embedded OLAP at all Every aggregation you route to the backend costs you latency, breaks offline scenarios, and generates backend load that scales with user engagement. The real question is *where does the data already live*. If it lives on device, move the query there. DuckDB's columnar execution model, vectorized query engine, and zero-dependency footprint make it the first credible candidate for this layer. The 2.0 LTS release gives you a stable dependency to ship against — that was the missing piece. --- ## Step 1 — Android: JNI bridging with Kotlin DuckDB ships a JVM artifact. Add it to your Gradle deps: dependencies { implementation("org.duckdb:duckdb_jdbc:2.0.0") } Here is the minimal setup to get this working: Class.forName("org.duckdb.DuckDBDriver") val conn = DriverManager.getConnection("jdbc:duckdb:/data/local/analytics.db") conn.createStatement().use { stmt -> val rs = stmt.executeQuery( "SELECT date_trunc('day', event_ts) AS day, COUNT(*) AS events " + "FROM read_parquet('/data/local/events/*.parquet') " + "GROUP BY 1 ORDER BY 1 DESC LIMIT 30" ) while (rs.next()) { Log.d("Analytics", "${rs.getString(1)}: ${rs.getLong(2)}") } } The JNI bridge adds negligible overhead for analytical queries — the bottleneck is I/O or the query itself, never the JVM boundary. Package `.so` for `arm64-v8a` and `x86_64` only; shipping `armeabi-v7a` for OLAP workloads is not worth the APK weight. --- ## Step 2 — iOS: Swift Package Manager Add the Swift package: .package(url: "duckdb/duckdb-swift", from: "2.0.0") import DuckDB let db = try Database(store: .file(atPath: analyticsPath)) let conn = try db.connect() let stmt = try conn.prepare(""" SELECT strftime('%Y-%m-%d', event_ts) AS day, COUNT(*) AS events FROM read_parquet(?) GROUP BY 1 ORDER BY 1 DESC LIMIT 30 """) let result = try stmt.execute(parquetGlob) Background thread execution via `async/await` or a `DispatchQueue` is mandatory here. You do not want columnar scans blocking the main run loop. --- ## Step 3 — Set your memory budget Mobile is not a server. Your process budget is typically 300–512 MB before the OOM killer gets interested. The docs do not always emphasize this, but setting these two lines is non-negotiable in production: SET memory_limit = '256MB'; SET temp_directory = '/data/local/tmp/duckdb_spill'; The spill directory lets DuckDB page intermediate results to disk under memory pressure. Reserve in-memory execution for foreground queries; allow disk spill for background aggregations. --- ## Performance baseline On a Pixel 7 (Android 14), against 1M rows: | Workload | SQLite | DuckDB 2.0 | |---|---|---| | COUNT + GROUP BY | ~1,800ms | ~90ms | | Filtered aggregation (10% selectivity) | ~900ms | ~35ms | | Multi-column window function | ~4,200ms | ~210ms | | Cold read, 50MB Parquet | ~600ms | ~180ms | The gains come from column pruning, SIMD-accelerated vector operations, and Parquet's native columnar layout aligning with DuckDB's execution model. --- ## Gotchas **Never interpolate file paths into query strings on iOS.** Always use prepared statement parameters — `parquetGlob` in the example above is derived from the app sandbox at init time. If it ever accepts external input, the prepared-parameter pattern is mandatory. **Cold-start latency.** DuckDB's first-query cold-start on a freshly opened connection adds perceptible latency. Pre-warm connections on app foreground if your latency budget is tight. **Do not use DuckDB for mutation-heavy workloads.** Frequent INSERT/UPDATE/DELETE patterns work against the engine's grain. Keep SQLite or Room for mutable app state; DuckDB owns the immutable analytics path. **Sub-10K rows?** SQLite is fast enough. The JNI/SPM overhead, binary size, and operational complexity do not pay off at low cardinality. --- ## Conclusion Three things to take away: 1. Adopt DuckDB 2.0 LTS for any mobile analytics feature with >100K rows. The columnar execution advantage over SQLite compounds significantly at this scale. 2. Set `memory_limit` explicitly and provide a spill directory. Do not let DuckDB discover your memory ceiling at runtime. 3. Structure data as append-only Parquet partitioned by time. This unlocks partition pruning and aligns perfectly with DuckDB's read path. The architecture is straightforward: SQLite/Room handles mutable app state, DuckDB + Parquet owns the analytics layer, and a background worker writes append-only Parquet files at the sync boundary. Clean separation, no lock contention, and the analytics layer becomes trivially replaceable.

    1 reaction

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

Scan pending.

Live mentions badge

Show how many posts mention your project in the last 30 days.

Live mentions · last 30 days
duckdb/duckdb-swift mentions in the last 30 days