ClickHouse: Release 26.10 Call

Author: Alexey Milovidov, 2026-10-22.

ClickHouse Release 26.10

ClickHouse release 26.10

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

2. (10 min) Q&A.

Autumn Release 26.10

— 104 new features 🎃

— 225 performance optimizations 🍂

— 739 fixes 🦉

Small And Nice Features

Function composition operator

Pass a pipeline of functions where a lambda is expected:

SELECT arrayMap(lower | reverse, ['Hello', 'World']); -- ['olleh', 'dlrow'] SELECT arrayMap(plus(_, 1) | multiply(_, 2), [1, 2, 3]); -- [4, 6, 8] SELECT arrayMap(_1 * 2 + 1, [1, 2, 3]); -- [3, 5, 7] SELECT arrayMap(_1 + _2, [1, 2], [10, 20]); -- [11, 22]

— _, _1, _2 … are argument placeholders.
— Resolved during query analysis into an ordinary lambda.

Developers: Ivan Esipov, Alexey Milovidov.

Quotas on inserted rows

Limit what a user may write, in rows instead of bytes:

CREATE QUOTA ingest_limit FOR INTERVAL 1 minute MAX written_rows = 100000, query_inserts = 250 TO ingest; INSERT INTO events SELECT number FROM numbers(60000); -- Ok. INSERT INTO events SELECT number FROM numbers(60000); -- Quota for user `ingest` for 60s has been exceeded: -- written_rows = 120000/100000. (QUOTA_EXCEEDED)

As usual, shown in system.quota_usage, system.quota_limits.

Developer: Alexey Milovidov.

Format-preserving encryption of IPs 🧪

The IPCrypt specification — anonymize IP addresses, keep them IPs:

SET enable_ipcrypt = 1; SELECT ipcryptEncrypt('192.0.2.1', unhex('2b7e151628aed2a6abf7158809cf4f3c')); -- 1dbd:c1b9:fff1:7586:7d0b:67b4:e76e:4777 (deterministic: same IP → same output) SELECT ip, ipcryptPrefixEncrypt(toIPv4(ip), key) AS enc, ipcryptPrefixDecrypt(enc, key) ... -- 10.0.0.1 19.214.210.198 10.0.0.1 -- 10.0.0.2 19.214.210.197 10.0.0.2 ← the same /24 -- 10.0.1.1 19.214.211.73 10.0.1.1 ← the same /16 -- 192.168.1.1 137.62.183.100 192.168.1.1

— Prefix-preserving: aggregate by network after anonymization.

Developers: Frank Denis, Alexey Milovidov.

Functions to pack bits

Turn an array into a bitmask with a lambda:

SELECT arrayPackBitsToUInt64(c -> c = 'Y', ['Y', 'N', 'N', 'Y']) AS x, bin(x); -- 144 | 10010000 -- binary quantization of an embedding: 768 floats → 96 bytes SELECT arrayPackBitsToFixedString(x -> x > 0, 96, embedding) AS bits, bitHammingDistance(bits, query_bits) AS distance … -- groups of several bits: 4-bit codes → 0x123F SELECT hex(arrayPackBitGroupsToString(x -> x, 4, [1, 2, 3, 15]));

Six functions: arrayPackBits/arrayPackBitGroups
  × ToUInt64, ToString, ToFixedString.

Developers: Eridanus, Alexey Milovidov.

Quoted-printable encoding

RFC 2045, the encoding of e-mail bodies and headers:

SELECT encodeQuotedPrintable('Grüße aus München! = 100%'); -- Gr=C3=BC=C3=9Fe aus M=C3=BCnchen! =3D 100% SELECT decodeQuotedPrintable( 'This is a very long line that was wrapped by the=\r\n mail client'); -- This is a very long line that was wrapped by the mail client

— Soft line breaks (= at the end of a line) are removed on decoding.

Developers: Jay Pokale, Alexey Milovidov.

Query normalization, ignoring the order

Group the workload by its shape, even if the columns are shuffled:

SELECT normalizedQueryHash('SELECT a, b FROM t GROUP BY a, b') = normalizedQueryHash('SELECT b, a FROM t GROUP BY b, a'); -- 0 SELECT normalizedQueryHashUnordered('SELECT a, b FROM t WHERE x = 1 GROUP BY a, b') = normalizedQueryHashUnordered('SELECT b, a FROM t WHERE x = 42 GROUP BY b, a'); -- 1 SELECT normalizedQueryHashUnordered(query) AS shape, count(), sum(query_duration_ms) FROM system.query_log GROUP BY shape ORDER BY 3 DESC LIMIT 10;

— Token hashes are summed: the order does not matter.
— Lossy on purpose: a - b and b - a are the same shape.

Developer: Yarik Briukhovetskyi.

New Array and Map functions

SELECT arrayMinIndex([5, 2, 9, 2]), -- 2 arrayMaxIndex(x -> length(x), ['a', 'abc', 'ab']); -- 2 SELECT arrayRemoveAt([10, 20, 30], 2), arrayRemoveAt([10, 20, 30], -1); -- [10, 30], [10, 20] SELECT mapRemove(map('a', 1, 'b', 2, 'c', 3), 'b'); -- {'a':1, 'c':3} SELECT mapEntries(map('a', 1, 'b', 2)); -- [('a',1), ('b',2)] SELECT mapContainsKeyValue(labels, 'env', 'prod'); -- uses text indexes -- the -Map combinator without a Map column: the key is the last argument SELECT sumMap(value, key), argMaxMap(name, value, key) FROM t; -- {'a':8, 'b':5}, {'a':'z', 'b':'y'}

Developers: Minh Vu, Jimmy Aguilar Mena, UnamedRus, Alexey Milovidov.

SQL-compatible integer division

In ClickHouse, 7 / 2 was always 3.5. In PostgreSQL, it is 3.

SELECT 7 / 2, -7 / 2, 7 / 2.0; -- 3.5 -3.5 3.5 SET sql_compatible_division_for_integers = 1; SELECT 7 / 2, -7 / 2, 7 / 2.0; -- 3 -3 3.5 (truncated toward zero) SELECT 1 / 0; -- Division by zero (ILLEGAL_DIVISION)

— Works up to Int256; overflow and division by zero throw.
— Disabled by default. Stored definitions keep the semantics
  they were created with.

Developer: Ilya Yatsishin.

Table function for data obfuscation

Like clickhouse-obfuscator (since 2017), but right inside a query:

SELECT URL, Title, SearchPhrase FROM obfuscate( SELECT URL, Title, SearchPhrase FROM hits WHERE SearchPhrase != '' LIMIT 100000) LIMIT 2 SETTINGS obfuscate_seed = 'demo'; https://delo.ua/search/cuZ29vZ2xlLnJ1L3lhbmRleC5ydS8mcGF0aD0=#_=_ Автоюристики для Samsung сделать электростовки 2013 https://delov.ru/index.ua/adasy/foto/69363/12#f640d Загороде мультфильм смотреть

— Keeps cardinalities, distributions, lengths, compressibility;
  replaces the values. For publishing near-real data for benchmarks.
— Deterministic for a seed; not a security tool.

Developers: Nikolay Degterinsky, Alexey Milovidov.

Performance
Improvements

Query condition cache for now() conditions

The bound is rounded onto a time grid, so now() filters can be cached:

How a condition with now() is rounded for the query condition cache: writes round the bound up, reads round it down

Example, today() - 3 over 100M rows: reads 100M → 3.2M rows on repeat.

Developer: Alexey Milovidov.

Two times less memory for hash tables

Large hash tables switch to Robin Hood hashing in place:

— max load factor 0.5 → 0.9: up to 2x smaller;
— chains ordered by the ideal position: a miss stops early;
— for GROUP BY, uniqExact, JOIN.

Example: GROUP BY with 10M keys, 1B rows, 48 threads:

26.9: 24.7 GB, 1.55 sec.   26.10: 16.3 GB, 1.28 sec.

Example: uniqExact of 100M distinct values:

26.9: 13.5 GB, 1.33 sec.   26.10: 4.6 GB, 0.31 sec.

Developer: Alexey Milovidov.

String filtering during the scan

LIKE, startsWith, endsWith, position are checked
while the column is deserialized; non-matching values are not copied:

SELECT count() FROM hits WHERE URL LIKE '%google%';

Example, peak memory of this query:

before: 275 MB.   26.10: 5 MB.

