<?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>Where Did the Time Go? Query Profiler in CHOps</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Fri, 04 Sep 2026 09:46:05 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/where-did-the-time-go-query-profiler-in-chops-2kk3</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/where-did-the-time-go-query-profiler-in-chops-2kk3</guid>
      <description>&lt;p&gt;A query takes 9 seconds. You read the SQL. You read &lt;code&gt;EXPLAIN&lt;/code&gt;. Nothing looks wrong.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; tells you what ClickHouse® &lt;em&gt;plans&lt;/em&gt; to do. It does not tell you where the 9 seconds actually went. For that you need to watch the query run — and that is what the CHOps Query Profiler does.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;SQL Tools → Query Profiler&lt;/strong&gt;, pick a query, and CHOps draws a flame graph of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a flame graph actually is
&lt;/h2&gt;

&lt;p&gt;A flame graph is a picture of where time went. That's it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;bottom bar&lt;/strong&gt; is where the query starts.&lt;/li&gt;
&lt;li&gt;Every &lt;strong&gt;bar&lt;/strong&gt; is one function that ran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Width&lt;/strong&gt; = time. Wider bar, more time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stacked bars&lt;/strong&gt; = the call chain. A called B, B called C.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read it bottom to top for &lt;em&gt;what called what&lt;/em&gt;, left to right for &lt;em&gt;what ran&lt;/em&gt;. But mostly you just find the widest bar. That's the bottleneck.&lt;/p&gt;

&lt;p&gt;You do not need to know C++ or ClickHouse® internals for this. The function names carry enough meaning on their own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If the widest bar says…&lt;/th&gt;
&lt;th&gt;The query is…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReadBufferFromFileDescriptor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;IO-bound — reading too much from disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HashTable::insert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;building hash tables for GROUP BY or JOIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MergeTreeDataSelectExecutor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;scanning MergeTree parts — likely missing the primary index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MemoryTracker::allocImpl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;allocating — expected at the top of a memory trace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Widest bar, read the name, act on it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First, understand what a "sample" is
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up, and it explains most of the confusion people have with the tool.&lt;/p&gt;

&lt;p&gt;ClickHouse® does &lt;strong&gt;not&lt;/strong&gt; record every function call. That would be ruinously slow. Instead it takes periodic snapshots: a timer fires, and whatever function each thread happens to be executing at that instant gets written as one row into &lt;code&gt;system.trace_log&lt;/code&gt;. That row is one &lt;strong&gt;sample&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A flame graph is nothing more than those rows counted and stacked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bar width = how many snapshots caught that function running.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default ClickHouse® takes &lt;strong&gt;one snapshot per second, per thread&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sampling period&lt;/th&gt;
&lt;th&gt;Snapshots/sec/thread&lt;/th&gt;
&lt;th&gt;A 5s query on 4 threads gives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;1000000000&lt;/code&gt; ns (1s — the default)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;~20 samples&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;10000000&lt;/code&gt; ns (10ms)&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;~2,000 samples&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty samples produce a few blocky bars and percentages that swing on every re-run. Two thousand produce a graph you can trust.&lt;/p&gt;

&lt;p&gt;So if your flame graph looks thin, the query is usually fine — the sampling rate is the problem.&lt;/p&gt;

&lt;p&gt;Raise it for that one 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="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;heavy&lt;/span&gt; &lt;span class="n"&gt;query&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;query_profiler_real_time_period_ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query_profiler_cpu_time_period_ns&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those go on &lt;strong&gt;the query you want to profile&lt;/strong&gt;, when you run it.&lt;/p&gt;

&lt;p&gt;They change how ClickHouse® records that query while it executes. Nothing on the server changes, and no other query is affected.&lt;/p&gt;

&lt;p&gt;Memory traces work on a different trigger: instead of a timer, ClickHouse® records a stack every time the query allocates another &lt;code&gt;memory_profiler_step&lt;/code&gt; bytes (4 MiB by default). Lower that value for a denser memory graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Set &lt;strong&gt;From&lt;/strong&gt; and &lt;strong&gt;To&lt;/strong&gt; to when the query ran. The range is capped at &lt;strong&gt;24 hours&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Load Queries&lt;/strong&gt;. The header shows how many queries in that window have trace data.&lt;/li&gt;
&lt;li&gt;Find your query. The picker searches by query text or query ID, and each row shows the ID, a SQL preview, the duration, the sample count and the timestamp.&lt;/li&gt;
&lt;li&gt;Click it — the selected ID appears below the list, with &lt;strong&gt;Clear&lt;/strong&gt; beside it.&lt;/li&gt;
&lt;li&gt;Choose a &lt;strong&gt;Trace Type&lt;/strong&gt;, then &lt;strong&gt;Generate Flame Graph&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sample count in the list is the number to watch.&lt;/p&gt;

&lt;p&gt;Single digits means you're about to get a graph that tells you nothing; go back and re-run the query with a higher sampling rate.&lt;/p&gt;

&lt;p&gt;Above the graph you get three quick stats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total samples&lt;/li&gt;
&lt;li&gt;Unique stacks&lt;/li&gt;
&lt;li&gt;Max depth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Unique stacks&lt;/strong&gt; is the one people overlook.&lt;/p&gt;

&lt;p&gt;A low number means the query took very few distinct code paths, so there's little to see regardless of sample count.&lt;/p&gt;

&lt;p&gt;If a query you just ran doesn't appear, run:&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;SYSTEM&lt;/span&gt; &lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="n"&gt;LOGS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trace data is buffered before it's written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace types: pick the one that matches your symptom
&lt;/h2&gt;

&lt;p&gt;Nine options, and picking the right one matters more than anything else on the page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trace Type&lt;/th&gt;
&lt;th&gt;Answers&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;All Types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;everything at once&lt;/td&gt;
&lt;td&gt;first look, general orientation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;where CPU went&lt;/td&gt;
&lt;td&gt;the default choice for a slow query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wall Clock (Real)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;where clock time went, waits included&lt;/td&gt;
&lt;td&gt;slow query, but CPU looks idle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory (Watermark)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;what caused the biggest allocations&lt;/td&gt;
&lt;td&gt;query was killed for memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory (Sampled)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the spread of memory use&lt;/td&gt;
&lt;td&gt;broad memory picture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Peak&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;what was running at the memory high point&lt;/td&gt;
&lt;td&gt;pinning down a spike&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Profile Events&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;which internal counters moved most&lt;/td&gt;
&lt;td&gt;advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Jemalloc Samples&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;what the allocator is doing&lt;/td&gt;
&lt;td&gt;advanced — fragmentation debugging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instrumentation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;XRay instrumentation traces&lt;/td&gt;
&lt;td&gt;advanced — needs &lt;code&gt;SYSTEM INSTRUMENT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Not all nine work out of the box.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some rely on collectors that are off by default, and if a collector is off you get an empty graph rather than an error.&lt;/p&gt;

&lt;p&gt;Worth knowing before you assume the tool is broken:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trace type&lt;/th&gt;
&lt;th&gt;On a stock server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU Time, Wall Clock (Real)&lt;/td&gt;
&lt;td&gt;work — but at 1 sample/sec/thread, thin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory (Watermark), Memory Peak&lt;/td&gt;
&lt;td&gt;work — a stack every 4 MiB allocated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory (Sampled)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;empty&lt;/strong&gt; — needs &lt;code&gt;memory_profiler_sample_probability &amp;gt; 0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Profile Events&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;empty&lt;/strong&gt; — needs &lt;code&gt;trace_profile_events = 1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jemalloc Samples&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;empty&lt;/strong&gt; unless the jemalloc profiler is enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instrumentation&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;empty&lt;/strong&gt; unless &lt;code&gt;SYSTEM INSTRUMENT&lt;/code&gt; is on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check where you stand:&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;value&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;settings&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%profiler%'&lt;/span&gt;
   &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'trace_profile_events'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph also re-labels itself for the trace type you picked.&lt;/p&gt;

&lt;p&gt;On a CPU or All Types graph, the stats read &lt;strong&gt;total samples&lt;/strong&gt; and the tooltip shows counts.&lt;/p&gt;

&lt;p&gt;Switch to a memory type and the same line reads &lt;strong&gt;total bytes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A caption next to the Generate button spells out what the current selection measures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory graphs get a second dropdown
&lt;/h2&gt;

&lt;p&gt;Pick a memory trace type and a &lt;strong&gt;Memory Context&lt;/strong&gt; filter appears next to it, with five options.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All Contexts&lt;/td&gt;
&lt;td&gt;everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global (server)&lt;/td&gt;
&lt;td&gt;server-wide allocations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User (user/merge)&lt;/td&gt;
&lt;td&gt;user and merge allocations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Process (query)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;just this query&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread&lt;/td&gt;
&lt;td&gt;thread-level allocations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choose &lt;strong&gt;Process (query)&lt;/strong&gt; when debugging one query.&lt;/p&gt;

