Virke DB
Full SQL at the edge. Virke DB gives you a SQLite database running on Fastly Compute@Edge via rusqlite, with auto-tiered storage that scales from zero with no hard size limit.
How It Works
- On each request, the SQLite database is loaded from KV Store (or Object Storage for larger databases)
- Your SQL query executes against the in-memory database using rusqlite
- For writes, the updated database is serialized back to storage
Auto-tiered storage
| Tier | Size | Backend | Read Latency | Write Latency |
|---|---|---|---|---|
| KV Store | 0–20 MB | Fastly KV Store | 9–120ms | 430–2,070ms |
| Object Storage | 20 MB+ | Fastly Object Storage + CDN | 2–12ms (cached) | Similar |
Create a Database
Via CLI
virke db create my-db
Via virke.toml
[project]
name = "my-app"
[database]
name = "my-app-db"
The database is created automatically on first deploy.
CLI Usage
Run a read query
virke db query "SELECT * FROM users WHERE active = 1"
id name email active
-- ------ ----------------- ------
1 Alice alice@example.com 1
3 Carol carol@example.com 1
Run a write statement
virke db execute "INSERT INTO users (name, email, active) VALUES ('Dave', 'dave@example.com', 1)"
View schema
virke db schema
Import a SQL dump
virke db import backup.sql
Database status
virke db status
Performance settings
virke db settings # view/update settings
virke db flush # force sync pending writes
Promote to higher tier
virke db promote
Promotes a database from KV Store to Object Storage for larger datasets.
SDK Usage (from Virke Run)
With Hono (recommended)
import { Hono } from "hono";
import { virke } from "@virke/runtime/hono";
const { fire, middleware, Bindings } = virke({ db: "my-app-db" });
const app = new Hono<{ Bindings: typeof Bindings }>();
app.use("*", middleware);
app.get("/users", async (c) => {
const result = await c.env.db.query("SELECT * FROM users");
return c.json(result.rows);
});
app.post("/users", async (c) => {
const { name, email } = await c.req.json();
const affected = await c.env.db.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
[name, email]
);
return c.json({ created: affected });
});
fire(app);
DB binding API
interface VirkeDB {
query(sql: string, params?: unknown[]): Promise<QueryResult>;
execute(sql: string, params?: unknown[]): Promise<number>;
}
interface QueryResult {
columns: string[];
rows: unknown[][];
rows_affected: number;
}
Parameterized Queries
Always use parameterized queries to prevent SQL injection:
// Safe — parameterized
const result = await env.db.query(
"SELECT * FROM users WHERE email = ?",
[userInput]
);
// Unsafe — never do this
// const result = await env.db.query(`SELECT * FROM users WHERE email = '${userInput}'`);
Performance
| Operation | Latency |
|---|---|
| Simple SELECT (single row) | 9–15ms |
| SELECT with JOIN (100 rows) | 25–50ms |
| Complex aggregation | 50–120ms |
| Single INSERT | 430–600ms |
| Batch INSERT (100 rows) | 800–2,070ms |
Tips for best performance
- Batch writes — combine multiple writes into a single transaction
- Index frequently queried columns — SQLite indexes work as expected
- Keep databases small — the entire database loads on each request
- Read-heavy patterns — optimized for workloads where writes are infrequent
Concurrency
Virke DB uses optimistic concurrency control (OCC) via Fastly KV Store's if_generation_match. No lost writes, no locks, and conflicts are detected and surfaced.
Migrations
Place SQL migration files in a migrations/ directory:
migrations/
001_create_users.sql
002_add_posts_table.sql
003_add_user_avatar.sql
-- migrations/001_create_users.sql
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at TEXT DEFAULT (datetime('now'))
);
virke db migrate
Limitations
- Single writer per database — only one write can succeed at a time (OCC ensures correctness)
- Eventual consistency — reads from different POPs may briefly see old data after a write
- No hard size limit — Object Storage tier has no size cap. Practical ceiling is ~100 MB for the serialize/deserialize model (deserialization scales at ~8ms/MB).
- No wall clock —
datetime('now')returns the request timestamp
Further Reading
- Virke Run — Build compute functions that use Virke DB
- CLI Commands — Full
virke dbcommand reference