---
title: "Semurg as a key-value store: put, get, delete, and where it stands"
slug: semurg-as-a-key-value-store
date: 2026-10-12
author: Semurg
section: how-to
tags: [key-value, cache, database, how-to]
summary: Put values under whole-number keys, get many back in one call, put only if absent and delete, and where Semurg stands while its key-value comparison is re-measured on new hardware.
status: published
covers: [route:POST /v1/query, op:object_put, op:object_get, op:object_delete]
---
A key-value store answers one question very often: what is stored under this key? Semurg answers it with the same door it uses for
objects and documents: a value under a whole-number key, read back by key. This post is about using that door as a key-value store,
and about where it stands while its comparison is re-measured.

> Diagram kv-ops: Under one whole-number key: a put stores a new version, a put-if-absent leaves a held value alone, a get answers present or absent, and a delete makes later gets answer deleted.

## Put, get and delete

Keys are whole numbers; values are base64 bytes. One call puts many, and `"content": true` makes it a put-if-absent: a key that
already holds a value keeps it, and the reply counts it as skipped.

> Diagram string-keys: A string key such as a user name or a session token is hashed in your code to a whole-number key, which the object door stores and reads.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op": "object_put", "content": true,
       "objects": [{"id": 900000000701, "value_b64": "c2Vzc2lvbjogZGFuYQ=="},
                   {"id": 900000000702, "value_b64": "c2Vzc2lvbjogbGVl"}]}'
```

```expect
expect: both keys are written, because neither held a value
assert: json:.ok==true and .written==2 and .skipped==0
```

Run that put a second time, before anything else, and both keys are skipped. Without `"content": true`, a put to a key that holds a value replaces it with a new version, and
reads return the newest.

Get many keys in one call; each answers in the order you asked:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op": "object_get", "ids": [900000000701, 900000000702, 900000000799]}'
```

```expect
expect: the two keys you put are present with their values, and the third is absent
assert: json:.objects[0].state=="present" and .objects[0].value_b64=="c2Vzc2lvbjogZGFuYQ==" and .objects[2].state=="absent"
```

Delete a key:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op": "object_delete", "ids": [900000000702]}'
```

```expect
expect: one key deleted
assert: json:.ok==true and .deleted==1
```

Read it again and the key answers `deleted`, with the version it was deleted at, rather than pretending it never existed:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -sS http://localhost:4000/v1/query \
  -H "Authorization: Bearer $SEMURG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"op": "object_get", "ids": [900000000702]}'
```

```expect
expect: the deleted key answers deleted
assert: json:.objects[0].state=="deleted"
```

## Where it stands

We have taken our published benchmark rows down while every domain is re-measured on new hardware, with a dataset in RAM and
one bigger than RAM running at the same time, as a stress test of Semurg itself. The re-measured key-value results will be on the
[benchmark page](/benchmark), wins and losses alike, with the release and the machine they ran on. Until then this post states
no comparison.

## What it does not do yet

- Expire keys after a time to live.
- List, scan or range over keys.
- Compare-and-set, increment or append: a put writes the whole value.
- Use string keys directly.
- Import from Redis or Valkey directly.

## Next

- [A document store that reads by id](/blog/semurg-as-a-document-store): the same door, for JSON documents.
- [An object store on your own disks](/blog/semurg-as-an-object-store): the same door, for large values.
