AltSql Core: The Alpha

The Alpha is the working prototype of AltSql Core, the engine the AltSql family is built around: one C file, its tests, a few tools and a demo that runs one engine on three simulated devices. It stores key-value pairs and time-series readings in flash on a microcontroller, answers SQL on a gateway, and copies records from device to gateway byte for byte.

Everything so far runs on a PC. The flash chips and radio links are simulated, carefully, but simulated. The code also compiles for four microcontroller families and its size was measured on each, but it has not yet run on any of them. That is the job of the Beta. The six prototypes built around Core have their own demos, listed on the engines page.

Where it stands. A working engine, heavily tested in simulation. Next it has to prove itself on real chips.

  • About 15 KB: the sensor build, with key-value, time-series and sync, compiled for a Cortex-M4 and not yet run on one.
  • Under 1.1 KB: engine RAM on each simulated demo device (672 to 1,048 bytes), plus up to 1.4 KB of stack.
  • 400,000: simulated power cuts in one run, with nothing saved lost.
  • 16 of 16: deliberately planted bugs caught by the tests.

On this page: In brief · How it is built · How it stores data · Size, memory, wear · Tests · Speed · Features and limits · The demo · Where it stands

The Alpha in Brief

Area Status Key figures
Engine Complete for the Alpha scope One C file, 3,907 lines, C99. The engine never calls malloc
Chip families Four microcontroller families compiled and measured, not run Arm Cortex-M0+, M4 and M33, and RISC-V. Everything runs on a PC (x86-64)
Tests Extensive, all in simulation 104,942 functional checks; 400,000 power cuts; 28 million fuzzed inputs (27.5 million coverage-guided)
Demo Three industries, simulated 32 KB to 512 KB of flash and 672 to 1,048 bytes of engine RAM per device
Speed on a PC Mixed against SQLite 1.6 to 1.9 times faster on grouping; 2.6 to 3.9 times slower on filters and sorts
On a microcontroller Not measured Beta work
Readiness TRL 4 “Technology validated in lab”, on the European Commission’s scale

How It Is Built

  • One file. A project adds altsql.h and defines ALTSQL_IMPLEMENTATION in one of its C files. There is nothing else to link.
  • C99, no heap. The engine never calls malloc (only the optional file-based flash for gateways does). The caller hands it one block of memory at start-up and the engine carves everything out of that block. Nothing is allocated later, so a device cannot run out of memory halfway through the day.
  • Few dependencies. A device build calls five C library functions (memcpy, memmove, memset, memcmp and strlen) plus the compiler’s floating-point helpers. The gateway build adds a few more for SQL and text, such as snprintf and strtod.
  • Compile-time switches. Four switches turn the time-series, SQL, sync and text layers on or off. Code that is switched off is not compiled at all. That is how one code base gives a 7 KB key-value build and a 38 KB gateway build.
  • Clean builds. No warnings with -Wall -Wextra -Wshadow -pedantic, from gcc 13 and clang 18 on the PC and in every cross-build.
  • License. Not chosen yet. The prototype is marked “all rights reserved” until its first public release.

The engine reaches flash through four small functions: read, write, erase and an optional map. The Alpha ships two sets of them. One keeps simulated flash in RAM and can cut the power on demand, for the tests and the demo. The other keeps the flash in a file, for gateways and tools. Drivers for real flash chips are Beta work.

How the Engine Stores Data

The engine is built in layers. Each layer uses only the one below it, and every layer above storage can be switched off:

The engine’s layers from bottom to top: flash driver, storage (a log of records), key-value, time-series, and on top sync, SQL and text. Three builds for a Cortex-M4: key-value only 7.2 KB, the sensor build with time-series and sync 15.0 KB, and the gateway build with everything 38.1 KB.

A Log That Only Grows at One End

