Master Bun runtime SQLite performance by choosing between in-memory and persistent storage. Learn how to optimize your backend database strategy today.
I recently spent three days refactoring a high-throughput microservice that was struggling with disk I/O latency. We were running on the Bun runtime, and while the speed of the engine itself was impressive, our database layer—SQLite—was becoming the bottleneck.
It’s easy to assume that because SQLite is "fast," it’s always the right choice for every workload. But when you’re pushing thousands of requests per second, the distinction between an in-memory database and a persistent file-backed one becomes critical to your database optimization strategy.
The Bun runtime includes a high-performance, built-in SQLite driver that is significantly faster than standard better-sqlite3 implementations in Node.js. It’s designed to handle concurrent reads and writes with minimal overhead. However, the storage medium you choose dictates how that speed translates into real-world latency.
In our case, we were seeing p99 latencies jump to around 280ms during traffic spikes. We initially assumed the issue was our query complexity, but after killing N+1 queries at the database layer, we realized the I/O wait times were still too high. We were writing to a standard NVMe drive, but the synchronous commits were killing our throughput.
If you’re building a cache, a temporary session store, or a scratchpad for data processing, in-memory SQLite is a superpower. Since the data lives entirely in RAM, you eliminate the physical disk latency entirely.
Here is how you initialize an in-memory database in Bun:
JAVASCRIPTimport { Database } from "bun:sqlite"; // The special CE9178">':memory:' string tells SQLite to keep it in RAM const db = new Database(":memory:"); db.query("CREATE TABLE cache(key TEXT PRIMARY KEY, value TEXT)").run();
The performance gain here is massive—usually an order of magnitude faster for write-heavy operations. However, the trade-off is durability. If your process crashes, your data is gone. I’ve found that using this for anything other than ephemeral data is a recipe for a 3 AM pager alert. If you're dealing with complex analytical data, you might instead prefer materialized views for database performance in complex analytical queries to cache results without losing persistence.
For most production apps, you need data that survives a restart. This is where SQLite performance tuning becomes more of an art. When you move to a persistent file, you are at the mercy of your filesystem's sync speed.
We initially tried to "fix" our slow writes by disabling fsync entirely, which is a dangerous move. Instead, we shifted our configuration to use Write-Ahead Logging (WAL) mode. WAL allows multiple readers and one writer to operate simultaneously without blocking each other.
JAVASCRIPTconst db = new Database("prod_data.db"); db.exec("PRAGMA journal_mode = WAL;"); db.exec("PRAGMA synchronous = NORMAL;");
By switching to NORMAL synchronous mode, we told SQLite to only sync the data to disk at critical moments. This gave us roughly a 1.8x speed increase in write operations without sacrificing the integrity of the data in the event of a power failure.
When deciding between these two approaches, ask yourself these three questions:
:memory:.We also looked into database schema design: implementing shadow columns for soft deletes to keep our persistent tables lean, which helped reduce the amount of data the engine had to scan during lookups.
Working with the Bun runtime has shifted how I view Node.js alternatives. The native integration of SQLite is cleaner, but it doesn't absolve you from understanding how the engine interacts with your OS.
Next time, I’d likely experiment with mounting the database file on a tmpfs (RAM disk) instead of using the :memory: flag. This would give us the performance of memory with the ability to perform manual backups by simply copying the file. It’s a middle ground that provides a safety net while keeping the performance metrics well within our SLAs.
Does Bun’s SQLite driver support multi-threading? Bun’s SQLite driver is designed for its event-loop model. While it handles concurrent requests well, SQLite itself is a single-writer engine. If you need massive write concurrency, you might need to reconsider your architecture.
Is it safe to use in-memory SQLite in a serverless environment? Only if your data is truly transient. Remember that serverless environments can spin down at any time, and you will lose all data in the memory space immediately.
Can I switch from in-memory to persistent later? Yes, but it requires significant code changes to handle the initialization and potential migration logic. It’s better to choose your storage strategy during the early design phase.
Master Postgres logical decoding for real-time CDC. Learn how to stream database changes effectively to build robust, event-driven architectures today.