What are Embedded Databases and Why Do They Exist?

Arpit Bhayani

Arpit Bhayani

Mar 25, 2022 • 10 min read

Play

Introduction: The Database You Carry Everywhere

When most software engineers think of a database, they picture robust, standalone server systems like MySQL, PostgreSQL, MongoDB, or Elasticsearch. These systems operate as dedicated services running on remote servers or containers, accepting queries over network sockets.

However, there is another foundational class of data storage systems that runs silently behind the scenes: embedded databases. Far from being esoteric edge cases, embedded databases are deployed on billions of devices worldwide. Every Android phone, iOS device, modern web browser, and framework like Django relies heavily on them.

Understanding embedded databases is critical not just for mobile or systems programmers, but for backend and distributed systems engineers looking to build ultra-low-latency caches, resilient ingestion pipelines, or large-scale partitioned storage systems.


Servered vs. Embedded Databases: Architectural Dissection

To understand why embedded databases exist, we must contrast their execution model with traditional client-server database architectures.

1. Servered (Client-Server) Databases

In a client-server database model, the database runs as an independent operating system process. It binds to a specific network port (e.g., port 3306 for MySQL, port 5432 for PostgreSQL) and listens for incoming connections.

[ Client / API Server ] --(TCP/IP Network Call)--> [ Database Server Process (Port 3306) ]
                                                                |
                                                          [ Local Disk ]
  • Separation of Concerns: The database lifecycle is decoupled from application servers. If an API server crashes, the database remains unaffected.
  • Network Overhead: Every read, write, and transaction incurs network serialization, socket communication, context switching, and network latency.
  • Operational Overhead: Requires dedicated provisioning, connection pooling, user access control, network topology management, and firewall configurations.

2. Embedded (In-Process) Databases

An embedded database does not run as an independent background daemon, nor does it open a network port. Instead, it is packaged as a library (in C, C++, Rust, Go, Java, etc.) that compiles directly into or links dynamically with your host application process.

+-------------------------------------------------------------+
| Host Application Process (e.g., Profile Service)            |
|                                                             |
|   [ Application Business Logic ]                            |
|                  |                                          |
|           (Function Call / C ABI)                           |
|                  v                                          |
|   [ Embedded Database Engine (SQLite / LevelDB / RocksDB) ] |
+------------------|------------------------------------------+
                   | (Local File I/O)
                   v
             [ Local Disk ]
  • In-Process Execution: The database code runs within the same memory address space as your application code. Queries and data manipulation operations are executed via direct in-memory function calls (or C Foreign Function Interfaces), completely eliminating TCP/IP round-trips.
  • Process Lifecycle Coupling: The database engine shares the application’s lifecycle. If the application process terminates, the database stops executing. The underlying data persists safely in local files on disk, ready to be reopened when the process starts again.
  • No External Access: External services cannot connect directly to an embedded database over a network port. Any interaction must pass through the parent application’s exposed APIs.

Naive Local Persistence vs. Embedded Engines

Consider an optimization problem: an API service needs to cache frequently accessed user profiles locally on a server to avoid calling a centralized database or network cache.

The Naive Approaches and Their Failures

  1. In-Memory Hash Maps (HashMap<ID, Profile>):

    • Limitation: Data is strictly volatile. If the service restarts, deployments occur, or memory pressure peaks, the cache is lost.
    • Memory Footprint: Constrained strictly by available physical RAM.
  2. Ad-Hoc File Storage (/data/profiles/{id}.json):

    • Limitation: Managing individual files per record leads to inode exhaustion, poor cache utilization, lack of atomic writes, concurrency race conditions, and abysmal random-read performance at scale.
    • Crash Resilience: Writing directly to disk without write-ahead logging (WAL) risks corrupted files during mid-write crashes.

Why an Embedded Database Wins

Embedded databases solve this exact niche. Rather than reinventing key-value persistence, transaction semantics, and index lookups, an embedded storage engine provides a battle-tested, crash-resilient mechanism directly inside your process. You get rich query semantics or blazing-fast key-value lookups with zero network overhead.


Prominent Embedded Database Engines

Different embedded databases specialize in different trade-offs:

DatabasePrimary ModelKey FeaturesNotable Usage
SQLiteRelational (SQL)Full ACID, single-file storage, rich indexing, compact footprintMobile OS (Android/iOS), Desktop apps, Django default DB
LevelDBKey-Value (LSM-Tree)High write throughput, sorted keys, single-threaded writerCreated by Google, used in Chrome for IndexedDB
RocksDBKey-Value (LSM-Tree)Multi-threaded parallelism, highly tunable for fast SSDs/NVMeForked from LevelDB by Meta, storage engine for Kafka Streams, CockroachDB, TiKV
Berkeley DBKey-Value / RelationalACID transactions, fine-grained locking, high concurrency, replicationEnterprise local caching, directory services
IndexedDBDocument / Key-ValueTransactional object store embedded inside web browsersClient-side web applications, browser URL history/autocomplete

SQLite: The Universal Relational Engine

SQLite implements a self-contained, serverless, zero-configuration SQL database engine. The entire database—tables, indices, triggers, and data—resides in a single cross-platform disk file. It provides full transactional integrity, allowing complex joins and index lookups without managing a database cluster.

LevelDB & RocksDB: The LSM-Tree Powerhouses

When raw write throughput and sequential I/O matter more than relational queries, Log-Structured Merge-tree (LSM) engines dominate:

  • LevelDB: Developed by Jeff Dean and Sanjay Ghemawat at Google. It accepts writes in memory (MemTable) and appends to a write-ahead log (WAL), later compacting them into immutable sorted string tables (SSTables) on disk in progressive levels. It excels at fast writes and sequential scans.
  • RocksDB: Forked from LevelDB by Facebook/Meta. LevelDB was designed when single-core processing and slower disks were the standard. RocksDB redesigned compaction algorithms, introduced multi-threaded background flushes, optimized CPU cache utilization, and adapted specifically for fast multi-core servers and flash/NVMe storage.

