engine 1.5.0 · storage format 2 · 151 tests green

Forty kilobytes.
A real database.

asmdb is a transactional database engine written from scratch in x86-64 assembly. NASM emits the executable directly — no linker, no C runtime, no libraries. It is fast because it does far less: one fixed-shape row, one hash-indexed table, no parser, no planner.

The asmdb mark: a database cylinder at the centre of a circuit die

11,959,261

rows per second, inserted in RAM.
Measured, one core.

42,733 B

the entire engine, PE64.
51,205 B as ELF64.

0

third-party dependencies.
No linker. No C runtime.

4,194,304

rows per instance.
256 bytes each, fixed.

~104 ms

to open a one-million-row
database. 5 MB resident.

The engine

A database is a table. That is the whole model.

One file holds one table. That same file is simultaneously the unit of durability, of locking, of transactions, of backup and of change capture. There is no catalogue to learn and nothing to configure — which is why it fits in forty kilobytes.

One row · 256 bytes · fixed at build time
idu64 — primary key, unique, ≥ 1 O(1) lookup through a hash index
valuei64 — the numeric column the only column RANGE can filter on
tag39 bytes — one token, no spaces longer input is refused, never truncated
content175 bytes — free text UTF-8 preserved byte for byte
created · updatedi64 — unix epoch milliseconds set by the engine, not the caller
$ asmdb notes
asmdb> INSERT 1001 5 project asmdb is written in assembly
[ OK ] 1 row inserted
asmdb> BEGIN
[ OK ] transaction started
asmdb> UPDATE 1001 9 project revised note
[ OK ] 1 row updated
asmdb> COMMIT
[ OK ] transaction committed
asmdb> VERIFY
[ OK ] verify: 1 row(s) checked, no problem found
Real output. FORMAT TSV switches the same commands to a machine-readable stream.

durability

Committed means committed

Every transaction goes through a write-ahead log: the changes are staged and flushed, a commit marker and its CRC-32 are flushed, a change frame is appended and flushed, and only then is the data file touched. A crash resumes from the log. Whole-table operations are announced in the header before they start, so an interrupted TRUNCATE finishes on the next open instead of leaving half a table.

capture

A durable log of every change

Each committed transaction appends one frame to an append-only change log — a total order, a dense sequence, and enough identity to refuse a log that belongs to a different database. Anything downstream can follow the database exactly, and CDCTRIM reclaims what has been acknowledged.

concurrency

One writer, unlimited readers

Reader sessions take no lock at all and run beside the writer. Each command is bracketed by the commit sequence, so a reader never observes a half-applied transaction. Writes do not scale within one instance — that is a real limit, and it is the reason the unit of scale here is the instance.

honesty

What it will not do

No SQL, no joins, no query planner, no secondary indexes — a FIND scans the whole slot region. No authentication, no encryption and no audit log inside the engine; that is the platform's job, and it is written down rather than implied.

Model Context Protocol

A front door built for agents, not bolted on.

Every instance speaks MCP over HTTP with the same tools as the local stdio server. Point a Model Context Protocol client at the endpoint and an agent has durable, transactional memory — with real timestamps, real transactions and a change log, instead of a vector blob it cannot audit.

db_insert

store a row by key or numeric id

db_get

read one row, whole and untruncated

db_update

overwrite, bumping updated

db_delete

remove one row by key

db_find

substring search, paginated

db_list

page through every row

db_count

how many rows are live

Identifiers and values cross the wire as decimal strings — a u64 key and an i64 value do not survive a JavaScript number, and silently rounding someone's primary key is not a trade-off worth making.

// claude_desktop_config.json — or any MCP client
{
  "mcpServers": {
    "asmdb": {
      "url": "https://db-xxxx.swedencentral.azurecontainerapps.io/mcp",
      "headers": {
        "Authorization": "Bearer <your token>"
      }
    }
  }
}
The endpoint and token are returned when you create the database.
# or run it locally against a file, no cloud at all
npx asmdb-mcp --db ./memory
The same tools, the same engine, over stdio.

Three ways in

CLI, HTTP, or your language of choice.

# REST — every field is a string, on purpose
curl -X POST https://db-xxxx.../v1/rows \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"id":"1001","value":"5",
       "tag":"project","content":"hello"}'

curl https://db-xxxx.../v1/rows?limit=100 \
  -H "Authorization: Bearer $TOKEN"
Bounded by default — limit caps at 1000.
# Python — the client speaks the engine's own protocol
from asmdb import Client

db = Client(url, token)
db.insert("1001", value="5", tag="project",
          content="asmdb in assembly")

for row in db.find("assembly", limit=50):
    print(row.id, row.content)
Python, C# and C clients ship in the repository.

Tiers

Every tier is the same engine.

Tiers buy latency and headroom — never features. The row ceiling, the durability guarantees and the change log are identical on all three, because they are properties of the engine, not of a price list.

Free

pricing at general availability

  • 0.25 vCPU · 0.5 GiB
  • Sleeps after 5 minutes idle
  • 3 databases
  • CLI · REST · MCP
  • 4,194,304 rows

Premium

pricing at general availability

  • 1 vCPU · 2 GiB
  • Always warm, never sleeps
  • 100 databases
  • CLI · REST · MCP
  • Priority on new engine builds

Provision

Create a database. It is a real container.

This calls the control plane, which creates an Azure Container App running the assembly engine and returns a live endpoint and a bearer token. The token is shown once and never stored in plaintext — copy it before you leave the page.

lowercase letters, digits and hyphens
Ready. Pick a tier and press create.
# the same thing, from a terminal
curl -X POST https://<control-plane>/api/v1/databases \
  -H "content-type: application/json" \
  -d '{"name":"my-notes","tier":"standard"}'

# -> { "id": "db_...", "endpoint": "https://db-....io",
#      "token": "shown once", "state": "provisioning" }
Provisioning returns immediately; the container is warm within seconds.

One instance is one engine process holding one table, with its own write-ahead log, its own change log and its own single-writer lock. Nothing is shared with anyone else's data — isolation is a container boundary, not a tenant column.