ClickHouse 26.7 was released on 2026-07-22, and it's one of the most significant releases in recent months. The ClickHouse 26.7 release introduces new SQL features, meaningful performance improvements, a long list of bug fixes, and an unusually large batch of backward-incompatible changes. With hundreds of changelog entries, it's easy to miss the updates that can directly affect production deployments.
This guide is organized the way engineers typically evaluate an upgrade: what will break, what's new that's worth adopting, what got faster without configuration changes, what changed under the hood, which bugs were fixed, and what you should verify before upgrading. Wherever helpful, you'll find practical query examples instead of a simple restatement of the release notes.
2. Backward Incompatible Changes
This is the section to read first, because these changes can silently alter behavior or break existing automation rather than fail with an obvious error.
S3 credentials no longer leak from the server into user queries
Before this release, a query like the following would work if the server itself had AWS credentials available - an IAM instance profile, environment variables, IMDS/IRSA, or role_arn-based STS:
SELECT count() FROM s3('https://my-bucket.s3.amazonaws.com/data/*.parquet');That's convenient, and it's also a privilege-escalation path: any user who can run SQL gets access to anything the server can reach in S3, regardless of what that user is actually supposed to access. In 26.7, S3 access originating from user SQL no longer resolves the server's own cloud credentials by default. Named collections default use_environment_credentials to 0. To keep the old behavior deliberately, you now opt in explicitly:
SET s3_allow_server_credentials_in_user_queries = 1;
SET use_environment_credentials = 1;There's a second-order effect: on server startup or RESTORE, a persistent S3/S3Queue table, dynamic S3 disk, or DataLakeCatalog whose definition resolves such credentials now loads anonymously - present, but inaccessible until re-credentialed - instead of blocking the whole server from starting. That's a better failure mode than an outage, but it means a table can silently exist-but-not-work after upgrade if you don't check for it.
Zip backups to object storage are rejected
BACKUP/RESTORE using zip or zipx format is now rejected for destinations backed by S3 or Azure Blob Storage - direct S3(...)/AzureBlobStorage(...) targets, or Disk(...) targets backed by either. Zip requires seeking to read its central directory, which is slow over object storage; use a tar.gz-based format instead. If any backup automation targets object storage with zip format, it will start failing outright on upgrade, not silently degrade - worth catching in a pre-upgrade check rather than a failed 3am backup job.
DateTime64 range extended - saturation behavior changes
The supported range grew from [1900-01-01, 2299-12-31] to [0000-01-01, 9999-12-31] (narrower at precision 8/9, since ticks are stored as Int64, capping nanosecond precision at 2262-04-11). Values that used to be clamped to the old boundary are now computed correctly instead. If any logic depended on the old saturation behavior for out-of-range dates - deliberately or not - results for those specific values will change after upgrade.
AggregatingMergeTree schema validation
Previously, a column in an AggregatingMergeTree table that was neither part of the sorting key nor an aggregate-state measure (AggregateFunction/SimpleAggregateFunction) would be silently collapsed to an arbitrary value during background merges - quietly producing wrong results for any query that filtered or grouped on it. That's now rejected at table creation time. allow_dimensions_outside_sorting_key = 1 restores the old, broken behavior if you have a reason to need it during migration.
Config-based workload scheduling is gone
The resources and workload_classifiers server config sections are removed in favor of CREATE RESOURCE and CREATE WORKLOAD queries. The old config sections are ignored with a warning, not an error - meaning if you don't check your logs after upgrading, workload scheduling can silently stop applying without any query failing.
Insert deduplication hashing is unified
All inserts, sync and async, now use a single unified deduplication hash; legacy per-insert hash behaviors are removed. If you never set insert_deduplication_version, this doesn't affect you. If you did set it to a legacy value (old_separate_hashes or compatible_double_hashes), the server refuses to start on 26.7 until you migrate through an intermediate release that writes both legacy and unified hashes for a transition window (tied to replicated_deduplication_window_seconds for replicated tables, or insert count for non_replicated_deduplication_window). This is a real upgrade-path requirement, not just a setting to flip.
Deprecated Snowflake functions are removed, not just deprecated
snowflakeToDateTime, snowflakeToDateTime64, dateTimeToSnowflake, and dateTime64ToSnowflake - deprecated since v24.6 - are gone. Use snowflakeIDToDateTime, snowflakeIDToDateTime64, dateTimeToSnowflakeID, dateTime64ToSnowflakeID. The allow_deprecated_snowflake_conversion_functions setting that used to re-enable the old functions no longer does anything.
toTime default behavior flips
use_legacy_to_time now defaults to 0, so toTime converts a value into the Time data type instead of converting a date-with-time into a fixed-date datetime. If your pipeline depends on the old semantics, either set use_legacy_to_time = 1 or switch explicitly to toTimeWithFixedDate.
Naive Bayes models must be recreated
Server-side XML-config Naive Bayes models are no longer supported; models must be recreated as dictionaries with the new NAIVE_BAYES layout. This is a hard migration, not a compatibility flag - but the new approach is dramatically more efficient (ClickHouse's own benchmark on a 9.7 MiB trigram model shows ~49x less memory and ~11x faster loading and classification), and ships with three new helper functions: naiveBayesClassifierWithProb, naiveBayesClassifierWithAllProbs, and naiveBayesNgrams.
hasColumnInTable remote mode removed
The hostname/username/password arguments for checking a column on an arbitrary remote server are gone; only hasColumnInTable(database, table, column) remains. This wasn't just a cleanup - the removed mode let any user trigger outbound connections to arbitrary hosts and leaked credentials into query logs, with no privilege gate at all.
**/ glob now matches zero directory levels
data/**/file.txt now also matches data/file.txt, which it previously did not. If glob-based table functions or file paths relied on **/ requiring at least one intermediate directory, check what they now pick up - this can silently expand the set of files a query reads.
3. New Features
EXPLAIN ANALYZE
Previously, understanding where a slow query actually spent its time meant cross-referencing system.query_log, EXPLAIN PIPELINE, and guesswork. EXPLAIN ANALYZE actually executes the query and annotates the familiar query plan format with real execution metrics at each step:
EXPLAIN ANALYZE
SELECT customer_id, sum(amount)
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY customer_id
ORDER BY sum(amount) DESC
LIMIT 10;This is a genuinely useful default first step for query performance debugging, replacing a fair amount of manual log-joining.
Projections with WHERE clauses
Projections can now include a WHERE clause, so only matching rows are materialized, and the cost-based optimizer can use them when the query's WHERE implies the projection's WHERE. This is useful if you have a projection that only needs to serve a subset of queries (e.g., a "last 30 days" rollup) rather than the whole table.
The -Tuple aggregate combinator
-Tuple applies an aggregate function to each element of a Tuple column independently, preserving names: sumTuple(t) for t = (a, b) returns (sum(a), sum(b)). Multi-argument functions pair tuples by position - corrTuple((a1, a2), (b1, b2)) returns (corr(a1, b1), corr(a2, b2)). Unlike -ForEach over arrays, elements can have different types.
AT TIME ZONE / AT LOCAL
Standard SQL postfix operators as sugar for toTimeZone: expr AT TIME ZONE zone is equivalent to toTimeZone(expr, zone), and expr AT LOCAL to toTimeZone(expr, timeZone()). Small, but useful if you're porting SQL from other engines.
URL table function and engine now dispatch by scheme
file://, s3:///gs:///gcs:///oss://, az:///azure:///abfss:///abfs://, and hdfs:// now route automatically to the appropriate backend engine instead of everything going through the generic URL engine. Only the S3 schemes covered by the default url_scheme_mappers are dispatched - vendor-specific S3-compatible schemes (cos, obs, etc.) still require the s3 engine/function directly.
Remote and RemoteSecure table engines
Persistent counterparts to the remote/remoteSecure table functions: CREATE TABLE ... ENGINE = Remote('addresses', db, table, ...) now works alongside CREATE TABLE ... AS remote(...).
Unified SYSTEM commands for streaming and materialized views
SYSTEM STOP, START, PAUSE, CANCEL, and REFRESH (plus ... ALL BACKGROUND server-wide forms) now provide one interface across Kafka, RabbitMQ, NATS, S3Queue/AzureQueue tables, and refreshable materialized views. Worth flagging: as part of supporting this on NATS, JetStream tables now acknowledge messages only after a successful insert - previously messages were auto-acknowledged on delivery and could be lost if the insert failed. This is a reliability improvement, but it's a behavior change, not just a new command surface. A new nats_wait_for_flush_interval setting (default false, preserving the old low-latency behavior) can hold a consumption cycle open for the whole flush interval instead.
Query functions against external databases
mysql, postgresql, and sqlite table functions and engines now accept a raw query instead of just a table name, written as a subquery or via query('SELECT ...'). The resulting table structure is inferred from the query result and is read-only - useful for pushing filtering logic to the source database instead of pulling a full table across.
QueryRunner table engine
Rows inserted into a QueryRunner table represent queries the engine executes - useful for async query execution, batch execution of generated queries, directing queries to remote clusters, benchmarking, fuzzing, and shadow-traffic testing.
Vector search: quantization, stride, and Matryoshka support
This release invests meaningfully in vector search infrastructure. Quantized transposed distance functions (cosineDistanceTransposedQuantized, L2DistanceTransposedQuantized, dotProductTransposedQuantized) operate on QBit(Int8) codes, dequantizing on the fly for a query-time vs. stored-vector distance comparison. A new stride parameter on QBit (QBit(T, dimension, stride)) stores groups of dimensions in separate streams, enabling efficient reads of just a prefix of dimensions - directly useful for Matryoshka-style embeddings, where a model's output vector is still meaningful when truncated:
CREATE TABLE embeddings (
id UInt64,
vec QBit(Float32, 1536, 128)
) ENGINE = MergeTree ORDER BY id;
-- Search using only the first 256 dimensions for lower latency
SELECT id FROM embeddings
ORDER BY L2DistanceTransposed(vec, {reference_vector}, 256)
LIMIT 10;QBit(Int8) element type support and randomHadamardTransform (a deterministic randomized rotation useful for preprocessing embeddings before quantization) round out this batch of additions.
GeoJSON output format, new geo functions, and other additions
A GeoJSON output format writes FeatureCollection documents with one feature per row. New geospatial functions add geoToUTM/UTMToGeo and geoToMGRS/MGRSToGeo conversions, plus geometryIntersectCartesian/geometryIntersectSpherical for arbitrary geometry types. ALTER USER/ROLE/SETTINGS PROFILE ... SET name = value now lets you change a single setting in place without wiping the rest of a user's settings - a smaller but genuinely handy addition if you've ever accidentally cleared a user's settings profile with a bare SETTINGS clause.
Experimental features - opt-in only, treat accordingly
A few experimental additions are worth knowing about if you track ClickHouse's roadmap, but shouldn't be mistaken for production-ready:
- Packed data part storage (
data.packedarchive per part) - enabling this prevents downgrading the server, since older versions can't read the format. Quantizevector codec family and a two-stage approximate vector-search rewrite over quantized companion streams.SZ3(error-bounded lossy compression) andZXC(asymmetric LZ with fast decompression) codecs.- AI function credential handling changed: AI functions no longer take a named collection as the first positional argument; credentials now come from settings or a trailing
Map(String, String)argument, andaiEmbednow requiresmodelas a required positional argument. This is a breaking change if you're already using the experimental AI functions. - DeltaLake writes (
allow_experimental_delta_lake_writes) were promoted from experimental to Beta. - The experimental RocksDB-based Keeper storage (
experimental_use_rocksdb) was removed outright, with a replacement described as forthcoming.
4. Performance Improvements
This release has an unusually large performance section - dozens of entries - but a handful are worth calling out specifically because they change real-world query economics.
JOINs: faster and leaner, no query changes required
The right-hand side of a hash join now uses a compact 8-byte index-based row reference, so ALL joins are as memory-efficient as ANY joins for every key type. ClickHouse's benchmarks on large parallel_hash joins - 100-300 million row tables on UInt64 and String keys, both unique and duplicated - show the median query about 12% faster with about 15% less peak memory, with no query becoming slower in their testing. INNER joins on UInt64 keys gained the most: about 21% faster with 38% less memory.
Separately, JOINs can now use the primary key index or skip indexes on the left-hand table to prune granules before the join runs (enable_join_runtime_filters_index_analysis), and single-column non-Nullable LowCardinality(String) join keys are now natively supported in the hash join instead of falling back to a slower path.
If you've been working around join memory pressure with manual pre-filtering, smaller batch sizes, or aggressive max_bytes_in_join settings, it's worth re-testing whether those workarounds are still necessary.
Native Arrow reader/writer
A native reader and writer for Arrow and ArrowStream formats - not built on the Apache Arrow library - is now the default (input_format_arrow_use_native_reader, output_format_arrow_use_native_writer), avoiding extra copies and conversions. This is a straightforward win if you're moving data in or out of Arrow format regularly (e.g., interoperating with Spark, Pandas, or other Arrow-native tools).
Compression: libdeflate and vectorized codecs
gzip/zlib/deflate now use the libdeflate library, giving roughly 1.15x faster compression with a better ratio and 1.4–1.5x faster decompression for .gz/HTTP/URL/S3 data and the Parquet GZIP codec. On AArch64 (Graviton and similar), ZSTD decompression for columns with small match offsets - fixed-width integer columns, notably - is up to ~2.3x faster via NEON vectorization. The Delta codec's decompression is 1.5–5x faster, making scans of Delta-compressed columns up to 20% faster overall.
Regex compilation to native code
match, extract, extractAll, replaceRegexpOne, and replaceRegexpAll now compile simple regular expressions to native code via LLVM by default (compile_regular_expressions), with automatic fallback to RE2 for patterns outside the supported subset. If your workload does heavy regex filtering or extraction, this is worth benchmarking directly against your actual patterns.
Profile events at scale
Profile event updates are up to 30x faster, using per-CPU atomics for server-wide and per-user counters (user_profile_events_per_cpu). Worth a specific callout: this uses around 640 KiB per user on 64 cores, so if you have a very large number of distinct users, it may be worth explicitly disabling this rather than accepting the memory tradeoff by default.
Regressions fixed, not just new optimizations
A meaningful chunk of this release's "performance" section is ClickHouse clawing back performance it had itself regressed in prior versions - worth knowing so you don't mistake "fixed a regression" for "brand new speedup":
- Reading many small files from object storage via
s3and other table functions had stopped prefetching and fallen back to synchronous reads (a regression from an earlier change), which significantly slowed low-concurrency reads of lots of tiny files - now fixed, including when reads go through the filesystem cache. - A per-value settings lookup was causing reference-count contention when reading JSON/Dynamic columns with multiple threads, serializing what should have been parallel reads.
- PREWHERE performance on Map subcolumns had regressed; that's fixed.
- Default settings for canonical date-time parsing (
best_effortmode) had a parsing performance regression from an earlier default change; also recovered.
Other notable wins
IS NOT DISTINCT FROM and IS TRUE now use primary-key and minmax indexes for pruning, the same as = - previously both scanned all granules. LIKE/NOT LIKE patterns without wildcards now use an exact primary key range instead of a wider prefix range. Wide integer comparisons (Int128/UInt128/Int256/UInt256, and Decimal128) are up to 7x faster via branchless comparison code. Window function queries over wide tables with SELECT * use significantly less memory and run faster, and quantile/uniq/groupArray window aggregates are faster with both OVER () and OVER (ORDER BY ... RANGE ...).
5. Improvements Across the System
Beyond headline performance work, a large number of smaller improvements touch daily operational and development workflows.
Observability and diagnostics
New per-phase query pre-execution ProfileEvents (QueryParseMicroseconds, QueryAnalysisMicroseconds, QueryPlanBuildMicroseconds, QueryPipelineBuildMicroseconds) expose exactly where time goes before execution even starts - useful for diagnosing slow query planning, as distinct from slow query execution. A new UntrackedMemory asynchronous metric reports memory allocated by threads but not yet accounted for in global tracking, and MEMORY_LIMIT_EXCEEDED messages now include this figure directly, which should cut down on "why did this hit the memory limit when tracked usage looked fine" investigations.
Web UI: a genuinely different tool now
If your team uses the built-in Play UI for day-to-day query work, this release is worth a dedicated look. Additions include: persistent tabs that keep rendered results and running queries across reloads and browser sessions; autocompletion backed by system.completions; full keyboard navigation with visible focus outlines; pinned columns for wide result sets; inline syntax-error highlighting in the editor; per-column color-coding toggles (bar, heatmap, categorical); and a documentation search page at /docs that searches system.documentation directly with syntax highlighting and cross-links. Individually these are minor; together they close a lot of the practical gap between the Play UI and a dedicated SQL client - worth revisiting if your team defaulted to a third-party tool for lack of these basics.
Client and CLI improvements
clickhouse-client and clickhouse-local now show as-you-type "ghost text" autocompletion hints when the cursor is at the end of input, ranked by recently-used identifiers and terms already present in the query. clickhouse-disks gained du and wc commands. A new --echo-query-separator option makes it easier to distinguish a typed query from its reformatted echo.
Filesystem cache and memory efficiency
Several changes reduce memory pressure and lock contention in the filesystem cache: metadata per file segment and per key is smaller, lock contention on the space-reservation hot path is reduced via a new reserve_granularity setting, and cache file loading on startup is faster (cache file sizes are now encoded in filenames, avoiding a stat call per file). The text index header cache's memory usage is reduced by 2–2.5x, meaning more headers fit in cache for text search queries. BACKUP/RESTORE metadata handling was also reworked to stream rather than fully materialize backup metadata in memory - relevant if you've had large, especially incremental, backups consume unexpectedly large amounts of RAM during the backup or restore operation itself.
Settings and access control
ALTER USER, ALTER ROLE, and ALTER SETTINGS PROFILE now accept SET name = value to change one setting in place, rather than requiring a full SETTINGS clause that replaces the entire list (and risks wiping settings you didn't intend to touch). Several max_*_num_to_throw settings (named collections, tables, replicated tables, views, dictionaries, databases) are now changeable without a server restart. A new is_wildcard column in system.grants distinguishes wildcard grants (e.g., GRANT SELECT ON db*.*) from exact-name grants that happen to share a name - previously these were indistinguishable in the system table.
Data lake and external system integration
MySQL spatial column types (LINESTRING, POLYGON, MULTILINESTRING, MULTIPOLYGON, generic GEOMETRY) now map to ClickHouse's native geometric types instead of String. SHOW TABLES and system.tables queries against data lake catalogs (Iceberg REST, Glue, Unity, Hive, Paimon) now push namespace-bound predicates down to the catalog instead of doing a full catalog scan - a meaningful win if you have catalogs with a large number of namespaces or tables. OneLake authentication now supports pre-obtained bearer tokens as an alternative to sharing a long-lived client secret.
6. Bug Fixes (Grouped by Area)
The full bug-fix list runs into the hundreds of entries, and most are crash fixes for narrow edge cases that are unlikely to affect a typical workload. The subset below is grouped by area and filtered to fixes that represent silent data loss, silently wrong results, or a real operational hazard - the category that matters most, because you may already have been affected without knowing it.
Data integrity and inserts
- Async insert deduplication could silently drop legitimate rows. When several async-insert entries with different deduplication tokens were coalesced into a single flush spanning multiple partitions, each token was registered against every partition the flush touched - not just the partition its own rows landed in. A later insert reusing that token in a partition it had never actually written to was then silently deduplicated away. If you use
async_insert=1, async_insert_deduplicate=1with retry logic that reuses tokens and have seen inexplicably missing rows, this is a plausible cause. There's no retroactive recovery for data already lost to it. CREATE OR REPLACE MATERIALIZED VIEW ... POPULATEcould leave the new view unsubscribed from its source table, silently dropping every row inserted after the replace.- A race between
ALTER TABLE ... RENAME COLUMNand a concurrent background merge could silently replace the renamed column's data with default values. - Refreshable materialized views could leak disk space indefinitely: if the temporary inner table a refresh rotates out exceeded
max_table_size_to_drop, it couldn't be dropped, so every subsequent refresh created another temporary table, growing the view's data directory until disk exhaustion.
Query correctness (wrong results, not crashes)
- Primary key pruning silently dropped matching rows in two specific cases: reversed (
DESC) key columns on parts without a final mark (non-adaptive granularity), and several date functions (toStartOfDay,toRelativeHourNum,toRelativeWeekNum, and others) applied toDate32columns with values outside the range the function could correctly represent - these functions previously wrapped around silently instead of staying monotonic. - Minmax skip indexes on
LowCardinality(Nullable(...))columns causedIS NULLpredicates pushed to storage to prune every granule and return zero rows, even when the column actually contained NULLs. - Text indexes on
Array(LowCardinality(Nullable(String)))with NULL array elements threw an error rather than correctly skipping NULLs during index construction, unlike the equivalentArray(Nullable(String))case. distinct_overflow_mode = 'break'didn't behave as documented: instead of returning the partialDISTINCTresult accumulated up to the limit, the chunk that crossed the limit was discarded, and the query kept reading and inserting into the hash set to the end of input - sometimes ending inMEMORY_LIMIT_EXCEEDEDinstead of the documented partial-result behavior.- Wrong results from
ANY JOINwith a constantONcondition (e.g.,t1 ANY INNER JOIN t2 ON 1) - the query returned the full Cartesian product instead of the correctANYjoin semantics.
Iceberg, DeltaLake, and data lake reads
- A column type confusion when reading Iceberg tables with equality delete files: if a column's nullability differed between the equality delete file and the table schema, values were inserted into a column of the wrong type through an unchecked cast, corrupting the column and crashing the server.
PREWHEREand row-level policies on an Iceberg column renamed by schema evolution silently returned zero rows for old data files, whileWHEREreturned the correct rows - an inconsistency that could easily be missed if you only tested one filter form.- Partition pruning returned no rows for Iceberg tables partitioned by a timestamp (
DateTime64) column.
Replication, backup, and restore
- RESTORE for
ReplicatedMergeTreetables silently deduplicated duplicate-content parts from a backup instead of preserving them, which could produce fewer parts than the backup actually contained. - A rare crash and silent data loss during table startup when a rolled-back transactional MergeTree part intersected a committed part.
TRUNCATE TABLEandDROP PARTITIONcould fail on tables with many deduplication blocks if removing them in one ZooKeeper request exceeded the default 1 MBjute.maxbufferlimit - this only affects deployments still using Apache ZooKeeper rather than ClickHouse Keeper.
Security-relevant fixes
These deserve to be called out as a distinct category, not folded into general bug fixes: an SSRF vulnerability via S3 301-redirect handling that bypassed remote_url_allow_hosts (the equivalent check already existed for 307 redirects - it just wasn't applied consistently); SQL injection in the MySQL wire protocol, where SHOW TABLE STATUS LIKE and SET <mysql_setting> values sent over port 9004 were concatenated verbatim into rewritten queries instead of being parsed and re-quoted; a wildcard SSL certificate matching bug where a single * in a CN or DNS: SAN incorrectly matched multi-label subdomains, letting a certificate for a deeper subdomain authenticate as the wildcard user; an unauthenticated TCP client being able to probe table existence and replication status via the interserver port; and several credential and metadata leaks - into system.databases.engine_full, system.text_log, and OpenTelemetry spans - that are now redacted as [HIDDEN].
7. Upgrade Considerations
Pulling the above together into a concrete pre-upgrade checklist:
- Audit S3 access patterns. Search for tables, dictionaries, S3Queue definitions, and DataLakeCatalog databases that rely on implicit server credentials rather than explicit ones. After upgrading, check for anything that loaded anonymously.
- Check backup automation for zip-to-object-storage targets. These will start failing outright; switch to
tar.gzbefore upgrading, not after a failed backup job. - Check
insert_deduplication_version. If it's set to a legacy value, you cannot go straight to 26.7 - you need to pass through an intermediate release first. If you've never touched this setting, skip this step. - Grep your codebase for the removed Snowflake functions (
snowflakeToDateTime, etc.) and replace them with the*IDvariants before upgrading, since these now fail outright rather than just warning. - If you use Naive Bayes models via server config, plan the migration to dictionary-based models - this isn't a flag you can flip, it's a rebuild.
- If you rely on
**/glob patterns matching only when a directory level is present, verify your file-matching queries against the new zero-level-match behavior. - If you expose the MySQL wire protocol (port 9004) or work with third-party S3-compatible storage, treat the SQL injection and SSRF fixes as a priority patch, independent of your normal feature-upgrade cadence.
- If you use async insert deduplication with token reuse across partitions, be aware that any "missing rows" you attributed to application bugs in the past may have been this ClickHouse-side issue - worth a retrospective look if it's caused confusion before.
- Re-benchmark any manual join-memory workarounds (aggressive
max_bytes_in_join, pre-filtering) - the join memory and speed improvements may make some of them unnecessary. - If you're running low-precision
QBitvector search, re-run your evaluation - a real correctness bug in low-precision reconstruction is fixed in this release, and previous results at low precision may have carried much less signal than they appeared to.
None of this is exotic - it's the standard cost of a release with a large backward-incompatible-changes section. The point of listing it out explicitly is that most of it can be checked in an afternoon before upgrading, versus discovered piecemeal in production afterward.
8. Conclusion
ClickHouse® 26.7 is not a "safe, drop-in" release in the way some point releases are, and it would be misleading to describe it that way. It closes real security gaps (an SSRF path and a SQL injection path chief among them), fixes a genuine silent data-loss bug in async insert deduplication, and tightens S3 credential handling in a way that's clearly the right long-term default - but each of those improvements comes with something to check or migrate before you upgrade, not after.
Set against that, the release also delivers real value with no strings attached: a meaningfully faster and leaner join implementation, a native Arrow reader/writer, EXPLAIN ANALYZE as a genuinely useful debugging tool, faster compression across several codecs, and a Web UI that's closed a lot of ground on dedicated SQL clients.
The practical recommendation is straightforward: don't skip the backward-incompatible-changes section, budget time to check the items in the upgrade-considerations checklist against your own deployment, and then take the upgrade - the performance and tooling improvements are worth having, and several of the fixes address problems you may already have been living with unknowingly.



