<?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>Install CH-Ops in 10 Minutes: Docker, Binary, or Source</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 15 Aug 2026 15:29:16 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/install-ch-ops-in-10-minutes-docker-binary-or-source-56cj</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/install-ch-ops-in-10-minutes-docker-binary-or-source-56cj</guid>
      <description>&lt;p&gt;Getting started with a new database operations tool shouldn't take hours of setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CH-Ops&lt;/strong&gt; gives you three installation options depending on how you prefer to work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker&lt;/strong&gt; — the fastest way to get started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standalone Binary&lt;/strong&gt; — ideal for production deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build from Source&lt;/strong&gt; — perfect if you want to contribute or customize the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, you'll learn how to install CH-Ops using all three approaches, configure it, run it as a Linux service, troubleshoot startup issues, and connect it to your ClickHouse® deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is CH-Ops?
&lt;/h2&gt;

&lt;p&gt;CH-Ops is a browser-based operations platform for ClickHouse®. Instead of relying entirely on the command line or HTTP API, it provides a visual interface for managing your ClickHouse deployment.&lt;/p&gt;

&lt;p&gt;It provides functionality for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running SQL queries&lt;/li&gt;
&lt;li&gt;Monitoring cluster health&lt;/li&gt;
&lt;li&gt;Managing users&lt;/li&gt;
&lt;li&gt;Managing backups&lt;/li&gt;
&lt;li&gt;Configuring alerts&lt;/li&gt;
&lt;li&gt;Exploring dashboards&lt;/li&gt;
&lt;li&gt;Managing ClickHouse clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CH-Ops stores its own configuration in a local SQLite database. It does not modify your ClickHouse data unless you explicitly execute queries against your cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Your Installation Method
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Approximate Setup Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Fastest setup, testing, local development&lt;/td&gt;
&lt;td&gt;~5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standalone Binary&lt;/td&gt;
&lt;td&gt;Production deployments&lt;/td&gt;
&lt;td&gt;~5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build from Source&lt;/td&gt;
&lt;td&gt;Development and customization&lt;/td&gt;
&lt;td&gt;~10 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's look at each option.&lt;/p&gt;




&lt;h1&gt;
  
  
  Option 1: Install with Docker
&lt;/h1&gt;

&lt;p&gt;If you already have Docker installed, this is the quickest way to run CH-Ops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Clone the Repository
&lt;/h2&gt;

&lt;p&gt;Clone the CH-Ops repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Quantrail-Data/CH-Ops.git
&lt;span class="nb"&gt;cd &lt;/span&gt;CH-Ops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create Your Configuration
&lt;/h2&gt;

&lt;p&gt;Copy the example environment file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the required values in &lt;code&gt;.env&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;SUPER_ADMIN_1=admin
SUPER_ADMIN_1_PASSWORD=your_secure_password_here
SUPER_ADMIN_1_EMAIL=you@example.com
SESSION_SECRET=paste_a_random_string_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a secure session secret with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the required environment variables are configured before starting the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Start CH-Ops
&lt;/h2&gt;

&lt;p&gt;Start CH-Ops using Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the container has started, open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the CH-Ops login page.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Docker?
&lt;/h3&gt;

&lt;p&gt;Docker is a convenient option because it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal setup&lt;/li&gt;
&lt;li&gt;No need to install Bun separately&lt;/li&gt;
&lt;li&gt;Persistent SQLite storage&lt;/li&gt;
&lt;li&gt;Easy upgrades&lt;/li&gt;
&lt;li&gt;A consistent runtime environment&lt;/li&gt;
&lt;li&gt;A quick way to test CH-Ops&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember to configure all required environment variables before starting the container.&lt;/p&gt;




&lt;h1&gt;
  
  
  Option 2: Install Using the Standalone Binary
&lt;/h1&gt;

&lt;p&gt;A standalone binary is a good choice when you want to deploy CH-Ops directly on a server without running it through a development environment.&lt;/p&gt;

&lt;p&gt;CH-Ops can be packaged as a single executable with the application and its dependencies included.&lt;/p&gt;

&lt;p&gt;Prebuilt binaries can be provided for platforms such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;macOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to build the binary yourself, follow the steps below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Bun
&lt;/h2&gt;

&lt;p&gt;CH-Ops is built using Bun.&lt;/p&gt;

&lt;p&gt;Install the required Bun version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://bun.com/install | bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"bun-v1.3.13"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Clone the Repository
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Quantrail-Data/CH-Ops.git
&lt;span class="nb"&gt;cd &lt;/span&gt;CH-Ops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Install Dependencies
&lt;/h2&gt;

&lt;p&gt;Install the project dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Build a Standalone Binary
&lt;/h2&gt;

&lt;p&gt;Build a binary for your current platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build:binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also cross-compile for specific platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build:binary:linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build:binary:mac
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build:binary:windows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the target, the generated files can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chops-linux-x64
chops-darwin-arm64
chops-windows-x64.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the build process, the Vite build compiles the React frontend into static assets under &lt;code&gt;dist/&lt;/code&gt;. Bun then packages the backend, dependencies, and frontend assets into the standalone executable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the Binary
&lt;/h2&gt;

&lt;p&gt;For Linux, make the binary executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x chops-linux-x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure the required environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SUPER_ADMIN_1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SUPER_ADMIN_1_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_secure_password
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SUPER_ADMIN_1_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;you@example.com
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SESSION_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_secure_session_secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run CH-Ops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./chops-linux-x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a secure session secret with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CH-Ops automatically creates its internal SQLite database when it starts.&lt;/p&gt;

&lt;p&gt;Open the application at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now sign in using your configured administrator credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Choose the Binary?
&lt;/h3&gt;

&lt;p&gt;A standalone binary is useful when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single executable&lt;/li&gt;
&lt;li&gt;Minimal runtime dependencies&lt;/li&gt;
&lt;li&gt;Easy server deployment&lt;/li&gt;
&lt;li&gt;Simple distribution&lt;/li&gt;
&lt;li&gt;A production-oriented installation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to customize the application or contribute code, building directly from source is the better option.&lt;/p&gt;




&lt;h1&gt;
  
  
  Running CH-Ops as a Linux systemd Service
&lt;/h1&gt;

&lt;p&gt;When deploying CH-Ops on a Linux server, manually starting the application after every reboot isn't ideal.&lt;/p&gt;

&lt;p&gt;Instead, you can run CH-Ops as a &lt;strong&gt;systemd service&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemd&lt;/code&gt; is the default service manager on many modern Linux distributions. It can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start CH-Ops automatically during boot&lt;/li&gt;
&lt;li&gt;Restart the application if it crashes&lt;/li&gt;
&lt;li&gt;Run it under a dedicated user&lt;/li&gt;
&lt;li&gt;Manage permissions&lt;/li&gt;
&lt;li&gt;Centralize application logs through journald&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Dedicated Service User
&lt;/h2&gt;

&lt;p&gt;For security, avoid running CH-Ops as root.&lt;/p&gt;

&lt;p&gt;Create a dedicated system user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt; /usr/sbin/nologin chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then give the service user ownership of the CH-Ops installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; chops:chops /opt/chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;chops&lt;/code&gt; account exists only to run the application and cannot be used for interactive login.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Create the systemd Service File
&lt;/h2&gt;

&lt;p&gt;Create a service definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/systemd/system/chops.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;CH-Ops - ClickHouse Administration Dashboard&lt;/span&gt;
&lt;span class="py"&gt;Documentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://github.com/Quantrail-Data/CH-Ops&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;simple&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;chops&lt;/span&gt;
&lt;span class="py"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;chops&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/chops&lt;/span&gt;

&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/chops/chops&lt;/span&gt;

&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;

&lt;span class="py"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/chops/.env&lt;/span&gt;

&lt;span class="py"&gt;NoNewPrivileges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;ProtectSystem&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;strict&lt;/span&gt;
&lt;span class="py"&gt;ProtectHome&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;ReadWritePaths&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/opt/chops/data&lt;/span&gt;
&lt;span class="py"&gt;PrivateTmp&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="py"&gt;StandardOutput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;journal&lt;/span&gt;
&lt;span class="py"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;journal&lt;/span&gt;
&lt;span class="py"&gt;SyslogIdentifier&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;chops&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Service Configuration
&lt;/h2&gt;

&lt;p&gt;Each setting has a specific purpose:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&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;After=network.target&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts CH-Ops after networking is available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;User=chops&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs the application as a dedicated non-root user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Restart=on-failure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Automatically restarts CH-Ops if it crashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RestartSec=5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits five seconds before restarting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EnvironmentFile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Loads configuration from &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NoNewPrivileges=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prevents the process from gaining additional privileges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProtectSystem=strict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Makes most of the filesystem read-only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReadWritePaths=/opt/chops/data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows CH-Ops to write to its data directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PrivateTmp=true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provides an isolated temporary directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These settings help reduce the potential impact of accidental changes or application vulnerabilities while keeping the service manageable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Enable and Start the Service
&lt;/h2&gt;

&lt;p&gt;Reload systemd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable CH-Ops at boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is configured correctly, you should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active: active (running)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Troubleshooting Startup Issues
&lt;/h1&gt;

&lt;p&gt;If CH-Ops fails to start, the system journal is usually the first place to look.&lt;/p&gt;

&lt;p&gt;Follow the service logs in real time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you see startup errors and configuration problems as they happen.&lt;/p&gt;

&lt;p&gt;You can also inspect recent logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Common systemd Commands
&lt;/h1&gt;

&lt;p&gt;Once CH-Ops is running as a systemd service, these are the commands you'll use most often.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status chops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View recent logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-n&lt;/span&gt; 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow logs in real time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Logging in CH-Ops
&lt;/h1&gt;

&lt;p&gt;CH-Ops writes structured JSON logs to standard output.&lt;/p&gt;

&lt;p&gt;When running under systemd, these logs are automatically captured by &lt;strong&gt;journald&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A log entry can contain information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timestamp&lt;/li&gt;
&lt;li&gt;Log level&lt;/li&gt;
&lt;li&gt;HTTP method&lt;/li&gt;
&lt;li&gt;Request path&lt;/li&gt;
&lt;li&gt;Response status&lt;/li&gt;
&lt;li&gt;Request duration&lt;/li&gt;
&lt;li&gt;User&lt;/li&gt;
&lt;li&gt;Execution context&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-18T10:30:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET /api/alerts/rules 200 12ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ctx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/alerts/rules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"::1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View Human-Readable Logs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View JSON Logs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.MESSAGE | fromjson'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View Only Errors
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;-f&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'"level":"error"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View Logs from the Last Hour
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; chops &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable Debug Logging
&lt;/h2&gt;

&lt;p&gt;For more detailed diagnostic information during development, add the following to &lt;code&gt;.env&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;LOG_LEVEL=debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supported log levels include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debug&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info&lt;/code&gt; — default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;warn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CH-Ops can log API requests, scheduler activity, server startup events, and application errors while avoiding sensitive information such as passwords, authentication tokens, and request bodies.&lt;/p&gt;




&lt;h1&gt;
  
  
  Option 3: Build CH-Ops from Source
&lt;/h1&gt;

&lt;p&gt;If you're developing new functionality, customizing CH-Ops, or contributing to the project, building from source gives you complete control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Bun
&lt;/h2&gt;

&lt;p&gt;Install the required Bun version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://bun.com/install | bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"bun-v1.3.13"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Clone the Repository
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Quantrail-Data/CH-Ops.git
&lt;span class="nb"&gt;cd &lt;/span&gt;CH-Ops
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Install Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Create Your Configuration
&lt;/h2&gt;

&lt;p&gt;Copy the example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the required administrator and session variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUPER_ADMIN_1=admin
SUPER_ADMIN_1_PASSWORD=your_secure_password
SUPER_ADMIN_1_EMAIL=you@example.com
SESSION_SECRET=your_secure_session_secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate a secure session secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Run Database Migrations
&lt;/h2&gt;

&lt;p&gt;Initialize the application's database schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Start CH-Ops
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The development server will be available at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5173/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then start the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bun src/backend/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  First Login
&lt;/h1&gt;

&lt;p&gt;Once CH-Ops is running, open the application in your browser.&lt;/p&gt;

&lt;p&gt;Sign in using the Super Admin credentials configured in your environment.&lt;/p&gt;

&lt;p&gt;After logging in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Administration → Cluster Management&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add your ClickHouse® server.&lt;/li&gt;
&lt;li&gt;Test the connection.&lt;/li&gt;
&lt;li&gt;Save the configuration.&lt;/li&gt;
&lt;li&gt;Start exploring the available management and monitoring features.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once your ClickHouse cluster is connected, you can use CH-Ops for tasks such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Querying your database&lt;/li&gt;
&lt;li&gt;Monitoring cluster activity&lt;/li&gt;
&lt;li&gt;Managing users&lt;/li&gt;
&lt;li&gt;Managing backups&lt;/li&gt;
&lt;li&gt;Configuring alerts&lt;/li&gt;
&lt;li&gt;Exploring dashboards&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Which Installation Method Should You Choose?
&lt;/h1&gt;

&lt;p&gt;The right installation method depends on what you're trying to accomplish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Docker
&lt;/h3&gt;

&lt;p&gt;Use Docker when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The fastest setup&lt;/li&gt;
&lt;li&gt;Local testing&lt;/li&gt;
&lt;li&gt;Development environments&lt;/li&gt;
&lt;li&gt;A consistent deployment environment&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose the Standalone Binary
&lt;/h3&gt;

&lt;p&gt;Use the standalone binary when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A production-oriented deployment&lt;/li&gt;
&lt;li&gt;A single executable&lt;/li&gt;
&lt;li&gt;Minimal runtime dependencies&lt;/li&gt;
&lt;li&gt;Simple server installation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose Build from Source
&lt;/h3&gt;

&lt;p&gt;Build from source when you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To contribute to CH-Ops&lt;/li&gt;
&lt;li&gt;To customize the application&lt;/li&gt;
&lt;li&gt;To develop new features&lt;/li&gt;
&lt;li&gt;Full control over the build process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three approaches ultimately provide the same CH-Ops experience. The main difference is how you install and operate the application.&lt;/p&gt;




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

&lt;p&gt;Installing a ClickHouse® operations platform doesn't have to be complicated.&lt;/p&gt;

&lt;p&gt;With CH-Ops, you can choose the installation approach that matches your workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; for a quick start, &lt;strong&gt;standalone binaries&lt;/strong&gt; for production-oriented deployments, or &lt;strong&gt;source builds&lt;/strong&gt; for development and customization.&lt;/p&gt;

&lt;p&gt;Once CH-Ops is running, connect your ClickHouse® cluster, sign in with your administrator account, and start managing your database through a browser-based interface.&lt;/p&gt;

&lt;p&gt;For the fastest way to get started, Docker is a good first choice. For a Linux production server, a standalone binary combined with systemd provides a straightforward deployment model.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Quantrail-Data/CH-Ops" rel="noopener noreferrer"&gt;CH-Ops GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ch-ops.io/" rel="noopener noreferrer"&gt;CH-Ops Official Website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>analytics</category>
      <category>devops</category>
      <category>database</category>
    </item>
    <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>
  </channel>
</rss>
