Delta Store API
delta_store
Physical storage policy for the project's Delta tables.
Why this package exists
contracts owns each table's logical shape and meaning. This package owns its physical
layout: parquet writer properties (codec + per-column encodings), compression-friendly sort
orders, and significand-precision rounding, plus the write helpers that apply them. Dagster
assets stay thin by writing through this package rather than calling write_deltalake with
ad-hoc settings — and it becomes impossible to land rows in a table without its storage format
applied.
The flagship example is the internal power_forecasts table: ZSTD + DELTA_BINARY_PACKED
timestamps + BYTE_STREAM_SPLIT floats + member-adjacent sorting + rounding power_fcst to a
13-bit significand shrank the 403.6M-row development table from 6.33 GB to 0.73 GB.
Contents
precision.round_to_significand_bits()— rounds aFloat32expression to a chosen number of significand bits in pure Polars arithmetic (Veltkamp splitting), zeroing the low mantissa bits soBYTE_STREAM_SPLIT+ zstd can compress them away. The trick and its preconditions are rigorously documented on the function.power_forecasts— thepower_forecaststable's writer properties, sort order, precision policy, andwrite_power_forecasts().
delta_store.precision
Reduce the significand precision of Float32 columns so parquet compression can work.
Nearly every full-precision Float32 value in a large forecast or weather table is distinct,
so the low significand bits are incompressible noise — they defeat every general-purpose codec.
Rounding each value to a small number of significand bits zeroes those low bits, after which
BYTE_STREAM_SPLIT + zstd compress the column dramatically (see
delta_store.power_forecasts for measured numbers), at the cost of a strictly bounded
relative error.
The rounding here is pure Polars arithmetic (no bit-twiddling, no numpy round-trip), which works
because of a classical floating-point identity — Veltkamp splitting — documented in detail on
:func:round_to_significand_bits.
Attributes
FLOAT32_SIGNIFICAND_BITS = 24
module-attribute
IEEE 754 binary32 significand precision p: 23 explicit fraction bits + 1 implicit bit.
Functions:
round_to_significand_bits(expr, *, keep_bits)
Round a Float32 expression to keep_bits significand bits (round-to-nearest).
The result is exactly representable with a keep_bits-bit significand, so the low
24 - keep_bits explicit fraction bits of every finite output are zero — which is what
lets BYTE_STREAM_SPLIT + zstd compress the column — and the relative error is bounded by
the unit roundoff of a keep_bits-bit format:
|result - x| <= 2**-keep_bits * |x|
(e.g. keep_bits=13 -> max relative error 2^-13 ~= 1.2e-4). Rounding is to nearest, so
unlike truncation it introduces no systematic bias toward zero.
How it works — Veltkamp splitting. With s = 24 - keep_bits and the constant
C = 2**s + 1, the expression computes, entirely in Float32 round-to-nearest
arithmetic (RN)::
c = RN(x * C) # = RN(x*2^s + x)
result = RN(c - RN(c - x))
Veltkamp's theorem (Veltkamp 1968; Dekker 1971, "A floating-point technique for extending
the available precision", Numerische Mathematik 18; Muller et al., Handbook of
Floating-Point Arithmetic, 2nd ed., §4.4, Algorithm 4.9 "Split") states that for
2 <= s <= p - 2 and no overflow, both subtractions are exact and result is x
rounded to nearest onto p - s = keep_bits significand bits. The intuition: x*C
stacks a copy of x shifted s exponent positions above itself; rounding that sum to
p bits (forming c) is precisely what discards — with correct rounding — the low
s bits of the original x; the two exact subtractions then peel the shifted copy back
off, leaving x with its low s significand bits rounded away.
Preconditions — each one is load-bearing:
exprmust beFloat32. The splitting constant is pinned toFloat32, but Polars promotes mixed arithmetic: if aFloat64expression is passed, the whole computation runs atp = 53and silently keeps53 - sbits instead ofkeep_bits. (A.cast(pl.Float32)is deliberately not applied here — silently changing the dtype of aFloat64column would be its own trap; callers own their dtypes.)- Arithmetic must be evaluated operation-by-operation in IEEE round-to-nearest. Polars
(Rust) guarantees this: floats are never reassociated and
a*b + cis never contracted into an FMA, either of which would break the exactness of the subtractions. - Non-finite and overflow cases are guarded: for
|x| > f32_max / Cthe productcoverflows toinfand the naive result would beinf - inf = NaN; forxNaN or±inf,cis likewise non-finite. Wherevercis non-finite the input value is passed through unchanged (a huge-but-finite value is stored at full precision rather than corrupted;NaN/±infsurvive verbatim). - Subnormal
x(|x| < 2**-126) degrades gracefully: the relative-error bound loosens because precision is already at the absolute floor of the format, but the result is still a faithful nearby value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expr
|
Expr
|
A |
required |
keep_bits
|
int
|
Significand bits to keep, in |
required |
Returns:
| Type | Description |
|---|---|
Expr
|
A |
Expr
|
non-finite and overflowing inputs passed through unchanged. |
Source code in packages/delta_store/src/delta_store/precision.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
delta_store.power_forecasts
Storage policy for the internal power_forecasts Delta table.
Owns everything about how PowerForecast rows are laid out on disk: the parquet writer
properties (codec + per-column encodings), the compression-friendly row order, and the
power_fcst precision reduction. Callers write through :func:write_power_forecasts so it is
impossible to land rows in the table without this format applied.
Measured impact: rewriting the full 403.6M-row development table into this format shrank it from
6.33 GB (delta-rs defaults: SNAPPY, dictionary encoding, unsorted, full precision) to 0.73 GB.
See the POWER_FORECASTS_WRITER_PROPERTIES docstring for the per-lever breakdown.
Attributes
POWER_FCST_SIGNIFICAND_BITS = 13
module-attribute
Significand bits kept when storing power_fcst (1 implicit + 12 explicit fraction bits).
Rounding to nearest at 13 significand bits caps the relative error at 2⁻¹³ ≈ 1.2×10⁻⁴ — orders
of magnitude below forecast error — and zeroes the 11 low fraction bits, which are otherwise
pure entropy that defeats every compression codec (nearly every full-precision power_fcst
value is distinct). Mirrors the significand-rounding scheme delta_store.nwp applies to NWP
data — but with different writer properties; see that module's docstring for why the choice
doesn't transfer between tables. See POWER_FORECASTS_WRITER_PROPERTIES for the measured
size impact.
POWER_FORECASTS_SORT_COLS = ('time_series_id', 'power_fcst_init_time', 'valid_time', 'ensemble_member')
module-attribute
Within-file row order for power_forecasts writes.
Placing the ~51 ensemble members of one (series, init time, valid time) target on adjacent rows
makes power_fcst locally smooth and the timestamp columns stepped sequences — exactly what
the BYTE_STREAM_SPLIT and DELTA_BINARY_PACKED encodings in POWER_FORECASTS_WRITER_PROPERTIES
need to compress well. Leading with time_series_id also lets parquet row-group statistics
prune scans that filter on one series.
POWER_FORECASTS_WRITER_PROPERTIES = WriterProperties(compression='ZSTD', compression_level=3, column_properties={'valid_time': _TIMESTAMP_COLUMN_PROPERTIES, 'power_fcst_init_time': _TIMESTAMP_COLUMN_PROPERTIES, 'nwp_init_time': _TIMESTAMP_COLUMN_PROPERTIES, 'power_fcst': ColumnProperties(encoding='BYTE_STREAM_SPLIT', dictionary_enabled=False)})
module-attribute
Parquet writer settings for the power_forecasts Delta table.
The delta-rs defaults (SNAPPY + dictionary encoding everywhere) leave power_fcst — ~76% of
the bytes — essentially uncompressed. Measured on the table's largest single file (18.3M rows,
105 MB): ZSTD-3 alone → 80% of the original size; adding these column encodings plus the
POWER_FORECASTS_SORT_COLS row order → 50%; adding the POWER_FCST_SIGNIFICAND_BITS
precision reduction → 29%. (That file is the table's least compressible slice — across the full
table the format achieved 11.5%.)
Classes
Functions:
write_power_forecasts(forecasts, table_uri, *, replace_partition=None, replace_predicate_extra=None, storage_options=None)
Write PowerForecast rows to the power_forecasts Delta table in its storage format.
Applies the three storage levers before writing: rounds power_fcst to
POWER_FCST_SIGNIFICAND_BITS significand bits, sorts rows by
POWER_FORECASTS_SORT_COLS, and writes with POWER_FORECASTS_WRITER_PROPERTIES.
The table is partitioned by (experiment_name, fold_id). A multi-chunk materialisation
passes replace_partition on its first chunk — overwriting that partition so prior rows
are always replaced, even when the first chunk is empty — and None (append) on the rest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forecasts
|
DataFrame[PowerForecast]
|
Validated forecast rows. |
required |
table_uri
|
str | Path
|
Path or URI of the |
required |
replace_partition
|
tuple[str, str] | None
|
|
None
|
replace_predicate_extra
|
str | None
|
An additional |
None
|
storage_options
|
ObjectStoreOptions | None
|
delta-rs object-store options (credentials/endpoint) for a remote
|
None
|
Source code in packages/delta_store/src/delta_store/power_forecasts.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | |
delta_store.nwp
Storage policy for the nwp Delta table.
Owns everything about how Nwp rows are laid out on disk: the parquet writer properties, the
compression-friendly row order, and the significand-precision reduction of the continuous
weather variables. Callers write through :func:write_nwp so it is impossible to land rows in
the table without this format applied.
Stores plain Float32 + delta_store.precision.round_to_significand_bits — the technique
used by delta_store.power_forecasts, but with different writer properties. Measured on
real NWP data (9 partitions spread across two years of history): BYTE_STREAM_SPLIT made every
continuous column larger, not smaller — the opposite of the power_forecasts result.
Working hypothesis: significand rounding collapses NWP values into a small set of repeats (many
H3 cells / ensemble members round to the same value), which Parquet's default dictionary+RLE
encoding captures directly; BYTE_STREAM_SPLIT scatters that repetition across four separate
byte planes and loses more than it gains. power_forecasts's target values have no such
repetition (near-continuous ML output), so BYTE_STREAM_SPLIT wins there instead — the two
tables need different writer properties. See docs/architecture/overview.md for the measured
GB/yr and read-latency numbers.
Attributes
NWP_SIGNIFICAND_BITS = 13
module-attribute
Significand bits kept for every continuous NWP variable (1 implicit + 12 explicit fraction
bits) — the same budget as delta_store.power_forecasts.POWER_FCST_SIGNIFICAND_BITS. Caps the
relative error at 2⁻¹³ ≈ 1.2×10⁻⁴. Measured max absolute error on real data: ≤0.004 °C for
temperature, ≤8 Pa (0.08 hPa) for mean-sea-level pressure — both well inside tolerance
(temperature ≤0.25 K, MSL pressure ≤1 hPa).
NWP_SORT_COLS = ('init_time', 'ensemble_member', 'valid_time', 'h3_index')
module-attribute
Within-file row order for nwp writes — member before valid_time (the opposite
priority from power_forecasts, which sorts member-adjacent for a different reason: there
it's about compressing near-duplicate ensemble values; here it's about row-group pruning).
Sorting ensemble_member early means each ~1M-row Parquet row group spans only a handful of
member values instead of all ~51, so a single-member predicate (the control-member read every
training run does) can skip most row groups via min/max stats instead of decoding the whole
partition. Measured on a real 29-day/9-cell/control-member read: ~5x faster and ~5x less peak
memory than the previous valid_time-first sort, for a ~2% storage cost.
NWP_WRITER_PROPERTIES = WriterProperties(compression='ZSTD', compression_level=3)
module-attribute
Deliberately no per-column encoding overrides (no BYTE_STREAM_SPLIT,
DELTA_BINARY_PACKED, or disabled dictionary encoding) — see this module's docstring for why
that choice, which won for power_forecasts, measures worse here.
Classes
Functions:
write_nwp(nwp, table_uri, storage_options=None)
Append Nwp rows to the nwp Delta table in its storage format.
Rounds every continuous weather variable to NWP_SIGNIFICAND_BITS significand bits, sorts
rows by NWP_SORT_COLS, and writes with NWP_WRITER_PROPERTIES. The table is
partitioned by (nwp_model_id, init_time), matching Nwp.scan_delta's
partition-pruning assumptions; the first write creates the table.
Append-only: each (nwp_model_id, init_time) partition is written exactly once — the
daily ecmwf_ens asset downloads one brand-new NWP run per Dagster partition. (No
replace_partition option like write_power_forecasts: nothing re-materialises an
existing NWP partition today, and a partition-replace predicate on a Timestamp partition
column would need its own careful verification — add it only when a caller actually needs
it.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
nwp
|
DataFrame[Nwp]
|
Validated NWP rows for a single |
required |
table_uri
|
str | Path
|
Path or URI of the |
required |
storage_options
|
ObjectStoreOptions | None
|
delta-rs object-store options (credentials/endpoint) for a remote
|
None
|
Source code in packages/delta_store/src/delta_store/nwp.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |