morletdb

What audio codecs taught a sensor database

Every sample MorletDB stores goes through its chunk codec, Grizzbit. It is lossless: a value read back has the same bits as the value written. That rule never bends, so the only things a codec can improve are how small the data gets and how fast it moves. This post is about the second version that improves both well enough to ship, Grizzbit 2.1. It covers where its ideas came from, what a frozen study measured, and what comes next.

Where it started

Grizzbit 1 rests on one observation: most sensor floats are integers in disguise. A digitizer produces integer counts, and calibration turns them into floats by a scale and an offset. Grizzbit 1 recovers the integers, predicts each one from the previous few with a fixed polynomial predictor, and writes the small prediction errors with Rice codes. Floating-point rounding means a calibrated value does not sit exactly on its grid, so Grizzbit 1 also stores a correction in the last bits of every sample.

That already beat general-purpose compressors and the float codecs of time-series databases on calibrated waveforms. But vibration, motor currents and EEG are waveforms, just like audio, and lossless audio coding has had decades of work. So we asked a plain question: on the same integers, how far behind the best lossless audio coders is Grizzbit?

What the audio coders showed

We benchmarked Grizzbit against two strong lossless audio coders on our development data only: SRLA, and the reference coder of ITU-T T.261. On the same integers, Grizzbit 1 needed 9.75 bits per sample. SRLA needed 8.97 and T.261 8.87. The gap came from two places.

The third idea came from our own data, not from audio. A calibrated value is rounded the same way every time its integer comes up. So the correction depends only on the integer, not on the sample, and a signal that visits the same few hundred values thousands of times was paying for the same correction thousands of times.

What Grizzbit 2.1 does

The failure before it

Grizzbit 2.1 is not our first attempt. Grizzbit 2 added Pcodec as a second coding, aimed at floats without a value grid, where MorletDB loses to ClickHouse and Pcodec. Its frozen study, B5, failed. On two of five series it stayed 1.38× and 1.56× larger than ClickHouse's best, where we had required 1.25×. It also made one read measure slower and lost one result against QuestDB. It never shipped. Its format version, 4, is reserved and rejected by every reader.

How we tested 2.1

Grizzbit 2.1 went through its own frozen study, B6.

Criterion (fixed in advance)Result
Every chunk and read bit for bit exactheld: 36 inputs, 36 database loads, 0 mismatches
Six waveforms together at least 5% smaller, 4 of 6 smallerheld: 12.6% smaller, 6 of 6
No chunk and no input largerheld
Decode at least 50 and encode at least 10 M samples/s per waveformheld: lowest 53.1 and 10.8
Same bytes on every platformheld: macOS and Linux on ARM, Linux on x86, Raspberry Pi
Every B3 result against QuestDB still holdsheld: ingestion and reads unchanged

The gains are largest where MorletDB is meant to be used:

How much smaller Grizzbit 2.1 stores each benchmark input than Grizzbit 1Horizontal bars, study B6. Sleep EEG (Sleep-EDF): 31.0% smaller; Motor current, phase 2 (B3, Paderborn): 19.9% smaller; Motor current, phase 1 (B3, Paderborn): 18.3% smaller; Machine vibration (B3, Paderborn): 15.7% smaller; Cutting force (B3, Paderborn): 14.0% smaller; Bearing vibration, calibrated (Paderborn physical): 13.8% smaller; Vibration, decimal values (IMS): 4.8% smaller; Seismic counts (CX): 4.3% smaller; Bearing vibration, counts (Paderborn counts): 1.7% smaller; ECG (PTB-XL): 0.4% smaller; Spindle speed (B3, Paderborn): 0.0% smaller; Torque (B3, Paderborn): 0.0% smaller.0%10%20%30%Sleep EEG (Sleep-EDF): 31.0% smaller, 0.6900 of Grizzbit 1's sizeSleep EEGSleep-EDF31.0%Motor current, phase 2 (B3, Paderborn): 19.9% smaller, 0.8013 of Grizzbit 1's sizeMotor current, phase 2B3, Paderborn19.9%Motor current, phase 1 (B3, Paderborn): 18.3% smaller, 0.8173 of Grizzbit 1's sizeMotor current, phase 1B3, Paderborn18.3%Machine vibration (B3, Paderborn): 15.7% smaller, 0.8432 of Grizzbit 1's sizeMachine vibrationB3, Paderborn15.7%Cutting force (B3, Paderborn): 14.0% smaller, 0.8600 of Grizzbit 1's sizeCutting forceB3, Paderborn14.0%Bearing vibration, calibrated (Paderborn physical): 13.8% smaller, 0.8622 of Grizzbit 1's sizeBearing vibration, calibratedPaderborn physical13.8%Vibration, decimal values (IMS): 4.8% smaller, 0.9521 of Grizzbit 1's sizeVibration, decimal valuesIMS4.8%Seismic counts (CX): 4.3% smaller, 0.9565 of Grizzbit 1's sizeSeismic countsCX4.3%Bearing vibration, counts (Paderborn counts): 1.7% smaller, 0.9827 of Grizzbit 1's sizeBearing vibration, countsPaderborn counts1.7%ECG (PTB-XL): 0.4% smaller, 0.9960 of Grizzbit 1's sizeECGPTB-XL0.4%Spindle speed (B3, Paderborn): 0.0% smaller, 1.0000 of Grizzbit 1's sizeSpindle speedB3, PaderbornunchangedTorque (B3, Paderborn): 0.0% smaller, 1.0000 of Grizzbit 1's sizeTorqueB3, PaderbornunchangedSmaller with Grizzbit 2.1 than with Grizzbit 1, in bits per sample
Study B6, measured 2026-11-11 on inputs that none of 2.1’s development had read: the six waveforms judged by the size criterion and the six channels of B3’s machine data. Not shown: the 18 low-rate decimal series, 12 of which did not change at all. Every value reads back bit for bit. Data: B6 results.

What it does not do

Where it stands

Grizzbit 2.1 ships opt-in with the next release. One setting turns it on, and the default stays Grizzbit 1:

SQL
ALTER DATABASE plant SET morlet.chunk_format = 'grizzbit21';

It becomes the default only once it has run in the field and has more headroom on speed.

What comes next

Every change to the format gets its own frozen study before it ships, as 2.1 did. Some will fail, as B5 did, and we will say so.

Read it