ClickHouse: Release 26.9 Call

Author: Alexey Milovidov, 2026-09-24.

ClickHouse Release 26.9

ClickHouse release 26.9

1. (50 min) What's new in ClickHouse 26.9.

2. (10 min) Q&A.

Autumn Release 26.9

— 56 new features ๐Ÿ

— 135 performance optimizations ๐ŸŽ

— 464 bug fixes ๐Ÿฟ๏ธ

Small And Nice Features

Limits on table size and table count

Hard limits for multi-tenant, temporary, and demo services:

CREATE TABLE t (…) ENGINE = MergeTree ORDER BY id SETTINGS max_table_size_rows = 1000000; -- also max_table_size_bytes_compressed, ..._uncompressed INSERT INTO t … -- Table size limit exceeded: ... 1200000, which exceeds -- the 'max_table_size_rows' setting value (1000000). CREATE DATABASE tenant ENGINE = Atomic SETTINGS max_tables = 100; -- Too many tables in database `tenant`. (TOO_MANY_TABLES)

— Checked at the start of INSERT and when parts are committed.
— Tables, views, and dictionaries count towards max_tables.

Developer: Alexey Milovidov.

Parallel replicas for Merge tables

A Merge table can now be read with parallel replicas —
and the aggregation above it is distributed too:

CREATE TABLE logs_all ENGINE = Merge(default, '^logs_'); SELECT toYear(d), count() FROM logs_all GROUP BY 1 SETTINGS enable_parallel_replicas = 1, parallel_replicas_plan_based = 1, parallel_replicas_allow_merge_tables = 1; EXPLAIN: ReadFromParallelReplicas (Aggregating Union: ReadFromMergeTree (logs_2025), (logs_2026))

— The Merge is expanded into a union of MergeTree reads
  before the plan is distributed — nothing Merge-specific on replicas.
— FINAL and non-MergeTree children fall back to one replica.

Developer: Igor Nikonov.

Linear regression aggregate functions

The SQL-standard family, all nine of them:

SELECT regr_slope(y, x), regr_intercept(y, x), regr_r2(y, x), regr_count(y, x) FROM VALUES('x Float64, y Float64', (1, 3.1), (2, 4.9), (3, 7.2), (4, 8.8), (5, 11.1)); -- 1.99 โ”‚ 1.05 โ”‚ 0.9973 โ”‚ 5 โ€” y โ‰ˆ 2x + 1

— Also regr_avgx, regr_avgy, regr_sxx, regr_syy, regr_sxy.
— The same names as in PostgreSQL, Oracle, Snowflake
  — queries port as is.
— Combinators, GROUP BY, and window functions work as usual.

Developer: mosya415.

Bracket syntax for JSON subcolumns

Paths of a JSON column can be addressed like map keys:

SELECT json['user']['name'], json['tags'] FROM events; -- the same as json.user.name and json.tags SELECT json['user.name'], json['first name'] FROM events; -- keys with dots and spaces โ€” without backticks

— Translated by the parser to nested arrayElement calls;
  the result is the same Dynamic subcolumn.

— Familiar to everyone coming from Map columns.

Developer: Pavel Kruglov.

arrayFlattenedLength

How many elements are in a nested array — at all levels?

SELECT arrayFlattenedLength([[1, 2], [3], [4, 5, 6]]), length([[1, 2], [3], [4, 5, 6]]); -- 6 โ”‚ 3

length(arrayFlatten(arr)) without materializing the flattened array.

— Matches PostgreSQL's cardinality, which counts every level;
  our cardinality is an alias of length and counts the outer array.

Developer: David Meng.

system.statements

The documentation of every SQL statement — inside the server:

SELECT syntax FROM system.statements WHERE name = 'SELECT';

— Columns: name, syntax, description, parent_name, related.

— The same source that renders the docs - always matching the binary.

— Next to system.functions, system.settings, system.formats...

Demo

Developer: Robert Schulze.

system.session_query_ids

"Which queries did I just run?" — without assigning ids by hand:

SELECT count() FROM numbers(10); SELECT sum(number) FROM numbers(100); SELECT query, query_duration_ms FROM system.query_log WHERE query_id IN (SELECT query_id FROM system.session_query_ids) AND type = 'QueryFinish' ORDER BY event_time_microseconds; -- SELECT count() FROM numbers(10) โ”‚ 234 -- SELECT sum(number) FROM numbers(100) โ”‚ 112