Flash memory has an awkward rule. A write can only turn 1 bits into 0 bits, and only erasing a whole sector, typically 4 KB, turns them back into 1s. So AltSql never changes a record in place. Every change is appended as a new record at the head of a log that runs around the flash sectors in a circle. When free sectors run low, the engine reclaims the oldest one: it copies the key-value records that are still current to the head, drops the old time-series rows (this is rollover) and retires the sector, which is erased only when the log needs it again. Every sector takes its turn, so wear spreads evenly.

Record field Size Purpose
Marker 1 byte The value 0xA5: a record starts here
Type 1 byte Put, delete or time-series row
Length 2 bytes Size of the payload
Sequence number 4 bytes The record’s place in history. Sync uses it to resume
Checksum 4 bytes CRC-32 over header and payload. A record counts only if it matches
Payload Varies A key and its value, or a row packed in its series layout

Each sector starts with a 32-byte header: a magic number, the format version, the write alignment, a sequence number, the flash geometry, a reclaim marker and its own checksum. All numbers are stored little-endian, one byte at a time, so the format is identical on every chip. That is what lets a gateway keep a device’s records without converting them.

Surviving a Power Cut

Battery devices lose power at any moment, often in the middle of a write. Four rules keep the data safe:

  • A record counts only once its checksum is complete. A write torn by a power cut leaves bytes that fail the check, and the engine steps over them.
  • Records are never written over. New records always go after the last byte ever programmed, so a torn write is never overwritten. The one deliberate overwrite is retiring a sector, which zeroes its first bytes.
  • Reclaiming finishes before it retires anything. A sector is retired only after its live records are safe at the head. If power fails in between, the partial copy is discarded at start-up and reclaiming simply runs again.
  • Erase late. A sector is erased only when it is about to be reused, so a half-finished erase can never leave a damaged sector inside the log.

At start-up the engine rebuilds its view by scanning the log. After a power cut every acknowledged write is there, and the write that was in progress is either fully there or not there at all.

Memory

The caller gives AltSql one block of RAM. The engine takes a fixed part of it: 232 bytes, plus 16 per flash sector, 8 per key slot, 44 per series and one record buffer. A gateway gives it a larger block, and the rest becomes working memory for SQL.

Key-Value, Time-Series, SQL and Sync

  • Key-value. A small hash index in RAM points each key at its newest record. It is rebuilt from the log at start-up. If a device has more keys than its index was sized for, lookups fall back to scanning the log: slower, never wrong.
  • Time-series. A series is declared once, with a layout such as time:time,temp:float. A timestamped reading with one value takes 26 bytes, 28 in flash with 4-byte alignment. Rules on the device use a window call that returns count, minimum, maximum, sum and average since a given time. A float reads back as the decimal that was stored: 21.53, not 21.530000686645508.
  • SQL. The gateway build answers SELECT, INSERT and CREATE TABLE. Each series is a table, and the key-value store is a table called kv. Queries stream rows straight from flash. There are no indexes, but a filter on time skips every sector whose newest row is too old.
  • Sync. The device hands out its records exactly as stored, in messages of whatever size the link allows, from 51 bytes to 1 KB or more. The gateway checks them and appends them unchanged. Sequence numbers make a resend harmless, so a lost message is simply sent again. A filter chooses what travels, for example only summaries and alerts. A delete stays on the device until the gateway confirms it, so a gateway that was offline still learns about it.

Size, Memory and Flash Wear

The same engine compiled with clang 18 at -Os for five instruction sets. The figures are AltSql’s own code plus its constant data, in bytes:

Chip family Key-value Sensor: key-value, time-series, sync Gateway: everything
Arm Cortex-M0+ (ARMv6-M) 6,944 14,494 37,770
Arm Cortex-M4 (ARMv7E-M) 7,218 14,954 38,122
Arm Cortex-M33 (ARMv8-M) 7,218 14,954 38,122
RISC-V RV32IMC 8,140 17,118 44,644
x86-64 (a PC as gateway) 8,843 18,643 48,884

