---
title: "The latest reading of any series in one read: Semurg as a time-series database"
slug: semurg-as-a-time-series-database
date: 2026-10-12
author: Semurg
section: how-to
tags: [time-series, database, how-to]
summary: Write readings for many series in one call, read the latest value of each series by name, and see what the time-series door does not do yet.
status: published
covers: [route:POST /v1/query, op:ts_ingest, op:ts_latest]
constants: [64, 8]
---
Much of what a monitoring screen or a trading desk asks a time-series store is one question, many times: what is the latest reading
of this series, now? Semurg keeps, for every series, a small record that always points at its newest reading, so that question is one
read whatever the history behind it.

It answers that question, and not yet the others: there is no query over a range of time, no downsampling and no per-series
aggregate, so it is a store for current readings, not yet a replacement for a full time-series query engine.

> Diagram latest-reading: Each reading becomes a record of its own, counted into its series' record for that hour; when it is newer it moves the series head, so the latest reading is one read whatever the history, and dense columns are built in the background for scans.

## Write readings

Each reading is a row of three or four whole numbers after the series name: the time in milliseconds, the value, and optionally a size
or count. Values are integers, so keep a price in cents or a percentage in hundredths. One call can carry readings for many series:

```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": "ts_ingest",
       "rows": [["cpu.host1.usage", 1760259600000, 4210],
                ["cpu.host1.usage", 1760259660000, 4385],
                ["cpu.host2.usage", 1760259600000, 1970]]}'
```

```expect
expect: three readings for two series are written
assert: json:.ok==true and .series==2
```

The reply counts the rows you sent and the series they touched, and how many of those series it had not seen before. If two rows in one call
name the same series and the same time, the last one wins.

## Read the latest reading of each series

```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": "ts_latest", "series": ["cpu.host1.usage", "cpu.host2.usage", "cpu.host9.usage"]}'
```

```expect
expect: host1's newest reading, host2's only reading, and host9 marked absent
assert: json:.latest[0].value==4385 and .latest[1].value==1970 and .latest[2].absent==true
```

The answers come back in the order you asked, each with its time, value and size, or `absent` for a series with no readings. The
latest reading only moves forward in time: a reading older than the newest one is stored, but does not replace it.

## What a reading becomes on disk

Every reading is one 64-byte record of its own. Each series also keeps one record per hour of readings and one record that points at
its newest reading, and its name is stored once. The time, value and size are also laid out as dense columns of 8-byte numbers, which
the node builds in the background, for scans over a whole column.

## 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 time-series 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

- Query a range of time, downsample, or aggregate one series: the door answers the latest reading, and older readings are stored but not read back by time.
- Store decimal values or tags beyond the series name.
- Delete readings, or expire them after a retention period.
- Load from QuestDB, InfluxDB or TimescaleDB directly: there is no connector for them yet.

## Next

- [Ticks and research notes on one engine](/blog/build-quant-tick-store-and-research-memory): a quant desk's blueprint built on this door.
- [Talk to a Semurg node over plain HTTP](/blog/connect-over-rest): the token and every route.