— Query ids of the current session, in execution order.
— For test and benchmark scripts; bounded by
session_query_ids_history_size (1000); TRUNCATE clears it.

Demo

Developer: Vladimir Cherkasov.

S3Queue without Keeper coordination

A third mode for S3Queue, for a single ingesting server:

CREATE TABLE queue (WatchID UInt64, URL String, EventDate Date) ENGINE = S3Queue('s3://bucket/landing/*.parquet', NOSIGN, 'Parquet') SETTINGS mode = 'exclusive'; CREATE MATERIALIZED VIEW mv TO events AS SELECT * FROM queue;

unordered and ordered track every file in Keeper,
  so that many servers can share one queue.

exclusive tracks files in the memory of the server: no Keeper
  round trip per file — for a bucket that belongs to this server alone.

Developer: Ivan Tkatchev.

Performance
Improvements

min, max, count from column statistics

Column statistics (default since 26.4) store the exact min and max
of every numeric-like column per part. Aggregations read them now:

SELECT min(EventDate), max(EventDate), count() FROM hits; -- no data is read, answered from statistics EXPLAIN: Aggregating โ””โ”€โ”€ ReadFromPreparedSource (_statistics_min_max_projection)

Example: 26.8: 8–24 ms.   26.9: 2 ms.

— Parts without statistics are read normally and merged in.
— Setting: use_statistics_for_min_max_aggregation.

Developer: Alexey Milovidov.

Skip empty columns on inserts

Wide tables where most columns are empty in most inserts:
stop writing the emptiness.

CREATE TABLE wide (id UInt64, a String, b UInt64, c Array(UInt32), j1 JSON, j2 JSON) ENGINE = MergeTree ORDER BY id SETTINGS skip_empty_columns_on_insert = 1, serialization_info_version = 'with_missing_columns'; INSERT INTO wide (id, a, j1) … -- b, c, j2 are all defaults in this block -- serialization.json: "missing_columns": [{"name":"b","type":"UInt64"}, ...] -- files in the part: 49 โ†’ 33; no data streams at all for b, c, j2

— Reads reconstruct the default of the recorded type; the marker
  survives merges, mutations, replication, backups.
— Opt-in: keep the old serialization version during a rolling upgrade.

Developer: Amos Bird.

Faster native protocol

At the end of every query the server sends a burst of tiny messages:
profile info, progress, profile events, end of stream …

Each used to be its own TCP packet:

$ tcpdump -i lo 'tcp src port 9000' # SELECT 1, server -> client 26.8: 23 10 10 3133 14 10 1 - 7 packets 26.9: 23 3278 - 2 packets

— After an idle period Linux shrinks the congestion window;
  a burst longer than it waits for an ACK: a full extra round trip.

On a 30 ms link that was ~2x latency for clients that idle between queries.

— Now the end-of-query messages go in one socket write.

Developer: Kaviraj Kanagaraj.

LIKE and ILIKE with text indexes

The text index served %word% since 26.7 by scanning its dictionary.
Now prefix and suffix patterns work too:

SELECT count() FROM docs WHERE body LIKE 'clickhouse%'; -- startsWith SELECT count() FROM docs WHERE body LIKE '%clickhouse'; -- endsWith EXPLAIN indexes = 1: Prewhere: endsWith(body, 'clickhouse') AND __text_index_idx_endsWith_โ€ฆ Skip: Name: idx, Granules: 4/2442

— The index is a hint: posting lists select granules,
  the original condition re-checks the rows.
LIKE … ESCAPE uses text, ngram, token, sparse-gram indexes now.
— The array tokenizer serves arbitrary LIKE/ILIKE patterns.

Developers: Elmi Ahmadov.

Faster on AArch64

Graviton, Ampere, Apple, NVIDIA Grace — a batch of ARM-specific fixes:

memcpy: the small-copy helper in string, join, and reading paths
  compiled into a libc memcpy call on AArch64 — the exact call
  it exists to avoid. Fixed on x86 in 2024; now inline on ARM too.
Filters: a low-level optimization for WHERE using NEON register
  accumulation makes it up to 15x faster.
— Also: speed-up of T64 codec, 128-bit division, UTF-8 validation.

Example: DISTINCT over 100M short strings, one thread, Graviton3:

26.8: 12.4 sec.   26.9: 7.6 sec.

Developers: Joshua Dorst, Harikrishnan Prabakaran.

Faster k-way merges

Merging sorted streams is the core of MergeTree: merges, FINAL,
ORDER BY, optimize_on_insert. Three optimizations of k-way merge:

— Multi-column and collated keys: a sorted array of cursors
  instead of a heap — 7–8% faster.

Non-intersecting parts (time-ordered inserts): the whole cursor
  is one batch, detected in O(1) comparisons.

Runs of duplicate keys are skipped: FINAL on ReplacingMergeTree
  uses up to 40% less CPU, optimize_on_insert dedup is ~3x faster.

Maksim Kita's blog post with the ideas and explanations.

Developer: Alexey Milovidov.

Index usage after ARRAY JOIN

A condition on an ARRAY JOIN element now drives skip-index analysis,
as arrayJoin(col) IN (…) already did:

CREATE TABLE t (id UInt64, tags Array(String), INDEX bf tags TYPE bloom_filter) … SELECT count() FROM t ARRAY JOIN tags AS tag WHERE tag IN ('rare7', 'rare8'); -- 26.8: Granules 1223/1223 โ€” full scan -- 26.9: Skip bf โ€” Granules 2/1223

arrayJoin is now planned as the ARRAY JOIN operator internally.
— A WHERE on the element is applied before the expansion:
  non-matching elements never become rows.

Developers: Yarik Briukhovetskyi.

Faster subcolumn reads from object storage

A JSON column could be hundreds of substreams; on S3 their marks
were loaded one after another - one round trip each:

-- github.com/ClickHouse/datasets, attached from R2 as a plain_rewritable disk: ATTACH DATABASE datasets UUID 'e053c139-f7b8-4c82-acb7-6b259c5b19e8' ENGINE = Atomic SETTINGS disk = disk(type = 's3_plain_rewritable', endpoint = 'https://data.clickhouse.com/public-datasets/db/', no_sign_request = true, readonly = true), lazy_load_tables = true; SELECT data.wiki.:String AS wiki, count() AS edits FROM datasets.wikipedia_edits WHERE time >= now() - INTERVAL 1 DAY AND data.type.:String = 'edit' GROUP BY wiki ORDER BY edits DESC LIMIT 5; -- data is a JSON column

Example: 26.8: 8.3 sec, 1733 requests, 235 connections.
26.9: 6.3 sec, 1512 requests, 59 connections.

— Marks of all streams load asynchronously, in parallel by default;
  empty streams' marks are not loaded; JSON metadata is read only once.

Developers: Alexey Milovidov, Rory Shanks, Pavel Kruglov.

Better reuse of connections to object storage

In previous versions, connections that read to the end of a file,
were held instead of returned to the connection pool.

Unnecessary extra connections and frequent TLS handshakes.

-- one row of the bluesky_car_records JSON column, 4723 substreams: 26.8: 29,714 connections created โ€” over the hard limit of 25,000: Cannot create new connection to data.clickhouse.com:443 26.9: 2,885 connections, 265,563 reused -- wikipedia_edits, edits per wiki (Compact parts): 235 โ†’ 59 connections

— In 26.9, connections are better reused for subsequent requests.

Developer: Alexey Milovidov.

Conflict detectors in join reordering

Which join orders are valid with SEMI, ANTI, and OUTER joins?
Every non-inner join used to be a barrier: a selective SEMI join
stayed pinned on top of the inner joins feeding it.

SET query_plan_optimize_join_order_algorithm = 'dpsub', query_plan_optimize_join_order_use_conflict_detector_c = 1; EXPLAIN SELECT count() FROM big1 JOIN big2 ON big1.k = big2.k LEFT SEMI JOIN small ON big1.k = small.k; -- before: (big1 โ‹ˆ big2) โ‹‰ small -- 10M โ‹ˆ 10M first, then filter -- after: big2 โ‹ˆ (big1 โ‹‰ small) -- the 1000-row semi join goes first

— CD-A and CD-C from the SIGMOD'13 paper "On the Correct and Complete
  Enumeration of the Core Search Space"
— the state of the art.

Developer: Fisnik Kastrati.

Faster recursive CTEs

Each step of a recursive CTE writes its result into an intermediate
Memory table, which the next step reads — one block per chunk:

WITH RECURSIVE frontier AS ( SELECT 1 AS node, 0 AS depth UNION ALL SELECT e.dst, depth + 1 FROM edges e JOIN frontier f ON e.src = f.node WHERE depth < 6) SELECT count() FROM frontier;

— Graph searches built from many granule-sized reads (or ARRAY JOIN
  expansions) accumulated thousands of tiny blocks per step.
— Chunks are now squashed before the write, like a regular INSERT.

Developer: Zach Naimon.

Faster uniq and uniqCombined

Approximate distinct counting per group — the shape of every
"unique users per page" query:

SELECT URL, uniq(UserID) FROM hits GROUP BY URL; SELECT k, uniqCombined64(v) FROM t GROUP BY k;

— With many interleaved groups every row lands in a different state,
  and the hash set inside each state is a cache miss.
— Hashes of a batch are computed first, destination cells are
prefetched — the aggregator's own trick, applied inside the states.
— Also: hashing aggregate states, JSON, and Variant without a per-row
  buffer copy — uniqExact of 200-byte states is 33% faster.

Developers: Manuel.

JOIN: row-major hash table payload

The right side of a hash join is stored column by column; building
the output gathers one value per column per matched row.

before: col1 Int32 โ”‚ col2 String โ”‚ col3 UInt8 โ”‚ col4 Float64 โ€” 4 gathers per row after: ROW STORE (col1 + col3 + col4, contiguous per row) col2 String (columnar) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ col1 โ”‚ col3 โ”‚ col4 โ”‚ <- row N: one pointer, one copy โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

— Fixed-size columns are packed into a row store; on by default
  (enable_hash_join_row_store).
LEFT SEMI / ANTI joins that select no right column build a keys-only
  hash table and release the right blocks — up to 1.5x, half the memory.

Example: 100M × 30M LEFT SEMI JOIN: 26.8: 0.50 sec.   26.9: 0.37 sec.

Developers: Hechem Selmi, Nikita Taranov.

GROUP BY: three small things

GROUP BY without aggregate functions started prefetching at a much
  higher cardinality than GROUP BY with aggregates. Now both start where
  the hash table stops fitting in L2.

— The adaptive aggregator (26.8) falls back to the ordinary algorithm
  when keys or arguments are too wide to copy profitably — heavy
  string columns run at the ordinary speed and memory.

— The bucket top-K optimization (each two-level bucket keeps its best n
  groups for ORDER BY … LIMIT n) is visible in EXPLAIN and has a setting:
query_plan_aggregation_bucket_top_k.

Developers: Nikita Taranov, Nihal Z. Miaji.

Text indexes: faster everything

Merges: no bitmap allocation for embedded and small posting
  lists, no roaring conversion when writing the output list.

Lazy posting lists: hasToken, hasAnyTokens, hasAllTokens intersect
  and unite posting lists block by block, without materializing bitmaps.

Phrase search: positions in packed blocks; only the blocks
  that cover candidate rows are read.

— New: the keyValuePairs tokenizer answers map['key'] = 'value' from the index;
splitByRegexp(re, 1) extracts tokens by a capture group.

Developers: Anton Popov, Elmi Ahmadov, Jimmy Aguilar Mena.

Something Interesting

LIMIT with boundary conditions

Select rows by where they are in the stream, not by position:

SELECT number FROM numbers(10) ORDER BY number LIMIT 3 AFTER number >= 5; -- 5, 6, 7 SELECT number FROM numbers(10) LIMIT UNTIL number >= 3; -- 0, 1, 2 SELECT number FROM numbers(10) LIMIT AFTER number >= 2 UNTIL number >= 6; -- 2, 3, 4, 5 SELECT number FROM numbers(10) LIMIT 2 AFTER number IN (2, 6) ALL; -- 2, 3, 6, 7

— "From the first error until the next checkpoint" — in one pass,
  instead of window functions or client-side cuts.
AFTER is inclusive, UNTIL exclusive
— conditions may use unselected columns.

Developer: Zakhar Kravchuk, Nihal Miaji.

DISTINCT in external memory

DISTINCT keeps a hash set of every key — and ran out of memory
on high cardinality. Now it spills to disk, like GROUP BY and ORDER BY do:

SELECT count() FROM (SELECT DISTINCT number FROM numbers_mt(300000000)) SETTINGS max_memory_usage = 3000000000; -- 26.8: Query memory limit exceeded (MEMORY_LIMIT_EXCEEDED) SET max_bytes_before_external_distinct = 1000000000; -- default: max_bytes_ratio_before_external_distinct = 0.5

Example, 300M distinct keys: in memory: 12.9 GB, 12.3 sec.
spilling at 1 GB: 1.14 GB, 12.1 sec. — the same speed, a tenth of the memory.

— On by default. Sorted runs on disk, deduplicated at every stage.
DISTINCT after ORDER BY keeps the sorted order when it spills.

Developer: Nihal Z. Miaji.

GRANTS per authentication method

One user, several credentials — each with its own limit:

ALTER USER app ADD IDENTIFIED WITH sha256_password BY 'readonly_key' VALID FOR INTERVAL 30 DAY GRANTS (SELECT ON default.sales); -- a session authenticated with readonly_key: SELECT count() FROM default.sales; -- 1000 INSERT INTO default.sales VALUES (1, 'x', 1); -- ACCESS_DENIED

— Session rights = the user's grants intersected with the list.
  The clause never adds rights.
— Tied to the user: shown as the user in query_log;
  when the user is deleted, the auth is refused;
  narrows when the user loses grants.

Developer: Alexey Milovidov.

CREATE TOKEN

Tokens for applications - generated by the server, for the current user:

GRANT CREATE TOKEN ON *.* TO app; -- as app: CREATE TOKEN VALID FOR INTERVAL 7 DAY GRANTS (SELECT ON default.sales); โ”Œโ”€tokenโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€valid_untilโ”€โ” โ”‚ sbnvAg6tBx306T8mrhtLaCxwhiEKTktq โ”‚ 2026-09-24 00:25:21 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ $ clickhouse client --user app --password sbnvAg6tBx306T8mrhtLaCxwhiEKTktq

— Shortcut for ALTER USER <me> ADD IDENTIFIED WITH sha256_password BY '<random>'
— Can be run as simple as CREATE TOKEN.

Developer: Alexey Milovidov.

REFRESH … APPEND INCREMENTAL

Refreshable materialized views can process only the new rows
committed to the source since the previous refresh:

CREATE TABLE src (ts DateTime, k UInt32, v UInt64) ENGINE = MergeTree ORDER BY ts SETTINGS enable_block_number_column = 1, enable_block_offset_column = 1; CREATE MATERIALIZED VIEW hourly REFRESH EVERY 1 HOUR APPEND INCREMENTAL ENGINE = MergeTree ORDER BY k AS SELECT k, sum(v) AS s, count() AS c FROM src GROUP BY k;

— The cursor is the source's block numbers — hence the two settings.

— Between the incremental MV (per insert) and a full refresh:
  takes the delta from the source, on a schedule.

Developer: Smita Kulkarni.

Incremental replication to Iceberg

Refreshable materialized views can process only the new rows
committed to the source since the previous refresh:

CREATE TABLE src (ts DateTime, k UInt32, v UInt64) ENGINE = MergeTree ORDER BY ts SETTINGS enable_block_number_column = 1, enable_block_offset_column = 1; CREATE MATERIALIZED VIEW hourly REFRESH EVERY 1 HOUR APPEND INCREMENTAL ENGINE = MergeTree ORDER BY k AS SELECT k, sum(v) AS s, count() AS c FROM src GROUP BY k;

A use-case: replicating MergeTree tables into Iceberg.

Append into an Iceberg target is exactly-once: the cursor is in the snapshot.

Developer: Smita Kulkarni.

Prometheus HTTP API

A TimeSeries table speaks the Prometheus HTTP API — point Grafana at it:

$ cat config.d/prometheus_api.yaml http_handlers: defaults: {} rule: url_prefix: /prometheus/api/v1 handler: { type: prometheus_api_v1, database: default, table: metrics } $ curl --get 'http://server:8123/prometheus/api/v1/query' \ --data-urlencode 'query=sum by (status) (rate(http_requests_total[5m]))' {"status":"success","data":{"resultType":"vector","result":[ {"metric":{"status":"500"},"value":[1789606535.825,"1.52"]}, {"metric":{"status":"200"},"value":[1789606535.825,"1.39"]}]}}

— New in 26.9: /metadata, /labels, /label/<name>/values, /format_query;
  and recent /query, /query_range, /series, /write, /read.

