Oninit® UDR — TimeSeries API: Benchmark
The TimeSeries API against the same data held in a plain relational table: loading, writing, reading and size, and where each one is faster.
The short version
The API has a fixed cost on every call and a low cost per element after that. It is slow on a call that handles one element and fast on a call that handles many.
- Loading: a bulk load is about 3.8× faster than a
relational
LOAD FROM. - Writing: one element per call costs about
150 µs, which is 2 to 8× slower than an
INSERT. Batches of 50 or more are as fast as anINSERT. - Reading as JSON: about equal at one hour of data, 1.6 to 1.7× as fast from four hours up, against a relational table that has to build the same JSON.
- Reading as rows: about 5× slower than a plain relational
SELECTat every size. - Size: the data takes about half the space.
What was measured
10 series of 20,160 one-minute points (14 days), three metrics each: 201,600
points. The same values were loaded into a relational table with
PRIMARY KEY (series_id, ts) and into TimeSeries columns read and written
through oni_ts_read(), oni_ts_write(),
oni_ts_write_many(), oni_ts_write_file() and
oni_ts_read_rows(). Informix 14.10, TimeSeries 6.01.FC2, one shared
instance, timings are client wall clock with the connection set-up subtracted,
medians of 5 to 15 runs, measured on 19 October 2026. The relational side varied
by more than 10× between runs on a busy instance, so the direction of a result is
firmer than its size.
Loading and writing
| Operation | Relational SQL | TimeSeries API | Speed change |
|---|---|---|---|
| Bulk load, 201,600 points (5 fresh databases) | 10.2 s (LOAD FROM) | 2.7 s (oni_ts_write_file()) | +279% |
| 2,000 single-element writes, one transaction | 0.16 s | 0.33 s | −50% |
Speed change is the relational time divided by the API time, less one: plus is faster than the relational table, minus is slower. The bulk load was faster in four of five runs; one run of the API took 10 s against about 2 s in the others.
Writing 2,000 elements in one transaction, by how many go in each call:
| Method | µs per element | Speed change against an INSERT |
|---|---|---|
Relational INSERT | 19 | — |
oni_ts_write(), one element per call | 151 | −87% |
oni_ts_write_many(), 50 per call | 18 | +8% |
oni_ts_write_many(), 100 per call | 14 | +42% |
oni_ts_write_many(), 300 per call | 17 | +15% |
The batch rows are level with the INSERT: the relational timing itself
ranged from 0.012 to 0.511 s for 2,000 inserts. A one-element call locates the
series, opens it and closes it again every time, which is where its 150 µs
goes. A batch does that once. Single writes are 8 to 11× slower per element than
a batch of 50 or more. Every call stays inside the caller's transaction, so a
ROLLBACK removes the elements. The 0.33 s for 2,000 single writes in
the first table and the 151 µs per element here (0.30 s) come from different runs.
Reading
One read of a window of one series, milliseconds. The relational side builds
the same JSON document that oni_ts_read() returns.
| Window | Rows | Relational, as JSON | oni_ts_read() | Speed change |
|---|---|---|---|---|
| 1 hour | 60 | 0.68 | 0.65 | +5% |
| 4 hours | 240 | 3.01 | 1.89 | +59% |
| 1 day | 1,440 | 16.70 | 9.87 | +69% |
| 7 days | 10,080 | 152.63 | 90.70 | +68% |
The lead grows to about a day and then stays: seven days is no better than one. A response is limited to 32,739 bytes, so a long window is read in pages of 240 points (42 calls for seven days); those calls are in the figures.
The same read as rows, with oni_ts_read_rows(), against a plain
relational SELECT that returns its rows as they are:
| Window | Rows | Relational, plain rows | oni_ts_read_rows() | Speed change |
|---|---|---|---|---|
| 1 hour | 60 | 0.13 | 0.85 | −85% |
| 4 hours | 240 | 0.49 | 2.59 | −81% |
| 1 day | 1,440 | 2.50 | 12.62 | −80% |
| 7 days | 10,080 | 22.43 | 101.74 | −78% |
A relational read costs about 2 µs per row at every size. The API costs about 0.25 ms per call plus 10 µs per row for rows, and about 0.1 ms plus 9 µs per row for JSON. The gap narrows as the fixed part is spread over more rows and does not close.
What producing JSON costs
Most of the cost of a relational read that must return JSON is the JSON. Microseconds per row, over the four windows above:
| How the data is produced | µs per row | Against plain rows |
|---|---|---|
Relational SELECT, rows as they are | 1.7 to 2.2 | 1× |
oni_ts_read(), JSON built in C | 6.9 to 10.8 | 4 to 5× |
oni_ts_read_rows(), rows | 8.8 to 14.2 | 4.6 to 6.5× |
| Relational, the same JSON built in SPL string concatenation | 11.3 to 15.1 | 5 to 7× |
Relational, genbson(...)::json per row | 27.0 to 33.1 | 13 to 16× |
Building the JSON is 81 to 85% of the SPL version's time and 92 to 94% of the
genbson version's. genbson returns MongoDB extended JSON, with an
_id field and ISODate(...) values, which is a different document
from the one oni_ts_read() returns. The SPL version is interpreted code; a
compiled routine would be faster. For one day of data the relational side costs
17 to 39 ms to produce JSON, against 10 ms for oni_ts_read().
Size
| Relational | TimeSeries API | |
|---|---|---|
| 201,600 points | 13.4 MiB (8.6 MiB data and 4.9 MiB index, each rounded) | 6.3 MiB |
About 33 bytes a point against 70.
Which to use
| Workload | Faster |
|---|---|
| One element per call, many calls | Relational |
| Writes in batches of 50 or more, or file loads | TimeSeries API (level to much faster) |
| Reads of a few hours or more, as JSON | TimeSeries API (about 1.6× as fast) |
| Reads of any size, as plain rows | Relational (about 5×) |
| Reads of an hour or less | About equal as JSON, relational as rows |
| Space | TimeSeries API (about half) |
If a caller has several elements, send them together: oni_ts_write_many() costs about a tenth of a single call per element. To read as rows, state the row type with a cast: oni_ts_read_rows().
Limits of the measurement
- One shape of data: regular one-minute series, three numeric metrics, one index on the relational side. Irregular series, wider rows, concurrent readers and other series lengths were not measured.
- Timings are wall clock on a shared instance, including a start-up cost that is subtracted but not removed. Medians of 5 to 15 runs; the relational side varied by more than 10× between runs.
- The relational JSON is built in interpreted SPL, so those figures bound plain SQL and not the best a relational implementation could do.
- Loads and single writes are measured on
oni_ts_combined.4.0; the batch writes on5.0; the reads on6.5. The read path is the same in all.