&lt;p&gt;Otherwise background merges and server caches get mixed into the graph and you end up optimising something unrelated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the shapes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One very wide bar
&lt;/h3&gt;

&lt;p&gt;Easiest case.&lt;/p&gt;

&lt;p&gt;One function owns the query. Read its name, act on it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Many narrow towers
&lt;/h3&gt;

&lt;p&gt;Normal for queries with joins, subqueries and several aggregations — no single function dominates.&lt;/p&gt;

&lt;p&gt;Scan across all the towers for the widest single bar.&lt;/p&gt;

&lt;p&gt;Still your best lever.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two towers of similar width
&lt;/h3&gt;

&lt;p&gt;Work split across parallel paths.&lt;/p&gt;

&lt;p&gt;Widths are shares of the total, so two 50% towers means the cost is genuinely divided — fix both, or reduce the input feeding them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat, no towers
&lt;/h3&gt;

&lt;p&gt;One code path, or too few samples.&lt;/p&gt;

&lt;p&gt;Check the unique-stacks count before concluding anything about the query.&lt;/p&gt;

&lt;p&gt;Hover any bar for its full function name and share of the total.&lt;/p&gt;

&lt;p&gt;Click to zoom into that subtree; the toolbox has restore, download and full-screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CHOps is doing under the hood
&lt;/h2&gt;

&lt;p&gt;No magic, and the UI doesn't hide it — a &lt;strong&gt;View Generated SQL&lt;/strong&gt; panel under the graph shows the exact query CHOps ran:&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;arrayStringConcat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;arrayReverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;arrayMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;demangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addressToSymbol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="n"&gt;trace&lt;/span&gt;
            &lt;span class="p"&gt;)&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;stack&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;samples&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;trace_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;query_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&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;stack&lt;/span&gt;
&lt;span class="n"&gt;SETTINGS&lt;/span&gt; &lt;span class="n"&gt;allow_introspection_functions&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;Here's what each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;addressToSymbol&lt;/code&gt; turns raw addresses into C++ symbols.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;demangle&lt;/code&gt; makes those symbols readable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arrayReverse&lt;/code&gt; puts the root at the bottom, where a flame graph expects it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GROUP BY stack&lt;/code&gt; counts how often each call chain was sampled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CHOps folds those stacks into a tree and renders it.&lt;/p&gt;

&lt;p&gt;That folded-stack format is the same one every flame graph tool uses, which is why the output looks familiar if you've used &lt;code&gt;perf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note this query only &lt;em&gt;reads&lt;/em&gt; what was already captured.&lt;/p&gt;

&lt;p&gt;It can't create samples after the fact — which is why the sampling settings belong on the query you profile, not here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query Profiler or Processors Profile?
&lt;/h2&gt;

&lt;p&gt;CHOps ships two profilers.&lt;/p&gt;

&lt;p&gt;Different tables, different questions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Query Profiler (flame graph)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Processors Profile (pipeline)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system.trace_log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system.processors_profile_log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shows&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C++ functions inside the engine&lt;/td&gt;
&lt;td&gt;logical pipeline steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;engine-level debugging&lt;/td&gt;
&lt;td&gt;day-to-day query optimisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Example finding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;most of the time in a read-pool function&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ReadFromMergeTree&lt;/code&gt; 7.2s, &lt;code&gt;AggregatingTransform&lt;/code&gt; 0.3s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Start with Processors Profile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Its names map directly to query plan steps, so the fix is usually obvious.&lt;/p&gt;

&lt;p&gt;Switch to the Query Profiler when Processors Profile has told you &lt;em&gt;which step&lt;/em&gt; is slow and you need to know &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you get a graph
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;system.trace_log&lt;/code&gt; enabled&lt;/td&gt;
&lt;td&gt;that's where samples live&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SELECT count() FROM system.trace_log&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow_introspection_functions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;needed for symbol resolution&lt;/td&gt;
&lt;td&gt;CHOps passes it for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT on &lt;code&gt;system.trace_log&lt;/code&gt;, &lt;code&gt;system.query_log&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the CHOps ClickHouse® user needs read access&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT SELECT ON system.trace_log TO your_user&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;clickhouse-common-static-dbg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional — resolves system frames to names&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dpkg -l clickhouse-common-static-dbg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When it doesn't work
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graph is thin — a handful of bars&lt;/td&gt;
&lt;td&gt;default 1 sample/sec/thread&lt;/td&gt;
&lt;td&gt;re-run with &lt;code&gt;query_profiler_*_period_ns = 10000000&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"No trace data found"&lt;/td&gt;
&lt;td&gt;query finished before a snapshot fired&lt;/td&gt;
&lt;td&gt;longer query, or a higher sampling rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty graph on a specific trace type&lt;/td&gt;
&lt;td&gt;that collector is off by default&lt;/td&gt;
&lt;td&gt;see the trace-type table above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query missing from the picker&lt;/td&gt;
&lt;td&gt;trace data not flushed yet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SYSTEM FLUSH LOGS;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bars show raw hex addresses&lt;/td&gt;
&lt;td&gt;no debug symbols on the server&lt;/td&gt;
&lt;td&gt;install &lt;code&gt;clickhouse-common-static-dbg&lt;/code&gt;; only new traces resolve&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty query list&lt;/td&gt;
&lt;td&gt;nothing traced in that window&lt;/td&gt;
&lt;td&gt;widen the range (up to 24h)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Four habits worth forming
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Raise the sampling rate before you profile
&lt;/h3&gt;

&lt;p&gt;More snapshots, better picture.&lt;/p&gt;

&lt;p&gt;This single setting is the difference between a useless graph and a useful one.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Profile something heavy
&lt;/h3&gt;

&lt;p&gt;A query finishing in milliseconds leaves almost nothing to sample, whatever the rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Always compare CPU against Real
&lt;/h3&gt;

&lt;p&gt;Fastest way to tell computation from waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Take a before and after
&lt;/h3&gt;

&lt;p&gt;Generate a graph, add the index or projection, generate again.&lt;/p&gt;

&lt;p&gt;The change in bar widths is your proof the fix worked — a far better artifact for a PR than "feels faster now."&lt;/p&gt;

&lt;p&gt;That last one is the real value.&lt;/p&gt;

&lt;p&gt;Flame graphs get sold as a diagnosis tool, but they're just as good as a verification tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimisation without a before-and-after is guessing.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;CHOps is free and open source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Quantrail-Data/CH-Ops" rel="noopener noreferrer"&gt;https://github.com/Quantrail-Data/CH-Ops&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://www.ch-ops.io/docs/guide/query-profiler" rel="noopener noreferrer"&gt;https://www.ch-ops.io/docs/guide/query-profiler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker:&lt;/strong&gt; &lt;a href="https://hub.docker.com/r/quantrailadmin1/ch-ops" rel="noopener noreferrer"&gt;https://hub.docker.com/r/quantrailadmin1/ch-ops&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run one slow query with the sampling rate turned up.&lt;/p&gt;

&lt;p&gt;Profile it.&lt;/p&gt;

&lt;p&gt;Look at the widest bar.&lt;/p&gt;

&lt;p&gt;You'll know more in thirty seconds than in an hour of reading &lt;code&gt;EXPLAIN&lt;/code&gt; output.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>chops</category>
      <category>analytics</category>
    </item>
    <item>
      <title>CHOps SQL Editor: A Complete Guide to Faster ClickHouse® Query Development and Optimization</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:13:08 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/chops-sql-editor-a-complete-guide-to-faster-clickhouser-query-development-and-optimization-1ml6</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/chops-sql-editor-a-complete-guide-to-faster-clickhouser-query-development-and-optimization-1ml6</guid>
      <description>&lt;h2&gt;
  
  
  What is CHOps?
&lt;/h2&gt;

&lt;p&gt;CHOps is a browser-based operations platform built for managing ClickHouse® deployments. Instead of relying entirely on the command line or HTTP APIs, it provides a unified interface for executing SQL queries, monitoring clusters, managing users, backups, alerts, dashboards, and much more — all from a single web application.&lt;/p&gt;

&lt;p&gt;One of its most powerful features is the SQL Editor, designed to simplify the entire query development experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the CHOps SQL Editor
&lt;/h2&gt;

&lt;p&gt;When working with ClickHouse®, writing SQL is only one part of the job. Developers and Data Engineers often spend just as much time browsing schemas, remembering table names, checking execution plans, estimating query costs, and debugging performance issues.&lt;/p&gt;

&lt;p&gt;With traditional SQL editors, these tasks usually involve switching between multiple tools or browser tabs. That constant context switching slows development and makes query optimization more difficult.&lt;/p&gt;

&lt;p&gt;The CHOps SQL Editor is designed to solve these challenges by bringing everything into a single workspace. Whether you're exploring a new dataset, optimizing analytical queries, or troubleshooting production workloads, this editor provides all the tools needed to build, analyze, and optimize queries without leaving the page.&lt;/p&gt;

&lt;p&gt;Let's explore how these features streamline the ClickHouse® query development workflow and performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SQL Editor at a Glance
&lt;/h2&gt;

&lt;p&gt;The SQL Editor is organized into four main areas, each designed for a specific part of the query workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Left:&lt;/strong&gt; Schema Explorer for browsing databases and tables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Top of the center:&lt;/strong&gt; Query Tabs for working on multiple queries simultaneously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Center:&lt;/strong&gt; The SQL Editor and its toolbar where you write SQL, choose how to run it, and reach the history, bookmarks, share, export, fullscreen, and AI controls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bottom:&lt;/strong&gt; The Results Panel for viewing results, execution statistics, and debugging information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This layout keeps everything within reach, eliminating the need to jump between multiple applications while developing queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting First
&lt;/h2&gt;

&lt;p&gt;The SQL Editor allows direct authentication using ClickHouse® credentials.&lt;/p&gt;

&lt;p&gt;Simply enter your:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once authenticated, the SQL editor connects securely and unlocks schema browsing, query execution, and analysis features. This design separates application login from database authentication, improving security and flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built-in Database Explorer
&lt;/h2&gt;

&lt;p&gt;Once connected, the first place you'll likely visit is the Schema Explorer.&lt;/p&gt;

&lt;p&gt;The Schema Explorer lets you browse your ClickHouse® databases and tables directly from the left panel. Expand a database to view its tables, then select a table to insert its fully qualified name into the SQL editor.&lt;/p&gt;

&lt;p&gt;Need to check the table structure first? The code icon beside each table opens its complete CREATE TABLE statement, making it easy to review columns, sorting keys, and table engines without leaving the editor.&lt;/p&gt;

&lt;p&gt;For databases where AI assistance is needed, the sparkles icon provides quick access to AI SQL generation. We'll explore that workflow later in Generating SQL with AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smart SQL Autocomplete
&lt;/h2&gt;

&lt;p&gt;One of the biggest productivity boosters of the SQL Editor is Autocomplete.&lt;/p&gt;

&lt;p&gt;As you type:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the editor automatically suggests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL keywords&lt;/li&gt;
&lt;li&gt;ClickHouse® functions&lt;/li&gt;
&lt;li&gt;Database names&lt;/li&gt;
&lt;li&gt;Table names&lt;/li&gt;
&lt;li&gt;Column names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike a generic SQL editor, these suggestions come directly from your connected ClickHouse® cluster. Instead of showing generic SQL objects, the editor suggests only the databases, tables, functions, and columns that actually exist on your server.&lt;/p&gt;

&lt;p&gt;Autocomplete reduces syntax mistakes while making query writing significantly faster.&lt;/p&gt;

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

&lt;p&gt;Instead of remembering:&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;sales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_transactions_archive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you simply type:&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;sales&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and choose the required table from the suggestion list.&lt;/p&gt;

&lt;p&gt;Autocomplete also displays function signatures and descriptions, making it much easier to discover ClickHouse® functions without switching to external documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Your Query
&lt;/h2&gt;

&lt;p&gt;Once your query is ready, executing it is straightforward.&lt;/p&gt;

&lt;p&gt;Simply click &lt;strong&gt;Go&lt;/strong&gt;, or use &lt;code&gt;Ctrl + Enter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The action performed depends on the selected execution mode. You can either execute the SQL directly or choose one of the available EXPLAIN modes to analyze the query before running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand Queries with EXPLAIN
&lt;/h2&gt;

&lt;p&gt;Understanding how ClickHouse® executes a query is often more valuable than simply seeing the result.&lt;/p&gt;

&lt;p&gt;Normally, checking a query plan requires manually prefixing every query with &lt;code&gt;EXPLAIN&lt;/code&gt;, executing it, reviewing the output, and then removing the prefix before running the actual query.&lt;/p&gt;

&lt;p&gt;The CHOps SQL Editor simplifies this workflow. Simply select an EXPLAIN mode from the dropdown beside the &lt;strong&gt;Go&lt;/strong&gt; button and execute the query. No query rewriting is required.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Explain mode&lt;/th&gt;
&lt;th&gt;What it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Explain &amp;amp; Explain plan&lt;/td&gt;
&lt;td&gt;Displays the execution plan in order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain syntax&lt;/td&gt;
&lt;td&gt;Your query after ClickHouse®'s internal rewrites&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain query tree&lt;/td&gt;
&lt;td&gt;The analyzed, internal form of the query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain pipeline&lt;/td&gt;
&lt;td&gt;The actual processors that will do the work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain estimate&lt;/td&gt;
&lt;td&gt;Estimated rows, parts, and marks to be read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain AST (graph)&lt;/td&gt;
&lt;td&gt;Visualizes the parsed query structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain pipeline (graph)&lt;/td&gt;
&lt;td&gt;Displays the execution pipeline as an interactive graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain plan (JSON)&lt;/td&gt;
&lt;td&gt;Returns the execution plan in JSON format&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  AI-Powered SQL Generation
&lt;/h2&gt;

&lt;p&gt;Not every query needs to start from scratch. Sometimes you know what information you need but not the exact SQL syntax.&lt;/p&gt;

&lt;p&gt;The Generate SQL feature helps convert natural language into SQL queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example prompt:
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Show the top 10 products by revenue during the last month.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The generated SQL can then be reviewed, modified, and executed like any manually written query, making it useful for rapid prototyping and learning new schemas.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Debugging Tools Around the Query
&lt;/h2&gt;

&lt;p&gt;A few smaller features exist specifically to shorten the time between "something feels slow" and "here is why":&lt;/p&gt;

&lt;h3&gt;
  
  
  Estimate Query Cost
&lt;/h3&gt;

&lt;p&gt;Before executing an expensive query, it's often useful to understand how much work ClickHouse® is expected to perform.&lt;/p&gt;

&lt;p&gt;The Cost feature estimates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rows to be read&lt;/li&gt;
&lt;li&gt;Parts to be scanned&lt;/li&gt;
&lt;li&gt;Marks to be processed&lt;/li&gt;
&lt;li&gt;Available indexes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;without actually executing the query.&lt;/p&gt;

&lt;p&gt;This provides a quick way to identify potentially expensive queries before they impact your cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Max Rows Control
&lt;/h3&gt;

&lt;p&gt;Max Rows caps how many rows come back to the browser, so an accidental unbounded query does not lock up the tab while you are trying to debug something else.&lt;/p&gt;

&lt;p&gt;When larger datasets are required, the limit can be adjusted or the results can be exported instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Action Buttons After a Run
&lt;/h3&gt;

&lt;p&gt;Once a query finishes and ClickHouse® has assigned it a query ID, a row of buttons appears in the statistics bar to take you straight into the profiling tools for that exact query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query_id:&lt;/strong&gt; copies ClickHouse®'s ID for the query to your clipboard, useful for looking it up in system tables or handing to whoever administers the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flame Graph:&lt;/strong&gt; opens the Query Profiler with this query loaded, showing where it spent its time. Reach for this first when something is slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline:&lt;/strong&gt; opens the Processors Profile with the query loaded, rendering its execution as a diagram so you can see which step dominated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics:&lt;/strong&gt; opens Query Metrics with the query loaded, showing a second-by-second view of how it used resources while it ran.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  History
&lt;/h3&gt;

&lt;p&gt;History keeps every query you have run, with its timing and success or failure.&lt;/p&gt;

&lt;p&gt;Instead of rewriting or searching through old SQL files, you can quickly reopen previous queries, review execution results, and continue where you left off.&lt;/p&gt;

&lt;p&gt;History keeps your most recent queries and drops the oldest as new ones arrive. The &lt;strong&gt;Clear&lt;/strong&gt; button empties it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bookmarks
&lt;/h3&gt;

&lt;p&gt;Some queries become part of your daily workflow.&lt;/p&gt;

&lt;p&gt;Bookmarks let you save the ones you check often, like a recurring health check query, so you are not retyping the same debugging query every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Share Option
&lt;/h3&gt;

&lt;p&gt;Need a second opinion on a query?&lt;/p&gt;

&lt;p&gt;The Share feature generates a link containing the current SQL, allowing teammates to open the query directly in their own SQL Editor.&lt;/p&gt;

&lt;p&gt;Since every user reconnects using their own ClickHouse® credentials, sharing a query never grants access to your database. It simply shares the SQL itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare Query Performance
&lt;/h2&gt;

