engine 1.5.0 · storage format 2 · 151 tests green
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.
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
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.
id | u64 — primary key, unique, ≥ 1 O(1) lookup through a hash index |
|---|---|
value | i64 — the numeric column the only column RANGE can filter on |
tag | 39 bytes — one token, no spaces longer input is refused, never truncated |
content | 175 bytes — free text UTF-8 preserved byte for byte |
created · updated | i64 — 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
FORMAT TSV switches the same commands to a machine-readable stream.durability
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
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
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
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
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>" } } } }
# or run it locally against a file, no cloud at all npx asmdb-mcp --db ./memory
Three ways in
# 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"
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)
Tiers
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
Standard
—pricing at general availability
Premium
—pricing at general availability
Provision
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.
# 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" }
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.