What these numbers leave out. A KB of code here is 1,000 bytes; flash sizes are binary, so 64 KB of flash is 65,536 bytes. The figures count only AltSql’s own code. Finished firmware also needs the C library functions listed above and the compiler’s floating-point helpers. The device build does some of its math in 64-bit floating point, which these Arm chips do in software. The ESP32’s Xtensa cores were not measured, because the compiler used has no Xtensa target. The Beta measures whole firmware images on real boards.

Engine RAM on the Demo Devices

Device Flash Sectors Key slots Series Record buffer Engine RAM
Machine sensor 64 KB 16 × 4 KB 32 4 128 B 1,048 bytes
Cold-chain logger 512 KB 8 × 64 KB 32 4 128 B 920 bytes
Soil sensor 32 KB 8 × 4 KB 16 2 96 B 672 bytes

The stack comes on top. Every function’s stack frame was measured for a Cortex-M4 and the deepest chain of calls behind each API function added up. A sensor build needs at most 1,376 bytes, when it creates a series; appending a row or computing a window needs 1,352, and reading a key 696. SQL on the gateway needs about 4.3 KB for ordinary statements and more for deeply nested expressions, one more reason SQL lives on the gateway. All told, a sensor should budget about 2.5 KB of RAM for the engine.

Storage per Reading

A timestamped reading with one value takes 26 bytes: 12 for the record header and 14 for the series number, the timestamp and the value. In a benchmark with three-column rows AltSql used 32.3 bytes per row, 41% more than SQLite’s 22.9. The difference is mostly the record header. It pays for the power-cut safety and for the sequence numbers that sync relies on.

Flash Wear

Flash cells survive a limited number of erases. Separate NOR flash chips are typically rated for about 100,000 erase cycles. The flash built into a microcontroller is often rated for 10,000, as on ST’s STM32H5. The demo counted every byte written and every erase:

Machine sensor Cold-chain logger Soil sensor
Simulated time 1 hour 48 hours 30 days
Flash written (measured) 104,204 bytes 191,392 bytes 94,820 bytes
Sector erases (measured) 25 2 23
Erases per sector per day once the log wraps (computed) 38 0.18 0.10
Life of 10,000-cycle flash (computed) About 9 months About 150 years Over 250 years

The machine sensor is the warning. One reading a second into 64 KB of 10,000-cycle flash would wear it out in about nine months. That is a set-up problem, and the set-up can fix it:

Machine sensor set-up Flash life (computed)
64 KB of 10,000-cycle internal flash, every reading stored About 9 months
The same flash, storing 10-second summaries instead About 4.5 years
64 KB of 100,000-cycle external NOR flash, every reading stored About 7 years
1 MB of 100,000-cycle external NOR flash in 64 KB blocks, every reading stored Over 100 years

Computed from the bytes written in the demo, assuming the flash lasts exactly its rated cycles. Real chips vary; the Beta measures wear on hardware.

Tests

Every test runs on a PC against simulated flash that behaves like NOR flash, including what a power cut does to it in the middle of a write or an erase. All suites pass.

Suite What it checks Result
Functional Keys, series, SQL, text, reclaiming, full storage, key-index edge cases and time boundaries, on six flash layouts and the file store 104,942 checks, 0 failed
Power cuts A random mix of puts, deletes and appends, with the power cut at a random byte over and over. After each restart the database is checked against a model 10,000 cuts in the standard run; 400,000 in a long run; 0 failures
Sync Two gateways over lossy links, an outage, a summaries-only gateway, 51-byte messages, malformed series definitions 9,340 checks, 0 failed
Random-mutation fuzzing Damaged SQL, damaged sync batches and damaged text imports 675,000 inputs: no crash, database intact
Coverage-guided fuzzing libFuzzer with both sanitizers on four entry points: SQL, sync, text import and whole flash images 27.5 million inputs; 4 defects found and fixed; the last 10 million, on the final code, clean
Sanitizers and valgrind Every suite rebuilt with AddressSanitizer and UndefinedBehaviorSanitizer, and run under valgrind Clean
Planted bugs 16 deliberate bugs in storage, keys, sync, time-series, SQL and text, one at a time 16 of 16 caught