&lt;p&gt;Next to the connect control is a second switch: &lt;strong&gt;Regular&lt;/strong&gt; and &lt;strong&gt;Comparison&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Regular is the single query view described above.&lt;/p&gt;

&lt;p&gt;Comparison splits the screen into a current query on the left and an experimental rewrite on the right, each with its own cost estimate and run button.&lt;/p&gt;

&lt;p&gt;This is the mode built for tuning.&lt;/p&gt;

&lt;p&gt;Paste the slow query on one side, write a rewrite on the other, and check whether the rewrite actually reduces estimated cost before you touch production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Export Option
&lt;/h2&gt;

&lt;p&gt;Once your query is complete, exporting the results is just as simple.&lt;/p&gt;

&lt;p&gt;The SQL Editor supports exporting query results in multiple formats, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;Parquet&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exports are processed on the server, allowing large datasets to be generated without affecting the browser session.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Workflow
&lt;/h2&gt;

&lt;p&gt;A typical workflow inside the CHOps SQL Editor looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect to your ClickHouse® deployment.&lt;/li&gt;
&lt;li&gt;Browse databases and tables using the Schema Explorer.&lt;/li&gt;
&lt;li&gt;Use autocomplete to discover tables, columns, and functions.&lt;/li&gt;
&lt;li&gt;Write your SQL query.&lt;/li&gt;
&lt;li&gt;Use EXPLAIN to understand query execution.&lt;/li&gt;
&lt;li&gt;Estimate query cost before running expensive workloads.&lt;/li&gt;
&lt;li&gt;Execute the query.&lt;/li&gt;
&lt;li&gt;Review the results and execution statistics.&lt;/li&gt;
&lt;li&gt;Use Query Profiler, Pipeline, or Metrics when troubleshooting.&lt;/li&gt;
&lt;li&gt;Use Comparison Mode to test query improvements.&lt;/li&gt;
&lt;li&gt;Bookmark useful queries for future use.&lt;/li&gt;
&lt;li&gt;Share SQL with teammates when collaboration is needed.&lt;/li&gt;
&lt;li&gt;Export results when required.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Each feature in the SQL Editor solves a small part of the query development process.&lt;/p&gt;

&lt;p&gt;Autocomplete reduces typing, EXPLAIN helps you understand execution plans, Cost estimation highlights expensive queries before execution, and Comparison Mode makes performance tuning much easier.&lt;/p&gt;

&lt;p&gt;Individually, these features improve specific tasks.&lt;/p&gt;

&lt;p&gt;Together, they reduce context switching, simplify query optimization, and provide a smoother development experience for anyone working with ClickHouse®.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If you're new to CHOps or would like to explore it further, the following resources are a great place to start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting Started with CHOps (Installation Guide) - &lt;a href="https://www.ch-ops.io/blog/install-ch-ops-in-10-minutes-docker-binary-or-source" rel="noopener noreferrer"&gt;https://www.ch-ops.io/blog/install-ch-ops-in-10-minutes-docker-binary-or-source&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CHOps Official GitHub Repository - &lt;a href="https://github.com/Quantrail-Data/CH-Ops/tree/main" rel="noopener noreferrer"&gt;https://github.com/Quantrail-Data/CH-Ops/tree/main&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CHOps Demo Video - &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7489909665477091328" rel="noopener noreferrer"&gt;https://www.linkedin.com/feed/update/urn:li:activity:7489909665477091328&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>chops</category>
      <category>devops</category>
      <category>analytics</category>
    </item>
    <item>
      <title>ClickHouse® 26.8 LTS Release: What's New and Why It Matters</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 29 Aug 2026 05:45:03 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/clickhouser-268-lts-release-whats-new-and-why-it-matters-4lk4</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/clickhouser-268-lts-release-whats-new-and-why-it-matters-4lk4</guid>
      <description>&lt;p&gt;ClickHouse® 26.8 is the newest LTS release, and it is a substantial one: &lt;strong&gt;21 backward-incompatible changes, 49 new features, 127 performance improvements, and around 30 settings that changed their default value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article covers 26.8 on its own — what landed, what breaks, and what to verify before you upgrade.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Release status at time of writing (27 August 2026).&lt;/strong&gt; ClickHouse® 26.8 has been announced, but the release is not fully published yet and the upstream changelog still marks the 26.8 section as in progress. Confirm the current release status before you plan an upgrade window.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  In This Article
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;At a glance&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Breaking changes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ingestion and the write path&lt;/li&gt;
&lt;li&gt;Type and function semantics&lt;/li&gt;
&lt;li&gt;Query planning and output&lt;/li&gt;
&lt;li&gt;Security and configuration&lt;/li&gt;
&lt;li&gt;Removals&lt;/li&gt;
&lt;li&gt;Monitoring and introspection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Default settings that changed in 26.8&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Behaviour&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Performance (enabled by default)&lt;/li&gt;
&lt;li&gt;MergeTree - on-disk formats&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What's new&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL surface&lt;/li&gt;
&lt;li&gt;Server and operations&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Joins and text search&lt;/li&gt;
&lt;li&gt;Data lakes&lt;/li&gt;
&lt;li&gt;AI functions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upgrade checklist&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Summary&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  At a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Entries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backward incompatible changes&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New features&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experimental features&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance improvements&lt;/td&gt;
&lt;td&gt;127&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvements&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug fixes&lt;/td&gt;
&lt;td&gt;556&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Breaking Changes
&lt;/h1&gt;

&lt;p&gt;All 21 breaking changes, grouped by what they affect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingestion and the Write Path
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;max_insert_threads&lt;/code&gt; now defaults to &lt;code&gt;auto&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It resolves to the number of CPU cores available to the server, parallelising &lt;code&gt;INSERT SELECT&lt;/code&gt; by default. It can also parallelise the writing side of a plain &lt;code&gt;INSERT&lt;/code&gt; where the destination write path can safely fan out.&lt;/p&gt;

&lt;p&gt;Two consequences worth planning for: the number of parts created by such queries changes, and so does the order of inserted rows.&lt;/p&gt;

&lt;p&gt;If you have tight &lt;code&gt;parts_to_throw_insert&lt;/code&gt; thresholds, or anything depending on insertion order — a non-deterministic &lt;code&gt;ORDER BY&lt;/code&gt; tie-break, &lt;code&gt;_part&lt;/code&gt; or &lt;code&gt;_block_number&lt;/code&gt; assumptions, a ReplacingMergeTree without a proper version column — test this before rolling out.&lt;/p&gt;

&lt;p&gt;A detail that catches people diffing configurations: the declared default is 0 in both 26.7 and 26.8.&lt;/p&gt;

&lt;p&gt;What changed is the setting's type, from &lt;code&gt;UInt64&lt;/code&gt; to &lt;code&gt;MaxThreads&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Under &lt;code&gt;UInt64&lt;/code&gt;, both 0 and 1 meant single-threaded; under &lt;code&gt;MaxThreads&lt;/code&gt;, 0 resolves to auto.&lt;/p&gt;

&lt;p&gt;So the value column in &lt;code&gt;system.settings&lt;/code&gt; reads 0 before and after, while the behaviour has flipped.&lt;/p&gt;

&lt;p&gt;Upstream's own compatibility mapping records the change as 1 → 0 for this reason — 1 is what you now set to get the old behaviour, not what the declaration used to say.&lt;/p&gt;

&lt;p&gt;Restore with:&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;max_insert_threads&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 use a compatibility version below 26.8.&lt;/p&gt;




&lt;h3&gt;
  
  
  Lightweight &lt;code&gt;UPDATE&lt;/code&gt; patch parts use a new v2 on-disk format
&lt;/h3&gt;

&lt;p&gt;They are now sorted by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(sorting_key..., _block_number, _block_offset)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and applied with a new merging algorithm.&lt;/p&gt;

&lt;p&gt;Peak memory during apply is bounded by the largest equal-sort-key run rather than the full patch, and updates crossing merge boundaries no longer fall back to an in-memory Join apply.&lt;/p&gt;

&lt;p&gt;Old-format patch parts remain readable.&lt;/p&gt;

&lt;p&gt;For replicated clusters this requires a rolling-upgrade pin.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;patch_parts_version&lt;/code&gt; is a MergeTree setting rather than a session setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;merge_tree&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;patch_parts_version&amp;gt;&lt;/span&gt;v1&lt;span class="nt"&gt;&amp;lt;/patch_parts_version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/merge_tree&amp;gt;&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;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="k"&gt;MODIFY&lt;/span&gt; &lt;span class="n"&gt;SETTING&lt;/span&gt; &lt;span class="n"&gt;patch_parts_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'v1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hold that until every replica is on 26.8, then remove it.&lt;/p&gt;




