<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kanishga Subramani</title>
    <description>The latest articles on DEV Community by Kanishga Subramani (@kanishga_subramani_49ad73).</description>
    <link>https://dev.to/kanishga_subramani_49ad73</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3951880%2F08e2b1d3-1c3e-4280-91fc-99fd18e39198.jpg</url>
      <title>DEV Community: Kanishga Subramani</title>
      <link>https://dev.to/kanishga_subramani_49ad73</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kanishga_subramani_49ad73"/>
    <language>en</language>
    <item>
      <title>Day 100 of #100DaysOfClickHouse: Optimizing Data Lake Queries with ClickHouse® 26.3 LTS</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:14:07 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-100-of-100daysofclickhouse-optimizing-data-lake-queries-with-clickhouser-263-lts-9i7</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-100-of-100daysofclickhouse-optimizing-data-lake-queries-with-clickhouser-263-lts-9i7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Data lakes have become the backbone of modern analytics, allowing organizations to store petabytes of structured and unstructured data in cloud object storage services such as Amazon S3, Azure Blob Storage, and Google Cloud Storage. Open table formats like Apache Iceberg, Delta Lake, Apache Hudi, and Parquet have further accelerated this trend by enabling multiple analytics engines to access the same datasets without vendor lock-in or unnecessary data duplication.&lt;/p&gt;

&lt;p&gt;While querying data directly from cloud object storage offers tremendous flexibility, it also introduces performance challenges. Network latency, metadata lookups, and large Parquet file scans can significantly slow query execution compared to locally stored data.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 LTS addresses these challenges with several enhancements designed specifically for data lake workloads. These include improved parallel object storage reads, a built-in Parquet metadata cache, and asynchronous Iceberg metadata prefetching. Together, these optimizations reduce query latency and make interactive analytics over cloud-hosted datasets significantly faster.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how ClickHouse® queries data lakes, the challenges involved, the new optimizations introduced in version 26.3 LTS, and practical examples of how these improvements benefit real-world analytical workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Data Lake Query Optimization Matters
&lt;/h1&gt;

&lt;p&gt;Modern analytics architectures increasingly separate &lt;strong&gt;storage&lt;/strong&gt; from &lt;strong&gt;compute&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of copying data into multiple databases, organizations store analytical datasets in cloud object storage and allow various query engines to access the same files directly.&lt;/p&gt;

&lt;p&gt;This architecture provides several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower storage costs&lt;/li&gt;
&lt;li&gt;Vendor-neutral open formats&lt;/li&gt;
&lt;li&gt;Simplified data management&lt;/li&gt;
&lt;li&gt;Multiple analytics engines sharing the same datasets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it also introduces several performance challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote storage latency&lt;/li&gt;
&lt;li&gt;Metadata lookups&lt;/li&gt;
&lt;li&gt;Large file scans&lt;/li&gt;
&lt;li&gt;Network overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse® 26.3 introduces several optimizations that specifically target these bottlenecks.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding Data Lake Queries
&lt;/h1&gt;

&lt;p&gt;A data lake is a centralized repository that stores both raw and processed data using open formats, allowing multiple analytics engines to query the same datasets without duplication.&lt;/p&gt;

&lt;p&gt;Unlike traditional data warehouses, where data is imported into proprietary storage, data lakes keep information inside cloud object storage.&lt;/p&gt;

&lt;p&gt;Popular storage platforms include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon S3&lt;/li&gt;
&lt;li&gt;Azure Blob Storage&lt;/li&gt;
&lt;li&gt;Google Cloud Storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common open table formats include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Iceberg&lt;/li&gt;
&lt;li&gt;Delta Lake&lt;/li&gt;
&lt;li&gt;Apache Hudi&lt;/li&gt;
&lt;li&gt;Parquet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of importing data into MergeTree tables, ClickHouse® can query these datasets directly.&lt;/p&gt;

&lt;p&gt;During query execution, ClickHouse:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads metadata&lt;/li&gt;
&lt;li&gt;Identifies the required files&lt;/li&gt;
&lt;li&gt;Reads only the required columns&lt;/li&gt;
&lt;li&gt;Applies predicate pushdown where possible&lt;/li&gt;
&lt;li&gt;Returns results without copying data into native storage
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloud Object Storage
        │
        ▼
Parquet / Iceberg / Delta / Hudi
        │
        ▼
     ClickHouse
        │
        ▼
   SQL Analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture eliminates duplicate storage while allowing ClickHouse® to serve as a high-performance analytical engine over existing data lakes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Challenges of Querying Data Lakes
&lt;/h1&gt;

&lt;p&gt;Although cloud object storage is highly scalable and cost-effective, querying remote datasets introduces several challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote Storage Latency
&lt;/h2&gt;

&lt;p&gt;Unlike local disks, every query must retrieve data across the network, increasing response times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Metadata Overhead
&lt;/h2&gt;

&lt;p&gt;Open table formats maintain metadata describing snapshots, manifests, partitions, and data files.&lt;/p&gt;

&lt;p&gt;Before reading any actual data, ClickHouse® must first retrieve and process this metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  Large File Scans
&lt;/h2&gt;

&lt;p&gt;Poor partitioning or inefficient pruning may require scanning significantly more data than necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repeated Metadata Reads
&lt;/h2&gt;

&lt;p&gt;Interactive dashboards often execute the same queries repeatedly.&lt;/p&gt;

&lt;p&gt;Without caching, ClickHouse® must repeatedly download identical metadata from remote storage.&lt;/p&gt;




&lt;h1&gt;
  
  
  How ClickHouse® Queries Data Lakes
&lt;/h1&gt;

&lt;p&gt;ClickHouse® supports querying data directly from cloud object storage without requiring ingestion into MergeTree tables.&lt;/p&gt;

&lt;p&gt;Supported technologies include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Iceberg&lt;/li&gt;
&lt;li&gt;Delta Lake&lt;/li&gt;
&lt;li&gt;Apache Hudi&lt;/li&gt;
&lt;li&gt;Parquet files&lt;/li&gt;
&lt;li&gt;Amazon S3 and compatible object storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During query execution, ClickHouse® reads only the required files and columns, minimizing unnecessary I/O and enabling efficient analytics over remote datasets.&lt;/p&gt;




&lt;h1&gt;
  
  
  Data Lake Enhancements in ClickHouse® 26.3 LTS
&lt;/h1&gt;

&lt;p&gt;Version 26.3 introduces several improvements that significantly reduce latency when querying remote object storage.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Faster Parallel Reading
&lt;/h1&gt;

&lt;p&gt;One of the biggest improvements is enhanced parallel reading of remote data.&lt;/p&gt;

&lt;p&gt;When a query accesses a relatively small number of large files, ClickHouse® now distributes work more efficiently across available CPU cores.&lt;/p&gt;

&lt;p&gt;This optimization applies to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Iceberg&lt;/li&gt;
&lt;li&gt;Delta Lake&lt;/li&gt;
&lt;li&gt;Apache Hudi&lt;/li&gt;
&lt;li&gt;Object storage reads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better CPU utilization&lt;/li&gt;
&lt;li&gt;Faster remote file processing&lt;/li&gt;
&lt;li&gt;Lower overall query execution time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many workloads, queries become several times faster on multi-core systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Built-in Parquet Metadata Cache
&lt;/h1&gt;

&lt;p&gt;Reading Parquet files requires accessing the file footer to obtain schema and metadata information.&lt;/p&gt;

&lt;p&gt;Before ClickHouse® 26.3, repeated queries frequently reread this metadata from remote storage.&lt;/p&gt;

&lt;p&gt;The new &lt;strong&gt;Parquet Metadata Cache&lt;/strong&gt; stores footer information in memory.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced metadata reads&lt;/li&gt;
&lt;li&gt;Lower remote I/O&lt;/li&gt;
&lt;li&gt;Faster repeated queries&lt;/li&gt;
&lt;li&gt;Improved dashboard responsiveness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cache is enabled by default and automatically tracks object changes using file ETags to maintain consistency.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Asynchronous Iceberg Metadata Prefetching
&lt;/h1&gt;

&lt;p&gt;Apache Iceberg maintains metadata describing snapshots, manifests, partitions, and data files.&lt;/p&gt;

&lt;p&gt;Earlier versions often fetched this metadata during query execution, increasing planning time.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces asynchronous metadata prefetching.&lt;/p&gt;

&lt;p&gt;Instead of waiting during query execution, ClickHouse® refreshes Iceberg metadata in the background and serves queries from the cache whenever possible.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduced planning latency&lt;/li&gt;
&lt;li&gt;Faster repeated queries&lt;/li&gt;
&lt;li&gt;Improved dashboard responsiveness&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Architecture Overview
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Data Lake
                  │
                  ▼
        Cloud Object Storage
 (Amazon S3 • Azure Blob • GCS)
                  │
                  ▼
 Apache Iceberg • Delta Lake
 Apache Hudi • Parquet
                  │
                  ▼
      ClickHouse® 26.3 LTS

   • Parallel File Reads
   • Parquet Metadata Cache
   • Iceberg Metadata Prefetch

                  │
                  ▼
 Fast SQL Analytics &amp;amp; Dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Example 1: Querying a Parquet Dataset
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_events&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'https://my-bucket.s3.amazonaws.com/events/*.parquet'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this query, ClickHouse® reads Parquet files directly from cloud storage.&lt;/p&gt;

&lt;p&gt;Because only the required columns are read and filtering is applied early, significantly less data must be processed.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example 2: Querying an Iceberg Table
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;product_category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;iceberg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'analytics.sales'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;product_category&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, ClickHouse® uses Iceberg metadata to identify the necessary files.&lt;/p&gt;

&lt;p&gt;The metadata cache and asynchronous prefetching reduce planning overhead while enabling efficient execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-World Example
&lt;/h1&gt;

&lt;p&gt;Imagine an e-commerce company storing several years of clickstream data in Amazon S3 using Apache Iceberg.&lt;/p&gt;

&lt;p&gt;Instead of copying terabytes of historical data into ClickHouse®, analysts query the Iceberg tables directly.&lt;/p&gt;

&lt;p&gt;With ClickHouse® 26.3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enhanced parallel reading speeds up remote file processing.&lt;/li&gt;
&lt;li&gt;The Parquet metadata cache avoids repeatedly reading footer metadata.&lt;/li&gt;
&lt;li&gt;Iceberg metadata prefetching reduces planning latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, dashboards become significantly faster without requiring data duplication or complex ETL pipelines.&lt;/p&gt;




&lt;h1&gt;
  
  
  Performance Benefits
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Enhanced Parallel Reading&lt;/td&gt;
&lt;td&gt;Faster query execution&lt;/td&gt;
&lt;td&gt;Better CPU utilization when reading remote files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parquet Metadata Cache&lt;/td&gt;
&lt;td&gt;Lower query latency&lt;/td&gt;
&lt;td&gt;Eliminates repeated metadata reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iceberg Metadata Prefetching&lt;/td&gt;
&lt;td&gt;Faster query planning&lt;/td&gt;
&lt;td&gt;Metadata is available before execution&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together, these optimizations improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactive dashboards&lt;/li&gt;
&lt;li&gt;BI workloads&lt;/li&gt;
&lt;li&gt;Exploratory analytics&lt;/li&gt;
&lt;li&gt;Recurring analytical queries&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;To maximize performance when querying data lakes with ClickHouse®:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store analytical datasets in Parquet format.&lt;/li&gt;
&lt;li&gt;Partition datasets using commonly filtered columns.&lt;/li&gt;
&lt;li&gt;Select only the columns required by your queries.&lt;/li&gt;
&lt;li&gt;Apply filters as early as possible.&lt;/li&gt;
&lt;li&gt;Take advantage of the built-in Parquet metadata cache.&lt;/li&gt;
&lt;li&gt;Enable asynchronous Iceberg metadata prefetching for frequently queried datasets.&lt;/li&gt;
&lt;li&gt;Keep ClickHouse® updated to benefit from ongoing data lake optimizations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  When Should You Use These Features?
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 is particularly valuable for organizations that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store analytical datasets in cloud object storage&lt;/li&gt;
&lt;li&gt;Query Apache Iceberg tables&lt;/li&gt;
&lt;li&gt;Query Delta Lake datasets&lt;/li&gt;
&lt;li&gt;Analyze Apache Hudi data&lt;/li&gt;
&lt;li&gt;Perform ad hoc SQL analysis over Parquet files&lt;/li&gt;
&lt;li&gt;Build interactive BI dashboards&lt;/li&gt;
&lt;li&gt;Operate large-scale cloud-native analytics platforms&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Query cloud-hosted datasets without duplicating data.&lt;/li&gt;
&lt;li&gt;Parallel reading accelerates remote file processing.&lt;/li&gt;
&lt;li&gt;The Parquet metadata cache reduces repeated metadata access.&lt;/li&gt;
&lt;li&gt;Iceberg metadata prefetching lowers query planning latency.&lt;/li&gt;
&lt;li&gt;ClickHouse® 26.3 makes interactive analytics over modern data lakes significantly faster and more efficient.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;As organizations continue adopting cloud-native architectures and open table formats, efficiently querying data lakes has become essential for modern analytics. ClickHouse® already enables users to query Apache Iceberg, Delta Lake, Apache Hudi, and Parquet datasets directly from cloud object storage without duplicating data or building complex ETL pipelines.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 LTS further strengthens these capabilities with enhanced parallel object storage reads, a built-in Parquet metadata cache, and asynchronous Iceberg metadata prefetching. Together, these improvements reduce metadata overhead, minimize remote I/O, improve CPU utilization, and significantly accelerate query execution.&lt;/p&gt;

&lt;p&gt;Whether you're building business intelligence dashboards, exploring petabyte-scale datasets, or developing cloud-native analytics platforms, these enhancements make ClickHouse® an even more compelling engine for high-performance analytics directly on modern data lakes.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 99 - Efficient Random Sampling with system.numbers_mt: Parallel Number Generation in ClickHouse® 26.3</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 25 Jul 2026 08:16:28 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-99-efficient-random-sampling-with-systemnumbersmt-parallel-number-generation-in-4eil</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-99-efficient-random-sampling-with-systemnumbersmt-parallel-number-generation-in-4eil</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Every major ClickHouse® release introduces new features and performance improvements, but occasionally older experimental features are removed to simplify maintenance and improve long-term stability.&lt;/p&gt;

&lt;p&gt;One such change in ClickHouse® 26.3 is the removal of the experimental &lt;strong&gt;Hypothesis Skip Index (&lt;code&gt;TYPE hypothesis&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you experimented with this index type in earlier versions, you'll need to update your schema before upgrading to ClickHouse® 26.3. Otherwise, table creation or schema restoration involving this index type will fail.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what Hypothesis Skip Indexes were, why they were removed, how to identify affected tables, and the recommended migration path.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding Data Skipping Indexes
&lt;/h1&gt;

&lt;p&gt;Before discussing the deprecation, it's useful to understand how skip indexes work.&lt;/p&gt;

&lt;p&gt;Unlike traditional relational databases that rely on B-tree secondary indexes, ClickHouse® is a column-oriented database optimized for analytical workloads. Instead of locating individual rows, ClickHouse® stores data in &lt;strong&gt;granules&lt;/strong&gt; (blocks of rows).&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;data skipping index&lt;/strong&gt; stores metadata about each granule, allowing the query engine to determine whether an entire granule can be skipped during query execution.&lt;/p&gt;

&lt;p&gt;When a query contains filtering conditions, ClickHouse® evaluates the skip index before reading data. If a granule cannot possibly satisfy the filter, it is skipped entirely, reducing disk I/O and improving query performance.&lt;/p&gt;

&lt;p&gt;Depending on the workload, skip indexes can significantly reduce the amount of data scanned.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Was the Hypothesis Skip Index?
&lt;/h1&gt;

&lt;p&gt;The &lt;strong&gt;Hypothesis Skip Index&lt;/strong&gt; (&lt;code&gt;TYPE hypothesis&lt;/code&gt;) was an experimental skip index designed to precompute whether a particular boolean expression could evaluate to true within each granule.&lt;/p&gt;

&lt;p&gt;Instead of storing values themselves, it stored one of three states for every granule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stored Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expression is definitely false for all rows (granule can be skipped)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Expression may be true (granule must be scanned)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unknown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Insufficient information&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For queries using the same expression, ClickHouse® could immediately eliminate granules where the condition was guaranteed to be false.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="n"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="n"&gt;Float64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_large&lt;/span&gt; &lt;span class="n"&gt;UInt8&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;order_date&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_large&lt;/span&gt; &lt;span class="n"&gt;is_large&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="n"&gt;GRANULARITY&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In earlier releases, ClickHouse® would precompute whether &lt;code&gt;is_large&lt;/code&gt; could ever be true within each granule.&lt;/p&gt;

&lt;p&gt;During execution of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;is_large&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;granules known to contain only &lt;code&gt;is_large = 0&lt;/code&gt; could be skipped.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Was It Removed?
&lt;/h1&gt;

&lt;p&gt;Although technically interesting, the feature never matured beyond experimental status.&lt;/p&gt;

&lt;p&gt;Some of its limitations included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited production adoption&lt;/li&gt;
&lt;li&gt;Known issues with certain data types such as &lt;code&gt;FixedString&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Experimental behavior without long-term compatibility guarantees&lt;/li&gt;
&lt;li&gt;Similar optimization could be achieved using supported skip indexes together with materialized columns&lt;/li&gt;
&lt;li&gt;Additional maintenance burden for the ClickHouse® developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because of these reasons, the feature has been removed in ClickHouse® 26.3.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Changed in ClickHouse® 26.3?
&lt;/h1&gt;

&lt;p&gt;Starting with ClickHouse® 26.3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;INDEX ... TYPE hypothesis&lt;/code&gt; is no longer recognized.&lt;/li&gt;
&lt;li&gt;Creating new tables using this index type fails.&lt;/li&gt;
&lt;li&gt;Schemas containing this index must be updated before upgrading.&lt;/li&gt;
&lt;li&gt;Existing metadata referencing the deprecated index should be cleaned up.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  What Happens After Upgrading?
&lt;/h1&gt;

&lt;p&gt;Attempting to create a table with the removed index now results in an error similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unknown skip index type: hypothesis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, restoring backups or executing old DDL statements containing &lt;code&gt;TYPE hypothesis&lt;/code&gt; will fail.&lt;/p&gt;




&lt;h1&gt;
  
  
  Finding Affected Tables
&lt;/h1&gt;

&lt;p&gt;Before upgrading, review your table definitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the output contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that table requires modification before upgrading.&lt;/p&gt;

&lt;p&gt;For larger environments, searching exported DDL files or schema repositories for &lt;code&gt;TYPE hypothesis&lt;/code&gt; is also recommended.&lt;/p&gt;




&lt;h1&gt;
  
  
  Removing the Deprecated Index
&lt;/h1&gt;

&lt;p&gt;If the index is no longer required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_large&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the deprecated index definition without affecting the table's data.&lt;/p&gt;




&lt;h1&gt;
  
  
  Recommended Replacement Indexes
&lt;/h1&gt;

&lt;p&gt;Depending on your workload, ClickHouse® offers several supported skip indexes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skip Index&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minmax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Numeric and date range filtering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;set&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Low-cardinality equality filters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bloom_filter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;String equality and &lt;code&gt;IN&lt;/code&gt; predicates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ngrambf_v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Substring search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tokenbf_v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Token-based full-text search&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The replacement should be selected based on actual query patterns rather than simply replacing &lt;code&gt;TYPE hypothesis&lt;/code&gt; with another index.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Migration
&lt;/h1&gt;

&lt;p&gt;Old definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_large&lt;/span&gt; &lt;span class="n"&gt;is_large&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="n"&gt;GRANULARITY&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible replacement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_large&lt;/span&gt; &lt;span class="n"&gt;is_large&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt; &lt;span class="n"&gt;GRANULARITY&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GRANULARITY&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For existing data, materialize the new index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="n"&gt;MATERIALIZE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_large&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="n"&gt;MATERIALIZE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Materialization builds the new skip index for all previously stored parts.&lt;/p&gt;




&lt;h1&gt;
  
  
  Upgrade Checklist
&lt;/h1&gt;

&lt;p&gt;Before moving to ClickHouse® 26.3:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Search schemas for &lt;code&gt;TYPE hypothesis&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Remove deprecated indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Replace with supported skip indexes where appropriate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Materialize new indexes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Validate changes in a staging environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Proceed with the production upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;To avoid similar upgrade surprises in the future:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid using experimental features in production systems.&lt;/li&gt;
&lt;li&gt;Review release notes before every major upgrade.&lt;/li&gt;
&lt;li&gt;Choose skip indexes based on observed query workloads.&lt;/li&gt;
&lt;li&gt;Benchmark performance after index changes.&lt;/li&gt;
&lt;li&gt;Validate schema migrations in a staging environment before production deployment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Important Clarification
&lt;/h1&gt;

&lt;p&gt;One common point of confusion is the similarity between two different features.&lt;/p&gt;

&lt;p&gt;The deprecated feature discussed in this article is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; the same as the newer &lt;strong&gt;Hypothetical Indexes&lt;/strong&gt; feature introduced through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;HYPOTHETICAL&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are entirely different features with different purposes.&lt;/p&gt;

&lt;p&gt;This article focuses only on the removal of the experimental &lt;code&gt;TYPE hypothesis&lt;/code&gt; skip index in ClickHouse® 26.3.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The removal of the experimental &lt;strong&gt;Hypothesis Skip Index&lt;/strong&gt; is a relatively small but important breaking change in ClickHouse® 26.3. Organizations upgrading from earlier releases should review their schemas for any remaining &lt;code&gt;TYPE hypothesis&lt;/code&gt; definitions before upgrading.&lt;/p&gt;

&lt;p&gt;Fortunately, modern skip indexes such as &lt;code&gt;minmax&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;, and Bloom filter variants provide reliable, production-ready alternatives for most workloads. By auditing existing tables, replacing deprecated indexes where necessary, and validating the changes in a staging environment, you can ensure a smooth upgrade with no unexpected schema failures.&lt;/p&gt;

&lt;p&gt;As ClickHouse® continues to evolve, keeping schemas aligned with supported features is one of the simplest ways to maintain long-term performance, stability, and compatibility.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 98 of #100DaysOfClickHouse: ClickHouse® 26.3 JOIN Optimization – A Practical Guide to JOIN Types</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 25 Jul 2026 05:33:13 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-98-of-100daysofclickhouse-clickhouser-263-join-optimization-a-practical-guide-to-join-types-27oo</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-98-of-100daysofclickhouse-clickhouser-263-join-optimization-a-practical-guide-to-join-types-27oo</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Joins are among the most performance-sensitive operations in any analytical database. Whether you're combining fact and dimension tables, filtering records based on related datasets, or performing data quality checks, the efficiency of your JOIN operations directly impacts query execution time and memory consumption.&lt;/p&gt;

&lt;p&gt;ClickHouse® has continuously improved its JOIN execution engine over the years, making complex analytical queries faster and more resource-efficient. One of the notable enhancements in &lt;strong&gt;ClickHouse® 26.3&lt;/strong&gt; is the expansion of &lt;strong&gt;automatic JOIN reordering&lt;/strong&gt; to additional JOIN types, including &lt;strong&gt;SEMI&lt;/strong&gt;, &lt;strong&gt;ANTI&lt;/strong&gt;, and &lt;strong&gt;FULL OUTER JOIN&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Prior to version 26.3, the query optimizer could automatically reorder only &lt;strong&gt;INNER&lt;/strong&gt; and &lt;strong&gt;LEFT/RIGHT JOINs&lt;/strong&gt;. With this release, the optimizer can now evaluate table statistics and automatically choose a more efficient join order for a wider range of JOIN types, reducing memory usage and improving query performance without requiring manual query rewrites.&lt;/p&gt;

&lt;p&gt;In this article, we'll review the major JOIN types available in ClickHouse®, understand how automatic JOIN reordering works, explore what's new in ClickHouse® 26.3, and discuss practical best practices for writing efficient JOIN queries.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding JOIN Types
&lt;/h1&gt;

&lt;p&gt;Before exploring the optimizer improvements, it's important to understand what each JOIN type actually returns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JOIN Type&lt;/th&gt;
&lt;th&gt;Returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INNER JOIN&lt;/td&gt;
&lt;td&gt;Only matching rows from both tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT OUTER JOIN&lt;/td&gt;
&lt;td&gt;All rows from the left table plus matching rows from the right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RIGHT OUTER JOIN&lt;/td&gt;
&lt;td&gt;All rows from the right table plus matching rows from the left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FULL OUTER JOIN&lt;/td&gt;
&lt;td&gt;All rows from both tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT SEMI JOIN&lt;/td&gt;
&lt;td&gt;Left rows that have a matching row (without returning right-side columns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT ANTI JOIN&lt;/td&gt;
&lt;td&gt;Left rows that have no matching row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each JOIN serves a different purpose, and selecting the appropriate one can improve both correctness and performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Sample Tables
&lt;/h1&gt;

&lt;p&gt;Throughout this article, we'll use two simple tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="n"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Alice'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Bob'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Charlie'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="n"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="n"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="n"&gt;Float64&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;strong&gt;Charlie has not placed any orders&lt;/strong&gt;, making it easy to see how different JOIN types behave.&lt;/p&gt;




&lt;h1&gt;
  
  
  INNER JOIN
&lt;/h1&gt;

&lt;p&gt;An &lt;strong&gt;INNER JOIN&lt;/strong&gt; returns only rows that exist in both tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Charlie is excluded because no matching order exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You only need records that exist in both tables.&lt;/p&gt;




&lt;h1&gt;
  
  
  LEFT OUTER JOIN
&lt;/h1&gt;

&lt;p&gt;A LEFT JOIN returns every row from the left table, regardless of whether a matching row exists on the right.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Charlie appears because every customer is preserved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You need all rows from the left table.&lt;/p&gt;




&lt;h1&gt;
  
  
  RIGHT OUTER JOIN
&lt;/h1&gt;

&lt;p&gt;A RIGHT JOIN returns every row from the right table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;RIGHT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since every order belongs to a customer, the result contains only Alice and Bob.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You need all rows from the right table.&lt;/p&gt;




&lt;h1&gt;
  
  
  FULL OUTER JOIN
&lt;/h1&gt;

&lt;p&gt;A FULL OUTER JOIN returns every row from both tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;FULL&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;1200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;450&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rows without matches are filled with NULL values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; You need a complete combined view of both datasets.&lt;/p&gt;




&lt;h1&gt;
  
  
  LEFT SEMI JOIN
&lt;/h1&gt;

&lt;p&gt;A &lt;strong&gt;SEMI JOIN&lt;/strong&gt; checks whether a match exists but returns only columns from the left table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;SEMI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice that no columns from the orders table are returned.&lt;/p&gt;

&lt;p&gt;This is generally more efficient than using an INNER JOIN when you only need to verify that a related row exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; Checking existence without retrieving data from the right table.&lt;/p&gt;




&lt;h1&gt;
  
  
  LEFT ANTI JOIN
&lt;/h1&gt;

&lt;p&gt;ANTI JOIN is the opposite of SEMI JOIN.&lt;/p&gt;

&lt;p&gt;It returns rows that &lt;strong&gt;do not&lt;/strong&gt; have a matching record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;ANTI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;customer_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Only Charlie is returned because he has never placed an order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding orphaned records&lt;/li&gt;
&lt;li&gt;Data validation&lt;/li&gt;
&lt;li&gt;Missing relationships&lt;/li&gt;
&lt;li&gt;Customers who have never purchased&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  JOIN Comparison
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;JOIN Type&lt;/th&gt;
&lt;th&gt;Alice&lt;/th&gt;
&lt;th&gt;Bob&lt;/th&gt;
&lt;th&gt;Charlie&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INNER JOIN&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT JOIN&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (NULL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RIGHT JOIN&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FULL OUTER JOIN&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ (NULL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT SEMI JOIN&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LEFT ANTI JOIN&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  What's New in ClickHouse® 26.3?
&lt;/h1&gt;

&lt;p&gt;ClickHouse executes hash joins by building an in-memory hash table from one side of the JOIN.&lt;/p&gt;

&lt;p&gt;If the larger table becomes the hash table, memory consumption increases significantly.&lt;/p&gt;

&lt;p&gt;Before ClickHouse® 26.3, the optimizer could automatically swap JOIN order only for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INNER JOIN&lt;/li&gt;
&lt;li&gt;LEFT JOIN&lt;/li&gt;
&lt;li&gt;RIGHT JOIN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;SEMI&lt;/strong&gt;, &lt;strong&gt;ANTI&lt;/strong&gt;, and &lt;strong&gt;FULL OUTER JOIN&lt;/strong&gt;, developers often needed to manually arrange tables in the most efficient order.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 removes much of this manual work.&lt;/p&gt;

&lt;p&gt;The optimizer now evaluates table statistics and can automatically reorder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEMI JOIN&lt;/li&gt;
&lt;li&gt;ANTI JOIN&lt;/li&gt;
&lt;li&gt;FULL OUTER JOIN&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to build smaller hash tables whenever possible.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;ANTI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if the query isn't written in the optimal order, ClickHouse® 26.3 can internally rearrange the join plan to improve efficiency.&lt;/p&gt;




&lt;h1&gt;
  
  
  Collecting Statistics for Better Optimization
&lt;/h1&gt;

&lt;p&gt;Automatic JOIN reordering relies on accurate table statistics.&lt;/p&gt;

&lt;p&gt;It is recommended to collect statistics on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JOIN key columns&lt;/li&gt;
&lt;li&gt;Frequently filtered columns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful statistics include:&lt;/p&gt;

&lt;h3&gt;
  
  
  TDigest
&lt;/h3&gt;

&lt;p&gt;Provides data distribution and quantile estimates.&lt;/p&gt;

&lt;p&gt;Useful for estimating filter selectivity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;STATISTICS&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;tdigest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="n"&gt;MATERIALIZE&lt;/span&gt; &lt;span class="k"&gt;STATISTICS&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Uniq
&lt;/h3&gt;

&lt;p&gt;Estimates column cardinality.&lt;/p&gt;

&lt;p&gt;Useful for predicting JOIN selectivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  CountMinSketch
&lt;/h3&gt;

&lt;p&gt;Useful when filtering frequently on exact values.&lt;/p&gt;

&lt;p&gt;Provides approximate frequency estimates with minimal memory.&lt;/p&gt;




&lt;h1&gt;
  
  
  Performance Best Practices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Prefer SEMI JOIN over INNER JOIN
&lt;/h2&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;SEMI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids reading unnecessary columns.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Prefer ANTI JOIN over NOT IN
&lt;/h2&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;ANTI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ANTI JOIN is typically faster and more memory-efficient on large datasets.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Let the Optimizer Help
&lt;/h2&gt;

&lt;p&gt;Older ClickHouse versions often required manually placing the smaller table on the right.&lt;/p&gt;

&lt;p&gt;With ClickHouse® 26.3, automatic JOIN reordering reduces the need for manual optimization across many JOIN types.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Verify Query Plans
&lt;/h2&gt;

&lt;p&gt;Always inspect execution plans.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="n"&gt;ANTI&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EXPLAIN helps verify that the optimizer is selecting the expected execution strategy.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Will This Improvement Matter?
&lt;/h1&gt;

&lt;p&gt;You'll benefit the most if your workloads include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large analytical datasets&lt;/li&gt;
&lt;li&gt;Multi-table joins&lt;/li&gt;
&lt;li&gt;Frequent SEMI or ANTI JOIN queries&lt;/li&gt;
&lt;li&gt;FULL OUTER JOIN operations&lt;/li&gt;
&lt;li&gt;Memory-sensitive workloads&lt;/li&gt;
&lt;li&gt;Data warehouse environments with complex reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For smaller datasets, the improvement may not be immediately noticeable, but at scale it helps reduce memory usage and improves overall query execution.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Choosing the correct JOIN type is one of the simplest ways to improve query performance in ClickHouse®.&lt;/p&gt;

&lt;p&gt;While INNER, LEFT, and FULL OUTER JOIN cover most common scenarios, &lt;strong&gt;SEMI JOIN&lt;/strong&gt; and &lt;strong&gt;ANTI JOIN&lt;/strong&gt; are powerful alternatives that are often overlooked. They can reduce unnecessary data processing and improve memory efficiency when you only need to check whether matching rows exist.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 builds on these capabilities by extending &lt;strong&gt;automatic JOIN reordering&lt;/strong&gt; to &lt;strong&gt;SEMI&lt;/strong&gt;, &lt;strong&gt;ANTI&lt;/strong&gt;, and &lt;strong&gt;FULL OUTER JOINs&lt;/strong&gt;. By leveraging table statistics, the optimizer can automatically choose a more efficient execution plan, reducing memory consumption and eliminating much of the manual tuning previously required.&lt;/p&gt;

&lt;p&gt;It's another step toward making ClickHouse not only one of the fastest analytical databases available, but also one that increasingly optimizes itself behind the scenes—allowing developers to focus more on writing queries and less on fine-tuning execution strategies.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 97 of #100DaysOfClickHouse: Analyzing Memory Efficiency with Vertical Merges in ClickHouse® 26.3</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 25 Jul 2026 05:15:13 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-97-of-100daysofclickhouse-analyzing-memory-efficiency-with-vertical-merges-in-clickhouser-16e2</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-97-of-100daysofclickhouse-analyzing-memory-efficiency-with-vertical-merges-in-clickhouser-16e2</guid>
      <description>&lt;p&gt;Background merge operations are one of the most important components of ClickHouse®. Every insert creates immutable data parts, and the storage engine continuously merges these parts to keep query performance high and storage efficient. These merges also perform critical maintenance tasks such as applying TTL rules, removing expired records, recompressing data, and consolidating files.&lt;/p&gt;

&lt;p&gt;For most workloads, this process happens quietly in the background. However, for organizations storing massive analytical datasets with hundreds of columns, background merges can become one of the largest consumers of memory.&lt;/p&gt;

&lt;p&gt;ClickHouse® already introduced &lt;strong&gt;Vertical Merge&lt;/strong&gt;, an optimization designed to reduce memory usage during merge operations by processing columns independently instead of entire rows. In &lt;strong&gt;ClickHouse 26.3&lt;/strong&gt;, this optimization has been extended to &lt;strong&gt;TTL DELETE merges&lt;/strong&gt;, significantly lowering memory consumption when expired rows are removed automatically.&lt;/p&gt;

&lt;p&gt;This article explains why this enhancement matters, how Vertical Merge works internally, and why it improves the efficiency of large production deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Merge Memory Matters
&lt;/h1&gt;

&lt;p&gt;Unlike traditional databases that update data in place, ClickHouse stores data as immutable &lt;strong&gt;parts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every INSERT creates a new part, and background merge threads continuously combine smaller parts into larger ones.&lt;/p&gt;

&lt;p&gt;During these merges, ClickHouse performs several maintenance operations simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merging multiple parts into one&lt;/li&gt;
&lt;li&gt;Recompressing column files&lt;/li&gt;
&lt;li&gt;Applying TTL expressions&lt;/li&gt;
&lt;li&gt;Removing expired rows&lt;/li&gt;
&lt;li&gt;Rewriting data into optimized storage layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For small datasets, merge memory consumption is usually insignificant.&lt;/p&gt;

&lt;p&gt;Production analytical systems, however, often store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hundreds of columns&lt;/li&gt;
&lt;li&gt;Large String columns&lt;/li&gt;
&lt;li&gt;Nested structures&lt;/li&gt;
&lt;li&gt;Arrays&lt;/li&gt;
&lt;li&gt;Maps&lt;/li&gt;
&lt;li&gt;JSON data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During a merge, ClickHouse may need to read, decompress, merge, filter, and rewrite large amounts of column data.&lt;/p&gt;

&lt;p&gt;As tables become wider, the peak memory required during merges grows substantially.&lt;/p&gt;

&lt;p&gt;While query optimization often receives the most attention, reducing merge memory is equally important because merges execute continuously in the background.&lt;/p&gt;




&lt;h1&gt;
  
  
  Horizontal Merge vs Vertical Merge
&lt;/h1&gt;

&lt;p&gt;Historically, ClickHouse performed merges using a &lt;strong&gt;horizontal merge algorithm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In a horizontal merge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entire rows are processed together.&lt;/li&gt;
&lt;li&gt;All required columns remain active throughout much of the merge.&lt;/li&gt;
&lt;li&gt;Memory usage increases as the number of columns grows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach works well for narrow schemas but becomes increasingly expensive for wide analytical tables.&lt;/p&gt;

&lt;p&gt;Vertical Merge takes a different approach.&lt;/p&gt;

&lt;p&gt;Instead of processing complete rows, ClickHouse separates the merge into stages.&lt;/p&gt;

&lt;p&gt;First, it determines the correct row ordering using the primary key. After that, each remaining column is processed independently.&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Merge primary key columns.&lt;/li&gt;
&lt;li&gt;Build the merged row mapping.&lt;/li&gt;
&lt;li&gt;Process one non-key column at a time.&lt;/li&gt;
&lt;li&gt;Write the merged output.&lt;/li&gt;
&lt;li&gt;Release memory before processing the next column.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since only a small subset of columns is active at any moment, peak memory usage becomes dramatically lower.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagram 1: Horizontal Merge vs Vertical Merge
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Horizontal Merge

Part A          Part B
 |                |
 | Read ALL Columns
 |________________|
         |
   Merge Entire Rows
         |
   Write New Part


Vertical Merge

Part A          Part B
 |                |
 | Merge Primary Keys
 |_________________|
         |
   Build Row Mapping
         |
Column 1 -&amp;gt; Write
Column 2 -&amp;gt; Write
Column 3 -&amp;gt; Write
...
Column N -&amp;gt; Write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  TTL DELETE Before ClickHouse 26.3
&lt;/h1&gt;

&lt;p&gt;One of the most common maintenance tasks in ClickHouse is automatic data retention using &lt;strong&gt;TTL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;logs&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="nb"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;TTL&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rows older than 30 days are automatically removed during background merges.&lt;/p&gt;

&lt;p&gt;Before ClickHouse 26.3, TTL DELETE operations primarily relied on the &lt;strong&gt;horizontal merge algorithm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although this approach correctly removed expired records, it also meant that all participating columns needed to be processed together.&lt;/p&gt;

&lt;p&gt;For tables with hundreds of columns, peak memory usage during TTL cleanup could become quite large.&lt;/p&gt;

&lt;p&gt;This was rarely noticeable for small tables but became increasingly important for enterprise deployments storing billions of rows.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's New in ClickHouse 26.3?
&lt;/h1&gt;

&lt;p&gt;ClickHouse 26.3 extends &lt;strong&gt;Vertical Merge&lt;/strong&gt; support to &lt;strong&gt;TTL DELETE merges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of evaluating TTL rules while processing every column simultaneously, ClickHouse now performs TTL cleanup using the same memory-efficient vertical workflow already used for standard merges.&lt;/p&gt;

&lt;p&gt;The process now works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read and merge primary key columns.&lt;/li&gt;
&lt;li&gt;Evaluate the TTL expression.&lt;/li&gt;
&lt;li&gt;Identify rows that should be deleted.&lt;/li&gt;
&lt;li&gt;Build the row mapping.&lt;/li&gt;
&lt;li&gt;Process each remaining column independently.&lt;/li&gt;
&lt;li&gt;Write only the surviving rows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because non-key columns are handled individually, ClickHouse no longer needs to keep every column in memory simultaneously.&lt;/p&gt;

&lt;p&gt;The result is significantly lower peak memory usage during automatic data cleanup.&lt;/p&gt;




&lt;h1&gt;
  
  
  Before vs After ClickHouse 26.3
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Before 26.3&lt;/th&gt;
&lt;th&gt;ClickHouse 26.3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTL DELETE Merge&lt;/td&gt;
&lt;td&gt;Horizontal Merge&lt;/td&gt;
&lt;td&gt;Vertical Merge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak Memory Usage&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wide Table Performance&lt;/td&gt;
&lt;td&gt;Memory intensive&lt;/td&gt;
&lt;td&gt;More memory efficient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background Cleanup&lt;/td&gt;
&lt;td&gt;Higher resource consumption&lt;/td&gt;
&lt;td&gt;Reduced memory pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Limited by merge memory&lt;/td&gt;
&lt;td&gt;Better scalability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Why Vertical Merge Uses Less Memory
&lt;/h1&gt;

&lt;p&gt;The biggest improvement is that memory usage becomes far less dependent on the number of columns.&lt;/p&gt;

&lt;p&gt;Imagine a table containing &lt;strong&gt;400 columns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With a horizontal merge, many of those columns may be loaded and processed together.&lt;/p&gt;

&lt;p&gt;With Vertical Merge, ClickHouse processes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one column,&lt;/li&gt;
&lt;li&gt;writes the filtered data,&lt;/li&gt;
&lt;li&gt;releases memory,&lt;/li&gt;
&lt;li&gt;moves to the next column.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only a tiny portion of the table is active at any given time.&lt;/p&gt;

&lt;p&gt;The total amount of data processed remains identical.&lt;/p&gt;

&lt;p&gt;What changes is the &lt;strong&gt;maximum memory required at any instant&lt;/strong&gt;, which can be significantly smaller.&lt;/p&gt;

&lt;p&gt;For servers running multiple concurrent merges, this reduction can substantially improve overall system stability.&lt;/p&gt;




&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;

&lt;p&gt;Suppose an event logging platform stores billions of events and automatically deletes records older than 90 days.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="nb"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;TTL&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every day, background merges remove expired records.&lt;/p&gt;

&lt;p&gt;On previous versions, cleaning up a very wide table could temporarily consume a large amount of memory because every column participated in the merge simultaneously.&lt;/p&gt;

&lt;p&gt;With ClickHouse 26.3, the same cleanup benefits from Vertical Merge processing.&lt;/p&gt;

&lt;p&gt;The result is smoother background maintenance with lower peak memory usage and less pressure on the operating system.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where You'll Notice the Biggest Improvements
&lt;/h1&gt;

&lt;p&gt;This optimization is especially valuable when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tables contain hundreds of columns.&lt;/li&gt;
&lt;li&gt;Large MergeTree tables are merged frequently.&lt;/li&gt;
&lt;li&gt;TTL DELETE rules remove old data continuously.&lt;/li&gt;
&lt;li&gt;Memory resources are limited.&lt;/li&gt;
&lt;li&gt;Multiple background merges execute concurrently.&lt;/li&gt;
&lt;li&gt;Analytical workloads generate large numbers of data parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smaller tables may not show dramatic improvements because merge memory requirements are already relatively low.&lt;/p&gt;




&lt;h1&gt;
  
  
  Benefits of Vertical Merge for TTL DELETE
&lt;/h1&gt;

&lt;p&gt;Extending Vertical Merge to TTL DELETE operations provides several practical advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower peak memory consumption during background merges&lt;/li&gt;
&lt;li&gt;Better scalability for very wide schemas&lt;/li&gt;
&lt;li&gt;More stable merge execution&lt;/li&gt;
&lt;li&gt;Reduced memory pressure across the server&lt;/li&gt;
&lt;li&gt;Improved efficiency of automatic data retention&lt;/li&gt;
&lt;li&gt;Better utilization of available system resources&lt;/li&gt;
&lt;li&gt;More predictable performance under heavy workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although users may never interact with this feature directly, its impact can be significant for production environments processing terabytes or petabytes of data.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Background merges are essential to how ClickHouse maintains fast analytical performance. As datasets become larger and schemas become wider, however, merge operations can consume substantial amounts of memory.&lt;/p&gt;

&lt;p&gt;ClickHouse 26.3 addresses this challenge by extending the &lt;strong&gt;Vertical Merge&lt;/strong&gt; algorithm to &lt;strong&gt;TTL DELETE&lt;/strong&gt; operations. Instead of processing every column simultaneously, the storage engine now handles non-key columns independently, dramatically reducing peak memory requirements while preserving the correctness of TTL processing.&lt;/p&gt;

&lt;p&gt;This enhancement operates entirely behind the scenes, requiring no application changes or configuration updates. Yet for production workloads that rely on automated data retention and large MergeTree tables, it can lead to more stable background merges, improved resource utilization, and better overall scalability.&lt;/p&gt;

&lt;p&gt;It is another example of how ClickHouse continues refining its storage engine—not only to execute queries faster, but also to make the underlying maintenance operations increasingly efficient for modern analytical workloads.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 96/100 – Standard SQL Time Functions in ClickHouse® 26.3: Better ANSI SQL Compatibility</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Thu, 23 Jul 2026 14:01:58 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-96100-standard-sql-time-functions-in-clickhouser-263-better-ansi-sql-compatibility-4m4l</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-96100-standard-sql-time-functions-in-clickhouser-263-better-ansi-sql-compatibility-4m4l</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;One of the key goals of recent ClickHouse® releases has been improving compatibility with the ANSI SQL standard. While ClickHouse has always offered a rich set of date and time functions, some syntax differences compared to traditional relational databases required developers to modify existing SQL queries during migrations.&lt;/p&gt;

&lt;p&gt;A common example was retrieving the current date or timestamp. Earlier versions of ClickHouse relied on function calls such as &lt;code&gt;today()&lt;/code&gt; and &lt;code&gt;now()&lt;/code&gt;, whereas many popular databases use SQL-standard keywords like &lt;code&gt;CURRENT_DATE&lt;/code&gt; and &lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 bridges this gap by introducing support for &lt;strong&gt;SQL-standard time functions without parentheses&lt;/strong&gt;. Although this may seem like a small enhancement, it significantly improves portability for applications, BI tools, reporting platforms, and ORMs that generate ANSI SQL.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what's new, how these functions work, and why this seemingly simple feature makes migrating workloads to ClickHouse much easier.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why SQL Compatibility Matters
&lt;/h1&gt;

&lt;p&gt;Organizations rarely build analytics platforms from scratch.&lt;/p&gt;

&lt;p&gt;Many migrate workloads from databases such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;li&gt;Snowflake&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These systems generally follow ANSI SQL syntax for obtaining the current date and timestamp.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prior to ClickHouse® 26.3, these queries required modification before they could run successfully.&lt;/p&gt;

&lt;p&gt;Even small syntax differences become significant when migrating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thousands of SQL queries&lt;/li&gt;
&lt;li&gt;BI dashboards&lt;/li&gt;
&lt;li&gt;Stored reports&lt;/li&gt;
&lt;li&gt;ORM-generated SQL&lt;/li&gt;
&lt;li&gt;Data transformation pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reducing these incompatibilities simplifies migrations and improves developer productivity.&lt;/p&gt;




&lt;h1&gt;
  
  
  Before ClickHouse® 26.3
&lt;/h1&gt;

&lt;p&gt;Traditionally, ClickHouse used dedicated functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These functions remain fully supported and continue to be the recommended native ClickHouse approach.&lt;/p&gt;

&lt;p&gt;However, applications written for ANSI SQL databases often expected keyword-based syntax instead.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's New in ClickHouse® 26.3?
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 introduces support for several SQL-standard temporal keywords without requiring parentheses.&lt;/p&gt;

&lt;p&gt;Supported syntax includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These expressions behave exactly like their ClickHouse counterparts while following the SQL standard used by many other database systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  Function Comparison
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SQL Standard&lt;/th&gt;
&lt;th&gt;Traditional ClickHouse®&lt;/th&gt;
&lt;th&gt;Supported in 26.3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CURRENT_DATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;today()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;now()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NOW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;now()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes (without parentheses)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These additions provide alternative syntax—they do &lt;strong&gt;not&lt;/strong&gt; replace existing ClickHouse functions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Examples
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Current Date
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Equivalent native ClickHouse syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-03-18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Current Timestamp
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Equivalent ClickHouse function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-03-18 14:35:12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  NOW Without Parentheses
&lt;/h2&gt;

&lt;p&gt;Before ClickHouse® 26.3:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can simply write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both statements return the same current timestamp.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Matters for Migrations
&lt;/h1&gt;

&lt;p&gt;Many migration projects involve moving hundreds or thousands of SQL statements from existing analytical databases into ClickHouse.&lt;/p&gt;

&lt;p&gt;Consider a PostgreSQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;CURRENT_DATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Earlier versions required rewriting this as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With ClickHouse® 26.3, the original SQL can often run unchanged.&lt;/p&gt;

&lt;p&gt;This reduces migration effort while improving compatibility with third-party SQL generators.&lt;/p&gt;




&lt;h1&gt;
  
  
  Better Support for BI Tools
&lt;/h1&gt;

&lt;p&gt;Many reporting platforms automatically generate ANSI SQL.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Superset&lt;/li&gt;
&lt;li&gt;Metabase&lt;/li&gt;
&lt;li&gt;Tableau&lt;/li&gt;
&lt;li&gt;Power BI&lt;/li&gt;
&lt;li&gt;Looker&lt;/li&gt;
&lt;li&gt;JDBC-based reporting tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because these tools frequently generate &lt;code&gt;CURRENT_DATE&lt;/code&gt; or &lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt;, ClickHouse now accepts these expressions without requiring query modifications.&lt;/p&gt;

&lt;p&gt;This helps reduce compatibility issues during deployment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Existing Date and Time Functions Remain Available
&lt;/h1&gt;

&lt;p&gt;It's important to note that ClickHouse® 26.3 does &lt;strong&gt;not&lt;/strong&gt; introduce a new date-time engine.&lt;/p&gt;

&lt;p&gt;All existing functions remain available, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;today()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;now()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toDate()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toDateTime()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dateDiff()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dateAdd()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toStartOfMonth()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toStartOfDay()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toStartOfWeek()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toStartOfHour()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new feature simply adds SQL-standard alternatives for retrieving the current date and timestamp.&lt;/p&gt;




&lt;h1&gt;
  
  
  Benefits
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CURRENT_DATE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ANSI SQL compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Easier query portability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NOW&lt;/code&gt; without parentheses&lt;/td&gt;
&lt;td&gt;Familiar syntax for SQL users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduced query rewrites&lt;/td&gt;
&lt;td&gt;Faster database migrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better ORM compatibility&lt;/td&gt;
&lt;td&gt;Less application code modification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improved BI tool support&lt;/td&gt;
&lt;td&gt;Greater interoperability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;To get the most from this enhancement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use SQL-standard syntax when writing portable SQL that may run across multiple database systems.&lt;/li&gt;
&lt;li&gt;Continue using native ClickHouse functions if your environment is already optimized around them.&lt;/li&gt;
&lt;li&gt;When migrating applications, test existing SQL before rewriting—it may now work without changes.&lt;/li&gt;
&lt;li&gt;Validate ORM-generated SQL after upgrading to ClickHouse® 26.3, as many generated queries may become compatible automatically.&lt;/li&gt;
&lt;li&gt;Keep using ClickHouse's rich date and time functions for advanced analytical workloads, as these remain the primary tools for date manipulation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  What Didn't Change
&lt;/h1&gt;

&lt;p&gt;Although the syntax is new, the underlying behavior is not.&lt;/p&gt;

&lt;p&gt;These additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not change how ClickHouse calculates dates or timestamps.&lt;/li&gt;
&lt;li&gt;Do not replace existing functions such as &lt;code&gt;today()&lt;/code&gt; or &lt;code&gt;now()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Do not introduce new time zones or formatting options.&lt;/li&gt;
&lt;li&gt;Do not affect performance compared to their native equivalents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary goal is &lt;strong&gt;better SQL compatibility&lt;/strong&gt;, making ClickHouse easier to adopt for teams migrating from other database platforms.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Not every new feature needs to be a major performance optimization or a groundbreaking capability. Sometimes, small improvements can have a significant impact on developer experience.&lt;/p&gt;

&lt;p&gt;The addition of &lt;strong&gt;&lt;code&gt;CURRENT_DATE&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;CURRENT_TIMESTAMP&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;NOW&lt;/code&gt; without parentheses&lt;/strong&gt; in ClickHouse® 26.3 is one such enhancement. By embracing ANSI SQL syntax, ClickHouse reduces friction for organizations migrating from traditional relational databases and improves compatibility with BI tools, ORMs, and SQL-generating applications.&lt;/p&gt;

&lt;p&gt;Existing ClickHouse functions like &lt;code&gt;today()&lt;/code&gt; and &lt;code&gt;now()&lt;/code&gt; remain fully supported, giving developers the flexibility to choose the syntax that best fits their workflows. For teams building portable SQL or modernizing existing analytics platforms, this update makes the transition to ClickHouse just a little smoother—and that's a meaningful improvement in itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® 26.3 Release Notes&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – Date and Time Functions&lt;/li&gt;
&lt;li&gt;ANSI SQL Standard – Date and Time Expressions&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 95/100 – ClickHouse® 26.3 S3 Object Storage Read Enhancements: Faster Data Lake Queries</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Thu, 23 Jul 2026 13:50:08 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-95100-clickhouser-263-s3-object-storage-read-enhancements-faster-data-lake-queries-1gj9</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-95100-clickhouser-263-s3-object-storage-read-enhancements-faster-data-lake-queries-1gj9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Cloud object storage has become the foundation of modern data lake architectures. Services such as Amazon S3 and S3-compatible object stores provide virtually unlimited, cost-effective storage for massive analytical datasets, making them the preferred choice for storing historical logs, clickstream data, IoT events, machine learning datasets, and business intelligence workloads.&lt;/p&gt;

&lt;p&gt;Rather than copying every dataset into local storage, many organizations now query data directly from object storage using formats such as Parquet, Iceberg, Delta Lake, and Apache Hudi. This approach reduces storage costs, simplifies data sharing, and enables multiple analytics engines to work on the same data lake.&lt;/p&gt;

&lt;p&gt;ClickHouse® has long supported querying data directly from S3 using table functions and native storage engines. However, network latency and object storage access overhead have traditionally made remote reads slower than querying local MergeTree tables.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 significantly improves this experience with major enhancements to the S3 object storage read path. The release introduces faster parallel reads, smarter metadata caching, asynchronous Iceberg metadata prefetching, more efficient S3Queue ingestion, and reduced memory usage for semi-structured data.&lt;/p&gt;

&lt;p&gt;The result is dramatically faster queries against object storage without requiring schema changes, query rewrites, or application modifications.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore these improvements and understand how they benefit modern lakehouse architectures.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Object Storage Matters
&lt;/h1&gt;

&lt;p&gt;Today's analytical workloads increasingly separate storage from compute.&lt;/p&gt;

&lt;p&gt;Instead of keeping all datasets on local disks, organizations store data in cloud object storage while scaling compute independently.&lt;/p&gt;

&lt;p&gt;Common use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data lakes&lt;/li&gt;
&lt;li&gt;Data lakehouses&lt;/li&gt;
&lt;li&gt;Historical event storage&lt;/li&gt;
&lt;li&gt;Log analytics&lt;/li&gt;
&lt;li&gt;Clickstream analysis&lt;/li&gt;
&lt;li&gt;Machine learning datasets&lt;/li&gt;
&lt;li&gt;Business intelligence platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Popular table formats include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache Parquet&lt;/li&gt;
&lt;li&gt;Apache Iceberg&lt;/li&gt;
&lt;li&gt;Delta Lake&lt;/li&gt;
&lt;li&gt;Apache Hudi&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse can query these formats directly without first importing data into MergeTree tables.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Challenge Before ClickHouse® 26.3
&lt;/h1&gt;

&lt;p&gt;Although object storage offers excellent scalability and lower storage costs, remote reads naturally introduce additional overhead.&lt;/p&gt;

&lt;p&gt;Each query may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opening remote objects&lt;/li&gt;
&lt;li&gt;Reading metadata&lt;/li&gt;
&lt;li&gt;Fetching Parquet footers&lt;/li&gt;
&lt;li&gt;Downloading row groups&lt;/li&gt;
&lt;li&gt;Waiting on network latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For workloads scanning only a handful of large files, CPUs frequently became underutilized while waiting for remote I/O.&lt;/p&gt;

&lt;p&gt;As a result, applications experienced higher query latency compared to local storage.&lt;/p&gt;




&lt;h1&gt;
  
  
  Faster Parallel Reads
&lt;/h1&gt;

&lt;p&gt;The most significant improvement in ClickHouse® 26.3 is a redesigned object storage read path.&lt;/p&gt;

&lt;p&gt;Instead of waiting for individual remote read operations to complete sequentially, ClickHouse now parallelizes object storage reads across multiple CPU cores.&lt;/p&gt;

&lt;p&gt;This improvement benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon S3&lt;/li&gt;
&lt;li&gt;S3-compatible storage&lt;/li&gt;
&lt;li&gt;Apache Iceberg&lt;/li&gt;
&lt;li&gt;Delta Lake&lt;/li&gt;
&lt;li&gt;Apache Hudi&lt;/li&gt;
&lt;li&gt;Parquet files&lt;/li&gt;
&lt;li&gt;CSV files queried through the &lt;code&gt;s3()&lt;/code&gt; table function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest performance gains occur when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries read a relatively small number of files&lt;/li&gt;
&lt;li&gt;Files are large&lt;/li&gt;
&lt;li&gt;Multiple CPU cores are available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of leaving CPU cores idle while waiting on network operations, ClickHouse keeps multiple cores busy simultaneously.&lt;/p&gt;

&lt;p&gt;According to the ClickHouse® 26.3 release notes, these optimizations can make object storage reads &lt;strong&gt;tens of times faster&lt;/strong&gt; on multi-core systems for suitable workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  Smarter Parquet Metadata Caching
&lt;/h1&gt;

&lt;p&gt;Every Parquet file contains metadata stored in its footer.&lt;/p&gt;

&lt;p&gt;Before processing data, ClickHouse reads this footer to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schema&lt;/li&gt;
&lt;li&gt;Row groups&lt;/li&gt;
&lt;li&gt;Column statistics&lt;/li&gt;
&lt;li&gt;File layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repeatedly downloading this metadata for frequently queried files adds unnecessary latency.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces a new &lt;strong&gt;SLRU (Segmented Least Recently Used) cache&lt;/strong&gt; for Parquet metadata.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabled by default&lt;/li&gt;
&lt;li&gt;Up to &lt;strong&gt;2× fewer metadata reads&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Faster repeated queries&lt;/li&gt;
&lt;li&gt;Reduced network requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To ensure correctness, cached metadata is validated using each file's &lt;strong&gt;ETag&lt;/strong&gt;, preventing stale metadata from being used.&lt;/p&gt;




&lt;h1&gt;
  
  
  Faster Iceberg Metadata Access
&lt;/h1&gt;

&lt;p&gt;Iceberg users receive another major optimization.&lt;/p&gt;

&lt;p&gt;Traditionally, each query needed to communicate with the Iceberg catalog before execution.&lt;/p&gt;

&lt;p&gt;Although necessary for consistency, repeated catalog lookups increase query latency.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces &lt;strong&gt;asynchronous metadata prefetching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of retrieving metadata during every query, ClickHouse periodically refreshes metadata in the background.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_iceberg&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IcebergS3&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;SETTINGS&lt;/span&gt;
&lt;span class="n"&gt;iceberg_metadata_async_prefetch_period_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queries can specify acceptable metadata freshness.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_iceberg&lt;/span&gt;
&lt;span class="n"&gt;SETTINGS&lt;/span&gt;
&lt;span class="n"&gt;iceberg_metadata_staleness_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If cached metadata is sufficiently recent, ClickHouse avoids contacting the Iceberg catalog entirely during query execution.&lt;/p&gt;

&lt;p&gt;This removes catalog communication from the critical query path.&lt;/p&gt;




&lt;h1&gt;
  
  
  Better S3Queue Performance
&lt;/h1&gt;

&lt;p&gt;S3Queue continuously monitors object storage for newly uploaded files.&lt;/p&gt;

&lt;p&gt;Before ClickHouse® 26.3, queues frequently scanned the complete object prefix history to identify new files.&lt;/p&gt;

&lt;p&gt;For buckets containing millions of historical objects, repeated listing operations became increasingly expensive.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 improves ordered-mode S3Queue by using the &lt;strong&gt;StartAfter&lt;/strong&gt; parameter.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoids scanning entire bucket history&lt;/li&gt;
&lt;li&gt;Reduces ListObjects API requests&lt;/li&gt;
&lt;li&gt;Faster detection of new files&lt;/li&gt;
&lt;li&gt;Lower cloud API costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is particularly valuable for long-running ingestion pipelines.&lt;/p&gt;




&lt;h1&gt;
  
  
  Lower Memory Usage for JSON Data
&lt;/h1&gt;

&lt;p&gt;Many organizations store event data as JSON inside object storage.&lt;/p&gt;

&lt;p&gt;Queries often read only a small subset of JSON attributes.&lt;/p&gt;

&lt;p&gt;Earlier versions sometimes overestimated memory requirements for these partial reads.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces more accurate memory estimation for JSON subcolumns.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up to &lt;strong&gt;8× lower memory usage&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Better resource utilization&lt;/li&gt;
&lt;li&gt;Improved query stability&lt;/li&gt;
&lt;li&gt;More efficient semi-structured analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enhancement is especially useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event logs&lt;/li&gt;
&lt;li&gt;Clickstream data&lt;/li&gt;
&lt;li&gt;Application telemetry&lt;/li&gt;
&lt;li&gt;Observability platforms&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  What This Means for Your Architecture
&lt;/h1&gt;

&lt;p&gt;One of the best aspects of these improvements is that they require virtually no application changes.&lt;/p&gt;

&lt;p&gt;If you're already using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;s3()&lt;/code&gt; table functions&lt;/li&gt;
&lt;li&gt;IcebergS3&lt;/li&gt;
&lt;li&gt;DeltaLakeS3&lt;/li&gt;
&lt;li&gt;Hudi&lt;/li&gt;
&lt;li&gt;S3Queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;most improvements become available simply by upgrading to ClickHouse® 26.3.&lt;/p&gt;

&lt;p&gt;Organizations benefit from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower query latency&lt;/li&gt;
&lt;li&gt;Reduced network overhead&lt;/li&gt;
&lt;li&gt;Fewer API calls&lt;/li&gt;
&lt;li&gt;Better CPU utilization&lt;/li&gt;
&lt;li&gt;Improved scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without changing existing SQL queries.&lt;/p&gt;




&lt;h1&gt;
  
  
  Getting Started
&lt;/h1&gt;

&lt;p&gt;Most improvements are enabled automatically.&lt;/p&gt;

&lt;p&gt;For the best results, consider the following recommendations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify Parquet Metadata Caching
&lt;/h3&gt;

&lt;p&gt;Ensure metadata caching remains enabled for frequently queried datasets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use_parquet_metadata_cache = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Configure Iceberg Metadata Prefetch
&lt;/h3&gt;

&lt;p&gt;For busy Iceberg tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iceberg_metadata_async_prefetch_period_ms = 60000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Enable Ordered S3Queue
&lt;/h3&gt;

&lt;p&gt;Long-running ingestion pipelines benefit from ordered mode together with the StartAfter optimization.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;To maximize performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store analytical data using Parquet whenever possible.&lt;/li&gt;
&lt;li&gt;Partition data appropriately to minimize unnecessary scans.&lt;/li&gt;
&lt;li&gt;Enable metadata caching for repeated queries.&lt;/li&gt;
&lt;li&gt;Configure asynchronous Iceberg metadata refresh on heavily queried tables.&lt;/li&gt;
&lt;li&gt;Use ordered-mode S3Queue for continuous ingestion.&lt;/li&gt;
&lt;li&gt;Monitor object storage latency alongside ClickHouse performance metrics.&lt;/li&gt;
&lt;li&gt;Keep ClickHouse updated to benefit from ongoing object storage optimizations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Object storage has become the backbone of modern analytics, offering scalable and cost-effective storage for massive datasets. However, remote reads have traditionally introduced a performance gap compared to querying locally stored data.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 significantly narrows that gap by redesigning the object storage read path, improving parallelism, introducing intelligent Parquet metadata caching, optimizing Iceberg metadata access, enhancing S3Queue ingestion, and reducing memory consumption for JSON workloads.&lt;/p&gt;

&lt;p&gt;For organizations building lakehouse architectures on Amazon S3 or compatible object storage, these enhancements translate into faster queries, lower API costs, better resource utilization, and improved scalability—all without changing schemas, rewriting SQL, or redesigning ingestion pipelines.&lt;/p&gt;

&lt;p&gt;As more organizations adopt cloud-native analytics, these S3 improvements make ClickHouse an even stronger choice for high-performance querying directly against data lakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® 26.3 Release Notes&lt;/li&gt;
&lt;li&gt;ClickHouse® 26.3 Announcement&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – S3 Table Function&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – Iceberg Table Engine&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – S3Queue&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 94/100 – Automated Insert Batching in ClickHouse® 26.3: Higher Throughput with Asynchronous Inserts</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Wed, 22 Jul 2026 16:13:58 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-94100-automated-insert-batching-in-clickhouser-263-higher-throughput-with-asynchronous-al9</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-94100-automated-insert-batching-in-clickhouser-263-higher-throughput-with-asynchronous-al9</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Modern data platforms are expected to ingest massive volumes of data in real time. Whether it's application logs, IoT sensor readings, monitoring metrics, clickstream events, or messages from streaming platforms like Kafka, many workloads generate thousands—or even millions—of small &lt;code&gt;INSERT&lt;/code&gt; operations every second.&lt;/p&gt;

&lt;p&gt;ClickHouse® is designed for high-performance analytical workloads and can ingest data at remarkable speeds. However, one common performance bottleneck remains: &lt;strong&gt;a large number of very small inserts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each individual &lt;code&gt;INSERT&lt;/code&gt; requires ClickHouse to parse the query, validate the data, compress blocks, update metadata, and create new data parts. While these operations are efficient individually, repeating them thousands of times per second creates unnecessary overhead and increases the work performed by background merge processes.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 improves this scenario by enhancing the batching behavior of &lt;strong&gt;asynchronous inserts&lt;/strong&gt;. Instead of writing every small insert immediately, ClickHouse temporarily buffers incoming asynchronous insert requests and combines multiple small inserts into larger writes before flushing them to storage.&lt;/p&gt;

&lt;p&gt;The result is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher ingestion throughput&lt;/li&gt;
&lt;li&gt;Fewer data parts&lt;/li&gt;
&lt;li&gt;Reduced merge overhead&lt;/li&gt;
&lt;li&gt;Better compression efficiency&lt;/li&gt;
&lt;li&gt;Lower CPU and disk utilization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll explore how automated insert batching works, why it improves performance, and when you should consider enabling asynchronous inserts in your ClickHouse deployments.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; "Automated Insert Batching" is not an official ClickHouse feature name. Throughout this article, the term refers to the enhanced batching behavior of asynchronous inserts introduced in ClickHouse® 26.3, where multiple small insert requests are automatically grouped into larger writes before being persisted.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Why Small INSERT Statements Hurt Performance
&lt;/h1&gt;

&lt;p&gt;Imagine an application receiving telemetry events every second.&lt;/p&gt;

&lt;p&gt;Instead of accumulating events into larger batches, it continuously executes tiny insert operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT (10 rows)
INSERT (15 rows)
INSERT (8 rows)
INSERT (12 rows)
INSERT (20 rows)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although each insert contains only a handful of rows, ClickHouse still performs the complete insert workflow for every request.&lt;/p&gt;

&lt;p&gt;Each insert requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL parsing&lt;/li&gt;
&lt;li&gt;Query validation&lt;/li&gt;
&lt;li&gt;Block creation&lt;/li&gt;
&lt;li&gt;Data compression&lt;/li&gt;
&lt;li&gt;Metadata updates&lt;/li&gt;
&lt;li&gt;New data part creation&lt;/li&gt;
&lt;li&gt;Scheduling future merge operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When applications generate thousands of tiny inserts, these fixed costs are repeated continuously, reducing overall ingestion efficiency.&lt;/p&gt;




&lt;h1&gt;
  
  
  Traditional INSERT Workflow
&lt;/h1&gt;

&lt;p&gt;Without batching, every insert is processed independently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application

│
├── INSERT
├── INSERT
├── INSERT
├── INSERT
│
▼

ClickHouse

│
├── Part 1
├── Part 2
├── Part 3
├── Part 4
│
▼

MergeTree Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every insert creates its own data part.&lt;/p&gt;

&lt;p&gt;Although MergeTree is optimized for immutable data parts, creating thousands of tiny parts introduces unnecessary overhead throughout the storage engine.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Too Many Small Parts Are a Problem
&lt;/h1&gt;

&lt;p&gt;A large number of tiny data parts negatively impacts multiple areas of ClickHouse performance.&lt;/p&gt;

&lt;p&gt;Some common consequences include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More background merge operations&lt;/li&gt;
&lt;li&gt;Increased CPU utilization&lt;/li&gt;
&lt;li&gt;Higher disk I/O&lt;/li&gt;
&lt;li&gt;Larger metadata structures&lt;/li&gt;
&lt;li&gt;Slower query planning&lt;/li&gt;
&lt;li&gt;Increased memory consumption&lt;/li&gt;
&lt;li&gt;Additional storage fragmentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Background merges become especially busy trying to combine many small parts into larger ones.&lt;/p&gt;

&lt;p&gt;This is why ClickHouse documentation consistently recommends:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Avoid sending many tiny INSERT statements whenever possible.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Automated Insert Batching
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 improves this workflow by batching asynchronous inserts automatically.&lt;/p&gt;

&lt;p&gt;Instead of writing every request immediately, ClickHouse buffers incoming asynchronous insert requests for a short period.&lt;/p&gt;

&lt;p&gt;Multiple insert requests are then combined into one larger write.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application

INSERT
INSERT
INSERT
INSERT
INSERT

        │
        ▼

 Asynchronous Buffer

        │

Combine Requests

        ▼

Large INSERT

        ▼

ClickHouse Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the application's perspective, nothing changes.&lt;/p&gt;

&lt;p&gt;The application continues sending small inserts.&lt;/p&gt;

&lt;p&gt;Internally, however, ClickHouse performs fewer, larger writes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why This Improves Throughput
&lt;/h1&gt;

&lt;p&gt;Every insert has a fixed processing cost regardless of whether it contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 rows&lt;/li&gt;
&lt;li&gt;20 rows&lt;/li&gt;
&lt;li&gt;100 rows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When ClickHouse combines many small inserts into a single larger write, those fixed costs occur only once.&lt;/p&gt;

&lt;p&gt;Instead of repeating expensive operations thousands of times, ClickHouse performs them for the combined batch.&lt;/p&gt;

&lt;p&gt;Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fewer metadata updates&lt;/li&gt;
&lt;li&gt;Fewer compression operations&lt;/li&gt;
&lt;li&gt;Fewer data parts&lt;/li&gt;
&lt;li&gt;Reduced write amplification&lt;/li&gt;
&lt;li&gt;Lower CPU overhead&lt;/li&gt;
&lt;li&gt;Less merge activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overall result is significantly higher ingestion throughput.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example
&lt;/h1&gt;

&lt;p&gt;Suppose a monitoring platform generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2,000 INSERT statements&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20 rows per INSERT&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without batching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2,000 INSERTS

↓

2,000 Data Parts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With automated batching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2,000 INSERTS

↓

40 Large Batches

↓

40 Data Parts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The total number of inserted rows remains exactly the same.&lt;/p&gt;

&lt;p&gt;However, ClickHouse creates only a small fraction of the data parts.&lt;/p&gt;

&lt;p&gt;This dramatically reduces merge operations and improves overall system efficiency.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Asynchronous Inserts Work
&lt;/h1&gt;

&lt;p&gt;Automatic batching is powered by &lt;strong&gt;asynchronous inserts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of immediately writing each insert to disk, ClickHouse temporarily stores incoming insert requests in memory.&lt;/p&gt;

&lt;p&gt;The buffered data is flushed when configurable thresholds are reached.&lt;/p&gt;

&lt;p&gt;Typical controls include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maximum buffer size&lt;/li&gt;
&lt;li&gt;Flush timeout&lt;/li&gt;
&lt;li&gt;Queue size limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These settings allow administrators to balance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Insert latency&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depending on the workload, ClickHouse can automatically optimize write efficiency without requiring application changes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Enabling Asynchronous Inserts
&lt;/h1&gt;

&lt;p&gt;Asynchronous inserts can be enabled at the session level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;async_insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;wait_for_async_insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or configured for an individual insert statement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="n"&gt;SETTINGS&lt;/span&gt;
    &lt;span class="n"&gt;async_insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;wait_for_async_insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What do these settings mean?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;async_insert = 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Enables asynchronous inserts, allowing ClickHouse to buffer incoming requests before writing them to storage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wait_for_async_insert = 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Waits until the buffered data has been successfully written before acknowledging the insert to the client.&lt;/p&gt;

&lt;p&gt;These settings can also be configured globally using server configuration files, making them suitable for production deployments with consistent ingestion patterns.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Does Automatic Batching Help?
&lt;/h1&gt;

&lt;p&gt;Automatic batching is particularly valuable for workloads that continuously generate many small inserts.&lt;/p&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;h2&gt;
  
  
  IoT Platforms
&lt;/h2&gt;

&lt;p&gt;Thousands of sensors continuously publish measurements.&lt;/p&gt;

&lt;p&gt;Instead of immediately writing every reading, ClickHouse batches them into larger writes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Application Logging
&lt;/h2&gt;

&lt;p&gt;Modern applications generate logs every few milliseconds.&lt;/p&gt;

&lt;p&gt;Batching dramatically reduces write overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitoring Systems
&lt;/h2&gt;

&lt;p&gt;Monitoring agents continuously send metrics.&lt;/p&gt;

&lt;p&gt;Automatic batching helps reduce part creation while maintaining near real-time visibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Event Streaming
&lt;/h2&gt;

&lt;p&gt;Applications consuming events from Kafka, RabbitMQ, or Pulsar often insert relatively small batches.&lt;/p&gt;

&lt;p&gt;ClickHouse combines them automatically for higher throughput.&lt;/p&gt;




&lt;h2&gt;
  
  
  Clickstream Analytics
&lt;/h2&gt;

&lt;p&gt;User interactions arrive continuously throughout the day.&lt;/p&gt;

&lt;p&gt;Batching improves scalability without requiring changes to event producers.&lt;/p&gt;




&lt;h1&gt;
  
  
  Benefits
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Larger insert batches&lt;/td&gt;
&lt;td&gt;Higher throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fewer data parts&lt;/td&gt;
&lt;td&gt;Lower merge overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better compression&lt;/td&gt;
&lt;td&gt;Reduced storage usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower CPU utilization&lt;/td&gt;
&lt;td&gt;More efficient ingestion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower disk I/O&lt;/td&gt;
&lt;td&gt;Faster writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Better scalability&lt;/td&gt;
&lt;td&gt;Handles higher event rates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Before vs After
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Without Automatic Batching&lt;/th&gt;
&lt;th&gt;With Automatic Batching&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Many tiny writes&lt;/td&gt;
&lt;td&gt;Writes combined automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large number of data parts&lt;/td&gt;
&lt;td&gt;Significantly fewer parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frequent merges&lt;/td&gt;
&lt;td&gt;Reduced merge activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Higher CPU usage&lt;/td&gt;
&lt;td&gt;Lower CPU usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower throughput&lt;/td&gt;
&lt;td&gt;Higher throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Monitoring Insert Performance
&lt;/h1&gt;

&lt;p&gt;Several ClickHouse system tables help monitor insert behavior.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System Table&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system.parts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Active data parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system.part_log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Part creation history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system.merges&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Background merge activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;system.metrics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Insert-related metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;active_parts&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a table contains an unusually large number of active parts, it may indicate that inserts are arriving in very small batches and causing excessive merge activity.&lt;/p&gt;

&lt;p&gt;Monitoring these tables regularly can help identify ingestion bottlenecks before they affect query performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;To maximize ingestion throughput:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable asynchronous inserts for high-frequency workloads.&lt;/li&gt;
&lt;li&gt;Prefer larger batches whenever your application allows.&lt;/li&gt;
&lt;li&gt;Avoid single-row inserts whenever possible.&lt;/li&gt;
&lt;li&gt;Monitor active data parts using &lt;code&gt;system.parts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Watch background merges using &lt;code&gt;system.merges&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Tune asynchronous insert thresholds based on workload characteristics.&lt;/li&gt;
&lt;li&gt;Measure throughput before and after enabling batching to quantify improvements.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  When Automatic Batching Provides Limited Benefit
&lt;/h1&gt;

&lt;p&gt;Although batching improves many workloads, it isn't beneficial in every situation.&lt;/p&gt;

&lt;p&gt;Performance gains may be limited when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your application already sends large batch inserts.&lt;/li&gt;
&lt;li&gt;Insert operations occur infrequently.&lt;/li&gt;
&lt;li&gt;Immediate data visibility is more important than throughput.&lt;/li&gt;
&lt;li&gt;Your workload already produces relatively few data parts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these cases, batching provides little additional optimization because the application has already minimized insert overhead.&lt;/p&gt;




&lt;h1&gt;
  
  
  Things to Consider
&lt;/h1&gt;

&lt;p&gt;Automatic batching introduces a small trade-off.&lt;/p&gt;

&lt;p&gt;Because ClickHouse briefly buffers insert requests before writing them to disk, data may become visible slightly later than with synchronous inserts.&lt;/p&gt;

&lt;p&gt;For most analytical workloads, this delay is negligible.&lt;/p&gt;

&lt;p&gt;The improvements in throughput, storage efficiency, CPU utilization, and merge performance typically outweigh the small increase in insert latency.&lt;/p&gt;

&lt;p&gt;The optimal configuration depends on your workload's balance between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Freshness requirements&lt;/li&gt;
&lt;li&gt;Write throughput&lt;/li&gt;
&lt;li&gt;Resource utilization&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 continues to improve one of its greatest strengths—high-speed data ingestion.&lt;/p&gt;

&lt;p&gt;By enhancing the batching behavior of asynchronous inserts, ClickHouse automatically combines multiple small insert requests into larger writes, reducing the overhead associated with part creation, compression, metadata updates, and background merges.&lt;/p&gt;

&lt;p&gt;For workloads involving logs, metrics, IoT telemetry, event streaming, clickstream analytics, or real-time monitoring, enabling asynchronous inserts can significantly improve scalability while reducing CPU usage, disk I/O, and storage fragmentation.&lt;/p&gt;

&lt;p&gt;If your applications generate thousands of small inserts every second, automated insert batching is a simple optimization that can deliver substantial performance improvements with minimal changes to your existing ingestion pipeline.&lt;/p&gt;

&lt;p&gt;As ClickHouse continues to evolve, features like this demonstrate how thoughtful engineering can improve both performance and operational efficiency, making it even better suited for modern, data-intensive applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® 26.3 Release Notes&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – Asynchronous Inserts&lt;/li&gt;
&lt;li&gt;ClickHouse® Documentation – Bulk Inserts Best Practices&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>analytics</category>
      <category>database</category>
      <category>devops</category>
    </item>
    <item>
      <title>Day 93/100 – New Unicode String Functions in ClickHouse® 26.3: Better Text Search and Normalization</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Wed, 22 Jul 2026 16:03:13 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-93100-new-unicode-string-functions-in-clickhouser-263-better-text-search-and-normalization-1joe</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-93100-new-unicode-string-functions-in-clickhouser-263-better-text-search-and-normalization-1joe</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Modern data platforms process enormous volumes of text originating from users all over the world. Names, addresses, product descriptions, customer reviews, search queries, and application logs often contain accented characters, Unicode symbols, ligatures, emojis, and language-specific casing rules. While these characters make text accurate and meaningful for users, they also introduce challenges when performing searches, comparisons, joins, deduplication, or analytics.&lt;/p&gt;

&lt;p&gt;Consider a few common examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;résumé&lt;/strong&gt; and &lt;strong&gt;resume&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Straße&lt;/strong&gt; and &lt;strong&gt;STRASSE&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;crème brûlée&lt;/strong&gt; and &lt;strong&gt;creme brulee&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Different Unicode representations of visually identical characters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although these strings may look identical—or nearly identical—to people, they are stored differently internally. Traditional string comparison functions such as &lt;code&gt;lower()&lt;/code&gt; or &lt;code&gt;upper()&lt;/code&gt; often fail to recognize these differences, leading to missed search results, duplicate records, inconsistent grouping, and unexpected join failures.&lt;/p&gt;

&lt;p&gt;To simplify Unicode-aware text processing, ClickHouse® 26.3 introduces three powerful new string functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;caseFoldUTF8()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these functions make it much easier to build multilingual applications that perform reliable searches, comparisons, and text normalization while following Unicode standards.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore each function in detail, understand when to use it, and walk through practical SQL examples.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Unicode Normalization Matters
&lt;/h1&gt;

&lt;p&gt;Working with Unicode text is more complicated than it first appears.&lt;/p&gt;

&lt;p&gt;Many characters have multiple valid Unicode representations. Two strings can look identical on screen while having completely different binary representations.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The letter &lt;strong&gt;é&lt;/strong&gt; can exist as a single Unicode character.&lt;/li&gt;
&lt;li&gt;It can also be represented as the character &lt;strong&gt;e&lt;/strong&gt; followed by a combining accent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Similarly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Straße&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STRASSE&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;represent the same German word, but traditional lowercase conversion treats them differently because the German letter &lt;strong&gt;ß&lt;/strong&gt; is not equivalent to &lt;strong&gt;ss&lt;/strong&gt; under simple lowercase rules.&lt;/p&gt;

&lt;p&gt;Accent marks introduce similar problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;résumé&lt;/li&gt;
&lt;li&gt;resume&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both words may represent the same search intent, but standard string comparisons treat them as different values.&lt;/p&gt;

&lt;p&gt;Without Unicode normalization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Searches miss valid matches.&lt;/li&gt;
&lt;li&gt;Duplicate detection becomes unreliable.&lt;/li&gt;
&lt;li&gt;GROUP BY operations produce unexpected results.&lt;/li&gt;
&lt;li&gt;JOIN conditions fail.&lt;/li&gt;
&lt;li&gt;User-entered data becomes inconsistent across applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unicode-aware normalization solves these problems by transforming text into standardized forms before comparison.&lt;/p&gt;




&lt;h1&gt;
  
  
  Unicode Support Before ClickHouse® 26.3
&lt;/h1&gt;

&lt;p&gt;ClickHouse® already provided several Unicode normalization functions before version 26.3.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFC()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NFC normalization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFD()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NFD normalization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFKC()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compatibility composition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFKD()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compatibility decomposition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upperUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UTF-8 uppercase conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lowerUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UTF-8 lowercase conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These functions remain useful for Unicode normalization, but ClickHouse® 26.3 expands the toolkit with three new functions specifically designed for modern multilingual text processing.&lt;/p&gt;




&lt;h1&gt;
  
  
  New Functions Introduced in ClickHouse® 26.3
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. caseFoldUTF8()
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does it do?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;caseFoldUTF8()&lt;/code&gt; performs &lt;strong&gt;Unicode case folding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;lowerUTF8()&lt;/code&gt;, case folding is specifically designed for &lt;strong&gt;case-insensitive comparison&lt;/strong&gt; according to the Unicode standard.&lt;/p&gt;

&lt;p&gt;Instead of simply converting uppercase letters into lowercase letters, case folding also handles special Unicode characters that ordinary lowercase conversion cannot.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Straße'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'STRASSE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;value1&lt;/th&gt;
&lt;th&gt;value2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;strasse&lt;/td&gt;
&lt;td&gt;strasse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now compare them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Straße'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'STRASSE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;is_equal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;is_equal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice the difference.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;lowerUTF8()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Straße
↓

straße
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STRASSE
↓

strasse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are still different.&lt;/p&gt;

&lt;p&gt;Unicode case folding correctly transforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ß → ss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;making both strings identical.&lt;/p&gt;




&lt;h3&gt;
  
  
  Practical Example – Case-Insensitive Name Search
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'müller'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query successfully matches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Müller&lt;/li&gt;
&lt;li&gt;MÜLLER&lt;/li&gt;
&lt;li&gt;MüLLeR&lt;/li&gt;
&lt;li&gt;müller&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;regardless of how users entered the name.&lt;/p&gt;




&lt;h3&gt;
  
  
  More Examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello World'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;english&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'HÉLLO WÖRLD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;accented&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;caseFoldUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ω'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;greek&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;english&lt;/th&gt;
&lt;th&gt;accented&lt;/th&gt;
&lt;th&gt;greek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;hello world&lt;/td&gt;
&lt;td&gt;héllo wörld&lt;/td&gt;
&lt;td&gt;ω&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  2. removeDiacriticsUTF8()
&lt;/h1&gt;

&lt;h3&gt;
  
  
  What does it do?
&lt;/h3&gt;

&lt;p&gt;Many European languages use accent marks known as &lt;strong&gt;diacritics&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;é&lt;/li&gt;
&lt;li&gt;ü&lt;/li&gt;
&lt;li&gt;ñ&lt;/li&gt;
&lt;li&gt;å&lt;/li&gt;
&lt;li&gt;ç&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although visually important, many search systems ignore these marks so users can find results without typing accents.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt; removes these accent marks while preserving the base letters.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'crème brûlée'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;creme brulee&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  More Examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'résumé'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;example1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'naïve'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;example2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'São Paulo'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;example3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Zürich'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;example4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;example1&lt;/th&gt;
&lt;th&gt;example2&lt;/th&gt;
&lt;th&gt;example3&lt;/th&gt;
&lt;th&gt;example4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;resume&lt;/td&gt;
&lt;td&gt;naive&lt;/td&gt;
&lt;td&gt;Sao Paulo&lt;/td&gt;
&lt;td&gt;Zurich&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Practical Example – Accent-Insensitive Search
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;population&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cities&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lowerUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;removeDiacriticsUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lowerUTF8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sao paulo'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;São Paulo&lt;/li&gt;
&lt;li&gt;Sao Paulo&lt;/li&gt;
&lt;li&gt;SAO PAULO&lt;/li&gt;
&lt;li&gt;são paulo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without requiring multiple conditions.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. normalizeUTF8NFKCCasefold()
&lt;/h1&gt;

&lt;h3&gt;
  
  
  What does it do?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt; combines two powerful Unicode operations into one function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unicode Compatibility Normalization (NFKC)&lt;/li&gt;
&lt;li&gt;Unicode Case Folding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is the most comprehensive Unicode normalization function introduced in ClickHouse® 26.3.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;, this function &lt;strong&gt;does not remove accents&lt;/strong&gt;. Instead, it standardizes compatibility characters and applies Unicode-aware case folding.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is NFKC Normalization?
&lt;/h2&gt;

&lt;p&gt;Unicode includes many compatibility characters that look different but represent the same logical character.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Character&lt;/th&gt;
&lt;th&gt;Normalized Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ﬁ&lt;/td&gt;
&lt;td&gt;fi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ａ&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;²&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;NFKC converts these compatibility characters into their standard equivalents.&lt;/p&gt;




&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ﬁle'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;normalized&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ligature &lt;strong&gt;ﬁ&lt;/strong&gt; becomes the letters &lt;strong&gt;fi&lt;/strong&gt;, and case folding is also applied.&lt;/p&gt;




&lt;h3&gt;
  
  
  More Examples
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ﬁle'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ligature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Ａ Ｂ Ｃ'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;fullwidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'²²'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;superscript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Straße'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;german&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ligature&lt;/th&gt;
&lt;th&gt;fullwidth&lt;/th&gt;
&lt;th&gt;superscript&lt;/th&gt;
&lt;th&gt;german&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;a b c&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;strasse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Practical Example – Normalizing User Search Queries
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;normalized_query&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_logs&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="n"&gt;normalizeUTF8NFKCCasefold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'café'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query ensures that searches become consistent even when users type different Unicode compatibility characters or different letter cases.&lt;/p&gt;

&lt;p&gt;If you also want accent-insensitive matching, combine it with &lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Which Function Should You Use?
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Primary Purpose&lt;/th&gt;
&lt;th&gt;Typical Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;caseFoldUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unicode-aware case comparison&lt;/td&gt;
&lt;td&gt;Case-insensitive matching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Removes accents&lt;/td&gt;
&lt;td&gt;Accent-insensitive search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compatibility normalization + case folding&lt;/td&gt;
&lt;td&gt;Deduplication and equality checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removeDiacriticsUTF8(caseFoldUTF8())&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Case + accent normalization&lt;/td&gt;
&lt;td&gt;Flexible multilingual search&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Also New in ClickHouse® 26.3: naturalSortKey()
&lt;/h1&gt;

&lt;p&gt;Although not a Unicode normalization function, ClickHouse® 26.3 also introduces &lt;code&gt;naturalSortKey()&lt;/code&gt;, which provides human-friendly sorting for strings containing numbers.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;naturalSortKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;file1.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file2.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file10.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file20.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file100.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without &lt;code&gt;naturalSortKey()&lt;/code&gt;, standard lexicographical sorting would incorrectly place &lt;code&gt;file10.txt&lt;/code&gt; before &lt;code&gt;file2.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Natural sorting recognizes numeric values inside strings and orders them the way people naturally expect.&lt;/p&gt;




&lt;h1&gt;
  
  
  Quick Reference
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Example Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;caseFoldUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Straße&lt;/td&gt;
&lt;td&gt;strasse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;caseFoldUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MÜLLER&lt;/td&gt;
&lt;td&gt;müller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;crème brûlée&lt;/td&gt;
&lt;td&gt;creme brulee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;São Paulo&lt;/td&gt;
&lt;td&gt;Sao Paulo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ﬁle&lt;/td&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Straße&lt;/td&gt;
&lt;td&gt;strasse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;naturalSortKey()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;file10.txt&lt;/td&gt;
&lt;td&gt;Sorts after file2.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;caseFoldUTF8()&lt;/code&gt; instead of &lt;code&gt;lowerUTF8()&lt;/code&gt; whenever you need reliable Unicode-aware case-insensitive comparisons.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt; for search functionality where accents should not affect matching, such as names, cities, or product catalogs.&lt;/li&gt;
&lt;li&gt;Combine &lt;code&gt;removeDiacriticsUTF8(caseFoldUTF8(column))&lt;/code&gt; to support both case-insensitive and accent-insensitive searches.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt; when importing data from external systems that may contain compatibility characters, ligatures, or full-width Unicode characters.&lt;/li&gt;
&lt;li&gt;Store normalized values as &lt;strong&gt;MATERIALIZED&lt;/strong&gt; columns whenever possible so normalization occurs only once during inserts rather than on every query.&lt;/li&gt;
&lt;li&gt;Choose the least aggressive normalization function that satisfies your use case to avoid altering text more than necessary.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;As applications become increasingly global, handling multilingual text correctly is no longer optional. Reliable search, accurate deduplication, and consistent data processing all depend on understanding Unicode beyond simple uppercase and lowercase conversions.&lt;/p&gt;

&lt;p&gt;The new Unicode string functions introduced in ClickHouse® 26.3—&lt;code&gt;caseFoldUTF8()&lt;/code&gt;, &lt;code&gt;removeDiacriticsUTF8()&lt;/code&gt;, and &lt;code&gt;normalizeUTF8NFKCCasefold()&lt;/code&gt;—provide a modern toolkit for working with international text. Whether you're processing customer names, addresses, search queries, product catalogs, or user-generated content, these functions help ensure your comparisons behave consistently across languages and writing systems.&lt;/p&gt;

&lt;p&gt;Combined with existing Unicode normalization functions and new utilities like &lt;code&gt;naturalSortKey()&lt;/code&gt;, ClickHouse® continues to strengthen its support for real-world text processing while maintaining the high performance expected from a modern analytical database.&lt;/p&gt;

&lt;p&gt;As multilingual datasets continue to grow, incorporating these Unicode-aware functions into your data pipelines and queries will help improve search accuracy, reduce duplicate records, and deliver a better experience for users around the world.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 92 of #100DaysOfClickHouse - Read-Only Tables in ClickHouse® 26.3</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Tue, 21 Jul 2026 16:27:23 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-92-of-100daysofclickhouse-read-only-tables-in-clickhouser-263-2433</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-92-of-100daysofclickhouse-read-only-tables-in-clickhouser-263-2433</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Day 92 of #100DaysOfClickHouse&lt;/strong&gt;
&lt;/h2&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Read-Only Tables in ClickHouse® 26.3: How &lt;code&gt;table_readonly&lt;/code&gt; Works and When to Use It&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 introduces a useful new MergeTree table setting called &lt;code&gt;table_readonly&lt;/code&gt;, designed to help administrators protect finalized datasets from accidental modification while keeping them fully available for analytical queries. Although it is a relatively small feature compared to some of the larger additions in recent releases, it addresses a common operational challenge faced by many organizations that manage historical, archived, or compliance-sensitive data.&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;table_readonly&lt;/code&gt; setting is enabled, ClickHouse® rejects &lt;code&gt;INSERT&lt;/code&gt; operations and other write requests that would modify the contents of the table, while continuing to execute &lt;code&gt;SELECT&lt;/code&gt; queries normally. Existing applications, dashboards, reports, and analytical workloads can continue accessing the data without interruption, but any attempt to change the table is immediately blocked. This allows organizations to preserve the integrity of finalized datasets without relying solely on application logic or administrative procedures.&lt;/p&gt;

&lt;p&gt;The setting can be configured either during table creation or applied later using an &lt;code&gt;ALTER TABLE&lt;/code&gt; statement, making it easy to convert an existing MergeTree table into a read-only dataset without changing its schema or migrating any data. Because the configuration takes effect immediately, administrators can quickly protect a table once it has been validated, archived, or published for long-term reporting.&lt;/p&gt;

&lt;p&gt;One of the primary motivations behind introducing &lt;code&gt;table_readonly&lt;/code&gt; is the growing need to manage immutable datasets. In many analytical environments, data eventually reaches a stage where it should never change again. Historical event logs, archived customer activity, yearly sales summaries, completed ETL outputs, regulatory datasets, audit records, and approved financial reports are all examples of information that should remain unchanged after validation. Before ClickHouse® 26.3, preventing accidental writes typically required carefully managing user permissions, adding safeguards within applications, or relying on operational discipline. The new setting moves this protection directly into the database engine, ensuring that the table itself refuses any modification attempts regardless of where they originate.&lt;/p&gt;

&lt;p&gt;Another significant advantage is its impact on MergeTree's background processing. Under normal circumstances, MergeTree tables continuously execute background tasks such as merging data parts and performing maintenance operations to optimize storage. Since a table marked as read-only can no longer receive new data, these background merge threads become unnecessary. ClickHouse® therefore stops running these maintenance operations for read-only tables, reducing resource consumption and making the feature particularly beneficial for archived datasets that are expected to remain unchanged indefinitely.&lt;/p&gt;

&lt;p&gt;Although the feature provides strong protection against accidental writes, it should not be confused with a security mechanism. The &lt;code&gt;table_readonly&lt;/code&gt; setting does not authenticate users, enforce Role-Based Access Control (RBAC), manage user permissions, encrypt stored data, or determine who can query the table. Those responsibilities remain part of ClickHouse®'s existing security model. Instead, &lt;code&gt;table_readonly&lt;/code&gt; serves as an additional operational safeguard that complements authentication and authorization by ensuring that even authorized users or applications cannot accidentally modify a table that has been intentionally locked.&lt;/p&gt;

&lt;p&gt;The feature has numerous practical applications in production environments. Organizations managing data warehouses can mark yearly archive tables as read-only once reporting periods have closed. Financial institutions can protect approved monthly and quarterly reports from accidental updates after publication. Compliance and audit datasets can be preserved once collection periods end, helping maintain regulatory consistency. Static reference datasets, such as archived product catalogs, country code mappings, or historical configuration snapshots, can also benefit from being marked as immutable. In addition, &lt;code&gt;table_readonly&lt;/code&gt; provides an extra layer of defense against common operational mistakes, such as ETL jobs targeting the wrong table, deployment scripts inserting data into archived datasets, or manual SQL operations modifying production history.&lt;/p&gt;

&lt;p&gt;The article demonstrates these capabilities through a complete hands-on example. A MergeTree table is created and populated with sample sales data before being converted into read-only mode using &lt;code&gt;ALTER TABLE ... MODIFY SETTING table_readonly = 1&lt;/code&gt;. Once enabled, analytical queries continue to execute exactly as before, allowing dashboards and reports to function normally. However, any attempt to insert additional rows immediately fails because the database now rejects write operations against the protected table. If business requirements change and updates become necessary later, administrators can simply disable the setting by changing it back to &lt;code&gt;0&lt;/code&gt;, instantly restoring normal write behavior without affecting the stored data.&lt;/p&gt;

&lt;p&gt;Like any feature, &lt;code&gt;table_readonly&lt;/code&gt; has important limitations. It is available only for MergeTree-family tables and should not be considered a replacement for comprehensive access-control mechanisms. Organizations should continue using RBAC, user permissions, network security, and encryption where appropriate, while treating &lt;code&gt;table_readonly&lt;/code&gt; as an additional layer of operational protection. Furthermore, it is unsuitable for tables that receive continuous data ingestion, since enabling the setting would intentionally block normal write operations required by those workloads.&lt;/p&gt;

&lt;p&gt;Overall, &lt;code&gt;table_readonly&lt;/code&gt; is a practical quality-of-life improvement introduced in ClickHouse® 26.3 that simplifies the management of immutable datasets. By allowing MergeTree tables to reject write operations while remaining fully queryable, and by eliminating unnecessary background maintenance for archived tables, it provides administrators with a lightweight yet highly effective mechanism for protecting historical data. Whether used for financial reporting, compliance, long-term analytics, audit records, or archived operational data, &lt;code&gt;table_readonly&lt;/code&gt; helps ensure data integrity while reducing the risk of accidental modifications in production environments.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 91 of #100DaysOfClickHouse - Partition Pruning in ClickHouse®</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Tue, 21 Jul 2026 16:04:05 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-91-of-100daysofclickhouse-partition-pruning-in-clickhouser-4a5a</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-91-of-100daysofclickhouse-partition-pruning-in-clickhouser-4a5a</guid>
      <description>&lt;h1&gt;
  
  
  Partition Pruning in ClickHouse®: How It Really Works
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Partition pruning is one of the reasons a well-designed ClickHouse® table can answer queries like &lt;em&gt;"Show me last Tuesday's events"&lt;/em&gt; without scanning years of unrelated data.&lt;/p&gt;

&lt;p&gt;At first glance, the concept seems simple: &lt;strong&gt;skip the data you don't need&lt;/strong&gt;. However, partition pruning is also one of the most misunderstood optimization techniques in ClickHouse®. It's frequently confused with primary key indexing, can be silently disabled by an unsuitable &lt;code&gt;WHERE&lt;/code&gt; clause, and is often implemented on the wrong column altogether.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what partition pruning actually does, how ClickHouse® decides whether it can prune partitions, how to verify that it's working, and the situations where it quietly stops helping.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Partition?
&lt;/h2&gt;

&lt;p&gt;A partition is a logical grouping of data parts on disk.&lt;/p&gt;

&lt;p&gt;When creating a MergeTree table, you define a partitioning expression using &lt;code&gt;PARTITION BY&lt;/code&gt;. Every inserted row is assigned to a partition based on that expression, and every data part belongs to exactly one partition.&lt;/p&gt;

&lt;p&gt;Because each partition is stored separately on disk, ClickHouse® can completely skip partitions that cannot contain matching data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Hour&lt;/span&gt; &lt;span class="n"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClientID&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MergeTree&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;toYYYYMM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VisitDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every month's data is stored in its own partition.&lt;/li&gt;
&lt;li&gt;June 2026 data is stored separately from July 2026.&lt;/li&gt;
&lt;li&gt;August 2026 data is stored separately again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now consider this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since only June data can satisfy the filter, ClickHouse® opens only the June partition and completely ignores every other month's directory.&lt;/p&gt;

&lt;p&gt;This is partition pruning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Partition Pruning vs Primary Key Pruning
&lt;/h2&gt;

&lt;p&gt;Many users assume partition pruning is ClickHouse®'s primary indexing mechanism.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;primary key&lt;/strong&gt; (&lt;code&gt;ORDER BY&lt;/code&gt;) remains the most important optimization for query performance because it determines how rows are ordered inside each data part.&lt;/p&gt;

&lt;p&gt;Partitioning serves different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eliminating entire partitions before reading data&lt;/li&gt;
&lt;li&gt;Making retention operations extremely fast&lt;/li&gt;
&lt;li&gt;Simplifying storage tiering&lt;/li&gt;
&lt;li&gt;Reducing unnecessary disk access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partition pruning decides which folders to open.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Primary key pruning decides which files inside those folders to read.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both optimizations work together, but they solve different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seeing Partition Pruning in Action
&lt;/h2&gt;

&lt;p&gt;The easiest way to verify partition pruning is with &lt;code&gt;EXPLAIN&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;visits&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution plan includes partition selection details, showing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total partitions&lt;/li&gt;
&lt;li&gt;Selected partitions&lt;/li&gt;
&lt;li&gt;Which pruning rules were applied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If only one partition is selected while dozens exist, pruning is working correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inspecting Partitions
&lt;/h2&gt;

&lt;p&gt;You can also inspect your table's partitions directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;row_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'visits'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;partition&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing partitions&lt;/li&gt;
&lt;li&gt;Number of active data parts&lt;/li&gt;
&lt;li&gt;Rows stored inside each partition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose &lt;code&gt;EXPLAIN&lt;/code&gt; reports that only one partition is scanned, but that partition contains hundreds of small parts and millions of rows.&lt;/p&gt;

&lt;p&gt;In that case, partition pruning is working correctly.&lt;/p&gt;

&lt;p&gt;The issue is likely excessive fragmentation or an overly coarse partition key—not pruning itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Partition Pruning Works Automatically
&lt;/h2&gt;

&lt;p&gt;Partition pruning succeeds when ClickHouse® can determine which partitions satisfy the filter.&lt;/p&gt;

&lt;p&gt;One important reason this works is that ClickHouse® understands certain &lt;strong&gt;monotonic functions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A monotonic function always preserves ordering.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toDate()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toYYYYMM()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toYear()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider this table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;toYYYYMM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VisitDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though the query filters on &lt;code&gt;VisitDate&lt;/code&gt; rather than &lt;code&gt;toYYYYMM(VisitDate)&lt;/code&gt;, ClickHouse® can infer which monthly partitions are relevant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-06-01'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VisitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2026-07-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The optimizer understands that only the June partition can contain matching rows.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Partition Pruning Fails
&lt;/h2&gt;

&lt;p&gt;Partition pruning quietly stops working when ClickHouse® cannot infer the relationship between the filter and the partition expression.&lt;/p&gt;

&lt;p&gt;Consider this partition key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;PARTITION&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;cityHash64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although the filter clearly identifies one user, ClickHouse® cannot determine which hash bucket contains that user without evaluating the hash.&lt;/p&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every partition must be considered.&lt;/li&gt;
&lt;li&gt;No pruning occurs.&lt;/li&gt;
&lt;li&gt;The query becomes more expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pruning would only work if the query filtered using the exact same expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cityHash64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hash functions, modulo operations, and many other derived expressions are &lt;strong&gt;non-monotonic&lt;/strong&gt;, preventing ClickHouse® from working backwards to eliminate partitions.&lt;/p&gt;

&lt;p&gt;This behavior is expected and documented—it is not a bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing a Partition Key That Actually Helps
&lt;/h2&gt;

&lt;p&gt;A good partition key should reflect how users actually query the data.&lt;/p&gt;

&lt;p&gt;Some practical guidelines include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Partition by your most common filter
&lt;/h3&gt;

&lt;p&gt;If nearly every query filters by date, partition by date.&lt;/p&gt;

&lt;p&gt;Monthly partitions are usually the best balance between pruning effectiveness and operational overhead.&lt;/p&gt;

&lt;p&gt;Daily partitions are generally appropriate only for extremely high-volume workloads such as observability or logging systems.&lt;/p&gt;




&lt;h3&gt;
  
  
  Avoid high-cardinality partition keys
&lt;/h3&gt;

&lt;p&gt;Never partition by columns such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;session_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;order_id&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Doing so creates enormous numbers of tiny partitions, increasing merge overhead and reducing overall performance.&lt;/p&gt;

&lt;p&gt;These columns usually belong at the beginning of the &lt;code&gt;ORDER BY&lt;/code&gt; clause instead.&lt;/p&gt;




&lt;h3&gt;
  
  
  Keep the partition count reasonable
&lt;/h3&gt;

&lt;p&gt;Thousands of partitions are usually a warning sign.&lt;/p&gt;

&lt;p&gt;Each partition introduces additional metadata and background merge work.&lt;/p&gt;

&lt;p&gt;Fewer, larger partitions generally perform better than many tiny ones.&lt;/p&gt;




&lt;h3&gt;
  
  
  Align partitioning with TTL policies
&lt;/h3&gt;

&lt;p&gt;Partitioning also affects data lifecycle management.&lt;/p&gt;

&lt;p&gt;If your TTL removes old data by month, partitioning by month allows ClickHouse® to simply drop entire partitions.&lt;/p&gt;

&lt;p&gt;Without matching partition boundaries, TTL often falls back to slower row-level mutations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Partition pruning is one of the simplest yet most effective optimizations in ClickHouse®.&lt;/p&gt;

&lt;p&gt;Its job is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip entire partitions that cannot satisfy the query.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;However, getting the full benefit requires thoughtful schema design.&lt;/p&gt;

&lt;p&gt;Choose a partition key that matches your most common filtering patterns, verify pruning with &lt;code&gt;EXPLAIN indexes = 1&lt;/code&gt;, and inspect &lt;code&gt;system.parts&lt;/code&gt; whenever performance doesn't match expectations.&lt;/p&gt;

&lt;p&gt;When partitioning is designed around real query patterns instead of assumptions, ClickHouse® can eliminate massive amounts of unnecessary I/O before reading a single data granule.&lt;/p&gt;




&lt;p&gt;For more ClickHouse® optimization guides and operational best practices, explore the CHOps feature page:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.ch-ops.io/features" rel="noopener noreferrer"&gt;https://www.ch-ops.io/features&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 90 - Data Loss Warning: Why You Cannot Downgrade from ClickHouse® 26.3</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Mon, 20 Jul 2026 18:14:29 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-90-data-loss-warning-why-you-cannot-downgrade-from-clickhouser-263-2hkk</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-90-data-loss-warning-why-you-cannot-downgrade-from-clickhouser-263-2hkk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Upgrading ClickHouse® is typically a straightforward process that brings new features, performance improvements, bug fixes, and better SQL compatibility. However, every major release can also introduce changes that affect operational workflows. One of the most important changes introduced in &lt;strong&gt;ClickHouse® 26.3&lt;/strong&gt; is related to its on-disk storage format.&lt;/p&gt;

&lt;p&gt;Starting with ClickHouse® 26.3, once the server begins creating data using the new storage format introduced in this release, &lt;strong&gt;downgrading to an earlier ClickHouse® version is no longer supported&lt;/strong&gt;. Older versions cannot understand the new data layout, and attempting to downgrade after new-format data parts have been written may result in startup failures, unreadable tables, or even data loss.&lt;/p&gt;

&lt;p&gt;If your organization depends on rollback procedures as part of its upgrade strategy, this is a critical change to understand before deploying ClickHouse® 26.3 in production.&lt;/p&gt;

&lt;p&gt;In this article, we'll explain why downgrades are no longer supported, how the new storage format works, the risks involved, and the best practices for safely upgrading production clusters.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Can't You Downgrade?
&lt;/h1&gt;

&lt;p&gt;ClickHouse® stores data on disk using highly optimized internal storage formats. These formats evolve over time to support new capabilities, improve performance, reduce storage consumption, and enable new features.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces updates to some of these internal storage structures.&lt;/p&gt;

&lt;p&gt;The upgrade process looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse® 26.2
        │
        ▼
Upgrade to 26.3
        │
        ▼
New Data Parts Created
        │
        ▼
❌ Older Versions Cannot Read Them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike SQL syntax or configuration files, the physical storage format is tightly coupled to the ClickHouse® engine itself. Once data has been written using a newer storage format, previous ClickHouse® binaries no longer understand those structures.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens During an Upgrade?
&lt;/h1&gt;

&lt;p&gt;Immediately after upgrading, your existing data remains in its original format.&lt;/p&gt;

&lt;p&gt;However, ClickHouse® continuously performs background maintenance tasks.&lt;/p&gt;

&lt;p&gt;These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background merges&lt;/li&gt;
&lt;li&gt;INSERT operations&lt;/li&gt;
&lt;li&gt;Mutations&lt;/li&gt;
&lt;li&gt;OPTIMIZE commands&lt;/li&gt;
&lt;li&gt;TTL merges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As these operations execute, new data parts begin using the updated storage format.&lt;/p&gt;

&lt;p&gt;The process looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upgrade
   │
   ▼
Old Data Parts
   │
Background Merge
   │
   ▼
New Format Data Parts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, only a small portion of the data uses the new format. Over time, more and more data parts are rewritten until a significant portion of the database depends on the new storage structures.&lt;/p&gt;

&lt;p&gt;At that point, reverting to an older ClickHouse® version becomes unsafe.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Introduce a New Storage Format?
&lt;/h1&gt;

&lt;p&gt;Database storage engines continuously evolve.&lt;/p&gt;

&lt;p&gt;New storage formats often provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster query execution&lt;/li&gt;
&lt;li&gt;Better compression ratios&lt;/li&gt;
&lt;li&gt;Improved metadata handling&lt;/li&gt;
&lt;li&gt;Reduced storage overhead&lt;/li&gt;
&lt;li&gt;Support for new database features&lt;/li&gt;
&lt;li&gt;More efficient background processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These improvements are one of the reasons ClickHouse® continues to deliver excellent analytical performance.&lt;/p&gt;

&lt;p&gt;The downside is that older versions cannot always understand the new on-disk representation.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Can Happen If You Downgrade?
&lt;/h1&gt;

&lt;p&gt;Attempting to downgrade after new-format data parts have been created can cause several problems.&lt;/p&gt;

&lt;p&gt;Possible outcomes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® failing to start&lt;/li&gt;
&lt;li&gt;Tables becoming inaccessible&lt;/li&gt;
&lt;li&gt;Missing data parts&lt;/li&gt;
&lt;li&gt;Metadata inconsistencies&lt;/li&gt;
&lt;li&gt;Data corruption&lt;/li&gt;
&lt;li&gt;Permanent data loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the older server starts successfully, it may be unable to read tables containing newer storage structures.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Upgrade Timeline
&lt;/h1&gt;

&lt;p&gt;Let's walk through a typical scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 1
&lt;/h2&gt;

&lt;p&gt;The server runs ClickHouse® 26.2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;26.2

├── Part A
├── Part B
└── Part C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is stored using the older format.&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 2
&lt;/h2&gt;

&lt;p&gt;The administrator upgrades to ClickHouse® 26.3.&lt;/p&gt;

&lt;p&gt;At this point, existing data is still readable because it has not yet been rewritten.&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 3
&lt;/h2&gt;

&lt;p&gt;Background merges execute automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;26.3

├── Part D (New Format)
├── Part E (New Format)
└── Part F (New Format)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new storage format is now actively being used.&lt;/p&gt;




&lt;h2&gt;
  
  
  Day 4
&lt;/h2&gt;

&lt;p&gt;The administrator decides to reinstall ClickHouse® 26.2.&lt;/p&gt;

&lt;p&gt;Unfortunately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part D cannot be read.&lt;/li&gt;
&lt;li&gt;Part E cannot be read.&lt;/li&gt;
&lt;li&gt;Part F cannot be read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Downgrade Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What Does This Mean for Production?
&lt;/h1&gt;

&lt;p&gt;Organizations should treat upgrades to ClickHouse® 26.3 as &lt;strong&gt;one-way upgrades&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of depending on binary downgrades, build your deployment process around careful validation and reliable backups.&lt;/p&gt;

&lt;p&gt;A recommended production workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Backup
   │
   ▼
Upgrade Staging
   │
   ▼
Validate Applications
   │
   ▼
Upgrade Production
   │
   ▼
Monitor Cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach minimizes operational risk while providing a safe recovery path if problems occur.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Practices Before Upgrading
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Create a Full Backup
&lt;/h2&gt;

&lt;p&gt;Always create a verified backup before upgrading.&lt;/p&gt;

&lt;p&gt;Possible backup methods include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® &lt;code&gt;BACKUP&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;Filesystem snapshots&lt;/li&gt;
&lt;li&gt;Object storage backups&lt;/li&gt;
&lt;li&gt;Cloud provider snapshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A reliable backup is the safest rollback strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Test in a Staging Environment
&lt;/h2&gt;

&lt;p&gt;Before touching production, upgrade a staging cluster.&lt;/p&gt;

&lt;p&gt;Validate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL queries&lt;/li&gt;
&lt;li&gt;Dashboards&lt;/li&gt;
&lt;li&gt;Materialized Views&lt;/li&gt;
&lt;li&gt;Replication&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Applications&lt;/li&gt;
&lt;li&gt;Monitoring tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing beforehand significantly reduces upgrade risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Read the Release Notes
&lt;/h2&gt;

&lt;p&gt;Every major ClickHouse® release includes important information about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breaking changes&lt;/li&gt;
&lt;li&gt;New features&lt;/li&gt;
&lt;li&gt;Configuration updates&lt;/li&gt;
&lt;li&gt;Compatibility notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never skip the release notes.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Schedule a Maintenance Window
&lt;/h2&gt;

&lt;p&gt;Although rolling upgrades reduce downtime, production upgrades should still be scheduled during planned maintenance windows.&lt;/p&gt;

&lt;p&gt;This allows enough time for validation and troubleshooting if necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Monitor the Cluster After Upgrading
&lt;/h2&gt;

&lt;p&gt;Once the upgrade is complete, monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Background merges&lt;/li&gt;
&lt;li&gt;Replication queues&lt;/li&gt;
&lt;li&gt;Query latency&lt;/li&gt;
&lt;li&gt;Disk usage&lt;/li&gt;
&lt;li&gt;CPU utilization&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Server logs&lt;/li&gt;
&lt;li&gt;Application error rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Early monitoring helps detect issues before users notice them.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is the Recommended Rollback Strategy?
&lt;/h1&gt;

&lt;p&gt;Since binary downgrades are no longer reliable after new-format parts are created, the recommended recovery process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem Detected
        │
        ▼
Stop ClickHouse®
        │
        ▼
Restore Verified Backup
        │
        ▼
Install Previous Version
        │
        ▼
Start Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather than attempting to reuse upgraded storage files, restore the database from a backup created &lt;strong&gt;before&lt;/strong&gt; the upgrade.&lt;/p&gt;

&lt;p&gt;This is the only reliable rollback strategy.&lt;/p&gt;




&lt;h1&gt;
  
  
  Upgrade Checklist
&lt;/h1&gt;

&lt;p&gt;Before upgrading to ClickHouse® 26.3, verify the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Create verified backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Test upgrades in staging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Review release notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Verify application compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Schedule a maintenance window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Monitor after deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;Understand that downgrades are not supported once new-format data parts exist&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Frequently Asked Questions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Can I downgrade immediately after upgrading?
&lt;/h2&gt;

&lt;p&gt;Possibly—but &lt;strong&gt;only if ClickHouse® has not yet created any new-format data parts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once background merges, INSERT operations, mutations, or other maintenance tasks generate new-format parts, downgrading is no longer supported.&lt;/p&gt;




&lt;h2&gt;
  
  
  Does this affect replicated clusters?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;If one replica begins generating or exchanging new-format parts, replicas running older ClickHouse® versions will not be able to read them.&lt;/p&gt;

&lt;p&gt;Upgrade planning becomes even more important in replicated environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Is this unique to ClickHouse®?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Many database systems evolve their storage formats over time.&lt;/p&gt;

&lt;p&gt;As new optimizations and capabilities are introduced, maintaining backward compatibility with much older storage engines eventually becomes impossible.&lt;/p&gt;




&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create backups before upgrading&lt;/td&gt;
&lt;td&gt;Provides a safe recovery point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test upgrades in staging&lt;/td&gt;
&lt;td&gt;Reduces production risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review release notes&lt;/td&gt;
&lt;td&gt;Identifies breaking changes early&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Don't rely on binary downgrades&lt;/td&gt;
&lt;td&gt;Older versions cannot read the new storage format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitor after upgrading&lt;/td&gt;
&lt;td&gt;Detects issues before they become outages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Common Upgrade Mistakes
&lt;/h1&gt;

&lt;p&gt;Some of the most common mistakes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Skipping backups because "the upgrade should be safe."&lt;/li&gt;
&lt;li&gt;Upgrading production before testing staging.&lt;/li&gt;
&lt;li&gt;Assuming rollback simply means reinstalling an older package.&lt;/li&gt;
&lt;li&gt;Ignoring release notes.&lt;/li&gt;
&lt;li&gt;Not monitoring background merges after upgrading.&lt;/li&gt;
&lt;li&gt;Forgetting that storage formats evolve alongside database engines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoiding these mistakes greatly improves upgrade reliability.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;ClickHouse® 26.3 delivers valuable improvements in performance, SQL compatibility, and overall database capabilities. Alongside those improvements comes an important operational consideration: once ClickHouse® begins writing data using the new storage format, &lt;strong&gt;downgrading to an earlier version is no longer supported&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than viewing this as a limitation, it's an opportunity to adopt stronger upgrade practices. Reliable backups, staged testing, careful production rollouts, and post-upgrade monitoring provide a much safer recovery strategy than relying on binary downgrades.&lt;/p&gt;

&lt;p&gt;If you're planning to upgrade to ClickHouse® 26.3, treat it as a one-way transition. Validate thoroughly before deployment, maintain verified backups, and monitor your environment closely after the upgrade. With the right preparation, you can take advantage of the latest features while minimizing operational risk and protecting your data.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 89 - Leveraging WebAssembly (WASM) UDFs in ClickHouse® 26.3</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Mon, 20 Jul 2026 18:04:37 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-89-leveraging-webassembly-wasm-udfs-in-clickhouser-263-gpk</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-89-leveraging-webassembly-wasm-udfs-in-clickhouser-263-gpk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;ClickHouse® is well known for its extensive collection of built-in SQL functions. From string manipulation and mathematical operations to JSON processing, geospatial analytics, machine learning helpers, and date-time functions, the database provides everything needed for most analytical workloads.&lt;/p&gt;

&lt;p&gt;However, every organization eventually encounters business logic that cannot be expressed using built-in SQL functions alone. Financial institutions may require proprietary risk calculations, retailers may have custom loyalty score algorithms, and manufacturing companies may need domain-specific validation rules.&lt;/p&gt;

&lt;p&gt;Prior to ClickHouse® 26.3, implementing these custom operations often meant exporting data from ClickHouse®, processing it in an external application written in Python, Go, Rust, or Java, and then loading the results back into the database. Although functional, this approach introduces unnecessary data movement, increases latency, complicates data pipelines, and creates additional operational overhead.&lt;/p&gt;

&lt;p&gt;ClickHouse® 26.3 introduces an exciting new capability: &lt;strong&gt;WebAssembly (WASM) User-Defined Functions (UDFs)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of moving data outside the database, developers can now execute custom logic directly inside ClickHouse® through a secure WebAssembly runtime. Developers can write functions in languages such as Rust, C, C++, TinyGo, or AssemblyScript, compile them into a &lt;code&gt;.wasm&lt;/code&gt; module, register them with ClickHouse®, and invoke them just like built-in SQL functions.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what WASM UDFs are, how they work, where they are useful, their benefits and limitations, and best practices for using them effectively.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is a User-Defined Function (UDF)?
&lt;/h1&gt;

&lt;p&gt;A &lt;strong&gt;User-Defined Function (UDF)&lt;/strong&gt; is a custom function created by users to perform operations that are not available as built-in database functions.&lt;/p&gt;

&lt;p&gt;For example, suppose your organization calculates customer loyalty using a proprietary algorithm based on purchase frequency, lifetime value, product categories, and customer tenure.&lt;/p&gt;

&lt;p&gt;Without a UDF, this logic might be repeated across dozens of SQL queries or implemented in an external application.&lt;/p&gt;

&lt;p&gt;With a UDF, the entire calculation becomes reusable.&lt;/p&gt;

&lt;p&gt;Built-in functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SUM()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AVG()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COUNT()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LENGTH()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;loyalty_score&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once defined, it behaves like any other SQL function.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is WebAssembly (WASM)?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;WebAssembly (WASM)&lt;/strong&gt; is a portable binary instruction format designed for secure, high-performance execution.&lt;/p&gt;

&lt;p&gt;Unlike native plugins, which execute directly on the operating system, WebAssembly modules run inside an isolated sandbox.&lt;/p&gt;

&lt;p&gt;The typical workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rust / C / C++ / TinyGo / AssemblyScript
                │
                ▼
      Compile to .wasm
                │
                ▼
     ClickHouse® WASM Runtime
                │
                ▼
        SQL Query Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because WebAssembly is language-independent, developers are free to choose whichever supported language best fits their project.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why WASM UDFs Matter
&lt;/h1&gt;

&lt;p&gt;Before ClickHouse® 26.3, implementing custom business logic usually required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exporting data from ClickHouse®.&lt;/li&gt;
&lt;li&gt;Processing it in another application.&lt;/li&gt;
&lt;li&gt;Writing the processed results back into ClickHouse®.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This introduces several disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional infrastructure&lt;/li&gt;
&lt;li&gt;More network traffic&lt;/li&gt;
&lt;li&gt;Higher latency&lt;/li&gt;
&lt;li&gt;Extra storage&lt;/li&gt;
&lt;li&gt;More operational complexity&lt;/li&gt;
&lt;li&gt;Increased maintenance effort&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With WASM UDFs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Processing stays inside ClickHouse®.&lt;/li&gt;
&lt;li&gt;Data never leaves the database.&lt;/li&gt;
&lt;li&gt;SQL becomes more expressive.&lt;/li&gt;
&lt;li&gt;Business logic becomes reusable.&lt;/li&gt;
&lt;li&gt;Pipelines become simpler.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  How WASM UDFs Work
&lt;/h1&gt;

&lt;p&gt;The development workflow is straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write Function
      │
      ▼
Compile to WebAssembly (.wasm)
      │
      ▼
Register Module
      │
      ▼
Create SQL Function
      │
      ▼
Execute Inside SQL Queries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once registered, the custom function behaves just like any built-in ClickHouse® function.&lt;/p&gt;




&lt;h1&gt;
  
  
  Supported Programming Languages
&lt;/h1&gt;

&lt;p&gt;Any language capable of compiling to WebAssembly can be used.&lt;/p&gt;

&lt;p&gt;Common choices include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;TinyGo&lt;/li&gt;
&lt;li&gt;AssemblyScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Among these, &lt;strong&gt;Rust&lt;/strong&gt; is generally considered the preferred option because of its memory safety, excellent tooling, and mature WebAssembly ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  Enabling WASM UDF Support
&lt;/h1&gt;

&lt;p&gt;In ClickHouse® 26.3, WASM UDF support is still &lt;strong&gt;experimental&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It must be enabled explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;enable_wasm_udf&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/enable_wasm_udf&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After enabling the setting, restart ClickHouse®.&lt;/p&gt;

&lt;p&gt;The server can then load WebAssembly modules.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Because the feature is experimental, it should be thoroughly tested before enabling it in production environments.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Registering a WASM Module
&lt;/h1&gt;

&lt;p&gt;After compiling your code into a &lt;code&gt;.wasm&lt;/code&gt; file, register the module with ClickHouse®.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom_logic.wasm
        │
        ▼
ClickHouse®
        │
        ▼
Registered WASM Module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module can expose one or more exported functions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating a WASM Function
&lt;/h1&gt;

&lt;p&gt;After the module has been registered, create a SQL function that references one of the exported methods.&lt;/p&gt;

&lt;p&gt;Calling the function is no different from calling any built-in SQL function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;wasm_loyalty_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_spent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom logic executes internally while ClickHouse® processes each row.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example Use Case
&lt;/h1&gt;

&lt;p&gt;Imagine an insurance company that calculates premiums using hundreds of proprietary business rules written in Rust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse®

     │

Export Data

     │

Rust Application

     │

Import Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using WASM UDFs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse®

      │

WASM Premium Calculator

      │

Query Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything happens inside ClickHouse®, eliminating unnecessary data movement.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Financial Calculations
&lt;/h2&gt;

&lt;p&gt;Banks, insurance providers, and fintech companies frequently implement proprietary algorithms.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Credit scoring&lt;/li&gt;
&lt;li&gt;Loan eligibility&lt;/li&gt;
&lt;li&gt;Risk calculations&lt;/li&gt;
&lt;li&gt;Interest computations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Data Validation
&lt;/h2&gt;

&lt;p&gt;Organizations often need custom validation rules.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer ID validation&lt;/li&gt;
&lt;li&gt;Tax number verification&lt;/li&gt;
&lt;li&gt;Product code validation&lt;/li&gt;
&lt;li&gt;Checksum calculations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Text Processing
&lt;/h2&gt;

&lt;p&gt;Many companies require organization-specific text operations.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product normalization&lt;/li&gt;
&lt;li&gt;Keyword extraction&lt;/li&gt;
&lt;li&gt;Custom tokenization&lt;/li&gt;
&lt;li&gt;Domain-specific parsing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Scientific Computing
&lt;/h2&gt;

&lt;p&gt;Research and engineering teams can implement mathematical algorithms unavailable in standard SQL.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Machine Learning Preprocessing
&lt;/h2&gt;

&lt;p&gt;Prepare features before passing data into downstream ML pipelines.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature scaling&lt;/li&gt;
&lt;li&gt;Normalization&lt;/li&gt;
&lt;li&gt;Feature encoding&lt;/li&gt;
&lt;li&gt;Custom transformations&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Benefits of WASM UDFs
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandboxed execution&lt;/td&gt;
&lt;td&gt;Improved security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High performance&lt;/td&gt;
&lt;td&gt;Near-native execution speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-language support&lt;/td&gt;
&lt;td&gt;Rust, Go, C, C++, TinyGo, and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusable modules&lt;/td&gt;
&lt;td&gt;Write once, reuse everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL integration&lt;/td&gt;
&lt;td&gt;Functions behave like native SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Less infrastructure&lt;/td&gt;
&lt;td&gt;Eliminates external processing pipelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reduced latency&lt;/td&gt;
&lt;td&gt;Processing stays close to the data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  WASM UDFs vs External Processing
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WASM UDFs&lt;/th&gt;
&lt;th&gt;External Processing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runs inside ClickHouse®&lt;/td&gt;
&lt;td&gt;Runs outside the database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lower latency&lt;/td&gt;
&lt;td&gt;Higher latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No data movement&lt;/td&gt;
&lt;td&gt;Export and import required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrated with SQL&lt;/td&gt;
&lt;td&gt;Separate application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easier maintenance&lt;/td&gt;
&lt;td&gt;More infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Runtime Security
&lt;/h1&gt;

&lt;p&gt;One of WebAssembly's biggest advantages is isolation.&lt;/p&gt;

&lt;p&gt;The runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executes inside a secure sandbox.&lt;/li&gt;
&lt;li&gt;Prevents direct operating system access.&lt;/li&gt;
&lt;li&gt;Restricts unsafe operations.&lt;/li&gt;
&lt;li&gt;Protects the ClickHouse® server from untrusted native code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compared with traditional native plugins, this provides a much safer extension mechanism.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;p&gt;When developing WASM UDFs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep functions deterministic.&lt;/li&gt;
&lt;li&gt;Avoid unnecessary memory allocations.&lt;/li&gt;
&lt;li&gt;Focus each function on a single responsibility.&lt;/li&gt;
&lt;li&gt;Benchmark performance before deployment.&lt;/li&gt;
&lt;li&gt;Reuse compiled modules whenever possible.&lt;/li&gt;
&lt;li&gt;Prefer built-in ClickHouse® functions when they already solve the problem efficiently.&lt;/li&gt;
&lt;li&gt;Thoroughly test modules before production deployment.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Current Limitations
&lt;/h1&gt;

&lt;p&gt;Although powerful, WASM UDFs are still evolving.&lt;/p&gt;

&lt;p&gt;Current limitations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Experimental in ClickHouse® 26.3&lt;/li&gt;
&lt;li&gt;Small runtime overhead compared to built-in functions&lt;/li&gt;
&lt;li&gt;Not intended to replace native SQL functionality&lt;/li&gt;
&lt;li&gt;Requires additional testing before production use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the feature matures, many of these limitations are expected to improve.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Should You Use WASM UDFs?
&lt;/h1&gt;

&lt;p&gt;WASM UDFs are an excellent choice when implementing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proprietary business rules&lt;/li&gt;
&lt;li&gt;Financial calculations&lt;/li&gt;
&lt;li&gt;Data validation&lt;/li&gt;
&lt;li&gt;Feature engineering&lt;/li&gt;
&lt;li&gt;Specialized mathematical algorithms&lt;/li&gt;
&lt;li&gt;Domain-specific text processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, if an equivalent built-in ClickHouse® function already exists, it will generally remain the preferred option for simplicity and performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Architecture Overview
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Application
                    │
                    ▼
               SQL Query
                    │
                    ▼
          ClickHouse® 26.3
                    │
      ┌─────────────┴─────────────┐
      │                           │
      ▼                           ▼
Built-in SQL Functions      WASM Runtime
      │                           │
      └─────────────┬─────────────┘
                    ▼
              Query Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Best Practices for Production Adoption
&lt;/h1&gt;

&lt;p&gt;If you're planning to experiment with WASM UDFs, consider the following recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with non-critical workloads.&lt;/li&gt;
&lt;li&gt;Validate correctness against existing implementations.&lt;/li&gt;
&lt;li&gt;Benchmark execution times with realistic datasets.&lt;/li&gt;
&lt;li&gt;Monitor CPU and memory usage during execution.&lt;/li&gt;
&lt;li&gt;Version your WebAssembly modules for easier deployment and rollback.&lt;/li&gt;
&lt;li&gt;Keep your business logic modular so individual functions remain easy to maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following these practices will help you safely evaluate the feature while it continues to mature.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;WebAssembly User-Defined Functions represent one of the most exciting extensibility features introduced in ClickHouse® 26.3.&lt;/p&gt;

&lt;p&gt;By allowing developers to execute custom business logic directly inside the database, WASM UDFs reduce data movement, simplify analytics pipelines, and enable reusable domain-specific functionality without modifying the ClickHouse® source code.&lt;/p&gt;

&lt;p&gt;Although the feature is currently experimental, it opens the door to a wide range of possibilities—from financial calculations and data validation to machine learning preprocessing and scientific computing—all while benefiting from WebAssembly's secure sandboxed execution model.&lt;/p&gt;

&lt;p&gt;As ClickHouse® continues to evolve, WASM UDFs have the potential to become a powerful extension mechanism for organizations that need flexible, high-performance analytics with custom business logic executed exactly where the data lives.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>analytics</category>
      <category>database</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