The Power-Cut Test

This is the test that matters most for a device on a battery. A random workload runs until its budget of bytes runs out and the power is cut: the byte being written is left partly programmed, or the sector being erased half erased, as on a real chip. After the restart the database must open and match a model of everything acknowledged.

One cycle of the power-cut test: random work, a power cut at a random byte, a reboot, and a check against a model of everything acknowledged, repeated 400,000 times with no failures in 31.7 million checks.

Long run Result
Power cuts (of them during an erase) 400,000 (27,211)
Operations between the cuts 27,829,547
Sectors reclaimed / rows rolled over 1,038,578 / 13,805,625
Checks against the model / failures / databases that would not open 31,686,624 / 0 / 0

The same test, in a smaller size, runs in the live demo: its power-cut lab cuts the power 1,000 to 100,000 times in your browser and shows the checks as they happen.

Defects Found and Fixed

The last review added coverage-guided fuzzing and the planted-bug check, and read the code again with fresh eyes. That found seven defects. All are fixed, and each is now covered by a test:

Defect Found by Effect before the fix
1 A SQL query with 100,000 nested brackets Code review The gateway process crashed (stack overflow)
2 A 40-step alias chain that doubles each step Code review A query that would never finish
3 A bad character after CREATE TABLE IF NOT EXISTS libFuzzer An error with no message
4 Stray text after the last row of an INSERT Review, while fixing 3 The row was written before the error was reported
5 ROUND with a huge digit count, or of a value that is not a number libFuzzer with UBSan Undefined behavior in C
6 A series name with a zero byte, imported twice libFuzzer with UBSan The importer crashed
7 A malformed series name arriving by sync libFuzzer The gateway’s export failed from then on

All seven sat on the gateway’s input paths (SQL, import and sync), and none could lose data on a device. Earlier, the long power-cut runs had found and fixed one storage defect: repeated cuts while reclaiming could make the store report itself full.

What the Tests Do Not Cover Yet

  • Real hardware. Real chips add effects the simulation does not model, such as weak bits that read differently from one read to the next. Real radios, timing and batteries are untested too.
  • Firmware with several threads, or interrupt handlers using the database at once.
  • Time. The demo covers 30 simulated days at most; fuzzing ran for minutes per entry point.
  • A big-endian processor. The format is defined byte by byte and should be identical, but no test has run on one.
  • Independent review. Every test was written by the people who wrote the code.

Speed on a PC

There are speed figures for a PC only. On a microcontroller the pace will usually be set by flash writes and erases, and that has not been measured yet.

One million sensor rows (timestamp, machine number, temperature) were loaded into AltSql’s file store and into SQLite 3.45.1, and the same five queries run on both. Each time is the best of nine runs. SQLite ran without a journal or disk syncing, and neither side had an index unless stated.

Task AltSql SQLite Result
Hourly averages (GROUP BY hour) 203 ms 384 ms AltSql 1.9 times faster
Average per machine (GROUP BY machine) 166 ms 259 ms AltSql 1.6 times faster
Count with a filter on temperature 151 ms 38 ms SQLite 3.9 times faster
Last hour of one machine 0.6 ms 32 ms; 0.2 ms with an index on time AltSql 53 times faster without the index; SQLite 3 times faster with it
Top 5 temperatures (ORDER BY temp DESC LIMIT 5) 155 ms 60 ms SQLite 2.6 times faster
Loading one million rows 0.18 s 0.39 s AltSql 2.1 times faster
Storage per row 32.3 bytes 22.9 bytes AltSql 41% larger

The pattern makes sense. AltSql wins where its layout helps: grouping over a log kept in time order, and skipping whole sectors to reach recent data. It loses where SQLite’s mature query engine and compact pages help, on filters and sorts that touch every row.

