Skip to content

Commit a1861ab

Browse files
suristersvenklemm
authored andcommitted
Forwardport 2.27.0
1 parent 1db5f71 commit a1861ab

7 files changed

Lines changed: 287 additions & 203 deletions

File tree

CHANGELOG.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,89 @@ This page lists all the latest features and updates to TimescaleDB. When
55
you use psql to update your database, use the -X flag and prevent any .psqlrc
66
commands from accidentally triggering the load of a previous DB version.**
77

8+
## 2.27.0 (2026-05-12)
9+
10+
This release contains performance improvements and bug fixes since the 2.26.4 release. We recommend that you upgrade at the next available opportunity.
11+
12+
**Highlighted features in TimescaleDB v2.27.0**
13+
* The Hypercore engine now supports a vectorized implementation of filters by evaluating them inline through the standard Postgres function path. This expands the set of queries (including continuous aggregate refreshes) that can take the faster path through the columnstore, yielding speedups ranging from 30% up to 2x in benchmarks.
14+
* `UPDATE` and `DELETE` statements with equality predicates can now use bloom filters to skip decompressing batches whose compressed rows can't match. When multiple bloom filters apply, they are evaluated in decreasing order of column count (most selective first), and EXPLAIN now reports filtering activity via the new "Compressed batches filtered" and "Batches filtered after decompression counters". The query performance increases in some case up to 160 times.
15+
* `UPSERT` queries can now leverage bloom filters (including composite ones) to skip decompressing batches when the arbiter values are guaranteed not to be present, with the most-selective filter chosen automatically when multiple apply. EXPLAIN output adds new statistics — batches checked by bloom, batches pruned by bloom, batches without bloom, and bloom false positives — for visibility into pruning effectiveness.
16+
17+
**Upcoming PostgreSQL 15 EOL announcement**
18+
As a reminder, the upcoming TimescaleDB release in June 2026 will officially be the last version with support for PostgreSQL 15. This deprecation was initially announced in the [v2.23.0 changelog](https://github.com/timescale/timescaledb/releases/tag/2.23.0) on October 29, 2025, to provide users ample time to prepare. To ensure uninterrupted access to new features, bugfixes and performance enhancements, all instances must be upgraded to PostgreSQL 16 or greater.
19+
20+
**Backward-Incompatible Changes**
21+
* [#9579](https://github.com/timescale/timescaledb/pull/9579) The bloom filter sparse indexes on compressed `int2` columns could lead to `SELECT` queries not returning the rows that actually match the `WHERE` condition. The upgrade is blocked for the affected databases, and the incorrect indexes have to be dropped manually before the upgrade.
22+
* This release introduces a new naming convention for composite bloom filter metadata. While this change will not disrupt query processing, v2.27 cannot automatically utilize composite bloom filters generated in v2.26. To convert your existing v2.26 composite bloom filters, the legacy metadata columns must be renamed. This is a lightweight, catalog-only operation requiring zero data recompression, which can be done with [this migration script](https://github.com/timescale/timescaledb-extras/blob/main/utils/2.27.x-fix-composite-bloom-columns.sql).
23+
24+
**Features**
25+
* [#8868](https://github.com/timescale/timescaledb/pull/8868) Use `PG_MODULE_MAGIC_EXT` for `PG18`
26+
* [#8967](https://github.com/timescale/timescaledb/pull/8967) Rewriting queries with continuous aggregates exactly matching query aggregation
27+
* [#9192](https://github.com/timescale/timescaledb/pull/9192) Push down scalar array operations into the columnar metadata scan by transforming them into an `OR`/`AND` clause.
28+
* [#9355](https://github.com/timescale/timescaledb/pull/9355) Defer `segmentby` default for direct compress
29+
* [#9374](https://github.com/timescale/timescaledb/pull/9374) Use bloom filters to eliminate decompression of unrelated compressed batches during `UPSERT`s.
30+
* [#9396](https://github.com/timescale/timescaledb/pull/9396) Analyze and get `segmentby` during direct compress
31+
* [#9398](https://github.com/timescale/timescaledb/pull/9398) Fix chunk exclusion for `IN`/`ANY` on open (time) dimensions
32+
* [#9399](https://github.com/timescale/timescaledb/pull/9399) Use bloom filters to reduce decompression during `UPDATE`/`DELETE` commands.
33+
* [#9403](https://github.com/timescale/timescaledb/pull/9403) Set default `segmentby` during direct compress flush
34+
* [#9437](https://github.com/timescale/timescaledb/pull/9437) Allow running compression as part of refresh policy for compressed continuous aggregates
35+
* [#9443](https://github.com/timescale/timescaledb/pull/9443) Enable vectorized aggregation in some cases when the `WHERE` clause contains filters not handled through the "Vectorized Filters" facility. This includes e.g. filters on `time_bucket()`.
36+
* [#9458](https://github.com/timescale/timescaledb/pull/9458) Remove `_timescaledb_functions.repair_relation_acls`
37+
* [#9475](https://github.com/timescale/timescaledb/pull/9475) Calculate hashes for bloom filter predicates at planning time.
38+
* [#9504](https://github.com/timescale/timescaledb/pull/9504) Allow `ALTER TABLE RESET` on materialization hypertables
39+
* [#9521](https://github.com/timescale/timescaledb/pull/9521) Add support for reporting index creation progress
40+
* [#9559](https://github.com/timescale/timescaledb/pull/9559) Notice on compression settings change
41+
* [#9569](https://github.com/timescale/timescaledb/pull/9569) For nullable `orderby` columns do segmentwise decompress-compress instead of segmentwise recompress.
42+
* [#9583](https://github.com/timescale/timescaledb/pull/9583) Drop existing sparse indexes when dropping columns
43+
* [#9648](https://github.com/timescale/timescaledb/pull/9648) Support `ENABLE`/`DISABLE TRIGGER` on hypertables
44+
* [#9702](https://github.com/timescale/timescaledb/pull/9702) Allow Batch Sorted Merge for unordered chunks with no `segmentby` or when all `segmentby` columns are pinned to a `Const`
45+
46+
**Bugfixes**
47+
* [#9363](https://github.com/timescale/timescaledb/pull/9363) Change compression job status when chunks could be compressed
48+
* [#9413](https://github.com/timescale/timescaledb/pull/9413) Fix incorrect decompress markers on full batch delete
49+
* [#9414](https://github.com/timescale/timescaledb/pull/9414) Fix `NULL` compression handling in `estimate_uncompressed_size`
50+
* [#9417](https://github.com/timescale/timescaledb/pull/9417) Fix segfault in `bloom1_contains`
51+
* [#9479](https://github.com/timescale/timescaledb/pull/9479) Disallow sub-day offset for `time-bucket` on `Date`
52+
* [#9482](https://github.com/timescale/timescaledb/pull/9482) Forbid Batch Sort Merge on nullable `orderby` columns
53+
* [#9490](https://github.com/timescale/timescaledb/pull/9490) Disallow negative interval as `chunk_interval`
54+
* [#9500](https://github.com/timescale/timescaledb/pull/9500) Fix off-by-one error when building object name
55+
* [#9519](https://github.com/timescale/timescaledb/pull/9519) Remove self-referential `FOREIGN KEY` constraints from catalog
56+
* [#9561](https://github.com/timescale/timescaledb/pull/9561) Simplify job history retention by replacing binary search and temp table
57+
* [#9590](https://github.com/timescale/timescaledb/pull/9590) Fix policy skipping uncompressed chunks
58+
* [#9596](https://github.com/timescale/timescaledb/pull/9596) Remove unused `process_hypertable_invalidations` policy code
59+
* [#9604](https://github.com/timescale/timescaledb/pull/9604) Remove dead `post_parse_analyze_hook` capture in loader
60+
* [#9610](https://github.com/timescale/timescaledb/pull/9610) Fix use-after-free crash in `cache_destroy` during transaction abort
61+
* [#9632](https://github.com/timescale/timescaledb/pull/9632) Preserve chunk settings during recompress
62+
* [#9640](https://github.com/timescale/timescaledb/pull/9640) Fix `NULL` `datumCopy` crash in `segmentby` analysis
63+
* [#9680](https://github.com/timescale/timescaledb/pull/9680) Fix segfault in direct compress insert on hypertable with dropped column
64+
* [#9692](https://github.com/timescale/timescaledb/pull/9692) Fix internal "invalid perminfoindex 0 in RTE" error on `MERGE NOT MATCHED INSERT` into a hypertable
65+
* [#9705](https://github.com/timescale/timescaledb/pull/9705) Avoid double `TOAST` delete when `DELETE-after-compression` is enabled
66+
* [#9705](https://github.com/timescale/timescaledb/pull/9705) Only freeze compressed rows when truncating uncompressed chunk
67+
* [#9706](https://github.com/timescale/timescaledb/pull/9706) Use `bigint` in `estimate_uncompressed_size` calculations
68+
* [#9709](https://github.com/timescale/timescaledb/pull/9709) Reject mismatched element type in `bool`/`uuid` decompression
69+
* [#9710](https://github.com/timescale/timescaledb/pull/9710) Return `bigint` from `compressed_data_column_size`
70+
* [#9711](https://github.com/timescale/timescaledb/pull/9711) Fix registration row leak when continuous aggregate refresh fails
71+
* [#9697](https://github.com/timescale/timescaledb/pull/9697) Improve `pathkey` handling for compressed sub-paths during sort transformation
72+
* [#9743](https://github.com/timescale/timescaledb/pull/9743) Fix the composite bloom metadata column naming scheme
73+
* [#9767](https://github.com/timescale/timescaledb/pull/9767) Skip dropped chunks when trying to remove `ts_cagg_invalidation_trigger`
74+
* [#9747](https://github.com/timescale/timescaledb/pull/9747) Reject inheriting from a hypertable
75+
* [#9744](https://github.com/timescale/timescaledb/pull/9744) Use a fixed call string for the telemetry job in `ts_stat_statements` recording
76+
* [#9736](https://github.com/timescale/timescaledb/pull/9736) Do logical sparse index comparison
77+
* [#9731](https://github.com/timescale/timescaledb/pull/9731) Avoid creating overlapping batches during recompression for multi orderby configurations
78+
* [#9717](https://github.com/timescale/timescaledb/pull/9717) Reject non-positive time bucket width on cagg creation
79+
* [#9707](https://github.com/timescale/timescaledb/pull/9707) Fix policy name comparison in remove_policies
80+
81+
**New Settings**
82+
* `enable_cagg_rewrites`: enables rewriting queries with CAggs. Off by default. `cagg_rewrites_debug_info`: prints CAgg rewrites diagnostics. Off by default.
83+
* `enable_columnar_scan_filter_pushdown`: enables pushing the filters on columnar scan down to the compressed scan level. On by default.
84+
85+
**Thanks**
86+
* @fabriziomello for adding support for `PG_MODULE_MAGIC_EXT`
87+
* @maltalex for reporting an issue with index creation progress reporting
88+
* @pavanmanishd for the first version of the fix for #9743
89+
* @h0rn3t for reporting issue with recompression creating overlapping batches
90+
891
## 2.26.4 (2026-04-28)
992

1093
This release contains bug fixes since the 2.26.3 release. We recommend that you upgrade at the next available opportunity.

sql/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ set(MOD_FILES
6464
updates/2.26.0--2.26.1.sql
6565
updates/2.26.1--2.26.2.sql
6666
updates/2.26.2--2.26.3.sql
67-
updates/2.26.3--2.26.4.sql)
67+
updates/2.26.3--2.26.4.sql
68+
updates/2.26.4--2.27.0.sql)
6869

6970
# The downgrade file to generate a downgrade script for the current version, as
7071
# specified in version.config

sql/updates/2.26.4--2.27.0.sql

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
2+
DROP PROCEDURE IF EXISTS _timescaledb_functions.repair_relation_acls();
3+
DROP FUNCTION IF EXISTS _timescaledb_functions.makeaclitem(regrole, regrole, text, bool);
4+
5+
-- Create watermark record when required. This uses pure SQL to avoid calling
6+
-- C functions that need catalog access during ALTER EXTENSION UPDATE.
7+
-- this is only needed for users upgrading from before 2.11.0, as the watermark
8+
-- was added in that version.
9+
DO
10+
$$
11+
DECLARE
12+
ts_version TEXT;
13+
cagg_rec RECORD;
14+
max_val BIGINT;
15+
watermark_val BIGINT;
16+
bucket_width_val BIGINT;
17+
BEGIN
18+
SELECT extversion INTO ts_version FROM pg_extension WHERE extname = 'timescaledb';
19+
IF ts_version < '2.11.0' THEN
20+
RETURN;
21+
END IF;
22+
23+
FOR cagg_rec IN
24+
SELECT a.mat_hypertable_id,
25+
h.schema_name, h.table_name,
26+
d.column_name, d.column_type,
27+
bf.bucket_width, bf.bucket_fixed_width
28+
FROM _timescaledb_catalog.continuous_agg a
29+
LEFT JOIN _timescaledb_catalog.continuous_aggs_watermark w ON w.mat_hypertable_id = a.mat_hypertable_id
30+
JOIN _timescaledb_catalog.hypertable h ON h.id = a.mat_hypertable_id
31+
JOIN _timescaledb_catalog.dimension d ON d.hypertable_id = a.mat_hypertable_id AND d.num_slices IS NULL
32+
LEFT JOIN _timescaledb_catalog.continuous_aggs_bucket_function bf ON bf.mat_hypertable_id = a.mat_hypertable_id
33+
WHERE w.mat_hypertable_id IS NULL
34+
ORDER BY a.mat_hypertable_id
35+
LOOP
36+
-- Get max value from materialization hypertable converted to internal representation
37+
IF cagg_rec.column_type IN ('timestamptz'::regtype, 'timestamp'::regtype, 'date'::regtype) THEN
38+
EXECUTE format(
39+
'SELECT (pg_catalog.date_part(''epoch'', pg_catalog.max(%I)) * 1000000)::bigint FROM %I.%I',
40+
cagg_rec.column_name, cagg_rec.schema_name, cagg_rec.table_name
41+
) INTO max_val;
42+
ELSE
43+
EXECUTE format(
44+
'SELECT pg_catalog.max(%I)::bigint FROM %I.%I',
45+
cagg_rec.column_name, cagg_rec.schema_name, cagg_rec.table_name
46+
) INTO max_val;
47+
END IF;
48+
49+
IF max_val IS NULL OR cagg_rec.bucket_width IS NULL OR NOT cagg_rec.bucket_fixed_width THEN
50+
-- No data, no bucket function info, or variable-width bucket: use minimum value.
51+
-- The next cagg refresh will compute the correct watermark.
52+
watermark_val := '-9223372036854775808'::bigint;
53+
ELSE
54+
-- Fixed-width bucket: watermark is max value + bucket width
55+
IF cagg_rec.column_type IN ('timestamptz'::regtype, 'timestamp'::regtype, 'date'::regtype) THEN
56+
bucket_width_val := (pg_catalog.date_part('epoch', cagg_rec.bucket_width::interval) * 1000000)::bigint;
57+
ELSE
58+
bucket_width_val := cagg_rec.bucket_width::bigint;
59+
END IF;
60+
watermark_val := max_val + bucket_width_val;
61+
END IF;
62+
63+
INSERT INTO _timescaledb_catalog.continuous_aggs_watermark (mat_hypertable_id, watermark)
64+
VALUES (cagg_rec.mat_hypertable_id, watermark_val);
65+
END LOOP;
66+
END;
67+
$$;
68+
69+
-- Cleanup orphaned compression settings
70+
WITH orphaned_settings AS (
71+
SELECT cs.relid, cl.relname
72+
FROM _timescaledb_catalog.compression_settings cs
73+
LEFT JOIN pg_class cl ON (cs.relid = cl.oid)
74+
WHERE cl.relname IS NULL
75+
)
76+
DELETE FROM _timescaledb_catalog.compression_settings AS cs
77+
USING orphaned_settings AS os WHERE cs.relid = os.relid;
78+
79+
-- Remove self-referential foreign keys to eliminate pg_dump circular dependency warnings
80+
ALTER TABLE _timescaledb_catalog.hypertable DROP CONSTRAINT IF EXISTS hypertable_compressed_hypertable_id_fkey;
81+
ALTER TABLE _timescaledb_catalog.chunk DROP CONSTRAINT IF EXISTS chunk_compressed_chunk_id_fkey;
82+
83+
84+
-- Block upgrade if bloom filter sparse indexes exist on smallint (int2)
85+
-- columns. These bloom filters used PostgreSQL's hashint2extended while
86+
-- the new code uses bloom1_hash_2. Existing bloom data must be dropped
87+
-- before upgrading; recompress afterwards to rebuild with the new hash.
88+
DO $$
89+
DECLARE
90+
drop_commands text;
91+
BEGIN
92+
WITH bloom_entries AS (
93+
SELECT relid AS chunk_oid,
94+
compress_relid,
95+
columns,
96+
(SELECT string_agg(col, '_' ORDER BY ordinality)
97+
FROM jsonb_array_elements_text(columns)
98+
WITH ORDINALITY AS t(col, ordinality)) AS col_suffix
99+
FROM _timescaledb_catalog.compression_settings,
100+
jsonb_array_elements(index) AS elem,
101+
LATERAL (SELECT
102+
CASE jsonb_typeof(elem->'column')
103+
WHEN 'array' THEN elem->'column'
104+
ELSE jsonb_build_array(elem->'column')
105+
END AS columns
106+
) AS normalized
107+
WHERE elem->>'type' = 'bloom'
108+
AND compress_relid IS NOT NULL
109+
),
110+
bloom_column_names AS (
111+
SELECT chunk_oid, compress_relid, col_suffix, colname
112+
FROM bloom_entries,
113+
jsonb_array_elements_text(columns) AS bloom_column(colname)
114+
),
115+
int2_bloom_suffixes AS (
116+
SELECT DISTINCT compress_relid, col_suffix
117+
FROM bloom_column_names
118+
JOIN pg_attribute ON attrelid = chunk_oid
119+
AND attname = colname
120+
AND atttypid = 'int2'::regtype
121+
AND attnum > 0
122+
),
123+
bloom_cols_to_drop AS (
124+
SELECT compress_relid,
125+
attname AS bloom_attname
126+
FROM int2_bloom_suffixes
127+
JOIN pg_attribute ON attrelid = compress_relid
128+
AND attname IN (
129+
'_ts_meta_v2_bloom1_' || col_suffix,
130+
'_ts_meta_v2_bloomh_' || col_suffix,
131+
'_ts_meta_v2_bloomg_' || col_suffix
132+
)
133+
AND attnum > 0
134+
)
135+
SELECT string_agg(DISTINCT
136+
format('ALTER TABLE %s DROP COLUMN %I;',
137+
compress_relid::regclass, bloom_attname),
138+
E'\n' ORDER BY
139+
format('ALTER TABLE %s DROP COLUMN %I;',
140+
compress_relid::regclass, bloom_attname))
141+
INTO drop_commands
142+
FROM bloom_cols_to_drop;
143+
144+
IF drop_commands IS NOT NULL THEN
145+
RAISE EXCEPTION
146+
'existing bloom filter sparse indexes on smallint columns are incompatible '
147+
'with this version of TimescaleDB'
148+
USING
149+
DETAIL = E'These indexes must be dropped before upgrading. To do so, run the following commands:\n\n'
150+
|| E'SET timescaledb.restoring = on;\n'
151+
|| drop_commands || E'\n'
152+
|| 'SET timescaledb.restoring = off;',
153+
HINT = 'To rebuild the bloom filter indexes after upgrading, decompress and compress the affected chunks.';
154+
END IF;
155+
END
156+
$$;
157+
158+
159+
DROP FUNCTION IF EXISTS _timescaledb_functions.job_history_bsearch;
160+
161+
DROP FUNCTION IF EXISTS _timescaledb_functions.policy_process_hypertable_invalidations_check(JSONB);
162+
DROP PROCEDURE IF EXISTS _timescaledb_functions.policy_process_hypertable_invalidations(INTEGER, JSONB);
163+
DROP PROCEDURE IF EXISTS @extschema@.add_process_hypertable_invalidations_policy(REGCLASS, INTERVAL, BOOL, TIMESTAMPTZ, TEXT);
164+
DROP PROCEDURE IF EXISTS @extschema@.remove_process_hypertable_invalidations_policy(REGCLASS, BOOL);
165+
166+
-- Return type widened from INTEGER to BIGINT; per-batch byte count can
167+
-- exceed INT32_MAX for wide varlena columns and was silently wrapping.
168+
DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT);
169+
170+
-- Migration: refresh orderby sparse index entries in compression_settings
171+
UPDATE _timescaledb_catalog.compression_settings
172+
SET index = (
173+
SELECT COALESCE(jsonb_agg(elem), '[]'::jsonb)
174+
FROM jsonb_array_elements(index) AS elem
175+
WHERE elem->>'source' != 'orderby'
176+
)
177+
WHERE index IS NOT NULL
178+
AND index @> '[{"source": "orderby"}]';
179+
180+
UPDATE _timescaledb_catalog.compression_settings cs
181+
SET index = COALESCE(index, '[]'::jsonb) ||
182+
(
183+
SELECT jsonb_agg(jsonb_build_object(
184+
'type', 'minmax',
185+
'source', 'orderby',
186+
'column', elem))
187+
FROM unnest(cs.orderby) AS elem
188+
)
189+
WHERE cs.orderby IS NOT NULL;
190+

sql/updates/2.27.0--2.26.4.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
-- Re-add self-referential foreign keys
3+
ALTER TABLE _timescaledb_catalog.hypertable ADD CONSTRAINT hypertable_compressed_hypertable_id_fkey FOREIGN KEY (compressed_hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id);
4+
ALTER TABLE _timescaledb_catalog.chunk ADD CONSTRAINT chunk_compressed_chunk_id_fkey FOREIGN KEY (compressed_chunk_id) REFERENCES _timescaledb_catalog.chunk (id);
5+
6+
DROP FUNCTION IF EXISTS _timescaledb_functions.bloom1_contains_any_hashes(_timescaledb_internal.bloom1, bigint[]);
7+
DROP FUNCTION IF EXISTS _timescaledb_functions.bloom1_hash(anyelement);
8+
9+
-- Drop BIGINT-returning version so the downgrade script can recreate the
10+
-- INTEGER-returning version of compressed_data_column_size.
11+
DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT);

0 commit comments

Comments
 (0)