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 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
Numbers
Measured against what it replaces.
Ready to serve
from start to every engine open · lower is better
An application, 64 clients
requests a second · higher is better
An application, one client
requests a second · higher is better
An application, memory
MiB at 64 clients, program included · lower is better
A key read, one at a time
µs, median · lower is better
Metrics on disk
bytes a TSBS sample · lower is better
Logs on disk
% of LogHub's text · lower is better
- 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