# Why Replace Redis with SQLite for Bun Job Queues

Discover how bunqueue can use SQLite instead of Redis for durable single-broker queues, with a PostgreSQL multi-broker path when you scale.

Canonical: https://bunqueue.dev/blog/why-bunqueue/

---

import { Aside } from '@astrojs/starlight/components';
import BqIcon from '@components/BqIcon.astro';

<div class="bq-wrap bq-hero">
  <span class="bq-eyebrow">blog · design</span>
  <h1 class="bq-hero-h1 bq-bench-h1">A job queue without <em>Redis.</em></h1>
  <p class="bq-hero-sub">BullMQ, Bee-Queue, and friends all assume a running Redis server. bunqueue can keep a durable queue in-process with SQLite instead: simpler deployments and zero external services, with a separate PostgreSQL multi-broker path when you scale. Unconfigured queues use memory storage.</p>
</div>

## The Redis Problem

Redis is excellent software, but it introduces real operational complexity for job queues:

- **Another service to manage** - deploy, monitor, backup, scale
- **Network latency** - every job operation crosses the network
- **Memory limits** - Redis stores everything in RAM
- **Connection management** - pool sizing, reconnection, timeouts

For many applications, especially single-server deployments and small teams, this overhead isn't justified.

## Why SQLite?

SQLite runs in-process. There's no network hop, no connection pool, no separate service to manage.

<div class="bq-cards">
  <div class="bq-card">
    <BqIcon name="file" />
    <h3>Zero Dependencies</h3>
    <p>No Redis, no external services. Just your app and a file on disk, with <code>msgpackr</code>{' '} as the only runtime dependency.</p>
  </div>
  <div class="bq-card">
    <BqIcon name="speed" />
    <h3>In-Process Speed</h3>
    <p>Direct memory access instead of TCP round-trips to Redis.</p>
  </div>
  <div class="bq-card">
    <BqIcon name="shield" />
    <h3>Persistence When Selected</h3>
    <p>Set a SQLite data path for WAL-backed recovery with concurrent reads; without one, the queue is intentionally in memory.</p>
  </div>
  <div class="bq-card">
    <BqIcon name="network" />
    <h3>Simple Backups</h3>
    <p>Copy one file, or use built-in S3 backup.</p>
  </div>
</div>

## Bun Makes It Possible

Bun's native SQLite driver (`bun:sqlite`) is what makes this architecture viable. It's not a Node.js addon compiled from C - it's built directly into the runtime with zero-copy optimizations.

```typescript
import { Database } from 'bun:sqlite';

const db = new Database('./queue.db', { create: true });

// WAL mode for concurrent reads + writes
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA synchronous = NORMAL');
```

This gives us prepared statements, transactions, and WAL mode with performance that rivals in-memory datastores for our workload patterns.

## The Write Buffer Strategy

Raw SQLite writes are fast, but bunqueue goes further with a **write buffer** that batches disk operations:

```typescript
// Jobs are added to an in-memory priority queue immediately
// The write buffer flushes to SQLite every 10ms
const queue = new Queue('emails', {
  embedded: true,
  dataPath: './data/bunqueue.db',
});

// This returns instantly - job is in memory
await queue.add('send', { to: 'user@example.com' });

// For critical jobs, bypass the buffer:
await queue.add('payment', data, { durable: true });
```

The `durable: true` option bypasses bunqueue's write buffer and commits to
SQLite before `add()` returns, trading throughput for a smaller process-crash
window. Host, filesystem, and physical-media durability still depend on the
deployment.

<Aside type="tip">
  In the current native campaign, public on-disk Embedded `addBulk` reached a 186,384 jobs/s median,
  while sequential SQLite `durable: true` Embedded adds reached 60,835 ops/s. These are different
  workloads; see the [benchmark methodology](/guide/benchmarks/) before comparing them.
</Aside>

## When to Use bunqueue

bunqueue's SQLite mode is designed for **single-broker deployments**. Choose it when:

- You run your app on a single server or VPS
- You want zero operational overhead for background jobs
- You need reliable persistence without managing Redis
- You're building on Bun and want native performance

Choose bunqueue's PostgreSQL 15–18 server backend when several active brokers
must share one authoritative queue; PostgreSQL 18.6 is recommended. The Queue,
Worker, Flow, cron, retry, result, and DLQ contracts stay the same. BullMQ remains
a solid choice when Redis or Redis Cluster is already your required operational
standard. See [Storage backends](/guide/databases/) for the topology boundary.

<Aside type="note" title="A genuinely small install">
  Eliminating Redis isn't just an operational win, it shows up in your `node_modules` too.
  `msgpackr` is the only runtime dependency; Bun provides cron parsing, SQLite, HTTP, WebSocket and
  S3. The MCP server's `@modelcontextprotocol/sdk` is an optional peer dependency, loaded only if
  you run the `bunqueue-mcp` bin, so queue and worker users never download it.
</Aside>

## Go Deeper

Two deep dives into bunqueue's architecture pick up where this post leaves off: the [sharding deep dive](/blog/sharding-deep-dive/) covers how jobs are distributed across CPU cores, and the [auto-batching post](/blog/auto-batching/) explains how TCP mode gets close to embedded-mode throughput for concurrent producers.