&lt;h3&gt;
  
  
  Object-storage disk transactions
&lt;/h3&gt;

&lt;p&gt;Object-storage disk transactions now use the metadata storage's native transactions by default instead of the previous fake transactions.&lt;/p&gt;

&lt;p&gt;The upstream changelog does not explain why this is listed as backward-incompatible, so treat it as worth testing if you run object-storage disks.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;disable_insertion_and_mutation&lt;/code&gt; changes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;disable_insertion_and_mutation&lt;/code&gt; now also stops background consumption from &lt;code&gt;Kafka&lt;/code&gt;, &lt;code&gt;RabbitMQ&lt;/code&gt; and &lt;code&gt;NATS&lt;/code&gt; tables, while still permitting direct writes to external storage.&lt;/p&gt;

&lt;p&gt;Gated &lt;code&gt;Kafka2&lt;/code&gt;, &lt;code&gt;NATS&lt;/code&gt; and &lt;code&gt;RabbitMQ&lt;/code&gt; tables no longer initialise consumers for a direct &lt;code&gt;SELECT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Separately, &lt;code&gt;message_queue_disable_insertion&lt;/code&gt; now requires a server restart to take effect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Type and Function Semantics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unquoted JSON numbers are now Unix timestamps
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;JSONEachRow&lt;/code&gt; and similar formats, an unquoted number for a &lt;code&gt;DateTime&lt;/code&gt; or &lt;code&gt;DateTime64&lt;/code&gt; column is read as a Unix timestamp with optional sub-second precision, consistent with &lt;code&gt;Values&lt;/code&gt;, &lt;code&gt;CAST&lt;/code&gt; and &lt;code&gt;toDateTime64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Previously a fractional value such as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;was rejected outright, and a bare integer such as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;was read as the raw scaled value of &lt;code&gt;DateTime64&lt;/code&gt;, producing a 1970 timestamp.&lt;/p&gt;

&lt;p&gt;Quoted strings and ClickHouse®'s own JSON output are unaffected.&lt;/p&gt;

&lt;p&gt;For most pipelines this is a silent fix.&lt;/p&gt;

&lt;p&gt;If yours was compensating for the old raw-value behaviour, it will now double-correct.&lt;/p&gt;

&lt;p&gt;Restore with:&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;input_format_read_datetime_number_as_raw_value&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;h3&gt;
  
  
  &lt;code&gt;Date32&lt;/code&gt; range extended
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Date32&lt;/code&gt; range is extended from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1900-01-01, 2299-12-31]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0000-01-01, 9999-12-31]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;matching &lt;code&gt;DateTime64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Parsing and conversions accept the extended range instead of silently clamping.&lt;/p&gt;

&lt;p&gt;The compatibility note matters: in &lt;code&gt;toDate32(N)&lt;/code&gt;, values in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[120530, 2932896]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are now interpreted as day numbers — dates from 2300-01-01 to 9999-12-31 — rather than Unix timestamps in early 1970.&lt;/p&gt;

&lt;p&gt;Numbers below the day number of &lt;code&gt;0000-01-01&lt;/code&gt;, and timestamps after &lt;code&gt;9999-12-31&lt;/code&gt;, saturate to the new boundaries.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;arrayIntersect&lt;/code&gt; and &lt;code&gt;arraySymmetricDifference&lt;/code&gt; deduplication fixed
&lt;/h3&gt;

&lt;p&gt;A value repeated inside a single argument is no longer treated as though it appeared in several arguments.&lt;/p&gt;

&lt;p&gt;A value now counts for an argument only when it was present in every argument before it, so the result contains exactly the values present in all arguments.&lt;/p&gt;

&lt;p&gt;Examples:&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;arrayIntersect&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="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="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;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;-- []&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;arrayIntersect&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;2&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="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;1&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="c1"&gt;-- [2]&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;arraySymmetricDifference&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="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="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;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;-- [2, 1]&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;arraySymmetricDifference&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="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;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;-- [2, 1]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;arrayUnion&lt;/code&gt; is unaffected.&lt;/p&gt;

&lt;p&gt;As part of the same change, &lt;code&gt;arrayIntersect&lt;/code&gt; builds its hash table from the smallest argument rather than all of them — up to 1.85x faster and a third less memory when argument sizes differ significantly.&lt;/p&gt;




&lt;h3&gt;
  
  
  Window functions over &lt;code&gt;AggregateFunction&lt;/code&gt; columns are rejected
&lt;/h3&gt;

&lt;p&gt;A window &lt;code&gt;PARTITION BY&lt;/code&gt; or &lt;code&gt;ORDER BY&lt;/code&gt; over an &lt;code&gt;AggregateFunction&lt;/code&gt; column now raises &lt;code&gt;ILLEGAL_COLUMN&lt;/code&gt;, as top-level &lt;code&gt;ORDER BY&lt;/code&gt; over such a column already did.&lt;/p&gt;

&lt;p&gt;Previously at least one analyzer accepted it, and window &lt;code&gt;PARTITION BY&lt;/code&gt; partitioned differently depending on &lt;code&gt;max_threads&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The refusal covers states nested in &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Tuple&lt;/code&gt;, &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Variant&lt;/code&gt; or &lt;code&gt;SimpleAggregateFunction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;SimpleAggregateFunction&lt;/code&gt; over an ordinary type, &lt;code&gt;QBit&lt;/code&gt;, and &lt;code&gt;GROUP BY&lt;/code&gt; or &lt;code&gt;DISTINCT&lt;/code&gt; over a state are all unaffected.&lt;/p&gt;




&lt;h3&gt;
  
  
  Timespan settings that overflow &lt;code&gt;Int64&lt;/code&gt; microseconds are rejected
&lt;/h3&gt;

&lt;p&gt;This applies to millisecond and second settings.&lt;/p&gt;




&lt;h1&gt;
  
  
  Query Planning and Output
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Trivial views over &lt;code&gt;Distributed&lt;/code&gt; tables are pushed to the shards
&lt;/h3&gt;

&lt;p&gt;For a view whose body is a plain &lt;code&gt;SELECT&lt;/code&gt; over a single &lt;code&gt;Distributed&lt;/code&gt; table, the whole outer query now goes to the shards.&lt;/p&gt;

&lt;p&gt;This is the new:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;setting, enabled by default.&lt;/p&gt;

&lt;p&gt;Observable behaviour changes in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FINAL&lt;/code&gt; and &lt;code&gt;SAMPLE&lt;/code&gt; written on the view reference are now propagated to the shard-local table instead of being ignored.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extremes&lt;/code&gt; is not reported on single-shard clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you had views where &lt;code&gt;FINAL&lt;/code&gt; was silently a no-op, results will change.&lt;/p&gt;

&lt;p&gt;Set the setting to &lt;code&gt;0&lt;/code&gt; to restore the previous behaviour:&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;optimize_trivial_view_pushdown_to_distributed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;EXPLAIN SYNTAX&lt;/code&gt; returns a single record
&lt;/h3&gt;

&lt;p&gt;The reformatted query comes back as one &lt;code&gt;String&lt;/code&gt; value with embedded newlines rather than one record per line.&lt;/p&gt;

&lt;p&gt;So:&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="n"&gt;SYNTAX&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;To restore the old behaviour:&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;explain_syntax_single_record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other &lt;code&gt;EXPLAIN&lt;/code&gt; kinds — &lt;code&gt;PLAN&lt;/code&gt;, &lt;code&gt;PIPELINE&lt;/code&gt;, &lt;code&gt;AST&lt;/code&gt; — keep their per-line tree output.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security and Configuration
&lt;/h1&gt;

&lt;p&gt;A clear theme this release: the server no longer opens filesystem paths supplied from SQL, because it opens them with its own privileges.&lt;/p&gt;

&lt;h3&gt;
  
  
  MySQL source TLS credentials
&lt;/h3&gt;

&lt;p&gt;MySQL source TLS credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssl_ca&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssl_cert&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssl_key&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;can no longer be given as file paths from SQL — not in &lt;code&gt;CREATE NAMED COLLECTION&lt;/code&gt;, query arguments, or &lt;code&gt;CREATE DICTIONARY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Paths remain supported in the server configuration file.&lt;/p&gt;

&lt;p&gt;Elsewhere, pass the contents via the new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssl_ca_pem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssl_cert_pem&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssl_key_pem&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;parameters, which are masked in logs and &lt;code&gt;SHOW&lt;/code&gt; output the way passwords are.&lt;/p&gt;




&lt;h3&gt;
  
  
  NATS credentials move inline
&lt;/h3&gt;

&lt;p&gt;The new &lt;code&gt;nats_credentials&lt;/code&gt; setting takes the same payload as a &lt;code&gt;.creds&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nats_credential_file&lt;/code&gt; is no longer accepted from SQL.&lt;/p&gt;

&lt;p&gt;It can only be set in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a named collection defined in the server configuration, or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nats.credential_file&lt;/code&gt; in the server configuration itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A query may replace a configured path with inline &lt;code&gt;nats_credentials&lt;/code&gt; unless the operator pinned it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;nats_credential_file&lt;/span&gt; &lt;span class="na"&gt;overridable=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tables created before the restriction keep working.&lt;/p&gt;




&lt;h3&gt;
  
  
  PostgreSQL database engines respect &lt;code&gt;remote_url_allow_hosts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PostgreSQL&lt;/code&gt; and &lt;code&gt;MaterializedPostgreSQL&lt;/code&gt; database engines now honour &lt;code&gt;remote_url_allow_hosts&lt;/code&gt;, as the table engine, table function and DDL-created dictionaries already did.&lt;/p&gt;

&lt;p&gt;With it configured, &lt;code&gt;CREATE DATABASE&lt;/code&gt; and user-issued &lt;code&gt;ATTACH DATABASE&lt;/code&gt; pointing at disallowed hosts fail with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Existing databases still load at startup.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;SYSTEM ... CACHE ON CLUSTER&lt;/code&gt; privilege checks are granular
&lt;/h3&gt;

&lt;p&gt;Each command now uses its own privilege rather than the &lt;code&gt;SYSTEM DROP CACHE&lt;/code&gt; group.&lt;/p&gt;

&lt;p&gt;This lets a holder of a single granular cache privilege run its matching command, and prevents a holder of the group from running:&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;SYSTEM&lt;/span&gt; &lt;span class="n"&gt;SYNC&lt;/span&gt; &lt;span class="n"&gt;FILESYSTEM&lt;/span&gt; &lt;span class="k"&gt;CACHE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SYSTEM SYNC FILESYSTEM CACHE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;include_from&lt;/code&gt; no longer defaults to &lt;code&gt;/etc/metrika.xml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;That file was previously used for configuration substitutions whenever it existed, even with nothing in the ClickHouse® configuration referring to it.&lt;/p&gt;

&lt;p&gt;If you relied on it, add the element explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;include_from&amp;gt;&lt;/span&gt;/etc/metrika.xml&lt;span class="nt"&gt;&amp;lt;/include_from&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Separately loaded &lt;code&gt;users.xml&lt;/code&gt; and XML dictionary configs each need their own &lt;code&gt;include_from&lt;/code&gt; element.&lt;/p&gt;




&lt;h1&gt;
  
  
  Removals
&lt;/h1&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;library&lt;/code&gt; dictionary source is gone
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOURCE(LIBRARY(...))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now fails with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;dictionaries_lib_path&lt;/code&gt; server setting is obsolete with no effect.&lt;/p&gt;




&lt;h3&gt;
  
  
  Apache Arrow library-based reader and writer removed
&lt;/h3&gt;

&lt;p&gt;The Apache Arrow library-based reader and writer for &lt;code&gt;Arrow&lt;/code&gt; and &lt;code&gt;ArrowStream&lt;/code&gt; are removed.&lt;/p&gt;

&lt;p&gt;The native ClickHouse® implementation, default since 26.7, is now the only one.&lt;/p&gt;

&lt;p&gt;The following settings are still accepted but have no effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input_format_arrow_use_native_reader
output_format_arrow_use_native_writer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a query that set them to &lt;code&gt;0&lt;/code&gt; to force the Apache Arrow path now silently uses the native one.&lt;/p&gt;




&lt;h3&gt;
  
  
  Experimental &lt;code&gt;ALP(STD)&lt;/code&gt; codec changes
&lt;/h3&gt;

&lt;p&gt;The experimental &lt;code&gt;ALP(STD)&lt;/code&gt; codec now performs &lt;code&gt;Float32&lt;/code&gt; scaling arithmetic in &lt;code&gt;Float64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This improves compression ratios and eliminates exception-heavy compression of decimal data, but &lt;code&gt;Float32&lt;/code&gt; values written by earlier versions may decode 1 ULP differently.&lt;/p&gt;




&lt;h1&gt;
  
  
  Monitoring and Introspection
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Asynchronous metrics can now be &lt;code&gt;Map&lt;/code&gt;-typed
&lt;/h3&gt;

&lt;p&gt;Asynchronous metrics can now be &lt;code&gt;Map&lt;/code&gt;-typed, and the per-CPU-core and per-device metrics were consolidated.&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 plaintext"&gt;&lt;code&gt;OSUserTimeCPU0
OSUserTimeCPU1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;became a single:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;metric holding a map from core number to value.&lt;/p&gt;

&lt;p&gt;The same applies to the other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OS*TimeCPU*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CPUFrequencyMHz_*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temperature*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EDAC*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Block*_*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Network(Receive|Send)*_*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Disk*_*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*BlobsQueueEstimate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AsyncLogging*QueueSize&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;metrics.&lt;/p&gt;

&lt;p&gt;Downstream effects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;system.asynchronous_metrics&lt;/code&gt; gains &lt;code&gt;key_values Map(LowCardinality(String), Float64)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;value&lt;/code&gt; column is &lt;code&gt;NaN&lt;/code&gt; for these metrics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;system.asynchronous_metric_log&lt;/code&gt; logs one row per key via a new &lt;code&gt;key&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;The Prometheus endpoint exports them with a label, for example:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse®AsyncMetrics_BlockReadBytes{device="sda"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Graphite's &lt;code&gt;MetricsTransmitter&lt;/code&gt; sends them as:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;prefix&amp;gt;.&amp;lt;Metric&amp;gt;.&amp;lt;key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the change most likely to break existing dashboards.&lt;/p&gt;

&lt;p&gt;Panels keyed on the old metric names will not error — they will simply return nothing, which is easy to miss.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;system.users.valid_until&lt;/code&gt; changed type
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;system.users.valid_until&lt;/code&gt; changed type from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array(DateTime)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array(DateTime64(0))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so deadlines beyond the year 2106 are represented exactly.&lt;/p&gt;

&lt;p&gt;Tooling reading this column needs to handle the new type.&lt;/p&gt;

&lt;p&gt;This arrived alongside a new:&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;VALID&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;clause on &lt;code&gt;CREATE USER&lt;/code&gt; and &lt;code&gt;ALTER USER&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is a shorthand for &lt;code&gt;VALID UNTIL&lt;/code&gt;, where the deadline is computed at query execution time and stored in &lt;code&gt;VALID UNTIL&lt;/code&gt; form.&lt;/p&gt;




&lt;h1&gt;
  
  
  Default Settings That Changed in 26.8
&lt;/h1&gt;

&lt;p&gt;Around 30 core settings and several MergeTree settings changed their defaults.&lt;/p&gt;

&lt;p&gt;These do not appear in the breaking-change list but will change behaviour on upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behaviour
&lt;/h2&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;Previous&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max_insert_threads&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; - single-threaded&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auto&lt;/code&gt; - all available cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;input_format_read_datetime_number_as_raw_value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;optimize_trivial_view_pushdown_to_distributed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;explain_syntax_single_record&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;filesystem_cache_wait_for_concurrent_download_timeout_milliseconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;60000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Security
&lt;/h2&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;Previous&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_function_allow_insecure_endpoint&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai_function_max_api_calls_per_query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0&lt;/code&gt; (unbounded)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Performance (Enabled by Default)
&lt;/h2&gt;

&lt;p&gt;The following performance improvements are now enabled by default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;enable_adaptive_aggregator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enable_group_by_top_k_optimization&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enable_packed_string_keys_in_aggregation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enable_parallel_single_level_merge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;read_in_order_use_virtual_row&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query_plan_push_down_volume_reducing_functions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query_plan_short_circuit_constant_false_join&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use_query_condition_cache_for_top_k&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allow_distinct_partitions_independently&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allow_window_partitions_independently&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allow_creating_set_partitions_independently&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;optimize_trivial_count_with_sparsity_filter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input_format_parquet_spatial_filter_push_down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query_plan_optimize_count_from_text_index&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;materialize_statistics_on_insert&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last setting has a 25 GiB table-size cap.&lt;/p&gt;




&lt;h1&gt;
  
  
  MergeTree - On-Disk Formats
&lt;/h1&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;Previous&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;th&gt;Compatibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text_index_serialization_version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v1_with_codec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v2_with_positions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Older servers cannot read the new format. Pin to v1 during a rolling upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;packed_skip_index_max_bytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 MiB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;New parts only; still readable by older servers, though pre-26.6 ignores packed indices for pruning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compute_exact_num_defaults_for_sparse_columns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The flag in &lt;code&gt;serialization.json&lt;/code&gt; is ignored by older versions, so parts survive downgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text_index_posting_list_apply_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;materialize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lazy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Posting lists decoded on demand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text_index_max_memory_usage_before_flush&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;unlimited&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1 GiB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Memory-based flush trigger for index builders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One protocol-level note: the native protocol changed how &lt;code&gt;String&lt;/code&gt; columns are transmitted — a separate stream of cumulative byte offsets followed by concatenated data — once both peers are on revision 54489 or later.&lt;/p&gt;

&lt;p&gt;This is around 4x faster for client-side reads.&lt;/p&gt;

&lt;p&gt;It is negotiated by protocol revision, so old clients and servers are unaffected, but maintainers of custom native-protocol clients should handle the new revision.&lt;/p&gt;




&lt;h1&gt;
  
  
  What's New
&lt;/h1&gt;

&lt;h2&gt;
  
  
  SQL Surface
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pipe operators
&lt;/h3&gt;

&lt;p&gt;GoogleSQL-style &lt;code&gt;|&amp;gt;&lt;/code&gt; chaining is now supported.&lt;/p&gt;

&lt;p&gt;Each pipe wraps the preceding query in a subquery, so the resulting AST matches the nested equivalent.&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;FROM&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;
&lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;AGGREGATE&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&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;user_id&lt;/span&gt;
&lt;span class="o"&gt;|&amp;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;total&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="o"&gt;|&amp;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;In a query starting with &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;SELECT&lt;/code&gt; is now optional and defaults to:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;GROUPS&lt;/code&gt; window frame mode
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;GROUPS&lt;/code&gt; is a SQL:2011 window frame mode.&lt;/p&gt;

&lt;p&gt;Frame boundaries count whole peer groups rather than physical rows or value distances, completing the set alongside &lt;code&gt;ROWS&lt;/code&gt; and &lt;code&gt;RANGE&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Query AST as JSON
&lt;/h3&gt;

&lt;p&gt;New functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parseQueryToJSON
formatQueryFromJSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An experimental &lt;code&gt;ClickHouse®_json&lt;/code&gt; dialect is also available behind:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Also new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;arr[indexes]&lt;/code&gt; for subscripting an array with an array of positions&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notHas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gini&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mergedJSONPatch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Server and Operations
&lt;/h1&gt;

&lt;h3&gt;
  
  
  SQL-defined HTTP handlers
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CREATE HANDLER&lt;/code&gt;, &lt;code&gt;ALTER HANDLER&lt;/code&gt; and &lt;code&gt;DROP HANDLER&lt;/code&gt; define custom HTTP endpoints from SQL.&lt;/p&gt;

&lt;p&gt;Handlers can be persisted locally or in Keeper.&lt;/p&gt;

&lt;p&gt;Supporting additions include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;currentHandler()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;currentRequestURL()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system.handlers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http_handler_name&lt;/code&gt; in &lt;code&gt;system.query_log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;http_request_url&lt;/code&gt; in &lt;code&gt;system.query_log&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams maintaining small API services that exist only to expose a query over HTTP, this is worth evaluating.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;run_query_in_background&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The server accepts the query, returns immediately, and runs it to completion regardless of what happens to the connection.&lt;/p&gt;

&lt;p&gt;Track it by &lt;code&gt;query_id&lt;/code&gt; in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;system.processes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system.query_log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is aimed at long-running operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;INSERT SELECT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CREATE TABLE AS SELECT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POPULATE&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Atomic &lt;code&gt;POPULATE&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A plain:&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;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;POPULATE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is now locally atomic, controlled by:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and enabled by default.&lt;/p&gt;

&lt;p&gt;Rows inserted through the same server during population are no longer missed or duplicated.&lt;/p&gt;

&lt;p&gt;This applies to the local insert path only and requires a snapshot-capable source — the MergeTree family or Memory.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE OR REPLACE&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;Replicated&lt;/code&gt; databases keep the legacy path.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POPULATE&lt;/code&gt; also works with &lt;code&gt;TO&lt;/code&gt; now.&lt;/p&gt;




&lt;h3&gt;
  
  
  Introspection port
&lt;/h3&gt;

&lt;p&gt;A native-protocol TCP listener starts before tables attach and stops after detach completes.&lt;/p&gt;

&lt;p&gt;This makes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW PROCESSLIST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.stack_trace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;available during startup and shutdown.&lt;/p&gt;

&lt;p&gt;Alongside it, &lt;code&gt;shutdown_wait_unfinished&lt;/code&gt; moved from 5 seconds to 120 seconds.&lt;/p&gt;

&lt;p&gt;The old default was shorter than the connection poll interval.&lt;/p&gt;




&lt;h3&gt;
  
  
  Keeper on-disk storage
&lt;/h3&gt;

&lt;p&gt;Keeper now supports on-disk storage via a custom LSM tree.&lt;/p&gt;

&lt;p&gt;Enable with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use_lsmt_storage = true
storage_memory_only = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data_storage_path
&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 plaintext"&gt;&lt;code&gt;data_storage_disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can point at an &lt;code&gt;s3_plain&lt;/code&gt; disk for S3.&lt;/p&gt;

&lt;p&gt;The Keeper dashboard also gains a Cluster tab showing Raft membership as a topology graph.&lt;/p&gt;




&lt;h3&gt;
  
  
  HTTP URL-path access to tables
&lt;/h3&gt;

&lt;p&gt;Tables can now be accessed through HTTP URL paths such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/database/table.format.gz?filter=a&amp;gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is behind a set of &lt;code&gt;http_allow_*&lt;/code&gt; opt-ins.&lt;/p&gt;




&lt;h3&gt;
  
  
  Framing output formats
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;framing_output_format&lt;/code&gt; multiplexes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data chunks&lt;/li&gt;
&lt;li&gt;totals and extremes&lt;/li&gt;
&lt;li&gt;progress&lt;/li&gt;
&lt;li&gt;profile events&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;exceptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;into a single HTTP stream.&lt;/p&gt;

&lt;p&gt;Available formats include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EventStream
JSONEachPacketBase64
JSONEachPacketString
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Also new:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default_session_user&lt;/code&gt; as a server setting&lt;/li&gt;
&lt;li&gt;Prometheus constant labels via a &lt;code&gt;&amp;lt;labels&amp;gt;&lt;/code&gt; element inside &lt;code&gt;&amp;lt;prometheus&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ALTER TABLE ... MODIFY PROJECTION&lt;/code&gt; to change projection settings without a rebuild&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Projection changes are applied lazily via merges.&lt;/p&gt;




&lt;h1&gt;
  
  
  Observability
&lt;/h1&gt;

&lt;p&gt;Several useful observability improvements landed in 26.8:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;system.user_query_log&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Every user sees their own query log rows without needing access to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.query_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;create_union_system_log_tables&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This creates auto-maintained &lt;code&gt;all_...&lt;/code&gt; tables such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.all_query_log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;a log table&lt;/li&gt;
&lt;li&gt;its rotated versions&lt;/li&gt;
&lt;li&gt;the same table across cluster replicas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;remote&lt;/code&gt;, &lt;code&gt;remoteSecure&lt;/code&gt;, &lt;code&gt;cluster&lt;/code&gt; and &lt;code&gt;clusterAllReplicas&lt;/code&gt; now accept a trailing &lt;code&gt;SETTINGS&lt;/code&gt; clause.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;system.mutations.finish_time&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides mutation duration without having to infer it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;system.tables.skipping_indices_types&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Provides a cheap summary of which index types a table uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Play UI
&lt;/h3&gt;

&lt;p&gt;The Play UI now supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;server-side sorting&lt;/li&gt;
&lt;li&gt;filtering&lt;/li&gt;
&lt;li&gt;paging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These settings are encoded in the page URL so a shared link reproduces the result.&lt;/p&gt;




&lt;h1&gt;
  
  
  Joins and Text Search
&lt;/h1&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;IEJoin&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;IEJoin&lt;/code&gt; is a sort-based algorithm for &lt;code&gt;ON&lt;/code&gt; clauses containing two inequality comparisons.&lt;/p&gt;

&lt;p&gt;Previously such joins ran as a CROSS JOIN with a filter, and only as INNER.&lt;/p&gt;

&lt;p&gt;Enable by adding:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

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

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;code&gt;parallel_full_sorting_merge&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;parallel_full_sorting_merge&lt;/code&gt; shards a full sorting merge join by join-key hash across threads.&lt;/p&gt;

&lt;p&gt;Upstream benchmarks put it around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2.4x faster&lt;/li&gt;
&lt;li&gt;3.3x lighter on memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;than &lt;code&gt;parallel_hash&lt;/code&gt;, while keeping streaming memory behaviour.&lt;/p&gt;

&lt;p&gt;The result is unordered.&lt;/p&gt;




&lt;h3&gt;
  
  
  New text index tokenizers
&lt;/h3&gt;

&lt;p&gt;New tokenizers include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;japanese&lt;/code&gt; — MeCab&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chinese&lt;/code&gt; — jieba-style dictionary plus HMM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;icu&lt;/code&gt; — locale-aware Unicode segmentation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;splitByRegexp&lt;/code&gt; — keeps tokens such as &lt;code&gt;C++&lt;/code&gt; and &lt;code&gt;C#&lt;/code&gt; intact&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Data Lakes
&lt;/h1&gt;

&lt;p&gt;A number of data-lake integrations have been expanded.&lt;/p&gt;

&lt;h3&gt;
  
  
  BigQuery
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;bigquery&lt;/code&gt; table function and &lt;code&gt;BigQuery&lt;/code&gt; table engine are now available.&lt;/p&gt;

&lt;p&gt;Authentication supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth token&lt;/li&gt;
&lt;li&gt;service account JSON&lt;/li&gt;
&lt;li&gt;refresh token&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Snowflake Horizon
&lt;/h3&gt;

&lt;p&gt;Snowflake Horizon catalog support is available for reading and writing Iceberg.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 Tables
&lt;/h3&gt;

&lt;p&gt;The S3 Tables catalog now supports:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Puffin
&lt;/h3&gt;

&lt;p&gt;Puffin file format support has been added.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL database engine
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;URL&lt;/code&gt; database engine and &lt;code&gt;s3_base&lt;/code&gt; setting complete the URL unification.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ClickHouse®-local&lt;/code&gt;'s default database now uses it with a &lt;code&gt;file://&lt;/code&gt; base.&lt;/p&gt;

&lt;p&gt;This means:&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="s1"&gt;'https://example.com/data.csv'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works directly.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Functions
&lt;/h1&gt;

&lt;p&gt;The experimental:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aiSimilarity&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiFilter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aiRedact&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;functions were hardened in this release.&lt;/p&gt;

&lt;p&gt;Insecure &lt;code&gt;http&lt;/code&gt; endpoints to remote hosts are denied by default.&lt;/p&gt;

&lt;p&gt;Outbound calls per query are bounded at:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Provider error responses are also sanitised before logging.&lt;/p&gt;




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

&lt;p&gt;ClickHouse® 26.8 contains &lt;strong&gt;127 performance entries&lt;/strong&gt;, with the majority enabled by default.&lt;/p&gt;

&lt;p&gt;The improvements with the broadest reach include:&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregation
&lt;/h2&gt;

&lt;p&gt;A new adaptive parallel &lt;code&gt;GROUP BY&lt;/code&gt; allows each thread to aggregate into its own cache-resident hash table until it hits a threshold, then freezes it.&lt;/p&gt;

&lt;p&gt;Other aggregation improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bounded-heap pruning for &lt;code&gt;GROUP BY ... ORDER BY ... LIMIT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;smaller hash-table cells for single-&lt;code&gt;String&lt;/code&gt; keys&lt;/li&gt;
&lt;li&gt;parallelised final merge of single-level tables&lt;/li&gt;
&lt;li&gt;aggregations without aggregate functions now use &lt;code&gt;HashSet&lt;/code&gt; rather than &lt;code&gt;HashMap&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The latter can be up to 1.8x faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reads
&lt;/h2&gt;

&lt;p&gt;Lazy materialization for Parquet on object storage can significantly reduce I/O.&lt;/p&gt;

&lt;p&gt;For example, on a 200 MB S3 file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORDER BY ... LIMIT 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3.3 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;171 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That represents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;51x less I/O&lt;/li&gt;
&lt;li&gt;8x faster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;read_in_order_use_virtual_row&lt;/code&gt; is enabled by default and reduces peak memory when reading in primary key order across many parts.&lt;/p&gt;

&lt;p&gt;The Parquet V3 reader also gains dictionary-page-based row group skipping.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-Partition Processing
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DISTINCT&lt;/code&gt;, window functions and &lt;code&gt;IN (subquery)&lt;/code&gt; set building can now keep each partition's rows in a single stream when the partition expression is a deterministic function of the relevant columns.&lt;/p&gt;

&lt;p&gt;This skips the hash scatter entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Important Trade-Off
&lt;/h2&gt;

&lt;p&gt;The trade-off worth stating plainly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query plans and resource usage will change after upgrading, even for queries you did not touch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most workloads benefit, but plan for a period of observation rather than assuming parity.&lt;/p&gt;




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

&lt;h2&gt;
  
  
  Before Upgrading
&lt;/h2&gt;

&lt;p&gt;Check for dictionaries using the removed library source:&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="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;source&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;dictionaries&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="k"&gt;ILIKE&lt;/span&gt; &lt;span class="s1"&gt;'%library%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check non-default settings you are carrying:&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;default&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;settings&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&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;server_settings&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check by hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Named collections and dictionaries using MySQL &lt;code&gt;ssl_ca&lt;/code&gt; / &lt;code&gt;ssl_cert&lt;/code&gt; / &lt;code&gt;ssl_key&lt;/code&gt; paths&lt;/li&gt;
&lt;li&gt;NATS tables or collections using &lt;code&gt;nats_credential_file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reliance on the implicit &lt;code&gt;/etc/metrika.xml&lt;/code&gt; substitutions file&lt;/li&gt;
&lt;li&gt;PostgreSQL / MaterializedPostgreSQL databases against hosts outside &lt;code&gt;remote_url_allow_hosts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ingestion clients writing unquoted epoch numbers into &lt;code&gt;DateTime64&lt;/code&gt; columns via &lt;code&gt;JSONEachRow&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Queries using &lt;code&gt;toDate32&lt;/code&gt; on epoch-second values&lt;/li&gt;
&lt;li&gt;Anything parsing &lt;code&gt;EXPLAIN SYNTAX&lt;/code&gt; output&lt;/li&gt;
&lt;li&gt;Tooling reading &lt;code&gt;system.users.valid_until&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dashboards keyed on individual per-CPU or per-device asynchronous metric names&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  During a Rolling Upgrade
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;patch_parts_version = 'v1'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text_index_serialization_version = 'v1_with_codec'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;until every replica is on 26.8.&lt;/p&gt;

&lt;p&gt;Then remove both pins.&lt;/p&gt;




&lt;h2&gt;
  
  
  After Upgrading
&lt;/h2&gt;

&lt;p&gt;Watch part counts and merge queue depth in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.parts
system.merges
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for the &lt;code&gt;max_insert_threads&lt;/code&gt; effect.&lt;/p&gt;

&lt;p&gt;Also monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system.errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNACCEPTABLE_URL
ILLEGAL_COLUMN
UNKNOWN_ELEMENT_IN_CONFIG
BAD_ARGUMENTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These cover most of the new rejections.&lt;/p&gt;

&lt;p&gt;If you want to stage the transition:&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;compatibility&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'26.7'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This restores the majority of the default flips in one move, letting you upgrade the binaries first and enable the new behaviour deliberately afterwards.&lt;/p&gt;

&lt;p&gt;It does not cover removals or type changes — those need code fixes, which the checks above should surface.&lt;/p&gt;




&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The three changes most likely to affect a running deployment are:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;max_insert_threads&lt;/code&gt; defaulting to &lt;code&gt;auto&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This changes part creation patterns and insert row order on every:&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;SELECT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Asynchronous metric consolidation
&lt;/h3&gt;

&lt;p&gt;This can break dashboards silently rather than loudly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Patch parts v2 format
&lt;/h3&gt;

&lt;p&gt;This requires a rolling-upgrade pin on replicated clusters.&lt;/p&gt;

&lt;p&gt;Beyond that, 26.8 is a strong release.&lt;/p&gt;

&lt;p&gt;The security tightening around credential handling is overdue and welcome.&lt;/p&gt;

&lt;p&gt;The operational additions — SQL-defined handlers, background queries, atomic &lt;code&gt;POPULATE&lt;/code&gt;, and the introspection port — address real gaps.&lt;/p&gt;

&lt;p&gt;And the &lt;strong&gt;127 performance improvements&lt;/strong&gt;, mostly enabled by default, represent a meaningful return on the upgrade work.&lt;/p&gt;

&lt;p&gt;The key lesson is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat ClickHouse® 26.8 as a real upgrade project, not just a version bump.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Review the defaults, test workload behaviour, validate observability, and plan the rollout carefully.&lt;/p&gt;

&lt;p&gt;Read more... &lt;a href="https://www.ch-ops.io/blog/clickhouse-268-lts-release-whats-new-and-why-it-matters" rel="noopener noreferrer"&gt;https://www.ch-ops.io/blog/clickhouse-268-lts-release-whats-new-and-why-it-matters&lt;/a&gt;&lt;/p&gt;

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