A small storage runtime for applications.

SQL, key-value state, durable jobs, files, search, logs and metrics in one directory, with one lifecycle, one memory budget and one backup.

import { open } from '@tinyshed/tinystore'

await using store = await open('./data')

const db = await store.database('app', { migrations: './migrations' })
await db.exec`insert into users (id, name) values (${42}, ${'Ada'})`

const sessions = store.bucket<Session>('sessions', { idle: '30d' })
await sessions.set(digest(token), { userId: 42 })

const emails = store.queue<Email>('emails')
await emails.add({ to: 'ada@example.com' })

const avatars = store.files('avatars')
await avatars.put('42.png', Bun.file('avatar.png'))

store.logger('api').info('user created', { userId: 42 })
store.metrics.counter('signups_total').inc()
async with await tinystore.open("data") as store:
    db = await store.database("app", migrations="migrations")
    await db.exec("insert into users (id, name) values (?, ?)", 42, "Ada")

    sessions = store.bucket("sessions", Session, idle="30d")
    await sessions.set(digest(token), Session(user_id=42))

    emails = store.queue("emails", Email)
    await emails.add(Email(to="ada@example.com"))

    avatars = store.files("avatars")
    await avatars.put("42.png", Path("avatar.png").read_bytes())

    store.logger("api").info("user created", user_id=42)
    store.metrics.counter("signups_total").inc()
// errors left out
store, err := tinystore.Open(ctx, "data")
defer store.Close()

db, err := sqldb.Open(ctx, store, "app", sqldb.Migrations(os.DirFS("migrations")))
_, err = db.Exec(ctx, "insert into users (id, name) values (?, ?)", 42, "Ada")

sessions, err := kv.OpenBucket[Session](store, "sessions", kv.Idle(30*24*time.Hour))
err = sessions.Set(ctx, digest(token), Session{UserID: 42})

emails, err := jobs.OpenQueue[Email](store, "emails")
_, err = emails.Add(ctx, Email{To: "ada@example.com"})

avatars, err := blobs.OpenFiles(store, "avatars")
_, err = avatars.Put(ctx, "42.png", avatar)

slog.New(records.NewHandler(store, "api")).Info("user created", "userId", 42)
signups, err := metrics.OpenCounter(store, "signups_total")
signups.Inc()
let store = Store::open("data", Options::default())?;

let db = store.database("app").migrations("migrations").open()?;
db.exec(tinystore::sql!("insert into users (id, name) values (?, ?)", 42, "Ada"))?;

let sessions = store.bucket::<Session>("sessions").idle(Duration::from_hours(24 * 30)).open()?;
sessions.set(digest(&token), &Session { user_id: 42 })?;

let emails = store.queue::<Email>("emails").open()?;
emails.add(&Email { to: "ada@example.com".into() })?;

let avatars = store.files("avatars").open()?;
avatars.put("42.png", &avatar)?;

tracing::info!(user_id = 42, "user created"); // through store.logger("api").layer()
store.metrics()?.counter("signups_total").open()?.inc();

Memory at rest

MiB, every engine open · lower is better

TinyStore
16.7MiB
The five services
402MiB
TinyStore tinystore serve, the server open() starts beside a program, with every engine open.
The five services PostgreSQL 18, Redis 8.10, MinIO, VictoriaMetrics 1.154 and Loki 3.7, each as it ships.
  • Loki 132.8 MiB
  • PostgreSQL 125.8 MiB
  • MinIO 109.1 MiB
  • VictoriaMetrics 26.0 MiB
  • Redis 8.8 MiB

TinyStore 0.4.0 and each of the five services after a minute at rest, every engine open, medians of three passes from the at-rest round of 27 October. Every figure at rest

01 SQL Relational state. Your own SQLite databases: checked migrations, a query builder. 02 KV Application state. Values with expiry and versions; counters, rate limits, quotas, once. 03 Jobs Background work. Work that runs at its time: retries, steps, schedules, limits. 04 Blobs Files. Files by path, written whole or not at all, checked when read. 05 Search Words and vectors. Indexes of documents or of your tables, by words, by meaning, or both. 06 Channels Messages. Messages to whoever listens now, sent from a transaction at its commit. 07 Locks Coordination. Locks and semaphores with fences, across processes and connections. 08 Config Settings. Files and the environment in layers; secrets; settings changed live. 09 Records Logs and events. Read back by time, level, fields and text; followed as they arrive. 10 Metrics Time series. Samples kept bit for bit, answered exactly. 11 Backup The whole store. One checked zip of every engine, taken while it runs.

Numbers

Measured against what it replaces.

Ready to serve

from start to every engine open · lower is better

TinyStore 135 msThe five services 15.4 s

An application, 64 clients

requests a second · higher is better

TinyStore 26 kIn process 28 kThe five services 15 k

An application, one client

requests a second · higher is better

TinyStore 756In process 788The five services 2.0 k

An application, memory

MiB at 64 clients, program included · lower is better

TinyStore 205In process 196The five services 878

A key read, one at a time

µs, median · lower is better

In process 6TinyStore 86Redis 43Valkey 42

Metrics on disk

bytes a TSBS sample · lower is better

TinyStore 1.00VictoriaMetrics 0.71Prometheus 1.58

Logs on disk

% of LogHub's text · lower is better

TinyStore 14.2 %Loki 19.3 %zstd -19, a file 5.5 %
TinyStore
tinystore serve, the server open() starts beside a program, with every engine open.
In process
The same core loaded into the program: open(dir, { embedded: true }).
The five services
PostgreSQL 18, Redis 8.10, MinIO, VictoriaMetrics 1.154 and Loki 3.7, each as it ships.

v0.1.0, medians of three passes in one Linux container on a Ryzen 7 7700, driven from Bun 1.4.2 with Bun's own Redis, PostgreSQL and S3 clients; the memory and the start at rest are v0.4.0's, measured again there. These are not universal wins: with one to eight clients the services answer twice the requests, Redis takes writes that skip the disk sync six times faster, and VictoriaMetrics keeps a sample in fewer bytes. Every test, the wins and the losses · The round's report ↗

  • Bun
  • Python
  • Go
  • Rust
Start here
esc
↑↓ navigate ↵ open ⇥ filter 0 results