Skip to content

0.5.0 – 2026-10-17

Choose a tag to compare

@github-actions github-actions released this 17 Oct 04:40

Release Notes

Added

  • Dedup on read. In tables with dedup on, the Timelake reader (CLI, Python, Rust) returns only the latest write of
    each (series, time), before compaction has removed the older copies. Only files that may share a key are merged;
    the rest are read as before. DuckDB, other Iceberg readers and the TypeScript client still see the copies until
    compaction. It can be turned off per table (timelake.read-dedup = false) or per session
    (SET timelake.read_dedup = false, or --set timelake.read_dedup=false on the CLI). A second strategy,
    timelake.read-dedup-strategy = keys-first, lists the older copies first and leaves them out of a normal scan.
  • Cold partitions compact sooner. Once newer data is timelake.compact-cold-after (default 1 hour) past a
    partition's end, inline maintenance rewrites the partition into sorted, duplicate-free files when it has two small
    files, even if the append didn't touch it.
  • Insertion time. Time-series tables get an _ingest_ts column: each append fills it with the time the append
    started, unless the input brings its own values (an import keeping arrival times). Among rows with the same
    (series, time) the latest insertion wins, then the later commit, everywhere: within an append, on read, in
    compaction and in the last-value index. DuckDB can pick the same rows with
    QUALIFY row_number() OVER (PARTITION BY <series>, <time> ORDER BY _ingest_ts DESC) = 1. Leave it out with
    ingest_time=False (Python) or --no-ingest-time (CLI); timelake info shows it.
  • Settings. Every switch is declared in one registry, with its default, the levels it can be set at (creation,
    table, session, call) and the version that added it. timelake settings lists them, and with a table name shows
    the table's values; timelake alter <table> --set read-dedup=false --unset retention changes a table's settings
    (Table.set_properties in Python and Rust). Lake(..., settings={...}) in Python sets session settings for every
    query, and SHOW timelake.read_dedup shows a session's value. Names work with dashes or underscores, with or
    without timelake.. The docs page "Table properties" is now "Settings".
  • Rollups. CREATE MATERIALIZED ROLLUP iot.readings_1h AS SELECT device, time_bucket(INTERVAL '1 hour', time) AS bucket, avg(value), max(value), count(*) FROM iot.readings GROUP BY device, bucket creates a table of aggregates
    per time bucket and computes it. Maintenance keeps it up to date: inline after appends once its last refresh is
    older than timelake.rollup-refresh-interval (default 1 minute), and on every timelake maintain;
    REFRESH MATERIALIZED ROLLUP refreshes it now and DROP MATERIALIZED ROLLUP removes it. A GROUP BY time_bucket(…)
    query on the raw table with the same width and grouping reads the buckets the rollup has right from it, and the rest
    (buckets changed since the refresh, partial buckets at the edges of the time range) from the raw table, so the
    answer is the same as without it; SET timelake.rollup_routing = false turns that off. First version: one level from
    the raw table, grouped by series-key columns, with count, sum, min, max, avg, first_value and
    last_value. timelake maintain and Table.maintain() report rollups_refreshed and rollup_rows
    (--no-rollups, rollups=False to skip). The docs have a new page, "Rollups".
  • Block cache. Reads of files a table never changes (data files, manifests, manifest lists, last-value index
    files) go through a cache of 1 MiB blocks, in memory by default on object stores and HTTPS (256 MiB), and on local
    disk with timelake_cache=disk, shared by every process on the machine (4 GiB). A query still checks for a newer
    table version, so answers are as fresh as before. Storage options timelake_cache, timelake_cache_dir,
    timelake_cache_memory_bytes, timelake_cache_disk_bytes and timelake_cache_block_bytes; Lake.cache_stats()
    in Python and Storage::cache_stats() in Rust count hits and misses. On S3, TSBS IoT repeat queries went from
    54 to 16 ms with no requests, and 16 clients from 288 to 398 queries per second.

Changed

  • Table format 0.3.0 for new tables (the insertion-time column). Timelake 0.4 and older can't open them: upgrade
    every reader and writer first. timelake upgrade raises older tables to 0.3.0 but doesn't add _ingest_ts (a
    table's columns are fixed at creation for now).
  • Settings are checked. Creating or altering a table with an unknown timelake.* property, a bad value, or a
    setting fixed at creation (time-column, series-key, chunk-interval, dedup, ingest-time-column) as a
    property is an error; use the table options for those. timelake.format-version can no longer be set by hand.
    SET of a table-only setting in SQL is an error. timelake sql --set with a bad key reports an error instead of
    crashing.
  • Rust: SessionSettings has get and flag instead of public fields. Session::parse returns
    timelake::Statement (DataFusion's statement, or a rollup statement) for Session::execute.
    MaintainOptions has a rollups field and MaintainReport has rollups_refreshed and rollup_rows.
  • With dedup on, rows with the same (series, time) within one append keep only the last, so the append's rows
    counts them once. Files written by earlier versions may still hold such copies until compaction.
  • Benchmarks: Timelake's repeat runs on object storage now read from its memory cache, so their times drop from
    this version on; first runs, in a fresh process, are unaffected. timebench run --reuse also runs the concurrent
    dashboards, and TIMEBENCH_TIMELAKE_OPTIONS passes storage options to Timelake's readers.
  • The TypeScript client's WebAssembly reader is about 4% smaller (6.7 MB), built with Rust 1.99.

Fixed

  • Compaction could let an older write win. Inline compaction merged files of a similar size even when a file of
    another size was committed between them, and the merged file then counted as newer than that file: where both held
    the same (series, time), the older value won from then on. Compaction now merges only files next to each other in
    commit order, and a merged file keeps the data sequence number of the newest file it replaces.

Install timelake-cli 0.5.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/polythings/timelake/releases/download/v0.5.0/timelake-cli-installer.sh | sh

Install prebuilt binaries via powershell script

powershell -ExecutionPolicy Bypass -c "irm https://github.com/polythings/timelake/releases/download/v0.5.0/timelake-cli-installer.ps1 | iex"

Download timelake-cli 0.5.0

File Platform Checksum
timelake-cli-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
timelake-cli-x86_64-apple-darwin.tar.xz Intel macOS checksum
timelake-cli-x86_64-pc-windows-msvc.zip x64 Windows checksum
timelake-cli-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
timelake-cli-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum

Verifying GitHub Artifact Attestations

The artifacts in this release have attestations generated with GitHub Artifact Attestations. These can be verified by using the GitHub CLI:

gh attestation verify <file-path of downloaded artifact> --repo polythings/timelake

You can also download the attestation from GitHub and verify against that directly:

gh attestation verify <file-path of downloaded artifact> --bundle <file-path of downloaded attestation> --repo polythings/timelake