The Alpha
The Alpha is the working prototype of the AltSql engine: 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.
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 on a Cortex-M4, with key-value, time-series and sync.
- Under 1.1 KB: engine RAM per 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.hand definesALTSQL_IMPLEMENTATIONin 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:

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.

| 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.

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.