— For MergeTree and for Parquet pages (plain, delta, dictionary).
— Disables itself when most of the values match.
— Setting apply_string_filters_during_scan, now enabled by default.

Developer: Alexey Milovidov.

Top-K dynamic filtering for GROUP BY

GROUP BY k ORDER BY k LIMIT n and GROUP BY k LIMIT n keep a top-K heap. Now its boundary is sent to the table scan:

SELECT UserID, count(), any(URL) FROM hits GROUP BY UserID ORDER BY UserID LIMIT 10;

Example: read 9.45 GB → 144 MB.

— Rows beyond the boundary are dropped before other columns are read.
— Whole granules are skipped by the primary key or a minmax index.

Developers: Alexey Milovidov, Konstantin Bogdanov.

Top-K dynamic filtering for Parquet and Iceberg

ORDER BY column LIMIT n over Parquet files:

— rows that cannot enter the top-K are filtered inside the reader,
  before the other columns are read;
— row groups are skipped by min/max; the best ones are read first.

SELECT URL, EventTime FROM file('hits.parquet') ORDER BY EventTime DESC LIMIT 10;

Example: 26.9: 0.37 sec.   26.10: 0.077 sec.

— file, url, s3, azureBlobStorage, hdfs, Iceberg, …

Developer: Alexey Milovidov.

Merge JOIN, sharded by primary key ranges

Joining two tables on a prefix of their primary keys? The merge join
now runs as independent merge joins over key ranges, on all threads:

SELECT u.country, sum(o.amount) FROM orders AS o JOIN users AS u ON o.user_id = u.user_id GROUP BY u.country SETTINGS join_algorithm = 'full_sorting_merge'; -- both tables are ORDER BY user_id

Example, 200M ⋈ 50M rows:
26.9: 9.1 sec, 1.3 GB.   26.10: 0.78 sec, 256 MB.   (hash join: 2.2 sec, 2.1 GB)

— query_plan_join_shard_by_pk_ranges is now enabled by default.

Developers: Nikolai Kochetov, Alexey Milovidov.

Narrowing query pipelines on a high load

During high load, we create a narrower query pipeline,
so it doesn't always take, e.g., 96 processing lanes on a 96-core server.
— fewer partial states to merge, less memory, more throughput:

Example: 10 clients cycling over ClickBench, 96 cores:
p99 memory: 9.33 → 7.67 GiB, CPU time per query: −15%.

Developer: Alexey Milovidov.

Faster GROUP BY: LowCardinality, Strings

— LowCardinality keys are serialized straight from the dictionaries;
— repeating tuples of keys hit a cache of aggregate states.

SELECT region, os, browser, source, count(), sum(ResolutionWidth) FROM t GROUP BY region, os, browser, source; -- all LowCardinality(String)

Example: 26.9: 0.32 sec.   26.10: 0.22 sec.

SELECT UserID, SearchPhrase, count() FROM t GROUP BY UserID, SearchPhrase LIMIT 10;

Example: 26.9: 0.077 sec.   26.10: 0.026 sec.

Developers: artemkulyk, Samuel Krempaský, Alexey Milovidov.

Parquet: more pruning, less reading

— Bloom filters are checked before reading the dictionary pages:

SELECT UserID FROM file('hits.parquet') WHERE UserID = … -- reads 8 MB of dictionary pages → 4.6 KB of bloom filters (16 row groups)

— Query condition cache for ORDER BY … LIMIT over Parquet tables:
  a repeated query reads only the row groups that can hold its result.

SELECT SearchPhrase FROM hits_parquet WHERE SearchPhrase != '' ORDER BY EventTime LIMIT 10; -- repeat run: 773K → 78K rows read

— min/max without filters are taken from the column statistics.
— Top-K filtering inside the reader, lazy reading of other columns.

Developer: Alexey Milovidov.

Something Interesting

Query cache on disk

The query cache can keep its entries in a filesystem cache:

$ cat config.d/caches.yaml filesystem_caches: query_results: path: /var/lib/clickhouse/query_results_cache/ max_size: 100Gi

SELECT SearchPhrase, count() AS c FROM hits … GROUP BY SearchPhrase SETTINGS use_query_cache = 1, query_cache_on_disk_cache_name = 'query_results';

Example: 2.07 sec → 0.033 sec after a server restart.

— More space; survives restarts. In ClickHouse Cloud it supports distributed cache and is shared between replicas.

Developers: Alexey Milovidov, Konstantin Bogdanov.

Cluster database engine

The whole database of another cluster, by the cluster name:

CREATE DATABASE prod ENGINE = Cluster('prod_cluster', 'default'); SHOW TABLES FROM prod; SELECT count() FROM prod.events; -- reads all shards INSERT INTO prod.events VALUES … -- insert to a random shard

— The named-cluster counterpart of the Remote database engine (26.9),
  as cluster is to remote table function.
— Tables and their structure are fetched on demand.
— Stores no credentials: they come from the cluster configuration.

Developer: Alexey Milovidov.

Overlay database engine

A union of the tables of several databases:

CREATE DATABASE dev ENGINE = Overlay(staging, prod); SHOW TABLES FROM dev; -- orders ← from staging (the first one that has it) -- users ← from prod SHOW CREATE TABLE dev.orders; -- CREATE TABLE dev.orders ENGINE = Alias('staging', 'orders')

— Every table is an Alias: reads and writes go to the source.
— Sources are resolved on every access: new tables appear,
  a dropped table falls through to the next database.

Developers: Aly Kafoury, Alexey Milovidov.

JOIN with an arbitrary ON condition

No equality between the tables? Now it is a block nested loop join:

SELECT q, name FROM typos LEFT JOIN products ON editDistance(q, name) <= 2; clikhouse clickhouse kafak kafka mysql postgress postgres

— 26.9: Cannot determine join keys in JOIN ON expression.
— LEFT, RIGHT, FULL — any join kind; it can spill to disk.
— As slow as a cross join; the last resort of the planner.

Developers: Vladimir Cherkasov, Alexey Milovidov.

IN spills to disk

The set for IN (subquery) can also go to disk:

SELECT count() FROM numbers_mt(200000000) WHERE intHash64(number) IN (SELECT intHash64(number * 3) FROM numbers_mt(300000000)) SETTINGS max_memory_usage = '4G', max_bytes_before_external_set = '1G';

in memory: 12.9 GB, 19.7 sec.
with a 4 GB limit: MEMORY_LIMIT_EXCEEDED.
with spilling: 1.0 GB, 30 sec.

— A sorted run on disk, with a bloom filter and a cache of blocks.
— Disabled by default: max_bytes_before_external_set = 0.
— Workloads can spill all of them: ORDER BY, GROUP BY, IN,
  DISTINCT, JOIN — under memory pressure (next slide).

Developer: Nihal Z. Miaji.

Query-aware scheduling in workloads

How queries inside one workload share CPU and IO:

CREATE OR REPLACE WORKLOAD all SETTINGS scheduler = 'fair'; SELECT … SETTINGS workload = 'all', weight_lowering_cpu_seconds = 10, weight_lowering_factor = 0.1;

— fifo (as before), fair (weighted fair queueing),
  las (least attained service: short queries first), priority.

CREATE WORKLOAD all SETTINGS max_memory = '16Gi', max_memory_before_spill = '8Gi';

Example: 50 concurrent 1 GB queries succeed:
  without spilling: 9%, with spilling: 100%.

Developers: Sergei Trifonov, Azat Khuzhin.

Materialized CTE in a recursive query

A helper CTE is evaluated once, every recursive step reads the snapshot:

WITH RECURSIVE hubs AS MATERIALIZED ( SELECT src FROM edges GROUP BY src ORDER BY count() DESC LIMIT 100000), reach AS ( SELECT 1::UInt32 AS v, 0 AS depth UNION ALL SELECT e.dst, depth + 1 FROM reach JOIN edges AS e ON e.src = reach.v WHERE depth < 8 AND e.dst IN (SELECT src FROM hubs)) SELECT depth, count() FROM reach GROUP BY depth;

Example: without: 1.49 sec.   MATERIALIZED: 0.80 sec.

— Not only faster: a non-deterministic helper (LIMIT with ties)
  is the same set at every step.

Developer: Zach Naimon.

SQLite as a data format

We already had: the SQLite database engine, the SQLite table engine,
and the sqlite table function — since 21.9 (2021) — for local database files.
Schema inference since 23.5; passing a query to SQLite as is since 26.7.

Now a whole SQLite database file is also an import/export format:

SELECT SearchPhrase, count() AS c FROM hits WHERE SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 1000 INTO OUTFILE 'top.sqlite'; -- the format is chosen by the extension $ sqlite3 top.sqlite 'SELECT * FROM "table" LIMIT 1' карелки|70263 SELECT * FROM file('top.sqlite'); -- schema inference

— A result set in one file that every tool can open.

Developers: Yue Ni, Alexey Milovidov.

Experimental Features

LATERAL JOIN 🧪

A subquery in FROM that refers to the tables on its left:

SET allow_experimental_lateral_join = 1; SELECT c.CounterID, t.SearchPhrase, t.c FROM (SELECT CounterID FROM hits GROUP BY CounterID ORDER BY count() DESC LIMIT 3) AS c JOIN LATERAL ( SELECT SearchPhrase, count() AS c FROM hits WHERE hits.CounterID = c.CounterID AND SearchPhrase != '' GROUP BY SearchPhrase ORDER BY c DESC LIMIT 2 ) AS t ON true;

— The standard "top N per group" pattern; (CROSS APPLY in MS SQL Server).
— INNER and LEFT. Decorrelated: runs once for all outer rows,
  LIMIT becomes LIMIT BY, then a hash join.

Developers: Dimitri König, Alexey Milovidov.

Columns cache 🧪

Keeps deserialized columns in memory — no decompression,
no deserialization, no copying for hot data:

SET use_columns_cache = 1; SELECT count() FROM hits WHERE URL LIKE '%google%';

Example, hot data, without → with the cache: 0.26 sec → 0.064 sec.

SELECT SearchPhrase, min(URL), count() AS c FROM s3('s3://clickhouse-public-datasets/hits_compatible/hits.parquet') WHERE URL LIKE '%google%' AND SearchPhrase != '' GROUP BY SearchPhrase …

Example, Parquet on S3, both with the filesystem cache: 0.8 sec → 0.16 sec.

— Wide MergeTree parts and Parquet from files and object storage.

Developer: Alexey Milovidov.

PCO compression codec 🧪

Pcodec: Better Compression for Numerical Sequences (arXiv:2502.06112), the first page

pcodec — a codec for sequences of numbers:

SET enable_pco_codec = 1; CREATE TABLE t (time DateTime CODEC(PCO), …)

compression ratio ZSTD(3) DoubleDelta, PCO PCO, ZSTD(3) ZSTD(3) EventTime (sorted) 1.71 2.03 3.52 3.51 UserID 5.46 3.47 6.34 6.32 Float64 sensor 5.64 3.06 9.61 9.55

— Decompression is fast too: 0.086 sec vs. 0.125 for ZSTD(3)
  and 0.257 for DoubleDelta, ZSTD(3).

Read: Pcodec is awesome! 15% smaller and 2x faster than DoubleDelta

Developer: Alexey Milovidov.

XGBoost dictionaries 🧪

Train a gradient-boosted model at dictionary load, predict in SQL:

SET enable_xgboost = 1; CREATE DICTIONARY mobile_model (w Float64, h Float64, os Float64, ua Float64, is_mobile Float64) PRIMARY KEY (w, h, os, ua) -- features SOURCE(CLICKHOUSE(TABLE 'train')) -- 1M rows of hits LAYOUT(XGBOOST(objective 'binary:logistic' max_depth 6 num_iterations 50)) LIFETIME(0); SELECT IsMobile, avg((predictXGBoost('mobile_model', ResolutionWidth, ResolutionHeight, OS, UserAgent) > 0.5) = IsMobile) AS accuracy FROM hits WHERE WatchID % 100 = 1 GROUP BY IsMobile; -- 0 0.9999 -- 1 0.9978

— Trains in 2 sec on load; SYSTEM RELOAD DICTIONARY retrains.

Developer: Matheus Nerone.

LogsQL 🧪

The log query language of VictoriaLogs, over ClickHouse tables:

SET enable_logsql_dialect = 1, logsql_table = 'logs'; -- logsql_time_column = '...', logsql_message_column = '...'; :) /dialect logsql (logsql) :) _time:5m error | stats by (service) count() as errors | sort by (errors desc) (logsql) :) "disk full" | stats by (host) count() as n, avg(duration_ms) as avg_ms | sort by (n desc) | limit 3 (logsql) :) /dialect clickhouse

— Translated into an ordinary SELECT at parse time.

Bonus: /dialect switches the dialect in clickhouse-client and clickhouse-local.

Developer: Alexey Milovidov.

Elasticsearch table engine 🧪

Read documents from an Elasticsearch index as a JSON column:

SET enable_elasticsearch = 1; SELECT _index, _id, document.url, document.`@timestamp` FROM elasticsearch('http://localhost:9200', 'hits_docs') LIMIT 2; CREATE TABLE es_hits ENGINE = Elasticsearch('http://localhost:9200', 'hits_docs'); SELECT count() FROM elasticsearch('http://localhost:9200', 'hits_docs', query('{"term": {"is_mobile": "1"}}')); -- filtered by Elasticsearch

— A consistent snapshot: paged with a point in time; Elasticsearch 7.12+.
— Basic auth, bearer tokens, API keys, named collections. Read-only.

Developer: Kirill Kopnev.

Data Lakes

Hard links on plain_rewritable disks

s3_plain_rewritable keeps all metadata in the bucket.
Before 26.10: no mutations, no ALTERs except settings and comments.
Now they work — and with hard links, without copying:

$ cat config.d/disks.yaml storage_configuration: disks: s3_plain_rewritable: type: s3_plain_rewritable endpoint: https://s3.eu-west-1.amazonaws.com/bucket/data/ enable_hard_links: 1

— A mutated part refers to the unchanged files of the old one.
— Off by default: older servers can't read a disk with hard links.

Developers: yanglongwei, Alexey Milovidov.

Faster metadata of plain_rewritable disks

Loading a disk with 36 thousand directories, 500K objects:

before: 116 sec.   26.10: 37 sec.

— Listings are split by key prefix and run in parallel;
— files of all directories come from a few listings of the whole disk;
— per-path locks: one long DROP TABLE no longer blocks
  every insert and merge on the same disk;
— removing a part is one batched delete, without copies.

Developer: Alexey Milovidov.

Parquet VARIANT, also in Iceberg

The VARIANT type of Spark 4.0, Parquet, and Iceberg v3 — read as Dynamic:

SELECT n, dynamicType(v), v FROM file('variant.parquet'); 1 JSON {"a":1,"b":{"c":"x","d":{"e":true}}} 2 JSON {"f":[1,"s"]} 3 Array(JSON) ['{"k":1}','{"k":2,"m":"z"}'] 7 Int64 42

— 26.9: Tuple(metadata String, value String) — two binary blobs.
— Objects become JSON; read the column as JSON if all values are objects.
— The same for the variant type in Iceberg tables.
— TODO: shredded variants.

Developer: Konstantin Vedernikov.

Unity Catalog: Delta Lake and Iceberg 🧪

A new implementation of the unity catalog type:
both table formats from a single Unity Catalog.

CREATE DATABASE unity ENGINE = DataLakeCatalog( 'https://<workspace-id>.cloud.databricks.com/api/2.1/unity-catalog') SETTINGS catalog_type = 'unity', warehouse = 'main', catalog_credential = '<token>', use_unity_catalog_v2 = 1; SHOW TABLES FROM unity; -- Delta Lake and Iceberg tables

— Switch: ALTER DATABASE … MODIFY SETTING use_unity_catalog_v2 = 1.

Developers: Asya Shneerson, Melvyn Peignon.

Iceberg v3 deletion vectors 🧪

Iceberg v3 stores deleted rows as roaring bitmaps in Puffin files.
Now ClickHouse applies them when reading:

SELECT count() FROM iceberg('s3://…/table_v3/'); -- rows deleted by Spark are not returned -- inspect a deletion vector (Puffin format, since 26.8): SELECT referenced_data_file, deleted_rows FROM file('dv.puffin', Puffin); -- …/data/00000-0-15387e93-…-00001.parquet [2,5,7,100,65536]

— In v2, deletes were position delete files (Parquet);
  v3 deletion vectors are smaller and are merged per data file.

Developers: linjiayu, Asya Shneerson.

Bonus

Trino and Kusto dialects are in Beta

SET enable_trino_dialect = 1, dialect = 'trino'; SELECT date_trunc('day', EventTime) AS d, count(*), approx_distinct(UserID) FROM hits WHERE regexp_like(URL, 'google') GROUP BY 1 ORDER BY 1 LIMIT 3;

SET allow_experimental_kusto_dialect = 1, dialect = 'kusto'; hits | where SearchPhrase != '' | summarize c = count(), u = dcount(UserID) by SearchPhrase | top 3 by c desc

— Both were reimplemented in 26.9; now they are Beta.
— The setting names are still the same.

Developer: Alexey Milovidov.

Parts of Trino SQL, in ClickHouse SQL

No dialect switch needed:

SELECT TRY_CAST('abc' AS INTEGER), TRY_CAST('42' AS INTEGER); -- NULL, 42 SELECT ARRAY[1, 2, 3] AS a, slice(a, 2, 2); -- [1,2,3], [2,3] SELECT CAST(x AS ARRAY(INTEGER)), CAST(m AS MAP(VARCHAR, BIGINT)); SELECT starts_with(s, 'click'), regexp_like(s, '[0-9]+'), day_of_week(d), url_extract_host(url), bitwise_and(a, b), arbitrary(x), bool_and(y);

— Queries written for Trino, Presto, Athena run with fewer changes.

Developer: Alexey Milovidov.

Freeform data format 🧪

Any tabular text — figure out the columns automatically:

SELECT c0 AS ip, c5 AS request, c6 AS status, c7 AS bytes FROM file('access.log', Freeform); 93.184.216.34 GET /index.html HTTP/1.1 200 5123 10.0.0.15 POST /api/v1/query HTTP/1.1 500 87 172.16.4.2 GET /favicon.ico HTTP/1.1 404 0

— Tries delimiters, quoting, and types; picks the most consistent structure.
— Logs, df, ls -l, copy-pasted tables…
— The PR was opened in 2022 — and finally merged!

Developers: Tri Dao, Alexey Milovidov.

chdig 26.10

chdig — the top-like TUI for ClickHouse, built into the binary:

$ clickhouse dig

New in v26.10.1:

— Keeper browser;
— per-host summary for clusters;
— better filters.

Demo.

Developer: Azat Khuzhin.

Better PostgreSQL compatibility

psql just works:

$ psql -h localhost -p 9005 -U pg default default=> \dt Schema | Name | Type | Owner ---------+--------+-------+------- default | hits | table | pg … default=> SELECT format_type(1043, 68), current_setting('server_version'); character varying | 26.10.1.1 default=> SELECT sleepEachRow(1) FROM numbers(30); ^CCancel request sent ERROR: Query was cancelled ← cancellation never worked before

— postgresql table function can connect to another ClickHouse.
— COPY … WITH (FORMAT csv); comment-only pings from pgx.

Developer: Alexey Milovidov.

FileLog improvements

Subscribe to log files in ClickHouse. Now friendly to logrotate:

CREATE TABLE app_logs (level String, msg String) ENGINE = FileLog('/var/lib/clickhouse/user_files/app/*.log', 'JSONEachRow'); -- reads app.log, skips app.log.1, app.log.2.gz; -- a file renamed by rotation is read to the end SYSTEM RESET FILELOG app_logs; -- all files again SYSTEM RESET FILELOG app_logs FILE 'app.log' OFFSET 27; SYSTEM RESET FILELOG app_logs FILE 'app.log' TO END; -- only new data

— Globs: *, ?, {a,b}, {N..M} in the file name.
— Re-read without recreating the table; privilege SYSTEM RESET FILELOG.

Developers: Groene AI, Alexey Milovidov.

PromQL: compatibility

ClickHouse as a Prometheus: TimeSeries engine + PromQL.

— 100% on the basic compatibility tests
  (the Prometheus compliance suite of queries);

— 93% on the extended tests
  (Prometheus' own promqltest test files).

New: stddev_over_time, mad_over_time, first_over_time, timestamp,
min_of, max_of, step, range; stale markers; Prometheus 3.5 semantics.

Developers: Vitaly Baranov, Valery Petrov, Minh Vu, James Cunningham.
Contributions from RingCentral, Tradera. Feature requests from Mirantis, SigNoz, Tesla.

Notebooks in the Web UI

After a query has run, the [+] button adds more cells:
queries with their own results, or Markdown text.

— Run, reorder, insert, and delete cells individually;
— inactive editors collapse to one line; results scroll inside;
— the notebook is restored on reload;
— a tab with one cell looks exactly as before.

In the style of Jupyter and Observable — in a single HTML file
served by every ClickHouse server at /play.

Demo.

Developer: Alexey Milovidov.

Web UI: save, load, format

The Save tabs dialog of the Web UI

— Save all tabs, queries, and parameters
  as a Markdown file; load them back.

— The Format button:

select by, count() c, max(time) from hackernews where text ilike '%clickhouse%' and type = 'comment' group by by having c > 10 order by c desc limit 10 ↓ SELECT by, count() AS c, max(time) FROM hackernews WHERE (text ILIKE '%clickhouse%') AND (type = 'comment') GROUP BY by HAVING c > 10 ORDER BY c DESC LIMIT 10

Developer: Alexey Milovidov.

Integrations

Client libraries

Native TCP client for .NET — 1.5–2x faster than HTTP,
far fewer allocations; read columnar blocks
or map rows to your own C# classes.

clickhouse-c — a minimalist header-only library for embedded contexts:

— the Native protocol over TCP and Native format blocks;
— one core header, optional headers for TCP, compression, codecs;
— made for PostgreSQL extensions (palloc arenas, longjmp);
— clickhouse.h alone decodes clickhouse-local output from a pipe.

ClickHouse Cloud

Executable UDFs are generally available:
AWS, GCP, Azure; native runtimes, network access,
observability, API and Terraform.

JWT authentication: connect with short-lived tokens
from your identity provider instead of database passwords.

SCIM 2.0 provisioning: the identity provider creates, updates,
and removes members and syncs groups to ClickHouse roles.

Ecosystem

Microsoft Fabric:

— ClickHouse writes Apache Iceberg tables to OneLake;
— ClickHouse Workload for Fabric is in public preview.

Apache Airflow:

— the official ClickHouse provider: SQL operators, bulk inserts,
  self-managed Airflow and Astronomer.

chDB and Postgres:

— chDB Durable Layer for agent memory;
— pg_clickhouse and the chdb extension: encodings, nesting, types.

Meetups


— 🇮🇪 Dublin: Training, Oct 23
— 🇵🇱 Krakow: Training, Nov 3  ·  AI Builders and Databases, Nov 4
— 🇺🇸 New York: AI Builders Night, Nov 3
— 🇪🇸 Madrid: AI Builders and Databases, Nov 3
— 🇺🇸 Brooklyn: LibreChat Meetup, Nov 5
— 🇦🇺 Melbourne: The Agentic Data Stack, Nov 5
— 🇺🇸 Salt Lake City: ClickStack Training at KubeCon, Nov 9
— 🇺🇸 San Francisco: AI Builders Night, Nov 9
— 🇦🇺 Sydney, Nov 10  ·  🇦🇺 Brisbane, Nov 11: The Agentic Data Stack
— 🇮🇳 Bengaluru: The Agentic Data Stack, Nov 12
— 🇳🇿 Auckland, Nov 17  ·  🇳🇿 Wellington, Nov 18: The Agentic Data Stack
— 🇺🇸 San Francisco, Nov 17  ·  🇨🇦 Toronto, Nov 18: Data Engineering
— 🇫🇷 Paris: Training, Nov 25  ·  🇨🇾 Limassol, Nov 26

Reading Corner 📖

QR: youtube.com/watch?v=P7OiRMXeJf8 (ClickHouse Amsterdam Meetup: What to do with vectors)

https://clickhouse.com/blog/

— Chasing that 32 ms tail latency
— Postgres isn't slow. Your storage is.
— Running ClickHouse on NVIDIA Vera
— The Agentic Analytics Benchmark, GPT-6, Opus 5.5, Sonnet 5.5
— OLTP is expensive, you can ClickHouse it
— Why Redshift Serverless can’t match ClickHouse
— Why BigQuery can’t match ClickHouse
— Why Databricks can’t match ClickHouse
— How LinkedIn extended ClickHouse

Q&A