From e245c837f9d34538097bc11bd00ea304c60a25d2 Mon Sep 17 00:00:00 2001 From: Sam Gbafa Date: Sun, 18 Oct 2026 11:36:05 +0000 Subject: [PATCH] chore(release): 1.21.0 --- CHANGELOG.md | 12 ++++++++++++ Cargo.lock | 2 +- test/m1-realdata-e2e/Cargo.lock | 2 +- tinycloud-node-server/Cargo.toml | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccdce53b..f67c4401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ## [Unreleased] +## [1.21.0] - 2026-10-18 + +### Release notes + +- Request telemetry (TC-959 N2): no measurable latency change in a 10-pair A/B; CPU per request about +2% (median, within noise). +- **Rollback is restore-only.** Four migrations are unknown to v1.20.x (m20261014 delegation_cid_alias #291, m20261015 delegation_cursor_index #296, m20261017 epoch_order_space_index #341, m20261018 epoch_head #343); a binary-only rollback to v1.20.x is unsupported. +- The epoch_head backfill ships disabled (`TINYCLOUD_EPOCH_HEAD_BACKFILL__ENABLED=false`); it's enabled later once DB CPU has headroom. +- Upload bodies are capped at 95 MiB (413). +- `/attestation` is fixed (#295). + +### Changes + - Production compose disables the epoch_head backfill at boot; enable it later once DB CPU has headroom. - New migration `m20261017_000000_epoch_order_space_index` adds `idx_epoch_order_space_parent_child ON epoch_order (space, parent, child)` for the head-epoch anti-join that every write runs under its space lock (TC-1274). On a fresh database the build is instant. On PostgreSQL, if a valid index of that name and shape already exists, the migration issues no DDL and takes no lock on `epoch_order`. If the index of that name is INVALID or has a different shape, the boot fails, and nothing is applied, with an error naming the fix. On an existing database **without** the index, the migration builds it inside the boot's migration transaction. That holds a SHARE lock that blocks writes for the whole build, and the lock wait is bounded by the 30 s `lock_timeout` (TC-1176). **Runbook (PostgreSQL, before upgrading a node with history):** in autocommit, run `CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_epoch_order_space_parent_child ON epoch_order (space, parent, child);`. Then confirm `SELECT indisvalid FROM pg_index WHERE indexrelid = 'idx_epoch_order_space_parent_child'::regclass;` returns `t`. If it returns `f`, run `DROP INDEX CONCURRENTLY idx_epoch_order_space_parent_child;` and build it again. `down` keeps the index. MySQL is covered only by the generated statement, because the full MySQL migration chain does not run (TC-1314). Rolling back to v1.20.x: the older node refuses to start while `seaql_migrations` lists this migration (`Migration file of version 'm20261017_000000_epoch_order_space_index' is missing`). Delete that row first, and keep the index: v1.20.x runs the same anti-join, and a later upgrade finds the valid index and does nothing. - Writes no longer scan a space's whole epoch history to find its head epochs (TC-1274). A new `epoch_head` table (migration `m20261018_000000_epoch_head`) caches each space's heads. Every write maintains it in the same transaction, under the space's existing sequence lock. A write now reads O(heads) rows, normally one, instead of running the `epoch`/`epoch_order` anti-join, and usually costs one more statement, an `UPDATE` of the cached head. The cache is used only when its newest `seq` is the space's newest. Otherwise that space falls back to the old anti-join once and the write repairs the cache, so epochs appended by older releases (a rolling deploy, or a rollback and upgrade) never produce wrong parents. Heads are taken in `id` byte order. Previously the anti-join returned them in no defined order. This affects only a merge after a pre-TC-732 fork, and nothing re-derives an epoch hash from its parents. The migration only creates the empty table, without foreign keys, so it takes no locks on existing tables. After the node starts serving, a gentle online backfill checks each space once. It runs in one short transaction per space, under that space's lock, at no more than `epoch_head_backfill.spaces_per_second` (default 2) and pauses after each space for at least as long as the space took. It stops at its first error with one log line, and logs progress every `progress_every` spaces (default 500). Turn it off with `[epoch_head_backfill] enabled = false` or `TINYCLOUD_EPOCH_HEAD_BACKFILL__ENABLED=false`. Writes stay correct without it. Keep the `idx_epoch_order_space_parent_child` index: the fallback and the backfill use it. Rolling back to v1.20.x: as for `m20261017_000000_epoch_order_space_index`, the older node refuses to start until its rows are gone. Run `DELETE FROM seaql_migrations WHERE version IN ('m20261017_000000_epoch_order_space_index', 'm20261018_000000_epoch_head');` first. Keep the index and the `epoch_head` table: v1.20.x ignores the table. A later upgrade re-applies both migrations as no-ops (the index check and `CREATE TABLE IF NOT EXISTS`), and the `seq` check repairs every space the older node wrote to. diff --git a/Cargo.lock b/Cargo.lock index 9a04ebca..8a74e748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9677,7 +9677,7 @@ dependencies = [ [[package]] name = "tinycloud-node" -version = "1.20.7" +version = "1.21.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/test/m1-realdata-e2e/Cargo.lock b/test/m1-realdata-e2e/Cargo.lock index 8cd32859..e608a96f 100644 --- a/test/m1-realdata-e2e/Cargo.lock +++ b/test/m1-realdata-e2e/Cargo.lock @@ -7771,7 +7771,7 @@ dependencies = [ [[package]] name = "tinycloud-node" -version = "1.20.7" +version = "1.21.0" dependencies = [ "aes-gcm", "anyhow", diff --git a/tinycloud-node-server/Cargo.toml b/tinycloud-node-server/Cargo.toml index 9a1706ad..6ae82d2d 100644 --- a/tinycloud-node-server/Cargo.toml +++ b/tinycloud-node-server/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tinycloud-node" build = "build.rs" -version = "1.20.7" +version = "1.21.0" authors = ["TinyCloud Protocol"] edition = "2021" description = "TinyCloud Protocol Node"