Developers: Nikita Mikhaylov, Minh Vu, Vitaly Baranov.

PromQL: private preview

The TimeSeries engine, the PromQL dialect, and the timeSeries* functions
moved from experimental to private preview — a drop-in Prometheus replacement:

SET dialect = 'promql', promql_database = 'default', promql_table = 'metrics'; sum by (status) (rate(http_requests_total[5m])) -- [('status','500')] โ”‚ 2026-09-17 00:56:28.000 โ”‚ 1.245 -- or from SQL, with a time range and a step: SELECT tags, samples FROM prometheusQueryRange(default.metrics, 'sum by (status) (rate(http_requests_total[5m]))', now() - INTERVAL 1 HOUR, now(), INTERVAL 5 MINUTE);

— 85%+ of PromQL with exact Prometheus semantics; 26.9 adds absent,
count_values, *_over_time, predict_linear, SELECT from TimeSeries tables.
— Blog: Introducing ClickHouse's new TimeSeries engine.

Developers: Vitaly Baranov, Nikita Mikhaylov, Valery Petrov, Minh Vu.

Experimental Features

Kusto Query Language, reimplemented ๐Ÿงช

The KQL dialect was contributed in 2022 and stalled: it translated
tokens into SQL text and reparsed it,
28 functions registered that did nothing.

SET dialect = 'kusto', allow_experimental_kusto_dialect = 1; print q = 7 / 2, sub = substring('abcdefg', -3, 2), c = '50x' contains '50%' -- before: 3.5 โ”‚ 'ab' โ”‚ true (the needle was pasted into a LIKE pattern) -- now: 3 โ”‚ 'ef' โ”‚ false โ€” what Kusto returns

— Now a dedicated lexer, parser, and AST translation.

Developer: Alexey Milovidov.

Trino SQL dialect ๐Ÿงช

Queries written for Trino (and Presto, Athena) run as they are:

SET dialect = 'trino', enable_trino_dialect = 1; SELECT x, TRY_CAST('abc' AS INTEGER) AS c, cardinality(ARRAY[1, 2, 3]) AS n FROM UNNEST(ARRAY[10, 20, 30]) AS t(x) OFFSET 1 LIMIT 5; -- 20 โ”‚ NULL โ”‚ 3 / 30 โ”‚ NULL โ”‚ 3 SELECT transform(ARRAY[1, 2, 3], x -> x * 2), approx_percentile(v, 0.9), date_trunc('month', TIMESTAMP '2026-09-17 10:00:00'), ROW(1, 'a') FROM (VALUES (1), (2), (3), (10)) AS t(v); -- [2,4,6] โ”‚ 10 โ”‚ 2026-09-01 โ”‚ (1,'a')

— Not a separate grammar: token-level syntax translation, the standard parser,
  and an AST-level mapping of ~340 Trino functions (renames, lambdas last).

Developers: Alexey Milovidov, Ethan Lin.

Data Lakes

Create a new Delta Lake table

Reading Delta Lake since 23.x, writing since 26.2 — now ClickHouse
can create tables from scratch:

SET allow_delta_lake_writes = 1, allow_delta_lake_create_table = 1; CREATE TABLE trips (id Int64, name String, score Float64) ENGINE = DeltaLake('s3://bucket/warehouse/trips/', '<key>', '<secret>'); INSERT INTO trips VALUES (1, 'a', 1.5), (2, 'b', 2.5); -- _delta_log/00000000000000000000.json: -- {"commitInfo":{"operation":"CREATE TABLE","engineInfo":"ClickHouse",...}}

— Can also attach to an existing _delta_log or register into a Unity catalog. S3, Azure, local.

Developer: Smita Kulkarni.

Parallel decoding of Iceberg manifests

Planning a query over Iceberg walks its manifests: fetch, parse Avro,
prune every entry by partition and min/max. That walk ran serially.

— 26.8 prefetched the next manifest — overlapping fetch only.
— 26.9 decodes manifests concurrently, streaming the surviving
  entries into a bounded queue with backpressure.

Example (from the PR): 18 data manifests, 113 MB, ~75K entries:

serial: 8.45 sec.   4 threads: 2.55 sec.   16 threads: 1.68 sec.

iceberg_manifest_decode_concurrency (default 4) bounds it.

Developer: Aaron Harlap.

Pruning by the Iceberg manifest list