Most of AltSql’s scan time goes into checking each record’s checksum on every read. Dropping the check is out of the question, because it is what makes power cuts safe. A checksum computed from a 1 KB table made scans about 1.5 times faster in an experiment, and many microcontrollers have a hardware CRC unit that could do better still. Both are Beta items.

Other operations on the PC Result
Append a row 5.4 million a second
Put a key (1,000 keys, 10,000 writes) 3.5 million a second
Get a key 11 million a second
Window statistics over the last 60 seconds 0.05 ms
Start-up with one million rows: scan the log, rebuild the key index 90 ms

Features and Limits

Feature What the Alpha supports Limits
Flash Any NOR-style flash through four driver functions Sectors of 256 bytes or more (a power of two), at least 4 of them, up to 4 GB in all; writes aligned to 1, 2, 4, 8 or 16 bytes
Keys and values Put, get, delete, list Keys of 1 to 200 bytes. A key and its value fit in one record (256 bytes by default)
Time-series Declare, append, scan a time range, window statistics Up to 16 columns; types time, int, long, float, real and text; no empty (NULL) values
When flash fills The oldest rows roll over; keys are kept If current keys alone fill the flash, writes return FULL until something is deleted
SQL (gateway) SELECT with WHERE, GROUP BY, HAVING, ORDER BY, LIMIT and OFFSET; INSERT; CREATE TABLE No JOIN, UPDATE, DELETE, subqueries, DISTINCT, IN, CASE, indexes or transactions
SQL functions COUNT, SUM, AVG, MIN, MAX, ABS, ROUND, LENGTH, LOWER, UPPER; LIKE, BETWEEN, IS NULL; aliases Expressions up to 200 levels deep
Sync Device to gateway; resumable; filtered; any message size from 51 bytes One device per gateway copy. With several gateways, one that lags can miss a delete. No encryption or authentication yet
Text Export and import, one record per line Two databases with the same content export to identical text
Threads One caller at a time No locks inside: firmware with several threads must take turns

The Demo

The demo runs one unchanged engine on three simulated devices, one per industry. Each device’s flash is simulated as the engine sees real NOR flash, and each link is simulated with lost messages. What changes between the devices is the set-up: the flash layout, the memory, the data layout, the rule the device applies by itself, and what it sends over which link.

Bar chart of the bytes each demo device would send as JSON, stores as records and actually sent over its weak link. Machine sensor: 190,800, 96,133 and 2,533 bytes. Cold-chain logger: 328,374, 179,610 and 6,810. Soil sensor: 207,360, 86,400 and 1,140.

One run of the Alpha demo. Most of the saving comes from deciding and summarizing on the device; the compact format adds about a factor of two over lean JSON.

Machine sensor Cold-chain logger Soil sensor
Sent over the constrained link 2,533 bytes 6,810 bytes 1,140 bytes
Every record, as stored 96,133 bytes 179,610 bytes 86,400 bytes (computed)
Every reading as JSON 190,800 bytes 328,374 bytes 207,360 bytes
Saving from the format (JSON ÷ records) 2.0 times 1.8 times 2.4 times
Saving from deciding on the device (records ÷ sent) 38 times 26 times 76 times

The JSON baseline is lean on purpose, one compact object per reading with no MQTT, HTTP or TLS overhead, so real savings would be larger. The machine sensor’s constrained link is the one to gateway B, which takes summaries only; gateway A received every record. The soil sensor’s “every record” figure is computed: 2,880 readings of 30 bytes.

Each device has its own case study: machine monitoring, cold chain and agriculture. Or run it yourself in the live demo.

Where It Stands

Maturity by Part