Architectural Patterns & Scalability Use Cases

Embedded databases are not limited to client devices; they are frequently leveraged as foundational building blocks in distributed backend architectures.

1. Ultra-High-Throughput Local Write Ingestion

When a service experiences an extreme write burst, pushing every record immediately across the network to a central cluster (like MySQL or Cassandra) can saturate network bandwidth and cause connection timeouts.

[ High-Volume Traffic ]
          |
          v
[ Ingestion Service Process ]
  ├──> Writes immediately to Embedded Engine (e.g., RocksDB on local NVMe)
  │    (Zero network latency, sub-millisecond local append)

  └──> [ Async Background Draining Thread ]
             │ (Batched, smoothed network calls)
             v
       [ Central Storage Cluster / Data Lake ]

Because the embedded engine writes locally to disk with WAL guarantees, the ingestion node can acknowledge incoming writes almost instantaneously without risking data loss. A separate background worker thread drains and batches the data to the central database asynchronously.

2. High-Performance Read-Only Distributed Partitions

In scenarios requiring thousands of read operations per second across massive datasets (such as machine learning feature stores, lookup tables, or search indexes):

  1. A central batch job builds an immutable embedded database file (e.g., an SQLite or RocksDB data file) offline.
  2. This pre-indexed database file is distributed to hundreds or thousands of API servers.
  3. Each API server opens the embedded database file locally.
  4. Every read request is answered strictly through local disk or page-cache reads. Network latency is zero, and the system scales reads horizontally simply by launching more API server instances.

3. Building Custom Partitioned Distributed Databases

Most modern distributed databases do not write their own raw disk persistence layers from scratch; they use embedded databases as the internal storage engine.

                       [ Client Request: PUT(Key, Value) ]
                                        |
                                        v
                              [ Routing DB Proxy ]
                             /          |          \
         Hash(Key) % 3 == 0 /           | == 1      \ == 2
                           v            v            v
                      [ Node 1 ]   [ Node 2 ]   [ Node 3 ]
                      +--------+   +--------+   +--------+
                      | Engine |   | Engine |   | Engine |
                      | RocksDB|   | RocksDB|   | RocksDB|
                      +--------+   +--------+   +--------+
                      | Local  |   | Local  |   | Local  |
                      | Disk   |   | Disk   |   | Disk   |
                      +--------+   +--------+   +--------+
  • The application exposes a distributed coordination layer (a proxy or consistent hashing ring).
  • Incoming queries are routed to the appropriate physical node based on key partitioning.
  • The node running the embedded engine writes the record directly to its local RocksDB or LevelDB instance.
  • Systems like CockroachDB, TiKV, and Kafka Streams leverage embedded engines (like RocksDB) as their underlying storage kernels for node-level persistence while implementing replication (e.g., Raft/Paxos) on top.

4. Client-Side and Edge Storage

  • Web Browsers (IndexedDB): Browsers maintain browsing history, typed URLs, and cached assets locally using embedded engines so users can search their address bars instantly without network round-trips.
  • Mobile Applications: Android apps use SQLite directly for local settings, offline-first data synchronization, and contact lists.

5. Hermetic Integration and Automated Testing

Spinning up dedicated database containers or shared remote database instances for unit and integration testing creates flaky test suites, slow CI/CD pipelines, and state pollution.

Using an embedded database (such as SQLite) allows tests to spin up an isolated database in-memory or in a temporary file, run complex SQL queries, and instantly tear down the state by simply deleting the file when the test finishes.


Trade-Offs and Architectural Limitations

While embedded databases provide unmatched latency and simplicity, they introduce trade-offs that make them unsuitable for certain workloads:

+------------------------------------+-------------------------------------+
| ADVANTAGES                         | LIMITATIONS                         |
+------------------------------------+-------------------------------------+
| • Sub-microsecond local access     | • Vertical scaling boundary         |
| • No network serialization/latency | • No built-in remote connectivity   |
| • Zero daemon management overhead  | • Concurrency limited by process    |
| • Single-file simplicity & testing | • Manual replication / failover     |
+------------------------------------+-------------------------------------+
  1. Coupled Failures: Because the database lives inside the application process, a segmentation fault or out-of-memory (OOM) crash in the application terminates the database.
  2. Concurrency Constraints: While servered databases handle thousands of concurrent client connections over network pools, many embedded databases (like SQLite) use coarse-grained file locks for writes, limiting highly concurrent multi-threaded write throughput.
  3. No Native Distributed Replication: Embedded databases typically lack out-of-the-box multi-node consensus, failover, and distributed transactions. If a machine’s disk fails, the data on that node is lost unless replication is implemented in the parent application layer.

Key Takeaways

  • In-Process vs. Client-Server: Embedded databases run as linked libraries within your application process, accessing disk directly and eliminating all network round-trips.
  • Right Tool for the Right Job: Use servered databases (PostgreSQL, MySQL) when multiple disparate services must query a centralized source of truth. Use embedded databases (SQLite, RocksDB) when data is strictly bounded to a single host, application, or edge node.
  • Engines Power Distributed Systems: Large-scale distributed databases frequently rely on embedded storage engines (like RocksDB) to handle fast, local, crash-resilient disk writes while the higher-level application manages network routing and consensus.
  • Stop Reinventing the Wheel: When local persistence or high-performance disk caching is needed, avoid custom file-writing logic. An embedded database gives you atomic writes, transactions, indexing, and crash recovery with zero operational overhead.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses