← Rahul Pahuja
Blog

Secure DB Proposal

2026-05-07

We propose a reactive, coroutine-native embedded database runtime for mobile (Android/iOS) that unifies storage, sync, event streams, analytics, vector search, and security in one lightweight engine. It builds on proven ideas (e.g. MVCC snapshots, actor-based concurrency, CRDT sync) and modern mobile platforms (Kotlin Multiplatform + Swift). Unlike plain SQLite/Room, this runtime offers built-in RBAC/row-level security, a push-button sync queue, immutable event streaming, vector search for AI, and a user-friendly DSL for DDL/DML. Phased development (V1–V4) gradually layers features: starting with a coroutine/Flow-first ORM on SQLite, then adding sync, security and analytics, then vector/AI support, and finally optionally a custom storage engine. Key trade-offs include using SQLite (ubiquitous, small) vs. a new MVCC/LSM engine (high performance, complexity). We outline architecture diagrams, data models, concurrency and transaction semantics, and pipelines for sync and analytics. We compare against Room/SQLite, Realm, ObjectBox, SQLDelight, etc. This design addresses modern needs (offline-first, edge-AI, privacy) while minimizing boilerplate.

Goals and Scope

Target Platforms & Stack

Architecture Overview

Figure — Component breakdown. The LocalRuntime (ORM/SDK layer) invokes the StorageEngine (SQLite or custom) via an actor-based scheduler for thread safety. A ReactiveEngine tracks dependencies to update Flows. A SyncModule handles queued network sync. An EventBus logs every change. A SecurityLayer enforces RBAC policies. A VectorIndex sits atop storage for similarity search. An AnalyticsPipeline consumes events for aggregation/upload.

Storage Engine Options

SQLite (Phase 1): Use SQLite (WAL mode) as the disk backend. This yields a tiny footprint (<1 MB) and cross-platform support. We would shard the single-file DB if needed. On writes, a dispatcher routes queries through a single shared connection (Room's model). The downside: at most one writer at a time; heavy concurrent writes will queue (Room/SQLite have known scaling issues). Indices (B-tree) for exact lookups, plus SQLite FTS5 or RTree for some queries. We can add "vector" columns using JSON/BLOB + custom UDFs or rely on an external index (see Stoolap's EMBED/HNSW).

Custom Engine (Phase 4): Build a new KV/SQL engine in Rust or C++. Key features:

Concurrency Model

actor<DBCommand> { for (cmd in channel) { process(cmd) } }

This avoids manual locks and leverages Kotlin's structured concurrency. Actors can batch commands or serialize execution on table shards.

Reactive Query Model

Example usage:

// Kotlin: define a reactive query with DSL
val adultsFlow: Flow<List<User>> = db.query {
    SELECT * FROM Users WHERE age > 18 ORDER BY name
}.toFlow()

adultsFlow.collect { list ->
    // update UI with latest list of adult users
}

In Swift:

let adultsPublisher: AsyncStream<[User]> = db.query("Users")
    .filter("age", .greaterThan(18))
    .sorted(by: "name")
    .asPublisher()

Task { for await list in adultsPublisher { /* update UI */ } }

Sync Queue Design

Local Queue Table: All mutating actions that need server sync are first recorded in a persistent sync_queue table:

sync_queue(
  id UUID PK,
  action TEXT,        -- e.g. "InsertUser"
  payload BLOB,       -- JSON or protobuf of the data
  status TEXT,        -- "pending", "synced", "failed"
  priority INT,
  retries INT,
  last_attempt DATETIME
)

Figure — Sync queue pipeline. Application writes go to both the local table and sync_queue. A background worker processes the queue, communicates with the server, and updates statuses. Retries/backoff are handled in the worker.

Event Stream Model

Use cases:

Figure — Event bus. Each local DB mutation emits an event. Various subscribers (UI, analytics, sync, etc.) consume the immutable event stream.

Analytics Ingestion Pipeline

Example: if a user taps "purchase", log { event: "purchase", amount: 9.99, timestamp: 12345678 }. Later the pipeline sends daily or hourly batches to the cloud analytics endpoint, greatly reducing network calls and improving battery life.

Vector Store and Embeddings

Vector Columns: Allow tables to declare a column of type VECTOR<float> (e.g. dimension 128 or 384):

@Entity data class Doc(
    @Id val id: Long = 0,
    val content: String,
    @VectorIndex(dimensions = 384) val embedding: FloatArray
)

Under the hood, the embedding is stored as a BLOB or custom binary, and indexed with an HNSW graph.

Example:

// After storing document embeddings:
val results = db.query {
    SELECT * FROM Documents ORDER BY
    VEC_DISTANCE(embedding, vector) ASC LIMIT 3
}.execute()

This returns the 3 most semantically similar docs.

Security: RBAC and Encryption

Roles & Policies: Built-in support for user authentication and roles. The developer can declare roles and permissions in code:

role("admin") {
    canRead(usersTable)
    canWrite(ordersTable)
}
role("guest") {
    canRead(usersTable.filter { it.status == "public" })
}

Row-level security: attach policies to tables (like PostgreSQL RLS):

policy(usersTable) { currentUser.id == it.createdBy }

These policies are checked inside the DB layer, so no illicit data leaks through the API. Couchbase Mobile shows this pattern with RBAC and fine-grained controls.

Field-Level Permissions: Columns can be marked sensitive:

table<Payroll>("payroll") {
    column(Payroll::salary).onlyRole("manager", "hr")
}

Attempting to read the salary field without those roles throws an auth error.

Schema Migrations

Versioned Migrations: Use the DSL to define schema versions. Each schema change increments a version and provides a migration function. Ideally these are auto-generated or diffed by a KSP plugin:

database.migration(1, 2) { db ->
    db.addColumn("users", "lastLogin", type = "INTEGER")
}

Performance, Memory and Battery

Developer Ergonomics (DSL & APIs)

Kotlin DSL & Codegen: Use Kotlin type-safe builders and KSP to minimize boilerplate. Entity/table definitions:

table<User>("users") {
    column(User::id).primaryKey()
    column(User::email).unique()
    column(User::age)
    encryptedColumn(User::salary)  // field-level encryption
}

Queries via a fluent DSL or annotations:

val seniorAdmins: Flow<List<User>> = db.query {
    SELECT * FROM users
    WHERE role == "admin" AND age > 50
    ORDER BY lastLogin DESC
}.toFlow()

Swift DSL: On iOS, a Swift-friendly API:

let schema = Schema {
    Table<User>("users") {
        Column(\.id).primaryKey()
        Column(\.name).unique()
    }
    Table<Message>("messages") {
        Column(\.id).primaryKey()
        Column(\.text)
        Column(\.conversationId)
    }
}

Phased Roadmap (V1–V4)

Phase 1 — Core ORM + Reactive Queries on SQLite (6–9 months, 3–4 engineers)

Phase 2 — Security + Analytics + Stability (6 months)

Phase 3 — AI/Vector + Advanced Sync (6 months)

Phase 4 — Custom Engine (optional / long-term, 6+ months)

Prioritized Feature List

  1. Reactive queries & coroutines (core requirement)
  2. Sync queue & offline sync (enables offline-first)
  3. RBAC/encryption (security-by-default)
  4. Event streams & analytics (data-driven features)
  5. DSL for schema/queries (developer UX)
  6. Vector search/AI (forward-looking edge AI)
  7. Multiplatform bindings (KMP, Swift)
  8. Custom engine (MVCC) (long-term high performance)

Risk Analysis

Effort Estimates & Team Roles

Comparison vs Room/SQLite/Realm/ObjectBox/SQLDelight

Feature / Tool Room/SQLite Realm (Mongo) ObjectBox SQLDelight Proposed DB Runtime
Model SQL-relational Object DB Object (NoSQL) SQL with Kotlin codegen SQL/NoSQL hybrid with DSL
Transactions ACID, single-thread write MVCC (copy-on-write) ACID, MVCC-like ACID (wrapper) MVCC + actor model (snapshots)
Concurrency 1 writer, many readers (WAL) Readers non-blocking; writes block writers Multi-thread safe Single DB instance (native SQLite) Multi-writer, snapshot isolation
Reactive Queries Flow with InvalidationTracker Live objects (notifications) LiveData equivalents Manual (no built-in) Native Flow/Combine, reactive engine
Offline Sync DIY only Sync service (deprecated) DataSync product (paid) None Built-in sync queue + optional cloud
Vector Search None None Yes (HNSW in 4.0) None Yes, built-in HNSW and embed models
RBAC / RLS None No (deprecated) No None Yes, built-in roles & row policies
Encryption Optional (SQLCipher) Built-in encryption Transport; at-rest on request None Yes (AES-256 at-rest by default)
Schema via DSL Room annotations (strings) Realm Object schema Object entities / sync annotations Kotlin interfaces & .sq files Fluent Kotlin/Swift DSL, codegen
Multiplatform Android (Java/Kotlin) only Android, iOS Android, iOS, Flutter Android, iOS True KMP + Swift + WASM (planned)
Size (footprint) <1 MB (SQLite engine) ~8 MB (library) ~1–3 MB <1 MB (JNI) <5 MB (SQLite) or larger (custom)
Vendor lock-in None (open) Proprietary, being phased out Open core, enterprise Open source Open source / permissive

Room/SQLite are robust but boilerplate-heavy. Realm's Mongo Sync was sunset; ObjectBox recently added vectors. Couchbase Mobile (not in table) offers many features (RBAC, vector).

Recommended Sources for Further Reading