Part Maturity Evidence Gap to close
Storage and power-cut safety Working; heavily tested in simulation 400,000 simulated cuts; 16 of 16 planted bugs caught Real flash chips, weak bits, a hardware power-cut rig
Key-value Working, tested Functional and power-cut suites Locking for firmware with threads
Time-series Working, tested Functional suites and the demo A single-precision build for small chips
SQL on the gateway Working subset Fuzzed; faster than SQLite on grouping, slower on filters and sorts Faster checksum; more SQL only when users ask for it
Sync Working in simulation Lossy links, outages, 51-byte messages Each gateway’s position tracked separately, authentication and encryption, real radios, a written protocol specification
Chip support Compiles for four microcontroller families Measured code size Flash drivers, tests on the chips, Xtensa, whole-firmware size
Tools and documentation Basic Shell, demo, fuzzers, planted-bug check, README API reference, automatic testing on every change
Security Not started None Authenticated sync
Use in the field None None Beta devices, then pilots

Readiness Level

On the European Commission’s technology readiness scale, which runs from TRL 1 to TRL 9, the Alpha sits at TRL 4, “technology validated in lab”. The engine works, and it has been validated in a simulated lab. The Beta aims at TRL 5, “technology validated in relevant environment”: real boards with real flash and radios. Its week-long run of three working devices gives a first taste of TRL 6.

How Far From a First Release

An estimate, and only an estimate: the Alpha is perhaps a quarter of the way to a first production release. The hardest design questions have answers, and the engine is small, reasonably fast and well tested in simulation. Most of the remaining work is the unglamorous part: drivers and tests on real chips, security, documentation, tooling and pilots in the field. The Beta should take about 16 weeks with two to three engineers, and a production release about 6 to 9 months after that. Hardware surprises could stretch both.

Against the Alternatives

AltSql Alpha FlashDB SQLite
What it is Key-value and time-series on the device, SQL on the gateway, sync between them Key-value and time-series store for microcontrollers Full SQL database for systems with a file system
Code size 13.4 KB for key-value and time-series (Cortex-M4, clang); 15.0 KB with sync About 8.3 KB for the same two parts (STM32F4, IAR), as published Under 900 KiB with all features, as published
RAM About 0.7 to 1 KB, plus stack “Almost 0”, as published Needs a heap; far beyond small chips
SQL Gateway build None Full
Device-to-gateway sync Built in None None built in
Power-loss safety Designed in; tested in simulation Claimed as a feature Mature and extensively tested
Proven on hardware Not yet Yes: published figures on STM32 chips Yes, everywhere
License Not chosen Apache-2.0 Public domain

The fair reading: FlashDB is smaller and already proven on chips, and SQLite is far more capable and far more tested. AltSql’s case rests on doing both jobs with one format and one sync, so far shown in simulation only. Sources: the FlashDB README and About SQLite.

Technical Risks the Alpha Revealed

  • Flash wear at high data rates. As shown above, the set-up has to match the chip’s endurance.
  • Error-correcting flash. Some chips refuse to program the same flash word twice. AltSql does that in one place, when it zeroes the first bytes of a retired sector. Each Beta chip must accept it, or the engine must retire sectors another way.
  • Program and data in one flash chip. On the RP2040, the RP2350 and ESP32 chips the program runs from the flash chip that holds the data, so it must not run from flash during a write. The vendors’ flash functions handle this; the drivers must use them correctly.
  • Encrypted flash on ESP32. Encrypted writes must be 16 bytes, which the engine supports. Encryption may also change how erased flash reads back, and the engine relies on erased flash reading as 0xFF to find the end of its log. The Beta checks this on the chip.
  • 64-bit math on small chips. The time-series layer computes in 64-bit floating point, which most small chips do in software: slower, and more code.
  • Deletes with several gateways. The device remembers only the most advanced gateway’s position, so a gateway that lags behind can miss a delete once its sector is reclaimed. The Beta will track each gateway.
  • No authentication on sync. A gateway today trusts any correctly formed record. That is fine on a test bench and not in a product.

Every figure on this page was measured on the Alpha code in September 2026 unless it is marked as computed or estimated. The Alpha package includes the commands that reproduce each one.