ClickHouse 26.7: EXPLAIN ANALYZE, leaner joins and a stricter S3 credential model
26.7 finally gives you EXPLAIN ANALYZE, makes hash joins smaller and faster, speeds up decompression on every scan, and changes two defaults you must plan for: S3 credentials and insert deduplication.
Source: clickhouse.com
Why it matters: 26.7 is a release you can feel without changing a single query. Decompression, hash joins, GROUP BY and window functions all got cheaper by default, and EXPLAIN ANALYZE closes the long-standing gap with other databases when you need to know where a query actually spent its time. It is also a release with sharp upgrade edges: queries that read S3 with the server’s own cloud identity stop working unless you opt back in, and servers still configured with a legacy insert-deduplication mode refuse to start.
Everything below is taken from the official 26.7 changelog (released 2026-07-22) and the release post. The changelog lists 35 new features, 116 performance improvements, 133 improvements, 27 experimental features, 12 backward-incompatible changes and 349 bug fixes. PR numbers link to ClickHouse/ClickHouse on GitHub. None of the snippets here were run for this article: they use syntax newer than the 26.4 build the rest of this site tests against, so check them on your own 26.7 server.
EXPLAIN ANALYZE
EXPLAIN ANALYZE (#106586, #110668) executes the query and renders the actual execution metrics in the familiar plan format. According to the release post this includes planning time, execution time and parallelism information per step.
EXPLAIN ANALYZE
SELECT event_type, count()
FROM events
WHERE event_time >= now() - INTERVAL 1 DAY
GROUP BY event_type;
Until now you assembled this picture from EXPLAIN PIPELINE, system.query_log and system.processors_profile_log. The practical habit to build: when a query is slow, run EXPLAIN ANALYZE first and compare rows read per step with what the estimate-only EXPLAIN indexes = 1 promised. A step that reads far more than the index analysis predicted points at a filter that is not using the primary key.
Performance you get for free
Most of the performance section is on by default. The ones worth knowing because they change what you measure after an upgrade:
- Decompression. ZSTD decompression on AArch64 uses NEON for columns with small match offsets such as fixed-width integers; the changelog quotes up to roughly 2.3x for a
UInt64column on AWS Graviton 4 (#108049). TheDeltacodec decodes 1.5 to 5 times faster for 8/16/32-bit types, making scans of Delta-compressed columns up to 20% faster (#110189). gzip, zlib and deflate move tolibdeflate: about 1.4 to 1.5x faster decompression for.gzfiles, HTTP,urlands3data and the Parquet GZIP codec (#108074). If your cluster is CPU-bound on scans of compressed integer columns, this is the first thing to re-benchmark. - Hash joins. The right-hand side of a hash join now stores an 8-byte index-based row reference, so entries of
ALLjoins are as small as those ofANYjoins (#107189). On 100 to 300 million rowparallel_hashjoins the median query was about 12% faster with about 15% less peak memory, andINNERjoins onUInt64keys gained the most (about 21% faster, 38% less memory).LowCardinality(String)keys are handled natively (#107264), and lock contention inparallel_hashwas reduced (#108938). - Joins that prune the left side. With
enable_join_runtime_filters_index_analysis, a join can use the primary key or skip indexes of the left table to prune granules (#109085). The release post highlights the combined effect with aGROUP BYexample it reports as 313x faster with 592x less memory; treat that as a best case and measure your own shape. A newdpsubjoin-order algorithm provides optimal plans for more join shapes (#107351). - Aggregation.
allow_aggregate_partitions_independentlyis now on by default: when theGROUP BYkey suits the partition key, each partition is aggregated independently and the global merge is skipped, with runtime heuristics that back off when the partition layout would hurt (#105128).LIMITis pushed into aggregation-in-order for early termination (#104859). The-Ifvariants ofgroupBit*and the variance family got vectorized kernels, up to 4x faster (#110461). - Window functions use much less memory on wide tables with
SELECT *, andquantile*,uniq*andgroupArraywindows withOVER ()are faster (#108688). Filters onPARTITION BYcolumns are pushed below the window (#110114). - Regular expressions for simple patterns are JIT-compiled to native code through LLVM, with automatic fallback to RE2 for anything outside the supported subset (#108004, setting
compile_regular_expressions). The release post reports about 2.5x for matching. - Pruning. Partition minmax bounds now feed primary-key analysis when a key column is also an input of the partition key (#103480).
IS NOT DISTINCT FROMandIS TRUEuse primary-key and minmax indexes like=(#110006). ALIKEpattern without wildcards uses an exact key range (#107077). - Native Arrow reader and writer. The implementation that bypasses the Apache Arrow library is now the default for
ArrowandArrowStream(#106522). - A restored regression. Reading many small files from object storage had lost prefetching and fell back to synchronous reads; that is fixed (#108872). If your small-file ingestion from S3 got slower after an earlier upgrade, this is the fix.
Features worth trying
-
Projections with
WHERE(#102347) materialize only the rows that match the predicate, and the optimizer uses them when the query’sWHEREimplies the projection’s. That is a cheap way to accelerate one hot slice of a table without paying for a full copy of it; check theCREATE TABLEdocumentation for the exact definition syntax. -
Query pushdown to external databases (#107740). The
mysql,postgresqlandsqlitetable functions and engines accept a query (a subquery orquery('SELECT ...')) and send it to the remote database as is. The structure is inferred from the result and the table is read-only. Use it when the remote database should do the aggregation instead of shipping rows. -
url()scheme dispatch (#106093):s3://,gs://,az://,hdfs://andfile://URLs given tourlor theURLengine are routed to the matching storage engine, andRemoteandRemoteSecureexist as real table engines now (#106189). -
AT TIME ZONEandAT LOCAL(#106092) are plain syntax fortoTimeZone, which makes ported SQL work unchanged. -
Unified
SYSTEM STOP/START/PAUSE/CANCEL/REFRESHacrossKafka,RabbitMQ,NATS,S3Queue,AzureQueueand refreshable materialized views, plus... ALL BACKGROUNDforms (#107476). -
Query parameters as setting values:
SELECT ... SETTINGS max_threads = {threads:UInt64}(#108760). -
The built-in
/docspage serves a searchable, version-matched copy of the reference fromsystem.documentation(#108345), and the Play UI gained query tabs (#107826) and per-column color coding (#108873). -
Vector search with
QBit.QBitacceptsInt8, takes an optional stride so a search can read only the first dimensions (useful for Matryoshka embeddings), and gainsdotProductTransposedandrandomHadamardTransformfor rotating embeddings before quantization (#108105, #108103, #108100, #108227). Rescoring now computes the exact distance only for rows the vector index returned (#105591). -
Geospatial: a
GeoJSONoutput format that writes one feature per row (#108065),geoToUTMandgeoToMGRSand their inverses (#108939), andgeometryIntersectCartesian/geometryIntersectSphericalfor any geometry type (#110062). -
Text indexes accept a
postprocessorexpression applied to each token, such aslower(#98939).
Experimental, worth watching
The ZXC codec (#110620) is an asymmetric LZ codec: slow compression, very fast decompression, ratio between LZ4 and ZSTD. It needs allow_experimental_codecs = 1 and suits write-once, read-many columns. The Quantize vector codec family with a two-stage approximate search (#108565), plan-based parallel replicas (#108504), distributed FINAL reads (#108148), Iceberg manifest compaction through OPTIMIZE TABLE ... MANIFEST (#98178) and executable UDF “drivers” (#105131) are all opt-in. Writing to DeltaLake tables was promoted to Beta (#107034).
Bug fixes you should know about
The changelog lists 349 fixes. The ones that justify not sitting on an older minor:
- Silent data loss. A race between
ALTER TABLE ... RENAME COLUMNand a concurrentOPTIMIZE ... FINALor background merge could replace the renamed column’s data with defaults (#104822). Async inserts with deduplication could register eachinsert_deduplication_tokenin every partition a coalesced flush touched, silently dropping later inserts (#111049).S3Queue/AzureQueueskipped objects when Azure returned an empty listing page with a continuation token (#109761). - Wrong results. Primary-key pruning with a wider
FixedStringconstant dropped matching rows (#111106); reversed (DESC) key columns without a final mark pruned granules incorrectly (#109901); join runtime filters returned wrong results onJSONcolumns (#107663);NULLvalues inserted fromArrow/ORCintoLowCardinality(Nullable(...))columns became empty strings, a regression from 26.5 (#107532). - Security. SQL injection in the MySQL wire protocol (
SHOW TABLE STATUS LIKE,SET) (#108474); anssl_certificatewildcard that matched multi-label names, letting the holder of a deeper-subdomain certificate authenticate as the wildcard user (#108472); unauthenticated probing of table existence and replication status on the interserver port (#99854); S3301redirects followed without theremote_url_allow_hostscheck (#108466); secrets visible inEXPLAINoutput andsystem.query_views_log(#108386, #108214). - Crashes. A column-type confusion when reading Iceberg tables with equality delete files crashed the server (#109551);
parallel_view_processinghad been ignored since 25.10 (#109170).
Plan before you upgrade
Work through the backward-incompatible list in order of how likely it is to break production.
- S3 credentials from user SQL (#106855).
s3()and friends no longer pick up the server’s own cloud identity (environment variables, IMDS/IRSA, instance profile, AWS config files, STS role assumption, GCP metadata) for requests that originate in user SQL. Such a request now needs explicit credentials orNOSIGN; named collections defaultuse_environment_credentialsto0. To get the old behavior back you need bothuse_environment_credentials = 1on the request and the new settings3_allow_server_credentials_in_user_queries. PersistentS3/S3Queuetables and dynamic S3 disks whose definition depends on those credentials are loaded anonymously at startup instead of aborting it (s3_load_table_anonymously_if_credentials_restricted), which means a table that “works” after restart but returns access errors until you re-credential it. Grep your DDL for S3 tables without explicit keys before you upgrade. - Insert deduplication (#108361). ClickHouse now always uses the unified deduplication hash for sync and async inserts. If you ever set the server setting
insert_deduplication_versiontoold_separate_hashesorcompatible_double_hashes, the server will not start on 26.7. The documented path is to run a release that supportscompatible_double_hashesfirst, long enough to cover your deduplication window (one hour by default for replicated tables viareplicated_deduplication_window_seconds; count-based for non-replicated tables withnon_replicated_deduplication_window), then remove the setting before upgrading. If you never set it, ignore this. toTimenow returnsTime(#108729);use_legacy_to_time = 1ortoTimeWithFixedDatekeeps the old semantics.**/glob now also matches zero directory levels, sodata/**/file.txtmatchesdata/file.txt(#97676). Expect file counts from globs to go up.zip/zipxbackups on object storage are rejected for bothBACKUPandRESTORE(#101770); usetar.gz.AggregatingMergeTreerejects dimension columns outside the sorting key atCREATE(#108087); existing tables still load, andallow_dimensions_outside_sorting_key = 1restores the old behavior. Those columns were silently collapsed to an arbitrary value during merges, so this is worth fixing rather than overriding.DateTime64range grows from 1900-2299 to 0000-9999 (#107907). Out-of-range values are computed instead of clamped, so results can change if you relied on saturation. With precision 8 or 9 the upper bound stays at 2262-04-11.- Rotated system log tables (for example
system.text_log_0) are markedtable_readonly(#95079). Servers older than 26.3 fail to start with those tables, so drop the rotatedsystem.*_log_Ntables or reset the setting before any downgrade past 26.3. - Removals: the deprecated
snowflakeToDateTime*/dateTimeToSnowflake*functions (use the*IDvariants, #108711); config-based workload scheduling, replaced byCREATE RESOURCEandCREATE WORKLOAD(#108286); the remote mode ofhasColumnInTable(#110881); and the XML-filenaiveBayesClassifiermodels in favor of aNAIVE_BAYESdictionary layout (#108773).
A reasonable upgrade routine for 26.7: run SHOW CREATE over every table that uses an S3 or URL engine, search the server config for insert_deduplication_version, and replay a representative slice of production queries against a staging server with EXPLAIN ANALYZE on the join- and aggregation-heavy ones. The performance wins are real but uneven, and EXPLAIN ANALYZE is now the cheapest way to see which of them reach your workload.
Discussion
Comments live in GitHub Discussions. Sign in with GitHub to join in.