The manifest list carries per-manifest partition summaries: lower_bound,
upper_bound, contains_null. They now prune whole manifests before any is opened:

CREATE TABLE ice (id Int64, m UInt32, d Date, v Float64) ENGINE = Iceberg(…) PARTITION BY m; -- 8 inserts, one month each SELECT count() FROM ice WHERE m = 5; -- metadata files read: 41 -> 7 (use_iceberg_manifest_list_partition_pruning) -- data files listed: 39 -> 5 (per-entry partition pruning, as before)

— Three levels now: manifest list, manifest entries, Parquet row groups.
— ClickHouse also writes the partition summaries into the manifest list since 26.9.

Developer: Konstantin Vedernikov.

Bonus

Embedded SQL Console ๐Ÿงช

The SQL Console of ClickHouse Cloud, embedded in the server binary:

http://your-server:8123/ui

— A standalone build that talks directly to the HTTP endpoint
— Similar to ClickHouse Cloud Console.

— The main Web UI stays at /play.

Demo

Developer: Luis Neves.

AI functions are in Beta

Eight functions, three releases in the making, no experimental flag anymore:

SELECT title, aiClassify(title, ['database', 'AI', 'security', 'other']) FROM hackernews WHERE type = 'story' ORDER BY time DESC LIMIT 100; SELECT aiTranslate(comment, 'German'), aiRedact(comment), aiSimilarity(comment, 'a complaint about latency') FROM feedback;

aiGenerate, aiClassify, aiExtract, aiTranslate (26.4), aiEmbed (26.6),
aiFilter, aiRedact, aiSimilarity (26.8) — all beta in 26.9.
— Credentials in named collections; per-query quotas on tokens and calls.
— Blog: AI Functions in ClickHouse: Upgrade your SQL to the AI age.

Developers: George Larionov, Andriy Yakovlev.

Meetups


— ๐Ÿ‡บ๐Ÿ‡ธ Chicago Meetup, Sep 28
— ๐Ÿ‡ธ๐Ÿ‡ฌ Singapore: Build Better LLM Apps, Sep 29
— ๐Ÿ‡ซ๐Ÿ‡ท Paris: AI Builders and Databases, Sep 29
— ๐Ÿ‡ฌ๐Ÿ‡ง London: User Conference + Trainings, Sep 30
— ๐Ÿ‡ฉ๐Ÿ‡ช Munich: User Conference + Trainings, Oct 6
— ๐Ÿ‡ณ๐Ÿ‡ฟ Auckland: Postgres and ClickHouse, Oct 6
— ๐Ÿ‡ง๐Ÿ‡ท Sรฃo Paulo: ClickStack Training, Oct 8
— ๐Ÿ‡ธ๐Ÿ‡ช Stockholm: AI Builders and Databases, Oct 8
— ๐Ÿ‡บ๐Ÿ‡ธ South Bay Meetup, Oct 8
— ๐Ÿ‡ฎ๐Ÿ‡ฑ Tel Aviv: AI Builders and Databases, Oct 12
— ๐Ÿ‡ฎ๐Ÿ‡ช Dublin: SRECon Happy Hour, Oct 13
— ๐Ÿ‡ณ๐Ÿ‡ด Oslo: Training, Oct 14  ยท  ๐Ÿ‡ฆ๐Ÿ‡บ Melbourne: LLM Apps, Oct 15
— ๐Ÿ‡ฌ๐Ÿ‡ง London: AI Builders, Oct 21  ยท  ๐Ÿ‡จ๐Ÿ‡พ Limassol, Nov 26

Open House Roadshow

Open House by ClickHouse โ€” the real-time database for AI conference

๐Ÿ‡ฌ๐Ÿ‡ง London, Sep 30  ยท  ๐Ÿ‡ฉ๐Ÿ‡ช Munich, Oct 6

Reading Corner ๐Ÿ“–

QR: clickhouse.com/blog/introducing-promql

https://clickhouse.com/blog/

— The TimeSeries engine: a drop-in Prometheus replacement
— AI Functions: upgrade your SQL to the AI age
— ClickHouse is now available on the dbt platform
— Replica-aware routing: public beta
— WalShadow: sub-second Postgres replication
— On-Demand Compute for intensive workloads
— CostBench: performance per dollar under load
— ClickHouse Cloud vs. Snowflake
— chdb Postgres extension

Q&A