<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kanishga Subramani</title>
    <description>The latest articles on DEV Community by Kanishga Subramani (@kanishga_subramani_49ad73).</description>
    <link>https://dev.to/kanishga_subramani_49ad73</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3951880%2F08e2b1d3-1c3e-4280-91fc-99fd18e39198.jpg</url>
      <title>DEV Community: Kanishga Subramani</title>
      <link>https://dev.to/kanishga_subramani_49ad73</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kanishga_subramani_49ad73"/>
    <language>en</language>
    <item>
      <title>Day 76/100 - Working with ClickHouse® LowCardinality and Enum Types</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Mon, 13 Jul 2026 13:47:33 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-76100-working-with-clickhouser-lowcardinality-and-enum-types-e34</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-76100-working-with-clickhouser-lowcardinality-and-enum-types-e34</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Choosing the right data types is one of the simplest yet most effective ways to optimize a ClickHouse® database. While query tuning and hardware upgrades often receive the most attention, selecting appropriate column types can significantly reduce storage requirements, improve compression, accelerate query execution, and lower memory consumption—all without changing application logic.&lt;/p&gt;

&lt;p&gt;Two powerful optimization features available in ClickHouse® are &lt;strong&gt;LowCardinality&lt;/strong&gt; and &lt;strong&gt;Enum&lt;/strong&gt;. Although both are designed to store repeated values efficiently, they address different use cases and should not be considered interchangeable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LowCardinality&lt;/strong&gt; uses dictionary encoding to optimize columns containing many repeated string values, while &lt;strong&gt;Enum&lt;/strong&gt; stores predefined string values as compact integer codes. Choosing the right one depends on factors such as cardinality, schema flexibility, and how frequently values change over time.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how both data types work, compare their advantages and limitations, examine common use cases, and discuss best practices for designing efficient ClickHouse® schemas.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Data Types Matter in ClickHouse®
&lt;/h1&gt;

&lt;p&gt;ClickHouse® is a column-oriented analytical database.&lt;/p&gt;

&lt;p&gt;Because each column is stored independently, the selected data type directly influences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage utilization&lt;/li&gt;
&lt;li&gt;Compression efficiency&lt;/li&gt;
&lt;li&gt;Query execution speed&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Network transfer during distributed queries&lt;/li&gt;
&lt;li&gt;CPU utilization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even small schema improvements can have a significant impact.&lt;/p&gt;

&lt;p&gt;For columns containing highly repetitive string values, replacing a standard &lt;code&gt;String&lt;/code&gt; type with an optimized alternative often improves compression ratios by &lt;strong&gt;3× to 5×&lt;/strong&gt; while accelerating &lt;code&gt;GROUP BY&lt;/code&gt; operations by &lt;strong&gt;2× to 4×&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These improvements become increasingly valuable as datasets grow into hundreds of millions or billions of rows.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding LowCardinality
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LowCardinality&lt;/code&gt; is a special wrapper type that applies &lt;strong&gt;dictionary encoding&lt;/strong&gt; to columns containing many repeated values.&lt;/p&gt;

&lt;p&gt;Instead of storing the complete string in every row, ClickHouse stores each unique value once in a dictionary and replaces repeated occurrences with compact integer identifiers.&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;events&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;country&lt;/span&gt; &lt;span class="n"&gt;LowCardinality&lt;/span&gt;&lt;span class="p"&gt;(&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;browser&lt;/span&gt; &lt;span class="n"&gt;LowCardinality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&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;event_time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, the dictionary might look like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dictionary ID&lt;/th&gt;
&lt;th&gt;Value&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;India&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Germany&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;United States&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;France&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rows store only the integer IDs instead of repeatedly storing full strings.&lt;/p&gt;

&lt;p&gt;This significantly reduces storage and improves query performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  The 10,000-Value Rule
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;LowCardinality&lt;/code&gt; performs best when a data part contains &lt;strong&gt;fewer than approximately 10,000 unique values&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the number of unique values becomes significantly larger, dictionary encoding becomes less effective.&lt;/p&gt;

&lt;p&gt;In such situations, ClickHouse may internally fall back to treating the column similarly to a regular &lt;code&gt;String&lt;/code&gt;, reducing the performance benefits.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;LowCardinality&lt;/code&gt; particularly well suited for columns such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Country names&lt;/li&gt;
&lt;li&gt;Browser names&lt;/li&gt;
&lt;li&gt;Device types&lt;/li&gt;
&lt;li&gt;Product categories&lt;/li&gt;
&lt;li&gt;Application names&lt;/li&gt;
&lt;li&gt;Status values&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Late Deserialization
&lt;/h1&gt;

&lt;p&gt;One of the major reasons &lt;code&gt;LowCardinality&lt;/code&gt; improves query performance is a technique known as &lt;strong&gt;late deserialization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of immediately converting dictionary IDs back into strings, ClickHouse performs filtering, grouping, and aggregation directly on the compact integer values.&lt;/p&gt;

&lt;p&gt;Only when returning the final query results does ClickHouse translate the IDs back into human-readable strings.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rows
   │
   ▼
Dictionary IDs
   │
   ▼
Filtering / GROUP BY / Aggregation
   │
   ▼
Dictionary Lookup
   │
   ▼
Final Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Processing integers instead of long strings reduces CPU usage, memory bandwidth, and comparison costs.&lt;/p&gt;




&lt;h1&gt;
  
  
  How LowCardinality Improves Performance
&lt;/h1&gt;

&lt;p&gt;Dictionary encoding provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller storage footprint&lt;/li&gt;
&lt;li&gt;Better compression ratios&lt;/li&gt;
&lt;li&gt;Reduced memory consumption&lt;/li&gt;
&lt;li&gt;Faster integer comparisons&lt;/li&gt;
&lt;li&gt;Improved &lt;code&gt;GROUP BY&lt;/code&gt; performance&lt;/li&gt;
&lt;li&gt;Lower disk I/O&lt;/li&gt;
&lt;li&gt;Better cache efficiency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These improvements become increasingly noticeable as data volume grows.&lt;/p&gt;




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

&lt;p&gt;&lt;code&gt;Enum&lt;/code&gt; stores string values internally as fixed integer codes.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;LowCardinality&lt;/code&gt;, every possible value must be defined when the table is created.&lt;/p&gt;

&lt;p&gt;ClickHouse provides two Enum types:&lt;/p&gt;

&lt;h2&gt;
  
  
  Enum8
&lt;/h2&gt;

&lt;p&gt;Uses &lt;strong&gt;1 byte&lt;/strong&gt; per value.&lt;/p&gt;

&lt;p&gt;Supports up to &lt;strong&gt;256&lt;/strong&gt; unique string mappings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enum16
&lt;/h2&gt;

&lt;p&gt;Uses &lt;strong&gt;2 bytes&lt;/strong&gt; per value.&lt;/p&gt;

&lt;p&gt;Supports up to &lt;strong&gt;65,536&lt;/strong&gt; unique string mappings.&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;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;UInt64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;Enum8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'Pending'&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="s1"&gt;'Processing'&lt;/span&gt; &lt;span class="o"&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;'Completed'&lt;/span&gt; &lt;span class="o"&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;'Cancelled'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
    &lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although integer values are stored internally, queries remain easy to read.&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;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Completed'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers continue working with descriptive strings while ClickHouse stores compact numeric values behind the scenes.&lt;/p&gt;




&lt;h1&gt;
  
  
  LowCardinality vs Enum
&lt;/h1&gt;

&lt;p&gt;Although both optimize repeated values, they solve different problems.&lt;/p&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;LowCardinality&lt;/th&gt;
&lt;th&gt;Enum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dictionary encoding&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stores integer IDs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema flexibility&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easy to add new values&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Requires ALTER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best suited for&lt;/td&gt;
&lt;td&gt;Medium-cardinality strings&lt;/td&gt;
&lt;td&gt;Small fixed value sets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compression&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query readability&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simple guideline is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;LowCardinality&lt;/strong&gt; when values change over time.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Enum&lt;/strong&gt; when values are fixed and rarely change.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Choosing Between LowCardinality and Enum
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Good candidates for LowCardinality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Countries&lt;/li&gt;
&lt;li&gt;Cities&lt;/li&gt;
&lt;li&gt;Browsers&lt;/li&gt;
&lt;li&gt;Device models&lt;/li&gt;
&lt;li&gt;Product categories&lt;/li&gt;
&lt;li&gt;Application names&lt;/li&gt;
&lt;li&gt;Languages&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Good candidates for Enum
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Order status&lt;/li&gt;
&lt;li&gt;Payment status&lt;/li&gt;
&lt;li&gt;User roles&lt;/li&gt;
&lt;li&gt;Approval states&lt;/li&gt;
&lt;li&gt;Workflow stages&lt;/li&gt;
&lt;li&gt;Boolean-like states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Selecting the correct type improves both performance and long-term schema maintainability.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Not to Use LowCardinality
&lt;/h1&gt;

&lt;p&gt;Although powerful, &lt;code&gt;LowCardinality&lt;/code&gt; isn't suitable for every column.&lt;/p&gt;

&lt;p&gt;Avoid using it for values that are almost always unique.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;UUIDs&lt;/li&gt;
&lt;li&gt;Email addresses&lt;/li&gt;
&lt;li&gt;Session IDs&lt;/li&gt;
&lt;li&gt;Customer IDs&lt;/li&gt;
&lt;li&gt;Transaction IDs&lt;/li&gt;
&lt;li&gt;Random strings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When nearly every value is unique, maintaining a dictionary introduces overhead without meaningful benefits.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Not to Use Enum
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;Enum&lt;/code&gt; works best when values remain stable over time.&lt;/p&gt;

&lt;p&gt;It's usually a poor choice for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product names&lt;/li&gt;
&lt;li&gt;Customer names&lt;/li&gt;
&lt;li&gt;Tags&lt;/li&gt;
&lt;li&gt;Dynamic categories&lt;/li&gt;
&lt;li&gt;Free-text labels&lt;/li&gt;
&lt;li&gt;User-generated values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If new values are introduced frequently, schema modifications become inconvenient.&lt;/p&gt;

&lt;p&gt;In these cases, &lt;code&gt;LowCardinality&lt;/code&gt; is generally the better option.&lt;/p&gt;




&lt;h1&gt;
  
  
  Operational Considerations
&lt;/h1&gt;

&lt;p&gt;One important aspect of &lt;code&gt;Enum&lt;/code&gt; involves schema modifications.&lt;/p&gt;

&lt;p&gt;Adding a &lt;strong&gt;new value to the end&lt;/strong&gt; of an existing Enum definition is typically a lightweight metadata operation.&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;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;MODIFY&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="n"&gt;Enum8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'Pending'&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="s1"&gt;'Processing'&lt;/span&gt; &lt;span class="o"&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;'Completed'&lt;/span&gt; &lt;span class="o"&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;'Cancelled'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Refunded'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, modifying or reordering existing integer mappings forces ClickHouse to rewrite underlying data parts.&lt;/p&gt;

&lt;p&gt;On large production tables, this can become an expensive operation.&lt;/p&gt;

&lt;p&gt;Careful planning of Enum values helps avoid unnecessary maintenance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Measuring the Impact
&lt;/h1&gt;

&lt;p&gt;The effectiveness of these optimizations depends on your workload.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Column storage size&lt;/li&gt;
&lt;li&gt;Compression ratio&lt;/li&gt;
&lt;li&gt;Query latency&lt;/li&gt;
&lt;li&gt;GROUP BY execution time&lt;/li&gt;
&lt;li&gt;Memory consumption&lt;/li&gt;
&lt;li&gt;CPU utilization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse system tables make it easy to evaluate storage efficiency.&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="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;formatReadableSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_compressed_bytes&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;compressed_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;formatReadableSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_uncompressed_bytes&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;uncompressed_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data_uncompressed_bytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;data_compressed_bytes&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;compression_ratio&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;columns&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'events'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always benchmark representative production workloads before applying schema changes broadly.&lt;/p&gt;




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

&lt;p&gt;When designing ClickHouse® schemas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;LowCardinality&lt;/code&gt; for repeated string values.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Enum&lt;/code&gt; only for small, stable value sets.&lt;/li&gt;
&lt;li&gt;Avoid wrapping high-cardinality columns with &lt;code&gt;LowCardinality&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Benchmark before deploying schema changes.&lt;/li&gt;
&lt;li&gt;Monitor storage savings using &lt;code&gt;system.columns&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review cardinality as datasets evolve.&lt;/li&gt;
&lt;li&gt;Plan Enum definitions carefully to minimize future ALTER operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small schema decisions can produce substantial long-term performance improvements.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Decision Guide
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If your column contains...&lt;/th&gt;
&lt;th&gt;Recommended Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Countries&lt;/td&gt;
&lt;td&gt;LowCardinality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browsers&lt;/td&gt;
&lt;td&gt;LowCardinality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device types&lt;/td&gt;
&lt;td&gt;LowCardinality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product categories&lt;/td&gt;
&lt;td&gt;LowCardinality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order status&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment status&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User role&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow state&lt;/td&gt;
&lt;td&gt;Enum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUIDs&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email addresses&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This simple checklist can help when designing new schemas.&lt;/p&gt;




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

&lt;p&gt;Both &lt;strong&gt;LowCardinality&lt;/strong&gt; and &lt;strong&gt;Enum&lt;/strong&gt; are valuable optimization features in ClickHouse®, but they address different challenges. &lt;code&gt;LowCardinality&lt;/code&gt; provides flexible dictionary encoding for columns containing repeated string values, reducing storage requirements while accelerating filtering and aggregation. &lt;code&gt;Enum&lt;/code&gt;, on the other hand, offers highly compact storage for predefined value sets by mapping strings to fixed integer codes.&lt;/p&gt;

&lt;p&gt;Rather than treating these data types as interchangeable, evaluate each column based on its cardinality, expected growth, and schema stability. Applying the appropriate type can significantly improve compression, reduce memory usage, and speed up analytical queries—all without requiring changes to application logic.&lt;/p&gt;

&lt;p&gt;As with any optimization, benchmarking representative workloads and monitoring system metrics remain the best way to validate performance improvements and ensure your schema continues to scale efficiently as your data grows.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 75/100 - Bypassing the Page Cache for ClickHouse® I/O Operations</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Mon, 13 Jul 2026 13:31:40 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-75100-bypassing-the-page-cache-for-clickhouser-io-operations-1ljf</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-75100-bypassing-the-page-cache-for-clickhouser-io-operations-1ljf</guid>
      <description>&lt;h1&gt;
  
  
  Bypassing the Page Cache for ClickHouse® I/O Operations
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When discussing database performance, one recommendation often appears:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Bypass the operating system's page cache."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While this advice can be valuable in specific scenarios, it's frequently presented without explaining what the page cache actually does, why ClickHouse® relies on it by default, or when bypassing it improves performance.&lt;/p&gt;

&lt;p&gt;The Linux page cache is designed to accelerate file access by keeping recently read data in memory. For many workloads, this significantly reduces disk I/O and improves query latency. However, for large analytical scans that read vast amounts of data only once, populating the page cache may consume valuable memory without providing any future benefit.&lt;/p&gt;

&lt;p&gt;ClickHouse® offers the ability to bypass the Linux page cache using &lt;strong&gt;Direct I/O&lt;/strong&gt;, allowing qualifying reads to access storage directly. This behavior is controlled through the &lt;code&gt;min_bytes_to_use_direct_io&lt;/code&gt; setting.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how the Linux page cache works, how ClickHouse® interacts with it, what Direct I/O changes, and when enabling it can improve analytical workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding the Linux Page Cache
&lt;/h1&gt;

&lt;p&gt;Before examining ClickHouse®, it's important to understand the role of the Linux page cache.&lt;/p&gt;

&lt;p&gt;Whenever an application performs a standard buffered file read, Linux stores the retrieved data in an in-memory cache known as the &lt;strong&gt;page cache&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the same data is requested again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux serves it directly from memory if it already exists in the cache.&lt;/li&gt;
&lt;li&gt;Otherwise, it reads the data from disk and stores it in the page cache for future access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mechanism is completely transparent to applications. Most software doesn't need to implement its own file caching because the operating system automatically manages frequently accessed data.&lt;/p&gt;

&lt;p&gt;For many workloads, this dramatically reduces disk access and improves performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  How ClickHouse® Reads Data by Default
&lt;/h1&gt;

&lt;p&gt;Contrary to a common misconception, ClickHouse® &lt;strong&gt;does not bypass the Linux page cache by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead, ClickHouse performs &lt;strong&gt;buffered file reads&lt;/strong&gt;, allowing Linux to decide what should remain cached.&lt;/p&gt;

&lt;p&gt;The default read path looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Query
            │
            ▼
      ClickHouse®
            │
     Buffered Read
            │
            ▼
   Linux Page Cache
            │
            ▼
     Storage Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another query accesses the same data shortly afterward, Linux may satisfy the request directly from memory instead of reading from storage again.&lt;/p&gt;

&lt;p&gt;For workloads involving frequently accessed data, this behavior significantly improves performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why the Page Cache Isn't Always Ideal
&lt;/h1&gt;

&lt;p&gt;Although the page cache is extremely useful, it isn't always the optimal choice.&lt;/p&gt;

&lt;p&gt;Imagine a query scanning several hundred gigabytes of historical data exactly once.&lt;/p&gt;

&lt;p&gt;Linux loads every accessed page into memory—even if those pages are unlikely to be read again.&lt;/p&gt;

&lt;p&gt;This creates two potential problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Cache Pollution
&lt;/h2&gt;

&lt;p&gt;Large sequential scans can fill the page cache with data that has little chance of being reused.&lt;/p&gt;

&lt;p&gt;As a result, frequently accessed pages may be evicted from memory.&lt;/p&gt;

&lt;p&gt;This phenomenon is known as &lt;strong&gt;cache pollution&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Memory Competition
&lt;/h2&gt;

&lt;p&gt;The Linux page cache consumes available RAM.&lt;/p&gt;

&lt;p&gt;At the same time, ClickHouse also requires memory for operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query execution&lt;/li&gt;
&lt;li&gt;Hash tables&lt;/li&gt;
&lt;li&gt;Aggregations&lt;/li&gt;
&lt;li&gt;Sorting&lt;/li&gt;
&lt;li&gt;JOIN operations&lt;/li&gt;
&lt;li&gt;Internal caches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Allowing very large scans to populate the page cache reduces the memory available for these operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Does "Bypassing the Page Cache" Mean?
&lt;/h1&gt;

&lt;p&gt;Bypassing the page cache means reading data directly from storage instead of allowing Linux to cache file contents.&lt;/p&gt;

&lt;p&gt;On Linux, this is achieved using the &lt;strong&gt;O_DIRECT&lt;/strong&gt; file flag.&lt;/p&gt;

&lt;p&gt;Instead of the traditional read path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     │
     ▼
Page Cache
     │
     ▼
Disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the data path becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     │
     ▼
Disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File data isn't stored in the Linux page cache.&lt;/li&gt;
&lt;li&gt;Future reads won't benefit from cached file pages.&lt;/li&gt;
&lt;li&gt;RAM is preserved for other database operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's important to understand that Direct I/O bypasses the page cache &lt;strong&gt;only for the specific file operations that use O_DIRECT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; disable the page cache for the entire operating system.&lt;/p&gt;




&lt;h1&gt;
  
  
  How ClickHouse® Enables Direct I/O
&lt;/h1&gt;

&lt;p&gt;ClickHouse controls Direct I/O using the following setting:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;This setting defines the minimum amount of data a query must read before ClickHouse switches from buffered I/O to Direct I/O.&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;SET&lt;/span&gt; &lt;span class="n"&gt;min_bytes_to_use_direct_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;104857600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configures ClickHouse to use Direct I/O whenever a query is expected to read more than &lt;strong&gt;100 MB&lt;/strong&gt; of data.&lt;/p&gt;

&lt;p&gt;If the setting remains:&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="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Direct I/O is disabled, which is also the default behavior.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Isn't Direct I/O Enabled by Default?
&lt;/h1&gt;

&lt;p&gt;A common question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Direct I/O reduces memory usage, why doesn't ClickHouse always use it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is simple.&lt;/p&gt;

&lt;p&gt;The Linux page cache is extremely effective for workloads involving repeated access to the same data.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;BI dashboards&lt;/li&gt;
&lt;li&gt;Frequently executed reports&lt;/li&gt;
&lt;li&gt;Hot partitions&lt;/li&gt;
&lt;li&gt;Interactive analytics&lt;/li&gt;
&lt;li&gt;Repeated ad-hoc queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these situations, Linux serves many reads directly from RAM.&lt;/p&gt;

&lt;p&gt;If Direct I/O were always enabled, every query would need to fetch data from storage again, potentially increasing latency.&lt;/p&gt;

&lt;p&gt;For this reason, ClickHouse uses buffered I/O by default and allows administrators to enable Direct I/O only when appropriate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Direct I/O Does Not Disable ClickHouse® Caches
&lt;/h1&gt;

&lt;p&gt;Another common misconception is that enabling Direct I/O disables all caching inside ClickHouse.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;ClickHouse includes several independent caching mechanisms, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mark Cache&lt;/li&gt;
&lt;li&gt;Uncompressed Block Cache&lt;/li&gt;
&lt;li&gt;Query Cache (when enabled)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These caches operate independently of the Linux page cache.&lt;/p&gt;

&lt;p&gt;Enabling Direct I/O changes &lt;strong&gt;only how file data is read from storage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ClickHouse's internal caches continue functioning normally.&lt;/p&gt;




&lt;h1&gt;
  
  
  When Direct I/O Makes Sense
&lt;/h1&gt;

&lt;p&gt;Direct I/O is most beneficial for workloads that perform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very large sequential scans&lt;/li&gt;
&lt;li&gt;One-time analytical queries&lt;/li&gt;
&lt;li&gt;Bulk historical analysis&lt;/li&gt;
&lt;li&gt;Large ETL operations&lt;/li&gt;
&lt;li&gt;Data migration workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's particularly useful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data is unlikely to be read again&lt;/li&gt;
&lt;li&gt;Cache pollution would reduce performance&lt;/li&gt;
&lt;li&gt;Predictable memory usage is more important than maximizing cache hits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many large-scale analytical workloads fall into this category.&lt;/p&gt;




&lt;h1&gt;
  
  
  When You Should Avoid Direct I/O
&lt;/h1&gt;

&lt;p&gt;Direct I/O isn't a universal optimization.&lt;/p&gt;

&lt;p&gt;It may actually reduce performance when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same data is queried repeatedly&lt;/li&gt;
&lt;li&gt;The working dataset fits comfortably into memory&lt;/li&gt;
&lt;li&gt;BI dashboards frequently access hot partitions&lt;/li&gt;
&lt;li&gt;Storage latency is significantly higher than RAM latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these scenarios, the Linux page cache provides substantial performance benefits by serving repeated reads directly from memory.&lt;/p&gt;




&lt;h1&gt;
  
  
  Choosing Between Buffered I/O and Direct I/O
&lt;/h1&gt;

&lt;p&gt;The right choice depends on your workload.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Buffered I/O&lt;/th&gt;
&lt;th&gt;Direct I/O&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repeated queries&lt;/td&gt;
&lt;td&gt;Large sequential scans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot datasets&lt;/td&gt;
&lt;td&gt;One-time scans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboards&lt;/td&gt;
&lt;td&gt;Historical analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High cache reuse&lt;/td&gt;
&lt;td&gt;Low cache reuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faster repeated reads&lt;/td&gt;
&lt;td&gt;Lower cache pollution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relies on Linux page cache&lt;/td&gt;
&lt;td&gt;Bypasses Linux page cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is no universally "better" option.&lt;/p&gt;

&lt;p&gt;The optimal configuration depends on access patterns, available memory, storage performance, and workload characteristics.&lt;/p&gt;




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

&lt;p&gt;Before enabling Direct I/O, consider the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Measure query latency before and after changes.&lt;/li&gt;
&lt;li&gt;Monitor memory usage.&lt;/li&gt;
&lt;li&gt;Observe page cache behavior using Linux monitoring tools.&lt;/li&gt;
&lt;li&gt;Benchmark representative production workloads.&lt;/li&gt;
&lt;li&gt;Avoid enabling Direct I/O solely because it's perceived as faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance tuning should always be guided by real workload measurements rather than general recommendations.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;ClickHouse® uses the Linux page cache by default.&lt;/li&gt;
&lt;li&gt;Direct I/O is enabled using &lt;code&gt;min_bytes_to_use_direct_io&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Direct I/O relies on Linux's &lt;code&gt;O_DIRECT&lt;/code&gt; mechanism.&lt;/li&gt;
&lt;li&gt;Only qualifying reads bypass the page cache.&lt;/li&gt;
&lt;li&gt;ClickHouse's internal caches continue working with Direct I/O enabled.&lt;/li&gt;
&lt;li&gt;Direct I/O is best suited for very large sequential scans.&lt;/li&gt;
&lt;li&gt;Buffered I/O often performs better for frequently accessed datasets.&lt;/li&gt;
&lt;li&gt;Choosing between the two depends entirely on workload characteristics.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;The Linux page cache plays an important role in accelerating file access by keeping frequently used data in memory, and ClickHouse® leverages this behavior by default through buffered I/O. For many analytical workloads—particularly those involving repeated queries, dashboards, or hot datasets—this provides significant performance benefits.&lt;/p&gt;

&lt;p&gt;However, workloads that perform large one-time sequential scans can benefit from Direct I/O, which bypasses the page cache using Linux's &lt;code&gt;O_DIRECT&lt;/code&gt; mechanism. This helps prevent cache pollution and preserves memory for query execution, aggregations, joins, and ClickHouse's own caching layers.&lt;/p&gt;

&lt;p&gt;Ultimately, bypassing the page cache is &lt;strong&gt;not a universal performance optimization&lt;/strong&gt;. It's a workload-specific tuning technique. Understanding your query patterns, memory usage, and storage characteristics is essential before deciding whether buffered I/O or Direct I/O is the better choice for your ClickHouse deployment.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 74/100 - Cross-Datacenter Replication in ClickHouse®</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sun, 12 Jul 2026 13:46:41 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-74100-cross-datacenter-replication-in-clickhouser-2kh1</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-74100-cross-datacenter-replication-in-clickhouser-2kh1</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Modern applications increasingly operate across multiple geographical regions to improve availability, reduce latency, and ensure business continuity. Whether you're building a disaster recovery strategy, enabling multi-region analytics, or serving users closer to their location, replicating data across data centers has become a critical architectural requirement.&lt;/p&gt;

&lt;p&gt;ClickHouse® supports cross-datacenter replication through its &lt;strong&gt;ReplicatedMergeTree&lt;/strong&gt; table engine. Combined with a distributed ClickHouse cluster and a coordination service such as &lt;strong&gt;ClickHouse Keeper&lt;/strong&gt;, it allows organizations to maintain synchronized copies of data across multiple data centers while continuing to deliver high-performance analytical queries.&lt;/p&gt;

&lt;p&gt;This architecture ensures that if one data center becomes unavailable due to hardware failures, network outages, or maintenance, other replicas continue serving requests with minimal disruption.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how cross-datacenter replication works in ClickHouse®, the components involved, how to configure a replicated cluster, monitor replication health, troubleshoot common issues, and follow best practices for reliable deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Use Cross-Datacenter Replication?
&lt;/h1&gt;

&lt;p&gt;Replicating data across multiple data centers offers several operational and business advantages.&lt;/p&gt;

&lt;p&gt;Some of the primary benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High availability&lt;/li&gt;
&lt;li&gt;Disaster recovery&lt;/li&gt;
&lt;li&gt;Regional redundancy&lt;/li&gt;
&lt;li&gt;Business continuity&lt;/li&gt;
&lt;li&gt;Reduced query latency for geographically distributed users&lt;/li&gt;
&lt;li&gt;Improved fault tolerance&lt;/li&gt;
&lt;li&gt;Maintenance with minimal downtime&lt;/li&gt;
&lt;li&gt;Increased read scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cross-datacenter replication is commonly used in industries such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial services&lt;/li&gt;
&lt;li&gt;Telecommunications&lt;/li&gt;
&lt;li&gt;E-commerce&lt;/li&gt;
&lt;li&gt;Healthcare&lt;/li&gt;
&lt;li&gt;SaaS platforms&lt;/li&gt;
&lt;li&gt;Media and streaming services&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  How Replication Works in ClickHouse®
&lt;/h1&gt;

&lt;p&gt;A single-node ClickHouse deployment has one major limitation—if the server becomes unavailable, so does access to your data.&lt;/p&gt;

&lt;p&gt;Replication addresses this challenge by maintaining synchronized copies of data across multiple nodes.&lt;/p&gt;

&lt;p&gt;ClickHouse replication is powered by the &lt;strong&gt;ReplicatedMergeTree&lt;/strong&gt; table engine.&lt;/p&gt;

&lt;p&gt;Instead of copying entire tables repeatedly, ClickHouse replicates only the newly created data parts. Metadata about these parts is coordinated using &lt;strong&gt;ClickHouse Keeper&lt;/strong&gt;, while each replica maintains its own local copy of the data.&lt;/p&gt;

&lt;p&gt;This architecture minimizes network traffic while keeping replicas synchronized efficiently.&lt;/p&gt;

&lt;p&gt;Some key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fault tolerance—if one replica fails, others continue serving queries.&lt;/li&gt;
&lt;li&gt;High availability with no single point of failure.&lt;/li&gt;
&lt;li&gt;Read scalability by distributing SELECT queries across replicas.&lt;/li&gt;
&lt;li&gt;Zero-downtime maintenance by taking individual nodes offline without interrupting service.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Cluster Overview
&lt;/h1&gt;

&lt;p&gt;For this example, consider a cluster consisting of one shard with three replicas distributed across three different nodes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;IP Address&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node 1&lt;/td&gt;
&lt;td&gt;10.x.x.1&lt;/td&gt;
&lt;td&gt;Replica&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node 2&lt;/td&gt;
&lt;td&gt;10.x.x.2&lt;/td&gt;
&lt;td&gt;Replica&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node 3&lt;/td&gt;
&lt;td&gt;10.x.x.3&lt;/td&gt;
&lt;td&gt;Replica&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each node stores a complete copy of the data.&lt;/p&gt;

&lt;p&gt;If any single node becomes unavailable, the remaining replicas continue processing queries without data loss.&lt;/p&gt;

&lt;p&gt;The overall architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Application
                     │
                     ▼
            Distributed Table
                     │
     ┌───────────────┼───────────────┐
     ▼               ▼               ▼
Node 1          Node 2          Node 3
Replicated      Replicated      Replicated
MergeTree       MergeTree       MergeTree
     └───────────────┼───────────────┘
                     ▼
            ClickHouse® Keeper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Core Components
&lt;/h1&gt;

&lt;p&gt;Cross-datacenter replication relies on three primary components working together.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. ReplicatedMergeTree
&lt;/h2&gt;

&lt;p&gt;This is the storage engine responsible for replication.&lt;/p&gt;

&lt;p&gt;Each replica stores its own copy of the table while automatically synchronizing newly inserted data with other replicas.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. ClickHouse Keeper
&lt;/h2&gt;

&lt;p&gt;ClickHouse Keeper is the coordination service responsible for managing replication metadata.&lt;/p&gt;

&lt;p&gt;It tracks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Available replicas&lt;/li&gt;
&lt;li&gt;Newly created data parts&lt;/li&gt;
&lt;li&gt;Replication queues&lt;/li&gt;
&lt;li&gt;Leader election&lt;/li&gt;
&lt;li&gt;Replica synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeper uses the &lt;strong&gt;Raft consensus algorithm&lt;/strong&gt; to ensure fault-tolerant coordination.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Distributed Table
&lt;/h2&gt;

&lt;p&gt;Applications should interact with a &lt;strong&gt;Distributed&lt;/strong&gt; table instead of local replicated tables.&lt;/p&gt;

&lt;p&gt;The Distributed table automatically routes queries and inserts to the appropriate replicas while hiding the underlying cluster topology.&lt;/p&gt;




&lt;h1&gt;
  
  
  Configuration Overview
&lt;/h1&gt;

&lt;p&gt;Setting up replication requires several configuration files under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/clickhouse-server/config.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each file serves a different purpose.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration File&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;cluster.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines shards and replicas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;keeper.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configures ClickHouse Keeper and RAFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zookeeper.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Specifies Keeper endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;macros.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines shard and replica identifiers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After updating these configuration files, restart ClickHouse on every node before creating replicated tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cluster.xml&lt;/code&gt; is identical across all nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zookeeper.xml&lt;/code&gt; is identical across all nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keeper.xml&lt;/code&gt; contains node-specific &lt;code&gt;server_id&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;macros.xml&lt;/code&gt; contains node-specific replica names.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Creating Replicated Tables
&lt;/h1&gt;

&lt;p&gt;Once the cluster is configured and ClickHouse has been restarted on every node, create the database and tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Create the Database
&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;cluster_1S_3R&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Create the Local Replicated Table
&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders_local&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;cluster_1S_3R&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&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;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;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="n"&gt;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReplicatedMergeTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'/clickhouse/tables/{shard}/orders_local'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'{replica}'&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;The placeholders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{shard}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{replica}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are automatically replaced using values from &lt;code&gt;macros.xml&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Create the Distributed Table
&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders_distributed&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;cluster_1S_3R&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders_local&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;Distributed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cluster_1S_3R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;orders_local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applications should perform all inserts and queries using this Distributed table.&lt;/p&gt;




&lt;h1&gt;
  
  
  Verifying Replication
&lt;/h1&gt;

&lt;p&gt;Insert sample records.&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;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders_distributed&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="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2024-01-01'&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="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2024-01-02'&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;span class="mi"&gt;890&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2024-01-03'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Diana'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;670&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'2024-01-04'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now query the local table from each node.&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;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orders_local&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;Expected output on every replica:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;order_id&lt;/th&gt;
&lt;th&gt;customer&lt;/th&gt;
&lt;th&gt;amount&lt;/th&gt;
&lt;th&gt;order_date&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;1200.00&lt;/td&gt;
&lt;td&gt;2024-01-01&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;450.00&lt;/td&gt;
&lt;td&gt;2024-01-02&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;890.00&lt;/td&gt;
&lt;td&gt;2024-01-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Diana&lt;/td&gt;
&lt;td&gt;670.00&lt;/td&gt;
&lt;td&gt;2024-01-04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seeing identical data across all replicas confirms that replication is functioning correctly.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Replication Works Internally
&lt;/h1&gt;

&lt;p&gt;When an application inserts data into the Distributed table, ClickHouse performs the following sequence automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
      │
      ▼
Distributed Table
      │
      ▼
Selected Replica
      │
      ▼
Writes Data Part
      │
      ▼
ClickHouse Keeper Updates Metadata
      │
      ▼
Remaining Replicas Fetch Data
      │
      ▼
Replication Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process consists of these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Distributed table routes the insert to one replica.&lt;/li&gt;
&lt;li&gt;That replica writes a new data part locally.&lt;/li&gt;
&lt;li&gt;ClickHouse Keeper records the new data part.&lt;/li&gt;
&lt;li&gt;Other replicas detect the update.&lt;/li&gt;
&lt;li&gt;Replicas download the new part.&lt;/li&gt;
&lt;li&gt;Keeper confirms successful synchronization.&lt;/li&gt;
&lt;li&gt;Every replica now contains identical data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This synchronization occurs automatically without requiring manual intervention.&lt;/p&gt;




&lt;h1&gt;
  
  
  Monitoring Replication
&lt;/h1&gt;

&lt;p&gt;Monitoring replica health is essential for production clusters.&lt;/p&gt;

&lt;p&gt;Query the &lt;code&gt;system.replicas&lt;/code&gt; 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;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;replica_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_leader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_readonly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;total_replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;active_replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue_size&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;replicas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'orders_local'&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;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;database&lt;/th&gt;
&lt;th&gt;table&lt;/th&gt;
&lt;th&gt;replica_name&lt;/th&gt;
&lt;th&gt;is_leader&lt;/th&gt;
&lt;th&gt;is_readonly&lt;/th&gt;
&lt;th&gt;total_replicas&lt;/th&gt;
&lt;th&gt;active_replicas&lt;/th&gt;
&lt;th&gt;queue_size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;analytics&lt;/td&gt;
&lt;td&gt;orders_local&lt;/td&gt;
&lt;td&gt;replica_1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;analytics&lt;/td&gt;
&lt;td&gt;orders_local&lt;/td&gt;
&lt;td&gt;replica_2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;analytics&lt;/td&gt;
&lt;td&gt;orders_local&lt;/td&gt;
&lt;td&gt;replica_3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Healthy replication typically shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;active_replicas = total_replicas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_readonly = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;queue_size = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Checking Replication Errors
&lt;/h1&gt;

&lt;p&gt;To identify replication failures:&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;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;replica_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_exception&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;replicas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;last_exception&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any returned rows indicate replication issues that require investigation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Verifying ClickHouse Keeper
&lt;/h1&gt;

&lt;p&gt;You can verify Keeper availability using:&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;echo&lt;/span&gt; &lt;span class="s2"&gt;"ruok"&lt;/span&gt; | nc 10.x.x.1 9181
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected response:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If Keeper does not respond, replication will eventually stop because replicas can no longer coordinate metadata.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Issues and Solutions
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Likely Cause&lt;/th&gt;
&lt;th&gt;Recommended Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Replica becomes read-only&lt;/td&gt;
&lt;td&gt;Keeper unavailable&lt;/td&gt;
&lt;td&gt;Verify Keeper connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication queue continues growing&lt;/td&gt;
&lt;td&gt;Network latency or firewall&lt;/td&gt;
&lt;td&gt;Check network connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data isn't replicating&lt;/td&gt;
&lt;td&gt;Incorrect ReplicatedMergeTree path&lt;/td&gt;
&lt;td&gt;Verify ZooKeeper/Keeper paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lost Keeper quorum&lt;/td&gt;
&lt;td&gt;Too many Keeper nodes offline&lt;/td&gt;
&lt;td&gt;Restore quorum by bringing nodes back online&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;To build a reliable replicated ClickHouse deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy an odd number of Keeper nodes (3 or 5) to maintain quorum.&lt;/li&gt;
&lt;li&gt;Assign every Keeper node a unique &lt;code&gt;server_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Give every replica a unique name in &lt;code&gt;macros.xml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Always execute DDL statements using &lt;code&gt;ON CLUSTER&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Perform inserts through the Distributed table instead of local tables.&lt;/li&gt;
&lt;li&gt;Monitor &lt;code&gt;system.replicas&lt;/code&gt; regularly for replication lag.&lt;/li&gt;
&lt;li&gt;Restart cluster nodes one at a time.&lt;/li&gt;
&lt;li&gt;Ensure stable, low-latency connectivity between data centers.&lt;/li&gt;
&lt;li&gt;Batch inserts to reduce replication overhead.&lt;/li&gt;
&lt;li&gt;Continuously monitor Keeper health and replication queues.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  When Should You Use Cross-Datacenter Replication?
&lt;/h1&gt;

&lt;p&gt;Cross-datacenter replication is an excellent choice when your environment requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High availability&lt;/li&gt;
&lt;li&gt;Disaster recovery&lt;/li&gt;
&lt;li&gt;Multi-region deployments&lt;/li&gt;
&lt;li&gt;Read scalability&lt;/li&gt;
&lt;li&gt;Business continuity&lt;/li&gt;
&lt;li&gt;Low-latency regional analytics&lt;/li&gt;
&lt;li&gt;Fault-tolerant analytical platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations operating globally often combine replicated tables with distributed queries to provide seamless analytics regardless of where users connect.&lt;/p&gt;




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

&lt;p&gt;Cross-datacenter replication enables ClickHouse® to maintain synchronized copies of data across geographically distributed environments, improving availability, fault tolerance, and disaster recovery capabilities. By combining &lt;strong&gt;ReplicatedMergeTree&lt;/strong&gt;, &lt;strong&gt;ClickHouse Keeper&lt;/strong&gt;, and &lt;strong&gt;Distributed&lt;/strong&gt; tables, organizations can build resilient analytical platforms that continue serving queries even when individual nodes or data centers become unavailable.&lt;/p&gt;

&lt;p&gt;Although network latency and bandwidth should be considered when replicating across regions, following best practices such as batching inserts, maintaining a healthy Keeper cluster, monitoring replication queues, and using Distributed tables for application traffic helps ensure reliable and efficient synchronization.&lt;/p&gt;

&lt;p&gt;For organizations requiring both high-performance analytics and resilient multi-region deployments, ClickHouse provides a scalable and robust replication architecture that minimizes operational complexity while maximizing availability.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 73/100 - Using ClickHouse® for Vector Search and AI Workloads</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sun, 12 Jul 2026 13:27:44 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-73100-using-clickhouser-for-vector-search-and-ai-workloads-3j89</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-73100-using-clickhouser-for-vector-search-and-ai-workloads-3j89</guid>
      <description>&lt;h1&gt;
  
  
  Using ClickHouse® for Vector Search and AI Workloads
&lt;/h1&gt;

&lt;p&gt;Artificial Intelligence (AI) applications are changing how organizations search, analyze, and interact with data. Instead of relying only on exact keyword matches, modern AI systems understand the meaning behind text, images, audio, and other content using vector embeddings and semantic search.&lt;/p&gt;

&lt;p&gt;Traditionally, implementing vector search required deploying a dedicated vector database alongside your operational systems. While effective, this approach increases infrastructure complexity, data synchronization overhead, and operational costs.&lt;/p&gt;

&lt;p&gt;Recent versions of ClickHouse® introduce native support for vector similarity search, allowing you to store embeddings alongside structured data and perform high-performance semantic searches on millions or even billions of vectors. This enables AI applications such as Retrieval-Augmented Generation (RAG), recommendation engines, semantic document search, image similarity search, and intelligent chatbots—all within the same analytical database.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what vector search is, how ClickHouse stores vector embeddings, and how you can build scalable AI workloads using ClickHouse.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is Vector Search?
&lt;/h1&gt;

&lt;p&gt;Traditional search engines rely on keyword matching.&lt;/p&gt;

&lt;p&gt;Suppose your database contains the following product description: ""&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Lightweight running shoes for marathon training."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A user searches for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Best sneakers for long-distance running"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Although both sentences describe the same concept, a keyword search may miss the result because the exact words don't match.&lt;/p&gt;

&lt;p&gt;Vector search solves this problem.&lt;/p&gt;

&lt;p&gt;Instead of comparing words, it compares semantic meaning.&lt;/p&gt;

&lt;p&gt;Both pieces of text are converted into numerical vectors by an embedding model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Lightweight running shoes for marathon training."

↓

[0.21, -0.67, 0.44, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Best sneakers for long-distance running"

↓

[0.18, -0.71, 0.40, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since these vectors are close together in vector space, ClickHouse recognizes that they represent similar concepts and returns the relevant product.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Are Vector Embeddings?
&lt;/h1&gt;

&lt;p&gt;A vector embedding is a numerical representation of the meaning of an object.&lt;/p&gt;

&lt;p&gt;Embeddings can represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text&lt;/li&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;li&gt;Audio&lt;/li&gt;
&lt;li&gt;Videos&lt;/li&gt;
&lt;li&gt;Documents&lt;/li&gt;
&lt;li&gt;Products&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"ClickHouse is a fast analytics database"

↓

[0.13, -0.52, 0.78, 0.41, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern embedding models commonly generate vectors with dimensions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;384&lt;/li&gt;
&lt;li&gt;768&lt;/li&gt;
&lt;li&gt;1024&lt;/li&gt;
&lt;li&gt;1536&lt;/li&gt;
&lt;li&gt;3072&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each value captures part of the semantic meaning learned during model training. Similar objects generate vectors that are positioned close together within the embedding space.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why AI Applications Need Vector Search
&lt;/h1&gt;

&lt;p&gt;Modern AI systems rarely depend solely on keyword matching. Instead, they retrieve information based on semantic similarity.&lt;/p&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Search&lt;/td&gt;
&lt;td&gt;Find documents by meaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommendation Systems&lt;/td&gt;
&lt;td&gt;Recommend similar products or movies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval-Augmented Generation (RAG)&lt;/td&gt;
&lt;td&gt;Retrieve context for LLMs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Search&lt;/td&gt;
&lt;td&gt;Find visually similar images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Chatbots&lt;/td&gt;
&lt;td&gt;Retrieve relevant knowledge before responding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraud Detection&lt;/td&gt;
&lt;td&gt;Detect similar behavioral patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without vector search, these systems become slower and less accurate.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Use ClickHouse for Vector Search?
&lt;/h1&gt;

&lt;p&gt;Rather than maintaining separate databases for analytics and AI workloads, ClickHouse allows both to coexist within a single platform.&lt;/p&gt;

&lt;p&gt;Key benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store structured data and embeddings together&lt;/li&gt;
&lt;li&gt;High-performance analytical SQL&lt;/li&gt;
&lt;li&gt;Excellent compression&lt;/li&gt;
&lt;li&gt;Horizontal scalability&lt;/li&gt;
&lt;li&gt;Real-time ingestion&lt;/li&gt;
&lt;li&gt;Familiar SQL interface&lt;/li&gt;
&lt;li&gt;Support for billions of vectors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This unified architecture eliminates synchronization challenges between analytical databases and dedicated vector stores.&lt;/p&gt;




&lt;h1&gt;
  
  
  How ClickHouse Stores Vector Embeddings
&lt;/h1&gt;

&lt;p&gt;Embeddings are typically stored using the &lt;code&gt;Array(Float32)&lt;/code&gt; data 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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;documents&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;title&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;content&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;embedding&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Float32&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;title&lt;/th&gt;
&lt;th&gt;embedding&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;AI Basics&lt;/td&gt;
&lt;td&gt;[0.24, -0.11, 0.56, ...]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each embedding can contain hundreds or even thousands of floating-point values.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Vector Similarity Search Works
&lt;/h1&gt;

&lt;p&gt;A typical workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query
     │
     ▼
Embedding Model
     │
     ▼
Query Vector
     │
     ▼
ClickHouse Vector Search
     │
     ▼
Most Similar Records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;A user asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How does ClickHouse replication work?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The embedding model converts the question into a vector.&lt;/p&gt;

&lt;p&gt;ClickHouse compares that vector with stored document embeddings and returns the most semantically relevant results.&lt;/p&gt;




&lt;h1&gt;
  
  
  Distance Metrics Used in Vector Search
&lt;/h1&gt;

&lt;p&gt;Vector similarity is calculated using mathematical distance metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cosine Similarity
&lt;/h2&gt;

&lt;p&gt;Measures the angle between vectors.&lt;/p&gt;

&lt;p&gt;Best suited for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text embeddings&lt;/li&gt;
&lt;li&gt;Semantic search&lt;/li&gt;
&lt;li&gt;LLM applications&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Euclidean Distance (L2)
&lt;/h2&gt;

&lt;p&gt;Measures straight-line distance between vectors.&lt;/p&gt;

&lt;p&gt;Commonly used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Numerical features&lt;/li&gt;
&lt;li&gt;Image embeddings&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dot Product
&lt;/h2&gt;

&lt;p&gt;Measures similarity based on vector magnitude and direction.&lt;/p&gt;

&lt;p&gt;Frequently used by transformer-based embedding models.&lt;/p&gt;




&lt;h1&gt;
  
  
  Approximate Nearest Neighbor (ANN) Search
&lt;/h1&gt;

&lt;p&gt;Comparing a query against every stored vector becomes increasingly expensive as datasets grow.&lt;/p&gt;

&lt;p&gt;Approximate Nearest Neighbor (ANN) algorithms dramatically improve performance by searching only the most promising candidates instead of performing exhaustive comparisons.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Millisecond-level search latency&lt;/li&gt;
&lt;li&gt;High recall&lt;/li&gt;
&lt;li&gt;Efficient CPU utilization&lt;/li&gt;
&lt;li&gt;Scalability to billions of vectors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse supports ANN indexing to accelerate similarity searches while maintaining excellent accuracy.&lt;/p&gt;




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

&lt;p&gt;Consider an AI-powered documentation assistant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Documents
     │
     ▼
Embedding Model
     │
     ▼
ClickHouse
(Store Documents + Embeddings)
     │
     ▼
User Query
     │
     ▼
Embedding Model
     │
     ▼
Vector Search
     │
     ▼
Relevant Documents
     │
     ▼
Large Language Model
     │
     ▼
Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture forms the foundation of Retrieval-Augmented Generation (RAG).&lt;/p&gt;




&lt;h1&gt;
  
  
  Example 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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;knowledge_base&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;title&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;content&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;embedding&lt;/span&gt; &lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Float32&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After generating embeddings using models such as OpenAI Embeddings, Sentence Transformers, or other embedding models, store the vectors inside the &lt;code&gt;embedding&lt;/code&gt; column for semantic retrieval.&lt;/p&gt;




&lt;h1&gt;
  
  
  AI Workloads That Benefit from ClickHouse
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Semantic Search
&lt;/h3&gt;

&lt;p&gt;Retrieve documents based on meaning instead of exact keywords.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommendation Systems
&lt;/h3&gt;

&lt;p&gt;Recommend products, movies, music, or articles using vector similarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  RAG Pipelines
&lt;/h3&gt;

&lt;p&gt;Retrieve relevant context before sending prompts to a Large Language Model.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Chatbots
&lt;/h3&gt;

&lt;p&gt;Fetch accurate knowledge base articles before generating responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Similarity Search
&lt;/h3&gt;

&lt;p&gt;Search for visually similar images using image embeddings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log Analysis
&lt;/h3&gt;

&lt;p&gt;Perform semantic searches across operational logs instead of exact-text matching.&lt;/p&gt;




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

&lt;p&gt;For optimal performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store embeddings as &lt;code&gt;Float32&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Maintain consistent vector dimensions&lt;/li&gt;
&lt;li&gt;Use ANN indexes for large datasets&lt;/li&gt;
&lt;li&gt;Partition data appropriately&lt;/li&gt;
&lt;li&gt;Separate metadata from embedding storage&lt;/li&gt;
&lt;li&gt;Batch insert vectors&lt;/li&gt;
&lt;li&gt;Eliminate duplicate embeddings&lt;/li&gt;
&lt;li&gt;Continuously monitor query latency&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Advantages of Using ClickHouse for AI
&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;SQL Interface&lt;/td&gt;
&lt;td&gt;Easy integration with existing applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High Compression&lt;/td&gt;
&lt;td&gt;Lower storage costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast Analytics&lt;/td&gt;
&lt;td&gt;Combine AI retrieval with analytical queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-Time Ingestion&lt;/td&gt;
&lt;td&gt;Continuously ingest new embeddings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal Scaling&lt;/td&gt;
&lt;td&gt;Handle billions of vectors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unified Platform&lt;/td&gt;
&lt;td&gt;Analytics and AI in one database&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  When Should You Use ClickHouse for Vector Search?
&lt;/h1&gt;

&lt;p&gt;ClickHouse is an excellent choice when you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combine analytics and AI workloads in a single platform&lt;/li&gt;
&lt;li&gt;Build semantic search applications&lt;/li&gt;
&lt;li&gt;Implement Retrieval-Augmented Generation (RAG)&lt;/li&gt;
&lt;li&gt;Store millions or billions of embeddings&lt;/li&gt;
&lt;li&gt;Analyze structured data alongside vector data&lt;/li&gt;
&lt;li&gt;Support real-time ingestion with fast similarity searches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your application requires both analytical processing and semantic retrieval, ClickHouse provides a unified architecture that reduces operational complexity while delivering exceptional performance.&lt;/p&gt;




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

&lt;p&gt;Vector search enables applications to understand the meaning behind data instead of relying solely on exact keyword matches. By storing vector embeddings alongside structured data, ClickHouse makes it possible to build semantic search engines, recommendation systems, AI chatbots, image similarity search, and Retrieval-Augmented Generation (RAG) pipelines without introducing a separate vector database.&lt;/p&gt;

&lt;p&gt;With efficient storage, SQL-based querying, real-time ingestion, and support for Approximate Nearest Neighbor (ANN) indexing, ClickHouse provides a scalable foundation for AI-powered applications that combine large-scale analytics with semantic retrieval. As AI workloads continue to grow, ClickHouse offers a practical and high-performance platform for unifying analytical and vector search capabilities within a single database.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 72 - ClickHouse® Internals: How the Query Analyzer Works</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 11 Jul 2026 13:36:59 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-72-clickhouser-internals-how-the-query-analyzer-works-500</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-72-clickhouser-internals-how-the-query-analyzer-works-500</guid>
      <description>&lt;p&gt;When you execute a SQL query in ClickHouse®, it doesn't immediately start scanning data or reading storage files. Instead, the query passes through several stages that transform it from a raw SQL string into an executable pipeline.&lt;/p&gt;

&lt;p&gt;One of the most critical stages in this system is the &lt;strong&gt;Query Analyzer&lt;/strong&gt;. It bridges the gap between parsing a SQL statement and generating an execution plan by understanding what the query &lt;em&gt;actually&lt;/em&gt; means.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore where the Query Analyzer fits into the query execution pipeline, what problems it solves, and why it's a critical component of the ClickHouse® query engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Query Execution Pipeline
&lt;/h2&gt;

&lt;p&gt;A simplified view of the ClickHouse® query execution pipeline flows as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;       SQL Query
           │
           ▼
        Parser
           │
           ▼
  Abstract Syntax Tree (AST)
           │
           ▼
     Query Analyzer
           │
           ▼
       Query Tree
           │
           ▼
     Query Planner
           │
           ▼
   Execution Pipeline
           │
           ▼
    Data Processing

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

&lt;/div&gt;



&lt;p&gt;Each stage has a highly specific responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Parser:&lt;/strong&gt; Verifies that the SQL syntax is valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Query Analyzer:&lt;/strong&gt; Resolves the semantic meaning of the query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Query Planner:&lt;/strong&gt; Determines how to execute the resolved query efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Execution Pipeline:&lt;/strong&gt; Performs the actual work of reading, filtering, aggregating, and returning data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this separation helps explain why parsing and analysis are treated as independent architectural layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parsing: Understanding Query Structure
&lt;/h2&gt;

&lt;p&gt;The parser is responsible &lt;strong&gt;only&lt;/strong&gt; for recognizing SQL syntax. Consider the following 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="n"&gt;customer_id&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;amount&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;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&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;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;The parser checks strictly grammatical constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, and &lt;code&gt;GROUP BY&lt;/code&gt; keywords used correctly?&lt;/li&gt;
&lt;li&gt;Are parentheses balanced in &lt;code&gt;SUM(amount)&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Do punctuation marks (like commas) appear in valid positions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After successful parsing, ClickHouse® creates an &lt;strong&gt;Abstract Syntax Tree (AST)&lt;/strong&gt;. The AST represents the purely syntactic structure of the query; every clause, expression, function call, and identifier becomes a node in this tree.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;What the Parser Ignores:&lt;/strong&gt; At this stage, ClickHouse® still hasn't answered basic semantic questions: &lt;em&gt;Does the &lt;code&gt;sales&lt;/code&gt; table exist? Is &lt;code&gt;customer_id&lt;/code&gt; a valid column? Is &lt;code&gt;SUM()&lt;/code&gt; a known function? Are the data types compatible?&lt;/em&gt; Those questions are deferred to the Analyzer.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Is the Query Analyzer?
&lt;/h2&gt;

&lt;p&gt;The Query Analyzer gives &lt;strong&gt;semantic meaning&lt;/strong&gt; to the parsed query. While the parser understands &lt;em&gt;how&lt;/em&gt; the query is written, the analyzer determines &lt;em&gt;what data and operations&lt;/em&gt; the query actually references.&lt;/p&gt;

&lt;p&gt;Instead of passing raw syntax further down the chain, the analyzer builds a richer internal representation called the &lt;strong&gt;Query Tree&lt;/strong&gt; that encapsulates the validated logic, type systems, and database metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  7 Major Responsibilities of the Query Analyzer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Resolving Tables
&lt;/h3&gt;

&lt;p&gt;The parser sees only the identifier &lt;code&gt;sales&lt;/code&gt;. The analyzer queries the system catalog to verify that the &lt;code&gt;sales&lt;/code&gt; table exists, identifies its underlying storage engine (e.g., &lt;code&gt;MergeTree&lt;/code&gt;), and fetches its physical schema definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Resolving Columns
&lt;/h3&gt;

&lt;p&gt;The analyzer checks if the requested columns actually exist within the resolved table. It also checks for ambiguity—if a query joins multiple tables that share identical column names without explicit qualifiers, the analyzer throws an error &lt;em&gt;before&lt;/em&gt; resources are spent on planning.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Resolving Aliases
&lt;/h3&gt;

&lt;p&gt;Aliases simplify complex SQL text, but they must be mapped back to concrete expressions. If you write &lt;code&gt;price * quantity AS revenue&lt;/code&gt;, the analyzer ensures that any later references to &lt;code&gt;revenue&lt;/code&gt; point directly to the underlying arithmetic expression (&lt;code&gt;price * quantity&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Function Resolution
&lt;/h3&gt;

&lt;p&gt;ClickHouse® features a massive library of built-in functions. The analyzer maps a string like &lt;code&gt;lower(name)&lt;/code&gt; to its actual C++ execution kernel implementation, verifies that the function exists, checks that the argument count is correct, and ensures the argument types are valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Type Inference
&lt;/h3&gt;

&lt;p&gt;If a query evaluates &lt;code&gt;price * quantity&lt;/code&gt;, where &lt;code&gt;price&lt;/code&gt; is a &lt;code&gt;Decimal32&lt;/code&gt; and &lt;code&gt;quantity&lt;/code&gt; is a &lt;code&gt;UInt32&lt;/code&gt;, the analyzer computes the exact output data type. This precise type signature is mandatory for the Query Planner and the vectorized execution engine.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Expression Validation
&lt;/h3&gt;

&lt;p&gt;The analyzer catches logical SQL violations early. For instance, executing a aggregate function (&lt;code&gt;SUM(amount)&lt;/code&gt;) alongside an unaggregated column (&lt;code&gt;customer_id&lt;/code&gt;) without a matching &lt;code&gt;GROUP BY&lt;/code&gt; clause is syntactically valid but semantically illegal. The analyzer halts this query immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Building the Query Tree
&lt;/h3&gt;

&lt;p&gt;The final and most crucial output of the analyzer is the &lt;strong&gt;Query Tree&lt;/strong&gt;. Unlike the AST, which mimics the user's literal text, the Query Tree contains nodes bound to actual metadata objects, verified functions, and concrete data types.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deep Dive: AST vs. Query Tree
&lt;/h2&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;Abstract Syntax Tree (AST)&lt;/th&gt;
&lt;th&gt;Query Tree&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Focus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL Grammar &amp;amp; Syntax&lt;/td&gt;
&lt;td&gt;Query Semantics &amp;amp; Meaning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generated By&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parser&lt;/td&gt;
&lt;td&gt;Query Analyzer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Closely resembles raw SQL text&lt;/td&gt;
&lt;td&gt;Represents resolved objects and operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Raw text identifiers and strings&lt;/td&gt;
&lt;td&gt;Validated tables, columns, functions, and explicit data types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Core Question&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"What did the user write?"&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;"What does this query actually mean?"&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Doesn't the Planner Work Directly on the AST?
&lt;/h2&gt;

&lt;p&gt;The planner's sole job is optimization—determining the fastest way to read and process data.&lt;/p&gt;

&lt;p&gt;If the planner operated directly on the AST, its code would be heavily bogged down. It would have to repeatedly look up metadata tables, perform type checks, and resolve aliases for every single optimization pass. By offloading these tasks to the Query Analyzer, the planner receives a clean, uniform, and fully validated Query Tree, allowing it to focus entirely on performance optimization strategies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inspecting the Analyzer
&lt;/h2&gt;

&lt;p&gt;ClickHouse® exposes its internal analyzer mechanics via diagnostic commands. If you want to see exactly how ClickHouse® translates your query semantics, you can 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;EXPLAIN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="n"&gt;TREE&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="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;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sales&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;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;Rather than printing a physical execution plan (which shows read steps and thread allocations), this command outputs the structured Query Tree. It is incredibly useful for debugging complex queries, checking unexpected alias behavior, or validating nested subquery behavior.&lt;/p&gt;




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

&lt;p&gt;The Query Analyzer is the unsung hero of the ClickHouse® query engine. By cleanly separating syntactic validation from semantic resolution, ClickHouse® keeps its pipeline modular, predictable, and highly performant.&lt;/p&gt;

&lt;p&gt;The next time you execute a query, remember that long before a single byte of data is read from disk, the Query Analyzer has already mapped out the exact DNA of your SQL statement.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://clickhouse.com/docs/guides/developer/understanding-query-execution-with-the-analyzer" rel="noopener noreferrer"&gt;ClickHouse® Documentation: Understanding Query Execution with the Analyzer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>clickhouse</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Day 71 - Advanced Partitioning Strategies for Petabyte-Scale Tables</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 11 Jul 2026 11:20:29 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-71-advanced-partitioning-strategies-for-petabyte-scale-tables-3n1a</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-71-advanced-partitioning-strategies-for-petabyte-scale-tables-3n1a</guid>
      <description>&lt;p&gt;As datasets grow into the petabyte range, choosing the right partitioning strategy becomes one of the most important architectural decisions in ClickHouse®. Well-designed partitions improve query performance, simplify data lifecycle management, reduce maintenance overhead, and make large analytical workloads significantly more efficient. On the other hand, poor partitioning choices can lead to excessive numbers of parts, slower merges, increased storage overhead, and degraded query performance.&lt;/p&gt;

&lt;p&gt;Unlike traditional relational databases, ClickHouse® partitions are not indexes. They are primarily a data management mechanism that determines how data is physically organized on disk. Understanding this distinction is essential when designing schemas for large-scale deployments.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore advanced partitioning strategies, discuss common design patterns used in production environments, and examine best practices for managing petabyte-scale tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Partitions in ClickHouse®
&lt;/h2&gt;

&lt;p&gt;Every MergeTree table stores data as immutable parts.&lt;/p&gt;

&lt;p&gt;When a PARTITION BY expression is defined, ClickHouse® groups parts into logical partitions.&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
CREATE TABLE events&lt;br&gt;
(&lt;br&gt;
    event_time DateTime,&lt;br&gt;
    user_id UInt64,&lt;br&gt;
    country LowCardinality(String),&lt;br&gt;
    event_type String&lt;br&gt;
)&lt;br&gt;
ENGINE = MergeTree&lt;br&gt;
PARTITION BY toYYYYMM(event_time)&lt;br&gt;
ORDER BY (event_time, user_id);&lt;/p&gt;

&lt;p&gt;Each month becomes its own partition.&lt;/p&gt;

&lt;p&gt;This allows ClickHouse® to:&lt;/p&gt;

&lt;p&gt;Drop old data instantly&lt;br&gt;
Optimize merges within partitions&lt;br&gt;
Prune irrelevant partitions during queries&lt;br&gt;
Manage storage more efficiently&lt;/p&gt;

&lt;p&gt;However, partitioning alone does &lt;strong&gt;not&lt;/strong&gt; make queries fast. The sorting key (ORDER BY) is still the primary mechanism for efficient data retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Partitioning Becomes Critical at Petabyte Scale
&lt;/h2&gt;

&lt;p&gt;At small scales, inefficient partitioning may only waste some storage.&lt;/p&gt;

&lt;p&gt;At petabyte scale, it can impact:&lt;/p&gt;

&lt;p&gt;Merge performance&lt;br&gt;
Metadata size&lt;br&gt;
Disk utilization&lt;br&gt;
Query planning&lt;br&gt;
Background processing&lt;br&gt;
Backup and restore times&lt;br&gt;
Replication efficiency&lt;/p&gt;

&lt;p&gt;A poor partitioning strategy may generate millions of tiny parts, overwhelming background merge operations and increasing query overhead.&lt;/p&gt;

&lt;p&gt;Conversely, partitions that are too large can make data management tasks such as retention and deletion more expensive.&lt;/p&gt;

&lt;p&gt;Finding the right balance is essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Partition Key
&lt;/h2&gt;

&lt;p&gt;Selecting an appropriate partition key is the most important design decision.&lt;/p&gt;

&lt;p&gt;A good partition key should:&lt;/p&gt;

&lt;p&gt;Align with common query patterns&lt;br&gt;
Support retention policies&lt;br&gt;
Produce a manageable number of partitions&lt;br&gt;
Avoid excessive fragmentation&lt;br&gt;
Simplify operational maintenance&lt;/p&gt;

&lt;p&gt;Time-based partitioning is the most common strategy because many analytical workloads naturally query data over time ranges.&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
PARTITION BY toYYYYMM(event_time)&lt;/p&gt;

&lt;p&gt;sql&lt;br&gt;
PARTITION BY toYYYYWW(event_time)&lt;/p&gt;

&lt;p&gt;sql&lt;br&gt;
PARTITION BY toDate(event_time)&lt;/p&gt;

&lt;p&gt;The appropriate granularity depends on data volume and retention requirements.&lt;/p&gt;

&lt;p&gt;Daily partitions may work well for very high-ingestion workloads, while monthly partitions are often sufficient for moderate data volumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Dimensional Partitioning
&lt;/h2&gt;

&lt;p&gt;Large organizations frequently manage data across multiple regions, business units, or tenants.&lt;/p&gt;

&lt;p&gt;In such cases, composite partition keys can reduce operational complexity.&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
PARTITION BY (&lt;br&gt;
    region,&lt;br&gt;
    toYYYYMM(event_time)&lt;br&gt;
)&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
PARTITION BY (&lt;br&gt;
    tenant_id,&lt;br&gt;
    toYYYYMM(event_time)&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This enables independent lifecycle management for each tenant or region while preserving efficient pruning for time-based queries.&lt;/p&gt;

&lt;p&gt;However, high-cardinality columns should be avoided in partition keys. Using values such as user_id or device_id can create an excessive number of partitions, increasing metadata overhead and reducing merge efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partition Pruning
&lt;/h2&gt;

&lt;p&gt;One of the primary performance benefits of partitioning is partition pruning.&lt;/p&gt;

&lt;p&gt;When a query includes filters that match the partition key, ClickHouse® can skip entire partitions without reading them from disk.&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
SELECT count()&lt;br&gt;
FROM events&lt;br&gt;
WHERE event_time &amp;gt;= '2026-01-01'&lt;br&gt;
  AND event_time &amp;lt; '2026-02-01';&lt;/p&gt;

&lt;p&gt;If the table is partitioned by month, ClickHouse® only scans the relevant monthly partition instead of the entire dataset.&lt;/p&gt;

&lt;p&gt;Partition pruning significantly reduces disk I/O and query execution time, particularly for time-series workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partition Lifecycle Management
&lt;/h2&gt;

&lt;p&gt;Partitions simplify large-scale data management.&lt;/p&gt;

&lt;p&gt;Instead of deleting billions of individual rows, entire partitions can be removed instantly.&lt;/p&gt;

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

&lt;p&gt;Data retention&lt;br&gt;
Archiving&lt;br&gt;
Tiered storage&lt;br&gt;
Backup strategies&lt;/p&gt;

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

&lt;p&gt;sql&lt;br&gt;
ALTER TABLE events&lt;br&gt;
DROP PARTITION '202501';&lt;/p&gt;

&lt;p&gt;This metadata operation is significantly faster than executing large DELETE statements.&lt;/p&gt;

&lt;p&gt;Similarly, partitions can be moved between storage volumes using storage policies, allowing older data to reside on lower-cost disks while keeping recent data on faster storage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Partition Health
&lt;/h2&gt;

&lt;p&gt;Large deployments should continuously monitor partition-related metrics.&lt;/p&gt;

&lt;p&gt;Useful system tables include:&lt;/p&gt;

&lt;p&gt;system.parts&lt;br&gt;
system.part_log&lt;br&gt;
system.merges&lt;br&gt;
system.detached_parts&lt;/p&gt;

&lt;p&gt;Key indicators include:&lt;/p&gt;

&lt;p&gt;Active part count&lt;br&gt;
Partition count&lt;br&gt;
Average part size&lt;br&gt;
Merge backlog&lt;br&gt;
Detached parts&lt;br&gt;
Disk utilization&lt;/p&gt;

&lt;p&gt;Monitoring these metrics helps identify partitioning problems before they impact production workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Partitioning Mistakes
&lt;/h2&gt;

&lt;p&gt;Even experienced teams encounter partitioning issues.&lt;/p&gt;

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

&lt;p&gt;Partitioning by high-cardinality columns&lt;br&gt;
Creating daily partitions for low-volume tables&lt;br&gt;
Ignoring retention policies&lt;br&gt;
Using partitioning instead of an appropriate sorting key&lt;br&gt;
Creating thousands of tiny partitions&lt;br&gt;
Frequently dropping small partitions&lt;/p&gt;

&lt;p&gt;Understanding workload characteristics before designing a schema helps avoid these pitfalls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Petabyte-Scale Deployments
&lt;/h2&gt;

&lt;p&gt;For very large ClickHouse® clusters, consider the following recommendations:&lt;/p&gt;

&lt;p&gt;Keep partition keys low in cardinality.&lt;br&gt;
Design partitions around retention requirements.&lt;br&gt;
Use the sorting key to optimize query performance.&lt;br&gt;
Monitor active part counts regularly.&lt;br&gt;
Avoid over-partitioning.&lt;br&gt;
Test partitioning strategies with realistic production workloads.&lt;br&gt;
Use storage policies for hot and cold data.&lt;br&gt;
Review partition distribution as data volumes evolve.&lt;/p&gt;

&lt;p&gt;Partitioning decisions made early in a project's lifecycle can have long-term implications for performance and operational efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Partitioning is one of the foundational design decisions in ClickHouse®, especially for petabyte-scale analytical systems. A well-designed partition strategy enables efficient data lifecycle management, reduces maintenance costs, and improves query performance through effective partition pruning. At the same time, choosing the wrong partition key can create operational challenges that become increasingly difficult to correct as data grows.&lt;/p&gt;

&lt;p&gt;Rather than treating partitioning as a performance optimization in isolation, it should be considered alongside sorting keys, storage policies, merge behavior, and expected query patterns. Together, these design choices determine how efficiently ClickHouse® scales to handle massive datasets.&lt;/p&gt;

&lt;p&gt;To learn more about operating and monitoring ClickHouse® clusters, explore &lt;a href="https://www.ch-ops.io/features" rel="noopener noreferrer"&gt;CH-Ops&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://clickhouse.com/docs/engines/table-engines/mergetree-family/mergetree" rel="noopener noreferrer"&gt;Official ClickHouse® Documentation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.ch-ops.io/blog/drop-partition-vs-detach-partition-clickhouse" rel="noopener noreferrer"&gt;When DROP PARTITION Fails: A Hidden Data Duplication Risk in ClickHouse®&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.quantrail-data.com/date-partitioning-strategies-for-high-performance-clickhouse-queries" rel="noopener noreferrer"&gt;Date Partitioning Strategies&lt;/a&gt;&lt;br&gt;
&lt;a href="https://medium.com/stackademic/why-too-many-parts-destroy-clickhouse-performance-d143b75189e3" rel="noopener noreferrer"&gt;Why Too Many Parts Hurt ClickHouse Performance&lt;/a&gt;&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 70- Extending ClickHouse® with C++ UDFs</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Sat, 11 Jul 2026 10:07:51 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-70-extending-clickhouser-with-c-udfs-2hog</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-70-extending-clickhouser-with-c-udfs-2hog</guid>
      <description>&lt;p&gt;Introduction&lt;br&gt;
ClickHouse® comes with hundreds of built-in functions covering string manipulation, date/time&lt;br&gt;
operations, mathematical calculations, aggregations, and more. For most use cases, these&lt;br&gt;
built-in functions are more than sufficient.&lt;br&gt;
However, there are situations where you need custom logic that no built-in function covers such&lt;br&gt;
as a proprietary scoring algorithm, a domain-specific transformation, or a complex encoding&lt;br&gt;
scheme. In these cases, ClickHouse® allows you to extend its functionality through&lt;br&gt;
User-Defined Functions (UDFs).&lt;br&gt;
While ClickHouse® supports SQL User-Defined Functions (UDFs) and Executable UDFs,&lt;br&gt;
developers can also extend ClickHouse® by implementing custom C++ functions directly&lt;br&gt;
within the ClickHouse source code. This approach delivers the highest performance because&lt;br&gt;
the function becomes part of the ClickHouse execution engine.&lt;br&gt;
In this blog, we'll focus on C++ UDFs - the most performant approach for extending&lt;br&gt;
ClickHouse® with custom functions.&lt;br&gt;
What is a UDF?&lt;br&gt;
A User-Defined Function (UDF) is a custom function you write yourself and register with&lt;br&gt;
ClickHouse® so it can be used in SQL queries - just like any built-in function.&lt;br&gt;
sql&lt;br&gt;
-- Using a built-in function&lt;br&gt;
SELECT upper(name) FROM users;&lt;br&gt;
-- Using a custom UDF&lt;br&gt;
SELECT my_custom_function(name) FROM users;&lt;br&gt;
Once registered, a UDF behaves exactly like a built-in function - it can be used in SELECT,&lt;br&gt;
WHERE, GROUP BY, and any other SQL clause.&lt;/p&gt;

&lt;p&gt;Ways to Extend ClickHouse®&lt;br&gt;
ClickHouse® offers three approaches for adding custom functionality.&lt;br&gt;
Method Language Performance Best For&lt;/p&gt;

&lt;p&gt;SQL UDF SQL High Simple reusable expressions&lt;br&gt;
Executable UDF Python, Shell,&lt;/p&gt;

&lt;p&gt;etc.&lt;/p&gt;

&lt;p&gt;Moderate External business logic&lt;/p&gt;

&lt;p&gt;Custom C++ Function C++ Highest Performance-critical functionality&lt;br&gt;
For most use cases, SQL UDFs or Executable UDFs are sufficient. Custom C++ functions are&lt;br&gt;
typically reserved for advanced scenarios where maximum performance is required.&lt;/p&gt;

&lt;p&gt;Why Use C++ UDFs?&lt;br&gt;
C++ UDFs are the highest performance option because:&lt;br&gt;
● They are compiled to native machine code — no interpreter overhead&lt;br&gt;
● They run inside the ClickHouse® process — no inter-process communication&lt;br&gt;
● They have direct access to ClickHouse® column types — no serialization overhead&lt;br&gt;
● They can process entire columns at once using vectorized execution&lt;br&gt;
This makes C++ UDFs suitable for performance-critical custom logic that needs to run over&lt;br&gt;
billions of rows.&lt;/p&gt;

&lt;p&gt;Prerequisites&lt;br&gt;
Before writing a C++ UDF, make sure you have:&lt;br&gt;
● ClickHouse® installed and running&lt;br&gt;
● A C++ compiler (g++ or clang++)&lt;br&gt;
● Basic familiarity with C++&lt;br&gt;
● ClickHouse® source code (for header files)&lt;br&gt;
● cmake installed&lt;/p&gt;

&lt;p&gt;SQL-Based UDFs (Lambdas)&lt;br&gt;
Before writing a C++ UDF, consider whether a SQL-based UDF (available from ClickHouse®&lt;br&gt;
v21.11+) can solve your problem. These are much simpler to write and maintain.&lt;br&gt;
Create a simple SQL UDF:&lt;/p&gt;

&lt;p&gt;CREATE FUNCTION multiply_by_two AS (x) -&amp;gt; x * 2;&lt;br&gt;
Use it in a query:&lt;br&gt;
SELECT&lt;br&gt;
amount&lt;br&gt;
multiply_by_two(amount) AS doubled_amount&lt;br&gt;
FROM default.orders;&lt;br&gt;
Output:&lt;br&gt;
| amount | doubled_amount |&lt;br&gt;
|------------|------------------------|&lt;br&gt;
| 1200.00 | 2400.00 |&lt;br&gt;
| 450.00 | 900.00 |&lt;br&gt;
| 890.00 | 1780.00 |&lt;/p&gt;

&lt;p&gt;Use SQL UDFs first - they are simpler to write, easier to maintain, and perform well for&lt;br&gt;
most use cases. Only reach for C++ UDFs when SQL expressions are not sufficient or&lt;br&gt;
performance is critical.&lt;br&gt;
Executable UDFs - External Scripts&lt;br&gt;
Executable UDFs allow you to call external scripts written in any language( Python, Go, etc..,).&lt;br&gt;
ClickHouse® communicates with the script via stdin/stdout.&lt;br&gt;
Example: Python UDF&lt;br&gt;
Step 1: Write the Python script (categorize_amount.py)&lt;br&gt;
python&lt;/p&gt;

&lt;h1&gt;
  
  
  !/usr/bin/env python3
&lt;/h1&gt;

&lt;h1&gt;
  
  
  /usr/local/bin/categorize_amount.py
&lt;/h1&gt;

&lt;p&gt;import sys&lt;br&gt;
for line in sys.stdin:&lt;br&gt;
amount = float(line.strip())&lt;br&gt;
if amount &amp;gt;= 1000:&lt;br&gt;
print("Premium")&lt;br&gt;
elif amount &amp;gt;= 500:&lt;br&gt;
print("Standard")&lt;br&gt;
else:&lt;br&gt;
print("Basic")&lt;/p&gt;

&lt;p&gt;Make it executable:&lt;br&gt;
bash&lt;br&gt;
chmod +x /usr/local/bin/categorize_amount.py&lt;br&gt;
Step 2: Register the UDF in ClickHouse®&lt;br&gt;
Create a configuration file in /etc/clickhouse-server/:&lt;br&gt;
xml&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;br&gt;
executable&lt;br&gt;
categorize_amount&lt;br&gt;
String&lt;br&gt;
&lt;br&gt;
Float64&lt;br&gt;
amount&lt;br&gt;
&lt;br&gt;
TabSeparated&lt;br&gt;
categorize_amount.py&lt;br&gt;
&lt;br&gt;
&lt;br&gt;
Step 3: Restart ClickHouse®&lt;br&gt;
bash&lt;br&gt;
sudo systemctl restart clickhouse-server&lt;br&gt;
Step 4: Use the UDF in a query&lt;br&gt;
SELECT&lt;br&gt;
order_id,&lt;br&gt;
amount,&lt;br&gt;
categorize_amount(amount) AS tier&lt;br&gt;
FROM default.orders;&lt;br&gt;
Output:&lt;br&gt;
| order_id | amount | tier |&lt;br&gt;
|---------- |--------- |----------|&lt;br&gt;
| 1 | 1200.00 | Premium |&lt;br&gt;
| 2 | 450.00 | Basic |&lt;br&gt;
| 3 | 890.00 | Standard |&lt;/p&gt;

&lt;p&gt;C++ UDFs - Custom Native Functions&lt;br&gt;
When maximum performance is required, ClickHouse® can be extended by implementing&lt;br&gt;
custom C++ functions directly in its source code. Unlike Executable UDFs, these functions&lt;br&gt;
become part of the ClickHouse execution engine and run natively without external processes.&lt;br&gt;
Note: ClickHouse® does not currently support loading arbitrary C++ UDFs as&lt;br&gt;
standalone plugins. Instead, custom C++ functions are added to the ClickHouse&lt;br&gt;
source code, registered with the function factory, and become available after&lt;br&gt;
rebuilding the server.&lt;br&gt;
How Custom C++ Functions Work&lt;/p&gt;

&lt;p&gt;Write C++ Function&lt;br&gt;
│&lt;br&gt;
▼&lt;/p&gt;

&lt;p&gt;Implement IFunction Interface&lt;/p&gt;

&lt;p&gt;│&lt;br&gt;
▼&lt;/p&gt;

&lt;p&gt;Register with FunctionFactory&lt;/p&gt;

&lt;p&gt;│&lt;br&gt;
▼&lt;br&gt;
Build ClickHouse&lt;br&gt;
│&lt;br&gt;
▼&lt;br&gt;
Restart ClickHouse&lt;br&gt;
│&lt;br&gt;
▼&lt;br&gt;
Use in SQL Queries&lt;/p&gt;

&lt;p&gt;Step 1: Set Up the Build Environment&lt;br&gt;
you must build ClickHouse from source to add a native function, clone the repository&lt;br&gt;
along with its submodules:&lt;br&gt;
Install the required development tools and clone the ClickHouse® source code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Install required build tools
&lt;/h1&gt;

&lt;p&gt;sudo apt install -y build-essential cmake git&lt;/p&gt;

&lt;h1&gt;
  
  
  Clone the ClickHouse codebase
&lt;/h1&gt;

&lt;p&gt;git clone &lt;a href="https://github.com/ClickHouse/ClickHouse.git" rel="noopener noreferrer"&gt;https://github.com/ClickHouse/ClickHouse.git&lt;/a&gt;&lt;br&gt;
cd ClickHouse&lt;br&gt;
git submodule update --init --recursive&lt;br&gt;
Step 2: Implement the IFunction Interface&lt;br&gt;
ClickHouse functions process data column-by-column using vectorized execution. You&lt;br&gt;
must inherit from the IFunction interface and implement its core methods.&lt;br&gt;
Create your function file (e.g., src/Functions/FunctionMultiplyByTwo.cpp):&lt;br&gt;
cpp&lt;/p&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;h1&gt;
  
  
  include 
&lt;/h1&gt;

&lt;p&gt;namespace DB&lt;br&gt;
{&lt;br&gt;
class FunctionMultiplyByTwo : public IFunction&lt;br&gt;
{&lt;br&gt;
public:&lt;br&gt;
static constexpr auto name = "multiplyByTwo";&lt;br&gt;
String getName() const override&lt;br&gt;
{&lt;br&gt;
return name;&lt;br&gt;
}&lt;br&gt;
...&lt;br&gt;
};&lt;br&gt;
Inside the execution logic, process the input column and return the transformed result.&lt;br&gt;
Step 3: Register the Function&lt;br&gt;
Register the function with ClickHouse's FunctionFactory.&lt;/p&gt;

&lt;p&gt;At the bottom of your implementation file, you must register your class with ClickHouse's&lt;br&gt;
FunctionFactory so that the parser can map the SQL string to your execution logic.&lt;/p&gt;

&lt;p&gt;REGISTER_FUNCTION(MultiplyByTwo)&lt;br&gt;
{&lt;br&gt;
factory.registerFunction();&lt;br&gt;
}&lt;br&gt;
Step 4: Compile and Run&lt;br&gt;
Once your files are in place, compile the code base and spin up your newly&lt;br&gt;
extended server.&lt;br&gt;
Rebuild ClickHouse so the new function becomes part of the server.&lt;/p&gt;

&lt;h1&gt;
  
  
  Generate the build files via CMake
&lt;/h1&gt;

&lt;p&gt;mkdir build &amp;amp;&amp;amp; cd build&lt;br&gt;
cmake .. -G Ninja&lt;/p&gt;

&lt;h1&gt;
  
  
  Compile the ClickHouse binary
&lt;/h1&gt;

&lt;p&gt;ninja clickhouse&lt;/p&gt;

&lt;h1&gt;
  
  
  Start your custom compiled server
&lt;/h1&gt;

&lt;p&gt;sudo systemctl restart clickhouse-server&lt;br&gt;
Step 5: Execute via SQL&lt;/p&gt;

&lt;p&gt;The function is natively embedded within the query engine and can be used&lt;br&gt;
immediately without any prior registration or DLL loading statements:&lt;br&gt;
SELECT multiplyByTwo(25);&lt;br&gt;
Output&lt;br&gt;
multiplyByTwo&lt;br&gt;
50&lt;/p&gt;

&lt;p&gt;Comparing UDF Types&lt;br&gt;
| Feature | SQL UDF | Executable UDF | C++ UDF |&lt;br&gt;
|----------------------|----------------|-----------------|-----------------|&lt;br&gt;
| Ease of writing | Very easy | Easy | Complex |&lt;br&gt;
| Performance | High | Moderate | Highest |&lt;br&gt;
| Language | SQL | Any | C++ |&lt;br&gt;
| Setup complexity | None | Low | High |&lt;br&gt;
| Restart required | No | Yes | Yes |&lt;br&gt;
| Best for | Simple logic | Prototyping | Max performance |&lt;br&gt;
| Available since | v21.11 | v21.11 | Always |&lt;/p&gt;

&lt;p&gt;When to Use Each UDF Type&lt;br&gt;
Use SQL UDF when:&lt;br&gt;
● The logic can be expressed in SQL&lt;br&gt;
● You need quick iteration without restarting ClickHouse®&lt;br&gt;
● The transformation is simple (case statements, arithmetic, string operations)&lt;br&gt;
Use Executable UDF when:&lt;br&gt;
● You need logic that SQL cannot express&lt;br&gt;
● You want to use Python, R, or another language&lt;br&gt;
● Performance is not the primary concern&lt;br&gt;
● Prototyping a new function before optimizing&lt;br&gt;
Use C++ UDF when:&lt;br&gt;
● Maximum performance is required&lt;br&gt;
● The function will run over billions of rows&lt;br&gt;
● You need direct access to ClickHouse® internal column types&lt;br&gt;
● The logic is complex and cannot be expressed in SQL or external scripts&lt;/p&gt;

&lt;p&gt;Best Practices&lt;br&gt;
● Start with SQL UDFs — they are the simplest and often sufficient.&lt;br&gt;
● Test executable UDFs before investing in C++ implementation — validate the logic first.&lt;br&gt;
● Always restart ClickHouse® after registering executable or C++ UDFs.&lt;br&gt;
● Use meaningful function names — avoid names that conflict with built-in functions.&lt;br&gt;
● Document your UDFs — add comments explaining what the function does, its inputs,&lt;br&gt;
and outputs.&lt;/p&gt;

&lt;p&gt;● Monitor performance — use system.query_log to compare query times before and&lt;br&gt;
after adding a UDF.&lt;br&gt;
● Version control your UDF code — treat UDF source files like application code.&lt;/p&gt;

&lt;p&gt;Quick Reference&lt;br&gt;
| Task | Command / Action |&lt;br&gt;
|-----------------------------|-----------------------------------------------|&lt;br&gt;
| Create SQL UDF | CREATE FUNCTION name AS (args) -&amp;gt; expression |&lt;br&gt;
| Drop SQL UDF | DROP FUNCTION name |&lt;br&gt;
| List all UDFs | SELECT * FROM system.functions |&lt;br&gt;
| Register executable UDF | Add XML config to /etc/clickhouse-server/ |&lt;br&gt;
| Register C++ UDF | Copy .so file + add XML config |&lt;br&gt;
| Reload after config change | sudo systemctl restart clickhouse-server |&lt;br&gt;
| UDF storage directory | /var/lib/clickhouse/user_defined/ |&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
ClickHouse® UDFs provide a powerful way to extend the database with custom logic when&lt;br&gt;
built-in functions are not sufficient. For most use cases, SQL UDFs offer the best balance of&lt;br&gt;
simplicity and performance. For more complex logic, executable UDFs allow you to use any&lt;br&gt;
language. When maximum performance is required, C++ UDFs deliver native speed with direct&lt;br&gt;
access to ClickHouse® internals.&lt;br&gt;
Start simple — write a SQL UDF first. If performance becomes an issue or the logic is too&lt;br&gt;
complex for SQL, move to executable UDFs or C++ as needed.&lt;/p&gt;

&lt;p&gt;References&lt;br&gt;
● ClickHouse® Documentation — User Defined Functions&lt;br&gt;
● ClickHouse® Documentation — Executable UDFs&lt;br&gt;
● ClickHouse® Documentation — CREATE FUNCTION&lt;br&gt;
● ClickHouse® Documentation — system.functions&lt;br&gt;
● ClickHouse® GitHub — Function Examples&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 69 - Anatomy of a ClickHouse® Vertical Merge</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Fri, 10 Jul 2026 16:10:18 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-69-anatomy-of-a-clickhouser-vertical-merge-4dlo</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-69-anatomy-of-a-clickhouser-vertical-merge-4dlo</guid>
      <description>&lt;h1&gt;
  
  
  Anatomy of a ClickHouse® Vertical Merge
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;ClickHouse is designed to process analytical workloads at exceptional speed. One of the key reasons behind this performance is its background merge mechanism, which continuously combines smaller data parts into larger ones. These merges improve query performance, reduce the number of files on disk, and optimize data storage without interrupting ongoing queries.&lt;/p&gt;

&lt;p&gt;For most tables, ClickHouse performs a &lt;strong&gt;Horizontal Merge&lt;/strong&gt;, where all columns are processed together. However, when dealing with wide tables containing hundreds of columns, this approach can consume a significant amount of memory. To address this challenge, ClickHouse introduces &lt;strong&gt;Vertical Merge&lt;/strong&gt;, an optimization that processes columns in multiple stages instead of all at once.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how Vertical Merge works, why it was introduced, how ClickHouse decides when to use it, and how it helps reduce memory consumption during background merge operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before exploring Vertical Merge, it's helpful to understand a few core ClickHouse concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MergeTree tables&lt;/li&gt;
&lt;li&gt;Data parts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ORDER BY&lt;/code&gt; (sorting key)&lt;/li&gt;
&lt;li&gt;Background merge operations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Understanding Background Merges
&lt;/h2&gt;

&lt;p&gt;ClickHouse stores data in &lt;strong&gt;immutable data parts&lt;/strong&gt;. A data part is called immutable because once it is written to disk, ClickHouse never modifies it directly. Instead, when optimization is needed, ClickHouse creates a new merged part and removes the old ones after the merge completes successfully.&lt;/p&gt;

&lt;p&gt;Since every &lt;code&gt;INSERT&lt;/code&gt; operation creates a new data part, a busy table can quickly accumulate hundreds or even thousands of small parts. Having too many parts increases storage metadata, makes background maintenance more expensive, and can negatively impact query performance.&lt;/p&gt;

&lt;p&gt;To keep storage efficient and maintain fast query execution, ClickHouse continuously performs background merges that combine smaller parts into larger ones.&lt;/p&gt;

&lt;p&gt;For example, if you insert data five times into the same &lt;code&gt;MergeTree&lt;/code&gt; table, ClickHouse creates five separate data parts. These parts are stored independently until the background merge process combines them into a larger part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERTS
  │  Part A
  │  Part B
  │  Part C
  ▼
Background Merge
  │
  ▼
Merged Part

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

&lt;/div&gt;



&lt;p&gt;During a merge, ClickHouse:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads multiple source parts.&lt;/li&gt;
&lt;li&gt;Combines and sorts rows according to the table's sorting key.&lt;/li&gt;
&lt;li&gt;Applies engine-specific processing.&lt;/li&gt;
&lt;li&gt;Writes a new merged part.&lt;/li&gt;
&lt;li&gt;Removes the old parts after the merge completes successfully.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Horizontal Merge vs. Vertical Merge
&lt;/h2&gt;

&lt;p&gt;ClickHouse supports two approaches for merging data parts:&lt;/p&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;Horizontal Merge&lt;/th&gt;
&lt;th&gt;Vertical Merge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Column Processing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Processes all columns together&lt;/td&gt;
&lt;td&gt;Processes key columns first, then remaining columns separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory Usage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Higher memory usage&lt;/td&gt;
&lt;td&gt;Lower memory usage&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;Narrow tables&lt;/td&gt;
&lt;td&gt;Wide tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Workflow Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simpler merge process&lt;/td&gt;
&lt;td&gt;Multi-stage merge process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Faster for smaller tables&lt;/td&gt;
&lt;td&gt;Better scalability for large tables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Horizontal Merge remains the default merge strategy in ClickHouse because it performs efficiently for most workloads. Vertical Merge is used only when ClickHouse determines that processing all columns together would require excessive memory, making a staged merge more efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Was Vertical Merge Introduced?
&lt;/h2&gt;

&lt;p&gt;Horizontal Merge works efficiently for tables containing a relatively small number of columns. However, as analytical datasets grow wider—with hundreds of columns and millions or billions of rows—the amount of data that must be processed during a merge also increases. Processing every column simultaneously can consume a considerable amount of memory.&lt;/p&gt;

&lt;p&gt;Imagine an analytics table containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;250 columns&lt;/li&gt;
&lt;li&gt;Millions of rows&lt;/li&gt;
&lt;li&gt;Several large &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Array&lt;/code&gt;, or &lt;code&gt;JSON&lt;/code&gt; columns&lt;/li&gt;
&lt;li&gt;Continuous data ingestion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A traditional merge must read data from every column at the same time. As the number of columns increases, memory consumption grows significantly.&lt;/p&gt;

&lt;p&gt;Instead of loading every column simultaneously, &lt;strong&gt;Vertical Merge&lt;/strong&gt; divides the work into smaller stages, allowing ClickHouse to process only a small portion of the data at a time. This dramatically reduces peak memory usage while still producing the same final merged part.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Vertical Merge Works (Step-by-Step Example)
&lt;/h2&gt;

&lt;p&gt;Before understanding the internal workflow, let's look at a simple example. We'll use two small data parts to demonstrate how ClickHouse performs a Vertical Merge. The same process is used internally for much larger datasets.&lt;/p&gt;

&lt;p&gt;Let's assume we have two data parts that need to be merged:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1&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;id&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;sales&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;Chennai&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Delhi&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Part 2&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;id&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;sales&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Pune&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Assume the table is created 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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This means the final merged data must be ordered by the &lt;code&gt;id&lt;/code&gt; column.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Read Only the Sorting Key
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;MergeTree&lt;/code&gt; table defines a sorting key using the &lt;code&gt;ORDER BY&lt;/code&gt; clause. Instead of loading every column (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;city&lt;/code&gt;, and &lt;code&gt;sales&lt;/code&gt;) into memory at once, ClickHouse first reads &lt;strong&gt;only the sorting key columns&lt;/strong&gt; to determine the correct row order for the merged part.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1 Key:&lt;/strong&gt; &lt;code&gt;id&lt;/code&gt; (1, 3)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2 Key:&lt;/strong&gt; &lt;code&gt;id&lt;/code&gt; (2, 4)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, ClickHouse completely ignores the &lt;code&gt;city&lt;/code&gt; and &lt;code&gt;sales&lt;/code&gt; columns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Determine the Correct Row Order
&lt;/h3&gt;

&lt;p&gt;Using the &lt;code&gt;id&lt;/code&gt; values, ClickHouse determines how the rows should appear in the final merged part. The correct order is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Final Position&lt;/th&gt;
&lt;th&gt;id&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;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Internally, ClickHouse remembers where every row belongs. This information is called a &lt;strong&gt;row mapping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once the row mapping has been created, ClickHouse no longer needs to recalculate the row order while merging the remaining columns, making the process both efficient and memory-friendly. You can think of it as a set of instructions saying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Row from Part 1 $\rightarrow$ Position 1&lt;/li&gt;
&lt;li&gt;Row from Part 2 $\rightarrow$ Position 2&lt;/li&gt;
&lt;li&gt;Row from Part 1 $\rightarrow$ Position 3&lt;/li&gt;
&lt;li&gt;Row from Part 2 $\rightarrow$ Position 4&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mapping is created only once and is reused while processing every remaining column. It exists only for the duration of the merge operation and is discarded after the final merged part has been written.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Merge the Remaining Columns
&lt;/h3&gt;

&lt;p&gt;Now ClickHouse starts processing the remaining columns one at a time.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Merge the &lt;code&gt;city&lt;/code&gt; column
&lt;/h4&gt;

&lt;p&gt;Using the row mapping, ClickHouse reads the &lt;code&gt;city&lt;/code&gt; values from both parts and writes them in the correct position:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;code&gt;Chennai&lt;/code&gt;, &lt;code&gt;Delhi&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;code&gt;Mumbai&lt;/code&gt;, &lt;code&gt;Pune&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resulting Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;city
----
Chennai
Mumbai
Delhi
Pune

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Merge the &lt;code&gt;sales&lt;/code&gt; column
&lt;/h4&gt;

&lt;p&gt;Next, ClickHouse clears the previous column from memory and processes the &lt;code&gt;sales&lt;/code&gt; column using the exact same row mapping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;code&gt;500&lt;/code&gt;, &lt;code&gt;700&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;code&gt;600&lt;/code&gt;, &lt;code&gt;900&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resulting Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales
-----
500
600
700
900

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

&lt;/div&gt;



&lt;p&gt;Notice that ClickHouse never loads every column into memory simultaneously. Instead, it processes one column (or a small group of columns) at a time using the previously created row mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Build the Final Merged Part
&lt;/h3&gt;

&lt;p&gt;Finally, ClickHouse combines all processed columns into a single, comprehensive merged part on disk:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;city&lt;/th&gt;
&lt;th&gt;sales&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;Chennai&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Mumbai&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Delhi&lt;/td&gt;
&lt;td&gt;700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Pune&lt;/td&gt;
&lt;td&gt;900&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The old data parts are then unlinked and removed automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does This Save Memory?
&lt;/h2&gt;

&lt;p&gt;Imagine a table with 500 columns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Horizontal Merge
&lt;/h3&gt;

&lt;p&gt;ClickHouse tries to process all 500 columns together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Memory Buffer ] ──► Contains: id, city, sales, price, quantity, ... (All 500 columns simultaneously)

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

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Requires a massive chunk of RAM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Vertical Merge
&lt;/h3&gt;

&lt;p&gt;ClickHouse processes columns sequentially, using the mapping as a guide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Memory Buffer ] ──► id ──► (Done)
[ Memory Buffer ] ──► city ──► (Done)
[ Memory Buffer ] ──► sales ──► (Done)
...

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

&lt;/div&gt;



&lt;p&gt;At any given moment, only a small portion of the overall row data is in memory, which drastically minimizes peak memory usage.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Does ClickHouse Use Vertical Merge?
&lt;/h2&gt;

&lt;p&gt;ClickHouse automatically determines whether a Vertical Merge is more efficient than a Horizontal Merge. The decision is based on several internal factors, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of columns in the table&lt;/li&gt;
&lt;li&gt;Number of rows being merged&lt;/li&gt;
&lt;li&gt;Size of the source data parts&lt;/li&gt;
&lt;li&gt;Estimated memory required for the merge&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MergeTree&lt;/code&gt; configuration thresholds (e.g., &lt;code&gt;vertical_merge_algorithm_min_columns&lt;/code&gt;, &lt;code&gt;vertical_merge_algorithm_min_rows&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If ClickHouse estimates that a Horizontal Merge would consume excessive memory, it automatically switches to a Vertical Merge. In most production environments, users do not manually choose between Horizontal Merge and Vertical Merge; ClickHouse handles this choice completely under the hood.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; Vertical Merge optimizes the merge process itself to prevent Out-Of-Memory (OOM) errors. It does not directly improve &lt;code&gt;SELECT&lt;/code&gt; query performance. However, by reducing memory pressure during background operations and keeping parts organized, it contributes to overall system stability.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Advantages &amp;amp; Limitations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Advantages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduces Peak Memory:&lt;/strong&gt; Slashing RAM footprint during heavy background activity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improves Wide Table Scalability:&lt;/strong&gt; Perfect for massive schemas with hundreds of attributes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles Complex Types Efficiently:&lt;/strong&gt; Prevents large &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Array&lt;/code&gt;, and &lt;code&gt;JSON&lt;/code&gt; columns from hogging memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintains Server Stability:&lt;/strong&gt; Minimizes the resource impact of background operations on running production queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Processing Overhead:&lt;/strong&gt; Additional management stages (like generating and reading row mappings) introduce a minor amount of compute overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inefficient for Narrow Tables:&lt;/strong&gt; Small schemas or narrow tables generally perform better with a fast, direct Horizontal Merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workload Dependent:&lt;/strong&gt; Overall gains rely heavily on proper table design and data characteristics.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Monitoring Merge Activity
&lt;/h2&gt;

&lt;p&gt;ClickHouse provides several system tables to help administrators track and audit merge strategies:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. View Active Merges
&lt;/h3&gt;

&lt;p&gt;To monitor what the background merge pool is working on right now:&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;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;num_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;merges&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. View Active Parts
&lt;/h3&gt;

&lt;p&gt;To view how parts are currently organized on disk after being merged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bytes_on_disk&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="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. View Merge History
&lt;/h3&gt;

&lt;p&gt;To evaluate how frequently ClickHouse utilizes vertical vs. horizontal merge tracks, look into the part log:&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;event_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rows_read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;memory_usage&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;part_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MergeParts'&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;event_time&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;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Vertical Merge is the quiet hero behind several high-scale database architectures:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Why Vertical Merge Helps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IoT Sensor Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-metric schemas frequently span hundreds of specialized metric columns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Log Analytics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Massive text fields (&lt;code&gt;String&lt;/code&gt;) would otherwise trigger memory spikes if loaded concurrently.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clickstream Analytics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event tables tracking granular user interactions scale widely.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Warehouses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Denormalized tables containing wide schemas with embedded &lt;code&gt;JSON&lt;/code&gt; objects merge cleanly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;INSERT&lt;/code&gt; creates a brand-new, immutable data part.&lt;/li&gt;
&lt;li&gt;Background merges continuously combine small parts into larger, sorted parts to keep query performance fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal Merge&lt;/strong&gt; processes all columns at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertical Merge&lt;/strong&gt; maps out the row layout using the sorting key columns first, then handles remaining columns piece by piece.&lt;/li&gt;
&lt;li&gt;ClickHouse intelligently and automatically picks the right merge strategy based on your schema size and system configurations.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Vertical Merge is an intelligent optimization within ClickHouse that improves the efficiency of background merge operations for wide tables. By separating the processing of sorting key columns from the remaining columns, ClickHouse significantly reduces memory consumption without affecting the correctness of the final merged data.&lt;/p&gt;

&lt;p&gt;Although Vertical Merge works automatically behind the scenes, understanding its workflow helps database administrators and data engineers interpret merge behavior, troubleshoot performance issues, and design schemas that scale efficiently as datasets continue to grow.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>database</category>
      <category>analytics</category>
      <category>devops</category>
    </item>
    <item>
      <title>Day 68 of #100DaysOfClickHouse: Benchmarking ClickHouse® – Tools and Methodologies for Reliable Performance Testing</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Thu, 09 Jul 2026 11:04:45 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-68-of-100daysofclickhouse-benchmarking-clickhouser-tools-and-methodologies-for-reliable-leg</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-68-of-100daysofclickhouse-benchmarking-clickhouser-tools-and-methodologies-for-reliable-leg</guid>
      <description>&lt;p&gt;Performance claims are everywhere.&lt;/p&gt;

&lt;p&gt;One benchmark reports a query finishing in 20 milliseconds. Another claims millions of rows processed per second. Meanwhile, someone else publishes completely different numbers using what appears to be the same hardware.&lt;/p&gt;

&lt;p&gt;The reality is that benchmarking ClickHouse® involves much more than running a query and measuring its execution time. Hardware, storage, dataset size, caching, query patterns, concurrency, and system configuration all influence the final results. Without a structured methodology, benchmark numbers can quickly become misleading.&lt;/p&gt;

&lt;p&gt;Whether you're evaluating ClickHouse® for a new analytics platform, comparing infrastructure, or validating performance improvements after optimization, consistent benchmarking techniques are essential for producing meaningful and repeatable results.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore common benchmarking tools, discuss different benchmarking methodologies, and review best practices for measuring ClickHouse® performance with confidence.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Benchmarking Matters
&lt;/h1&gt;

&lt;p&gt;Benchmarking provides objective data that helps answer important operational questions, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can our infrastructure handle the expected workload?&lt;/li&gt;
&lt;li&gt;Did the latest optimization actually improve performance?&lt;/li&gt;
&lt;li&gt;Is storage becoming a bottleneck?&lt;/li&gt;
&lt;li&gt;Which hardware or configuration performs better?&lt;/li&gt;
&lt;li&gt;How much concurrency can our cluster sustain?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without benchmarking, performance tuning becomes guesswork.&lt;/p&gt;

&lt;p&gt;Reliable benchmarks provide measurable evidence, allowing engineering teams to make informed decisions instead of relying on assumptions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Define Your Benchmark Goals First
&lt;/h1&gt;

&lt;p&gt;Before choosing a benchmarking tool, clearly define what you're trying to measure.&lt;/p&gt;

&lt;p&gt;Different workloads require different metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Query Latency
&lt;/h2&gt;

&lt;p&gt;Query latency measures how quickly analytical queries complete.&lt;/p&gt;

&lt;p&gt;This metric is particularly important for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactive dashboards&lt;/li&gt;
&lt;li&gt;Business Intelligence (BI) platforms&lt;/li&gt;
&lt;li&gt;Ad-hoc analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lower latency generally translates into a better user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Throughput
&lt;/h2&gt;

&lt;p&gt;Throughput measures how much work the system can process over a period of time.&lt;/p&gt;

&lt;p&gt;This is especially useful when evaluating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise reporting platforms&lt;/li&gt;
&lt;li&gt;Multi-user analytics&lt;/li&gt;
&lt;li&gt;Production workload simulations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High throughput ensures the system continues performing well under sustained demand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Insert Performance
&lt;/h2&gt;

&lt;p&gt;Many ClickHouse® deployments ingest data continuously.&lt;/p&gt;

&lt;p&gt;Measuring insert performance is critical for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Streaming pipelines&lt;/li&gt;
&lt;li&gt;Kafka ingestion&lt;/li&gt;
&lt;li&gt;ETL workflows&lt;/li&gt;
&lt;li&gt;Log analytics platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A fast analytical database must also support efficient data ingestion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concurrent Workloads
&lt;/h2&gt;

&lt;p&gt;Real production systems rarely execute a single query at a time.&lt;/p&gt;

&lt;p&gt;Testing concurrent workloads helps determine how performance changes as multiple users submit queries simultaneously.&lt;/p&gt;

&lt;p&gt;Concurrency testing provides a much more realistic picture of production behavior than isolated single-query benchmarks.&lt;/p&gt;




&lt;h1&gt;
  
  
  Choosing Representative Data
&lt;/h1&gt;

&lt;p&gt;One of the most common benchmarking mistakes is using datasets that are too small.&lt;/p&gt;

&lt;p&gt;ClickHouse® is designed for analytical workloads involving millions—or even billions—of rows.&lt;/p&gt;

&lt;p&gt;Testing with only a few thousand records often produces unrealistic results because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything fits into memory.&lt;/li&gt;
&lt;li&gt;Compression behaves differently.&lt;/li&gt;
&lt;li&gt;Storage isn't stressed.&lt;/li&gt;
&lt;li&gt;Index efficiency is not representative.&lt;/li&gt;
&lt;li&gt;Background merges have little impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, benchmark using datasets that closely resemble your production environment.&lt;/p&gt;

&lt;p&gt;Popular public datasets include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UK Property Price Data&lt;/li&gt;
&lt;li&gt;NYC Taxi Dataset&lt;/li&gt;
&lt;li&gt;Stack Overflow&lt;/li&gt;
&lt;li&gt;GitHub Events&lt;/li&gt;
&lt;li&gt;Amazon Reviews&lt;/li&gt;
&lt;li&gt;Wikimedia Pageviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Large datasets expose realistic query patterns and produce far more meaningful benchmark results.&lt;/p&gt;




&lt;h1&gt;
  
  
  Popular Benchmarking Tools
&lt;/h1&gt;

&lt;p&gt;Several tools are commonly used to benchmark ClickHouse®.&lt;/p&gt;

&lt;h2&gt;
  
  
  clickhouse-benchmark
&lt;/h2&gt;

&lt;p&gt;The official benchmarking utility included with ClickHouse®.&lt;/p&gt;

&lt;p&gt;It repeatedly executes SQL queries and reports metrics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries per second&lt;/li&gt;
&lt;li&gt;Average latency&lt;/li&gt;
&lt;li&gt;Percentile latency&lt;/li&gt;
&lt;li&gt;Throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because it communicates directly with ClickHouse®, it is ideal for measuring SQL execution performance.&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 shell"&gt;&lt;code&gt;clickhouse-benchmark &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"SELECT count() FROM events"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  clickhouse-compressor
&lt;/h2&gt;

&lt;p&gt;Although it doesn't benchmark SQL queries, &lt;code&gt;clickhouse-compressor&lt;/code&gt; is useful for evaluating storage efficiency.&lt;/p&gt;

&lt;p&gt;It measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compression ratio&lt;/li&gt;
&lt;li&gt;Compressed size&lt;/li&gt;
&lt;li&gt;Compression speed&lt;/li&gt;
&lt;li&gt;Decompression performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding compression performance helps optimize storage usage and query efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Apache JMeter
&lt;/h2&gt;

&lt;p&gt;Apache JMeter is commonly used to simulate many concurrent users.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;HTTP interfaces&lt;/li&gt;
&lt;li&gt;Authentication testing&lt;/li&gt;
&lt;li&gt;Dashboard traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JMeter is well suited for evaluating performance under sustained concurrent load.&lt;/p&gt;




&lt;h2&gt;
  
  
  k6
&lt;/h2&gt;

&lt;p&gt;k6 has become one of the most popular modern load-testing frameworks.&lt;/p&gt;

&lt;p&gt;Its advantages include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript-based scripting&lt;/li&gt;
&lt;li&gt;Cloud execution support&lt;/li&gt;
&lt;li&gt;HTTP benchmarking&lt;/li&gt;
&lt;li&gt;CI/CD integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For API-driven ClickHouse® workloads, k6 offers a flexible and developer-friendly testing experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  sysbench
&lt;/h2&gt;

&lt;p&gt;sysbench focuses primarily on benchmarking hardware rather than databases.&lt;/p&gt;

&lt;p&gt;It can evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU performance&lt;/li&gt;
&lt;li&gt;Storage performance&lt;/li&gt;
&lt;li&gt;File I/O&lt;/li&gt;
&lt;li&gt;Memory throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since hardware significantly influences ClickHouse® performance, benchmarking infrastructure separately helps identify bottlenecks before evaluating database performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cold Cache vs Warm Cache
&lt;/h1&gt;

&lt;p&gt;Caching is one of the most overlooked factors in database benchmarking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warm Cache
&lt;/h2&gt;

&lt;p&gt;In a warm cache scenario, frequently accessed data has already been loaded into memory.&lt;/p&gt;

&lt;p&gt;Because disk reads are minimized, query execution is often significantly faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cold Cache
&lt;/h2&gt;

&lt;p&gt;A cold cache benchmark clears the operating system's file cache before testing.&lt;/p&gt;

&lt;p&gt;This measures first-time query performance and better reflects workloads where data is not already cached.&lt;/p&gt;

&lt;p&gt;Both scenarios are valuable because real production systems experience both cache hits and cache misses.&lt;/p&gt;

&lt;p&gt;Whenever benchmark results are published, always specify whether they represent warm-cache or cold-cache performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Measure More Than Query Time
&lt;/h1&gt;

&lt;p&gt;Execution time alone rarely tells the complete story.&lt;/p&gt;

&lt;p&gt;A comprehensive benchmark should also monitor system resources, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU utilization&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Disk throughput&lt;/li&gt;
&lt;li&gt;Network bandwidth&lt;/li&gt;
&lt;li&gt;Read amplification&lt;/li&gt;
&lt;li&gt;Background merges&lt;/li&gt;
&lt;li&gt;Compression efficiency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse® exposes many of these metrics through its system tables, making it easier to correlate query performance with resource consumption.&lt;/p&gt;

&lt;p&gt;Looking beyond query latency often reveals the real cause of performance bottlenecks.&lt;/p&gt;




&lt;h1&gt;
  
  
  Keep the Environment Consistent
&lt;/h1&gt;

&lt;p&gt;Reliable benchmarking depends on repeatable conditions.&lt;/p&gt;

&lt;p&gt;Maintain consistency across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ClickHouse® version&lt;/li&gt;
&lt;li&gt;Hardware&lt;/li&gt;
&lt;li&gt;Storage type&lt;/li&gt;
&lt;li&gt;Dataset&lt;/li&gt;
&lt;li&gt;Query workload&lt;/li&gt;
&lt;li&gt;Configuration settings&lt;/li&gt;
&lt;li&gt;Number of execution threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Changing multiple variables simultaneously makes it difficult to determine which factor actually influenced the results.&lt;/p&gt;

&lt;p&gt;Consistency is essential for fair comparisons.&lt;/p&gt;




&lt;h1&gt;
  
  
  Avoid Common Benchmarking Mistakes
&lt;/h1&gt;

&lt;p&gt;Many published benchmarks unintentionally produce misleading conclusions.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Benchmarking tiny datasets&lt;/li&gt;
&lt;li&gt;Measuring only cached queries&lt;/li&gt;
&lt;li&gt;Ignoring concurrent workloads&lt;/li&gt;
&lt;li&gt;Comparing different hardware configurations&lt;/li&gt;
&lt;li&gt;Using different table schemas&lt;/li&gt;
&lt;li&gt;Changing compression codecs&lt;/li&gt;
&lt;li&gt;Reporting only average latency&lt;/li&gt;
&lt;li&gt;Running too few benchmark iterations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of relying solely on average latency, analyze percentile latency (P95, P99) and repeat each benchmark multiple times to reduce variability.&lt;/p&gt;




&lt;h1&gt;
  
  
  Automating Benchmarks
&lt;/h1&gt;

&lt;p&gt;Performance testing should become part of your development workflow rather than an occasional task.&lt;/p&gt;

&lt;p&gt;Many engineering teams integrate benchmarking into their CI/CD pipelines by automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loading representative datasets&lt;/li&gt;
&lt;li&gt;Executing benchmark suites&lt;/li&gt;
&lt;li&gt;Collecting performance metrics&lt;/li&gt;
&lt;li&gt;Comparing previous benchmark results&lt;/li&gt;
&lt;li&gt;Generating performance reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automated benchmarking helps detect regressions early and ensures that performance improvements remain consistent across software releases.&lt;/p&gt;




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

&lt;p&gt;Benchmarking ClickHouse® isn't about producing impressive numbers—it's about generating reliable, reproducible measurements that reflect real-world workloads.&lt;/p&gt;

&lt;p&gt;Defining clear objectives, selecting representative datasets, using appropriate benchmarking tools, and maintaining consistent testing conditions all contribute to trustworthy performance evaluations.&lt;/p&gt;

&lt;p&gt;Looking beyond simple query execution time to include latency, throughput, concurrency, and resource utilization provides a much more complete understanding of system behavior.&lt;/p&gt;

&lt;p&gt;As analytical workloads continue to grow, disciplined benchmarking becomes an essential engineering practice for validating optimizations, planning infrastructure, and ensuring that improvements translate successfully into production.&lt;/p&gt;

&lt;p&gt;For teams operating ClickHouse® at scale, combining systematic benchmarking with strong operational visibility enables more confident tuning decisions, better capacity planning, and more predictable long-term performance.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Day 67 of #100DaysOfClickHouse: Securing ClickHouse® with SSL/TLS and LDAP Authentication</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:40:26 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-67-of-100daysofclickhouse-securing-clickhouser-with-ssltls-and-ldap-authentication-1cl9</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-67-of-100daysofclickhouse-securing-clickhouser-with-ssltls-and-ldap-authentication-1cl9</guid>
      <description>&lt;p&gt;Security is often treated as something to configure after a ClickHouse® deployment is already serving queries. In practice, that approach leaves databases exposed to unnecessary risks such as unencrypted credentials, unauthorized access, and inconsistent user management.&lt;/p&gt;

&lt;p&gt;ClickHouse® provides built-in capabilities to address these challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSL/TLS&lt;/strong&gt; encrypts communication between clients and the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LDAP integration&lt;/strong&gt; centralizes user authentication using an existing enterprise directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although these technologies are often deployed together, they solve different problems. SSL/TLS protects data while it is being transmitted, whereas LDAP verifies whether a user is allowed to authenticate with the database.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn what each feature does, when to use it, and the practical considerations for deploying them securely in production.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Security Matters in ClickHouse®
&lt;/h1&gt;

&lt;p&gt;ClickHouse® powers many business-critical analytical workloads, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Business Intelligence (BI) platforms&lt;/li&gt;
&lt;li&gt;Customer analytics&lt;/li&gt;
&lt;li&gt;Log analytics&lt;/li&gt;
&lt;li&gt;Observability platforms&lt;/li&gt;
&lt;li&gt;Financial reporting&lt;/li&gt;
&lt;li&gt;Enterprise data warehouses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These systems frequently store customer information, operational metrics, financial records, and other sensitive business data.&lt;/p&gt;

&lt;p&gt;Without proper security controls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login credentials may be intercepted.&lt;/li&gt;
&lt;li&gt;Query traffic can be monitored.&lt;/li&gt;
&lt;li&gt;Sensitive query results may be exposed.&lt;/li&gt;
&lt;li&gt;Managing user accounts across multiple clusters becomes increasingly difficult.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A secure ClickHouse® deployment focuses on three key objectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypt every network connection.&lt;/li&gt;
&lt;li&gt;Authenticate users through a centralized identity provider.&lt;/li&gt;
&lt;li&gt;Enforce least-privilege access using ClickHouse® roles and permissions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Understanding SSL/TLS in ClickHouse®
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;SSL (Secure Sockets Layer)&lt;/strong&gt; and its successor &lt;strong&gt;TLS (Transport Layer Security)&lt;/strong&gt; encrypt communication between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applications&lt;/li&gt;
&lt;li&gt;BI tools&lt;/li&gt;
&lt;li&gt;ClickHouse® clients&lt;/li&gt;
&lt;li&gt;ClickHouse® servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without encryption, information travels across the network in plain text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   ├── Username
   ├── Password
   ├── SQL Queries
   └── Query Results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anyone with access to the network could potentially inspect this traffic.&lt;/p&gt;

&lt;p&gt;When TLS is enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
   │
   └──── Encrypted Connection ───► ClickHouse® Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the client and the server can decrypt the transmitted data.&lt;/p&gt;




&lt;h1&gt;
  
  
  What SSL/TLS Protects
&lt;/h1&gt;

&lt;p&gt;TLS secures &lt;strong&gt;data in transit&lt;/strong&gt;.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Login credentials&lt;/li&gt;
&lt;li&gt;SQL queries&lt;/li&gt;
&lt;li&gt;Query results&lt;/li&gt;
&lt;li&gt;HTTP API traffic&lt;/li&gt;
&lt;li&gt;Native ClickHouse® protocol traffic&lt;/li&gt;
&lt;li&gt;Inter-server communication (when configured)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, TLS does &lt;strong&gt;not&lt;/strong&gt; protect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data stored on disk&lt;/li&gt;
&lt;li&gt;User permissions&lt;/li&gt;
&lt;li&gt;Access control policies&lt;/li&gt;
&lt;li&gt;Authentication logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those responsibilities are handled by other security mechanisms.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Typical Production Deployment
&lt;/h1&gt;

&lt;p&gt;A secure production architecture often resembles the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BI Tool
      │
   HTTPS / TLS
      │
 Application
      │
      TLS
      │
ClickHouse®
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every network connection is encrypted before user authentication begins.&lt;/p&gt;




&lt;h1&gt;
  
  
  Configuring TLS
&lt;/h1&gt;

&lt;p&gt;Deploying TLS typically involves four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create or obtain server certificates.&lt;/li&gt;
&lt;li&gt;Configure ClickHouse® to use those certificates.&lt;/li&gt;
&lt;li&gt;Configure clients to trust the Certificate Authority (CA).&lt;/li&gt;
&lt;li&gt;Verify encrypted connectivity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For production environments, certificates issued by a trusted Certificate Authority or an organization's internal Public Key Infrastructure (PKI) are recommended.&lt;/p&gt;

&lt;p&gt;Self-signed certificates are useful for development, testing, and learning, but they are generally not suitable for production deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Client Certificate Authentication
&lt;/h1&gt;

&lt;p&gt;ClickHouse® also supports authentication using &lt;strong&gt;X.509 client certificates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of transmitting passwords, the client presents a certificate during the TLS handshake.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Passwordless authentication&lt;/li&gt;
&lt;li&gt;Strong identity verification&lt;/li&gt;
&lt;li&gt;Reduced credential management&lt;/li&gt;
&lt;li&gt;Better integration with enterprise PKI environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Client certificate authentication is supported through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS interface&lt;/li&gt;
&lt;li&gt;Native ClickHouse® protocol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; supported through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gRPC interface&lt;/li&gt;
&lt;li&gt;PostgreSQL compatibility interface&lt;/li&gt;
&lt;li&gt;MySQL compatibility interface&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Introducing LDAP
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;LDAP (Lightweight Directory Access Protocol)&lt;/strong&gt; is a centralized directory service used by organizations to manage user identities.&lt;/p&gt;

&lt;p&gt;Rather than creating separate user accounts for every application, organizations typically maintain identities in systems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Active Directory&lt;/li&gt;
&lt;li&gt;OpenLDAP&lt;/li&gt;
&lt;li&gt;Other LDAP-compatible directory services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applications authenticate users against this central directory.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why LDAP Is Useful
&lt;/h1&gt;

&lt;p&gt;Imagine an organization with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;600 employees&lt;/li&gt;
&lt;li&gt;25 internal applications&lt;/li&gt;
&lt;li&gt;Multiple ClickHouse® clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Managing separate user accounts for every database quickly becomes difficult.&lt;/p&gt;

&lt;p&gt;Using LDAP provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Employees keep a single corporate account.&lt;/li&gt;
&lt;li&gt;Password policies remain centrally managed.&lt;/li&gt;
&lt;li&gt;Disabling an employee's directory account immediately prevents future authentication.&lt;/li&gt;
&lt;li&gt;Administrators avoid maintaining duplicate credentials across multiple systems.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  How LDAP Works in ClickHouse®
&lt;/h1&gt;

&lt;p&gt;ClickHouse® supports LDAP in two primary ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. External Authentication
&lt;/h2&gt;

&lt;p&gt;Local ClickHouse® users authenticate using an LDAP server instead of locally stored passwords.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
   │
Username + Password
   │
ClickHouse®
   │
LDAP Server
   │
Authentication Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ClickHouse® validates the supplied credentials against the LDAP directory before allowing access.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. LDAP User Directory
&lt;/h2&gt;

&lt;p&gt;ClickHouse® can also use LDAP as a remote user directory.&lt;/p&gt;

&lt;p&gt;In this configuration, users do not need to be created locally inside ClickHouse®. Instead, user information is retrieved directly from LDAP during authentication.&lt;/p&gt;

&lt;p&gt;This simplifies user administration for large organizations.&lt;/p&gt;




&lt;h1&gt;
  
  
  LDAP Security Considerations
&lt;/h1&gt;

&lt;p&gt;LDAP authentication does &lt;strong&gt;not&lt;/strong&gt; automatically encrypt network traffic.&lt;/p&gt;

&lt;p&gt;If ClickHouse® communicates with an LDAP server over plain LDAP (&lt;code&gt;ldap://&lt;/code&gt;), credentials travel without transport encryption.&lt;/p&gt;

&lt;p&gt;For production deployments, secure communication should always be used.&lt;/p&gt;

&lt;p&gt;ClickHouse® supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LDAPS (&lt;code&gt;ldaps://&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;StartTLS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server configuration includes options for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabling TLS&lt;/li&gt;
&lt;li&gt;Minimum TLS protocol version&lt;/li&gt;
&lt;li&gt;Certificate verification&lt;/li&gt;
&lt;li&gt;Trusted CA certificates&lt;/li&gt;
&lt;li&gt;Client certificates&lt;/li&gt;
&lt;li&gt;Cipher suite selection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using encrypted LDAP connections is the recommended production approach.&lt;/p&gt;

&lt;p&gt;Plain LDAP should generally be limited to isolated development or testing environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  SSL/TLS and LDAP Solve Different Problems
&lt;/h1&gt;

&lt;p&gt;Although often deployed together, SSL/TLS and LDAP serve different purposes.&lt;/p&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;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSL/TLS&lt;/td&gt;
&lt;td&gt;Encrypts communication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LDAP&lt;/td&gt;
&lt;td&gt;Authenticates user identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClickHouse® Roles&lt;/td&gt;
&lt;td&gt;Controls authorization and permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simplified security flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLS
   │
Creates a secure connection
   │
LDAP
   │
Verifies user identity
   │
ClickHouse® Roles
   │
Determines what the user is allowed to access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer complements the others to create a secure deployment.&lt;/p&gt;




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

&lt;p&gt;When deploying ClickHouse® in production, consider the following recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable TLS for all client connections.&lt;/li&gt;
&lt;li&gt;Use certificates issued by a trusted CA or enterprise PKI.&lt;/li&gt;
&lt;li&gt;Always verify server certificates.&lt;/li&gt;
&lt;li&gt;Use LDAPS or StartTLS for LDAP integration.&lt;/li&gt;
&lt;li&gt;Never transmit credentials over unencrypted LDAP.&lt;/li&gt;
&lt;li&gt;Apply the principle of least privilege using ClickHouse® roles.&lt;/li&gt;
&lt;li&gt;Rotate certificates before they expire.&lt;/li&gt;
&lt;li&gt;Monitor authentication failures and certificate expiration dates.&lt;/li&gt;
&lt;li&gt;Test security configuration changes in non-production environments before rollout.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Common Misconceptions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  "Enabling TLS secures the entire database."
&lt;/h2&gt;

&lt;p&gt;Not entirely.&lt;/p&gt;

&lt;p&gt;TLS protects network communication but does not replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access control&lt;/li&gt;
&lt;li&gt;Disk encryption&lt;/li&gt;
&lt;li&gt;Backups&lt;/li&gt;
&lt;li&gt;Auditing&lt;/li&gt;
&lt;li&gt;User permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These controls remain essential for a secure deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  "LDAP replaces ClickHouse® permissions."
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;LDAP is responsible for &lt;strong&gt;authentication&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Authorization—what a user is allowed to access—is still managed inside ClickHouse® through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Roles&lt;/li&gt;
&lt;li&gt;Privileges&lt;/li&gt;
&lt;li&gt;Quotas&lt;/li&gt;
&lt;li&gt;Row policies&lt;/li&gt;
&lt;li&gt;Other access control mechanisms&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  "Self-signed certificates are sufficient for production."
&lt;/h2&gt;

&lt;p&gt;Generally, no.&lt;/p&gt;

&lt;p&gt;Self-signed certificates are appropriate for development and testing environments.&lt;/p&gt;

&lt;p&gt;Production deployments should typically use certificates issued by a trusted Certificate Authority or an internal enterprise PKI so clients can verify the server's identity automatically.&lt;/p&gt;




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

&lt;p&gt;Security in ClickHouse® is built through multiple complementary layers rather than a single feature.&lt;/p&gt;

&lt;p&gt;SSL/TLS protects data while it travels across the network, reducing the risk of intercepted credentials and query traffic. LDAP simplifies identity management by allowing organizations to authenticate users through a centralized directory instead of maintaining separate credentials for every database server.&lt;/p&gt;

&lt;p&gt;Neither technology replaces authorization. A secure ClickHouse® deployment combines encrypted communication, centralized authentication, and carefully managed roles and permissions to provide defense in depth.&lt;/p&gt;

&lt;p&gt;By implementing SSL/TLS alongside LDAP and following security best practices, organizations can build ClickHouse® environments that are both secure and easier to manage as they scale.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 66/100 - ClickHouse Keeper vs ZooKeeper: Migration and Benchmarks</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Wed, 08 Jul 2026 11:49:21 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-66100-clickhouse-keeper-vs-zookeeper-migration-and-benchmarks-1eep</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-66100-clickhouse-keeper-vs-zookeeper-migration-and-benchmarks-1eep</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Distributed ClickHouse® clusters rely on a coordination service to manage replication, distributed DDL execution, leader election, and metadata consistency across nodes. Without a reliable coordination layer, replicas can become inconsistent, distributed queries may fail, and cluster management becomes significantly more complex.&lt;/p&gt;

&lt;p&gt;For many years, Apache ZooKeeper has served as the default coordination service for ClickHouse®. While ZooKeeper is proven and widely adopted across the big data ecosystem, it introduces additional infrastructure, operational complexity, and resource consumption because it must be deployed and maintained as a separate cluster.&lt;/p&gt;

&lt;p&gt;To simplify distributed deployments, ClickHouse® introduced &lt;strong&gt;ClickHouse Keeper&lt;/strong&gt;—a native coordination service built specifically for ClickHouse workloads. It implements the ZooKeeper protocol, allowing it to serve as a drop-in replacement while offering lower operational overhead and improved performance for ClickHouse clusters.&lt;/p&gt;

&lt;p&gt;In this article, we'll compare ZooKeeper and ClickHouse Keeper, understand their architectural differences, walk through a migration process, and examine why Keeper has become the recommended choice for modern ClickHouse deployments.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is Apache ZooKeeper?
&lt;/h1&gt;

&lt;p&gt;Apache ZooKeeper is an open-source distributed coordination service used by many large-scale distributed systems, including Kafka, Hadoop, HBase, and ClickHouse®.&lt;/p&gt;

&lt;p&gt;Within ClickHouse®, ZooKeeper is responsible for coordinating operations across cluster nodes.&lt;/p&gt;

&lt;p&gt;Its primary responsibilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracking replicated table metadata&lt;/li&gt;
&lt;li&gt;Coordinating replication between replicas&lt;/li&gt;
&lt;li&gt;Executing distributed DDL queries&lt;/li&gt;
&lt;li&gt;Maintaining cluster topology information&lt;/li&gt;
&lt;li&gt;Supporting leader election&lt;/li&gt;
&lt;li&gt;Synchronizing metadata changes across nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ZooKeeper provides reliable coordination, but it was designed as a general-purpose coordination system rather than one optimized specifically for ClickHouse.&lt;/p&gt;




&lt;h1&gt;
  
  
  Limitations of ZooKeeper
&lt;/h1&gt;

&lt;p&gt;Although ZooKeeper has been the standard coordination service for years, it comes with several operational challenges.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Requires a dedicated cluster of at least three nodes for fault tolerance&lt;/li&gt;
&lt;li&gt;Introduces additional infrastructure to manage&lt;/li&gt;
&lt;li&gt;Consumes extra CPU and memory resources&lt;/li&gt;
&lt;li&gt;Requires separate monitoring and maintenance&lt;/li&gt;
&lt;li&gt;Adds operational complexity during upgrades&lt;/li&gt;
&lt;li&gt;Can become a bottleneck in heavily replicated ClickHouse clusters&lt;/li&gt;
&lt;li&gt;Requires administrators to manage another distributed system alongside ClickHouse®&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As clusters grow larger, these operational costs become increasingly significant.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is ClickHouse Keeper?
&lt;/h1&gt;

&lt;p&gt;ClickHouse Keeper is ClickHouse®'s native coordination service.&lt;/p&gt;

&lt;p&gt;Rather than relying on an external ZooKeeper installation, Keeper is integrated directly into ClickHouse and is designed specifically for ClickHouse replication workloads.&lt;/p&gt;

&lt;p&gt;Most importantly, Keeper implements the ZooKeeper protocol, making it compatible with existing ClickHouse replication mechanisms without requiring application changes.&lt;/p&gt;

&lt;p&gt;Key characteristics include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native integration with ClickHouse®&lt;/li&gt;
&lt;li&gt;ZooKeeper protocol compatibility&lt;/li&gt;
&lt;li&gt;Uses the Raft consensus algorithm&lt;/li&gt;
&lt;li&gt;Can run embedded inside ClickHouse servers&lt;/li&gt;
&lt;li&gt;Can also run as a standalone service&lt;/li&gt;
&lt;li&gt;Lower latency for ClickHouse coordination workloads&lt;/li&gt;
&lt;li&gt;Reduced resource consumption&lt;/li&gt;
&lt;li&gt;Simplified deployment and operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most new ClickHouse deployments, Keeper is now the recommended coordination service.&lt;/p&gt;




&lt;h1&gt;
  
  
  ZooKeeper vs ClickHouse Keeper
&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;ZooKeeper&lt;/th&gt;
&lt;th&gt;ClickHouse Keeper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Built into ClickHouse&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consensus Algorithm&lt;/td&gt;
&lt;td&gt;ZAB&lt;/td&gt;
&lt;td&gt;Raft&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZooKeeper Protocol Compatibility&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;Compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate Installation Required&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational Complexity&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;Resource 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;Performance&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Optimized for ClickHouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended for New Deployments&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;
  
  
  Why Migrate to ClickHouse Keeper?
&lt;/h1&gt;

&lt;p&gt;Migrating to Keeper provides several operational and performance benefits.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Simplified architecture&lt;/li&gt;
&lt;li&gt;Lower infrastructure costs&lt;/li&gt;
&lt;li&gt;Fewer servers to maintain&lt;/li&gt;
&lt;li&gt;Reduced operational overhead&lt;/li&gt;
&lt;li&gt;Faster startup and recovery&lt;/li&gt;
&lt;li&gt;Lower CPU and memory usage&lt;/li&gt;
&lt;li&gt;Native ClickHouse integration&lt;/li&gt;
&lt;li&gt;Easier configuration management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations deploying new ClickHouse clusters typically benefit from using Keeper unless they already depend heavily on an existing ZooKeeper ecosystem.&lt;/p&gt;




&lt;h1&gt;
  
  
  Typical Cluster Architecture
&lt;/h1&gt;

&lt;h2&gt;
  
  
  ZooKeeper-Based Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse Node 1 ──┐
ClickHouse Node 2 ──┤──── ZooKeeper Cluster
ClickHouse Node 3 ──┘

                    ├── ZooKeeper Node 1
                    ├── ZooKeeper Node 2
                    └── ZooKeeper Node 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ZooKeeper deployment requires an entirely separate coordination cluster that must be monitored, upgraded, and maintained independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  ClickHouse Keeper Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClickHouse Node 1 (+ Keeper) ──┐
ClickHouse Node 2 (+ Keeper) ──┤── Keeper Raft Cluster
ClickHouse Node 3 (+ Keeper) ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No external ZooKeeper installation is required.&lt;/p&gt;

&lt;p&gt;The coordination service becomes part of the ClickHouse infrastructure itself.&lt;/p&gt;




&lt;h1&gt;
  
  
  Deploying ClickHouse Keeper
&lt;/h1&gt;

&lt;p&gt;Keeper supports two deployment models.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mode 1: Embedded Deployment
&lt;/h2&gt;

&lt;p&gt;In embedded mode, Keeper runs inside the ClickHouse server process.&lt;/p&gt;

&lt;p&gt;Example configuration:&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;keeper_server&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tcp_port&amp;gt;&lt;/span&gt;9181&lt;span class="nt"&gt;&amp;lt;/tcp_port&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;server_id&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/server_id&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;log_storage_path&amp;gt;&lt;/span&gt;
        /var/lib/clickhouse/coordination/log
    &lt;span class="nt"&gt;&amp;lt;/log_storage_path&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;snapshot_storage_path&amp;gt;&lt;/span&gt;
        /var/lib/clickhouse/coordination/snapshots
    &lt;span class="nt"&gt;&amp;lt;/snapshot_storage_path&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;coordination_settings&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;operation_timeout_ms&amp;gt;&lt;/span&gt;10000&lt;span class="nt"&gt;&amp;lt;/operation_timeout_ms&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;session_timeout_ms&amp;gt;&lt;/span&gt;30000&lt;span class="nt"&gt;&amp;lt;/session_timeout_ms&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;raft_logs_level&amp;gt;&lt;/span&gt;warning&lt;span class="nt"&gt;&amp;lt;/raft_logs_level&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/coordination_settings&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;raft_configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;server&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;hostname&amp;gt;&lt;/span&gt;clickhouse-node-1&lt;span class="nt"&gt;&amp;lt;/hostname&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9234&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/server&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;server&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;2&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;hostname&amp;gt;&lt;/span&gt;clickhouse-node-2&lt;span class="nt"&gt;&amp;lt;/hostname&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9234&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/server&amp;gt;&lt;/span&gt;

        &lt;span class="nt"&gt;&amp;lt;server&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;hostname&amp;gt;&lt;/span&gt;clickhouse-node-3&lt;span class="nt"&gt;&amp;lt;/hostname&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9234&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/server&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/raft_configuration&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/keeper_server&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deployment is ideal for most production clusters because it eliminates the need for additional coordination servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mode 2: Standalone Deployment
&lt;/h2&gt;

&lt;p&gt;Keeper can also run as an independent service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clickhouse-keeper &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/clickhouse-keeper/config.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standalone mode is often preferred in larger enterprise deployments where coordination services are managed independently from database nodes.&lt;/p&gt;




&lt;h1&gt;
  
  
  Migrating from ZooKeeper to ClickHouse Keeper
&lt;/h1&gt;

&lt;p&gt;Migration can usually be completed with minimal disruption by following a structured process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Verify Replication Health
&lt;/h2&gt;

&lt;p&gt;Before making any changes, ensure all replicas are synchronized.&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;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_readonly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue_size&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;replicas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;queue_size&lt;/code&gt; of &lt;strong&gt;0&lt;/strong&gt; indicates that replication is fully caught up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Configure Keeper
&lt;/h2&gt;

&lt;p&gt;Add the Keeper configuration to &lt;code&gt;config.xml&lt;/code&gt; on every ClickHouse node.&lt;/p&gt;

&lt;p&gt;Each node must have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A unique &lt;code&gt;server_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Correct Raft configuration&lt;/li&gt;
&lt;li&gt;Shared cluster membership information&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 3: Update Cluster Configuration
&lt;/h2&gt;

&lt;p&gt;Replace ZooKeeper endpoints with Keeper endpoints.&lt;/p&gt;

&lt;p&gt;Old configuration:&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;zookeeper&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;node&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;zk1&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;2181&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/node&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/zookeeper&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;New configuration:&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;zookeeper&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;node&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;keeper1&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9181&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/node&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/zookeeper&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because Keeper implements the ZooKeeper protocol, most replication settings remain unchanged.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Restart ClickHouse
&lt;/h2&gt;

&lt;p&gt;Restart each ClickHouse server individually.&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 clickhouse-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restarting one node at a time minimizes disruption and keeps the cluster available throughout the migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Validate the Migration
&lt;/h2&gt;

&lt;p&gt;Verify that replication remains healthy.&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;database&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;is_session_expired&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;replicas&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Healthy output should show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queue_size = 0&lt;/li&gt;
&lt;li&gt;is_session_expired = 0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This confirms successful migration.&lt;/p&gt;




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

&lt;p&gt;The following table summarizes typical operational characteristics.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;ZooKeeper&lt;/th&gt;
&lt;th&gt;ClickHouse Keeper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Startup Time&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;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;CPU 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;Coordination Latency&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Complexity&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance Effort&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClickHouse Integration&lt;/td&gt;
&lt;td&gt;External&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In many production environments, Keeper also demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster write coordination&lt;/li&gt;
&lt;li&gt;Lower memory consumption&lt;/li&gt;
&lt;li&gt;More consistent latency under heavy workloads&lt;/li&gt;
&lt;li&gt;Faster replica synchronization&lt;/li&gt;
&lt;li&gt;Faster recovery after failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Actual performance varies depending on workload, hardware, cluster size, and configuration.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Migration Challenges
&lt;/h1&gt;

&lt;p&gt;Administrators may encounter several issues during migration.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Duplicate or incorrect &lt;code&gt;server_id&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;Missing Raft configuration&lt;/li&gt;
&lt;li&gt;Firewall rules blocking ports 9181 or 9234&lt;/li&gt;
&lt;li&gt;Unsynchronized replicas before migration&lt;/li&gt;
&lt;li&gt;Version incompatibilities&lt;/li&gt;
&lt;li&gt;Incorrect hostname resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always verify replication health before redirecting production workloads to Keeper.&lt;/p&gt;




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

&lt;p&gt;To ensure a smooth migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Schedule maintenance windows whenever possible.&lt;/li&gt;
&lt;li&gt;Back up ZooKeeper metadata.&lt;/li&gt;
&lt;li&gt;Verify replica synchronization before migration.&lt;/li&gt;
&lt;li&gt;Restart one node at a time.&lt;/li&gt;
&lt;li&gt;Monitor &lt;code&gt;system.replicas&lt;/code&gt; continuously.&lt;/li&gt;
&lt;li&gt;Validate distributed DDL execution.&lt;/li&gt;
&lt;li&gt;Ensure compatible ClickHouse versions across the cluster.&lt;/li&gt;
&lt;li&gt;Test failover after migration completes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Choosing Between ZooKeeper and Keeper
&lt;/h1&gt;

&lt;p&gt;Choose &lt;strong&gt;ZooKeeper&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You already maintain a mature ZooKeeper infrastructure.&lt;/li&gt;
&lt;li&gt;Multiple applications depend on ZooKeeper.&lt;/li&gt;
&lt;li&gt;Operational processes are already built around ZooKeeper clusters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose &lt;strong&gt;ClickHouse Keeper&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're deploying a new ClickHouse cluster.&lt;/li&gt;
&lt;li&gt;You want a simpler architecture.&lt;/li&gt;
&lt;li&gt;You want fewer infrastructure components.&lt;/li&gt;
&lt;li&gt;Your coordination workload is primarily ClickHouse replication.&lt;/li&gt;
&lt;li&gt;You want lower operational overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most modern ClickHouse deployments, Keeper is the preferred option.&lt;/p&gt;




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

&lt;p&gt;ClickHouse Keeper simplifies distributed ClickHouse® deployments by replacing the need for a dedicated ZooKeeper cluster with a lightweight, native coordination service.&lt;/p&gt;

&lt;p&gt;By implementing the ZooKeeper protocol while optimizing specifically for ClickHouse workloads, Keeper reduces infrastructure complexity, lowers operational costs, improves resource efficiency, and simplifies cluster administration.&lt;/p&gt;

&lt;p&gt;Migrating from ZooKeeper is generally straightforward because existing replication configurations require minimal changes. By validating replication health, carefully updating cluster configurations, and following a phased migration process, organizations can transition safely with minimal downtime.&lt;/p&gt;

&lt;p&gt;As ClickHouse continues to evolve, Keeper has become the recommended coordination service for most new deployments, offering a simpler, more efficient, and highly reliable foundation for managing distributed ClickHouse clusters.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;ClickHouse® clusters require a coordination service for replication and distributed operations.&lt;/li&gt;
&lt;li&gt;Apache ZooKeeper has traditionally provided this functionality but requires separate infrastructure.&lt;/li&gt;
&lt;li&gt;ClickHouse Keeper is a native coordination service optimized specifically for ClickHouse.&lt;/li&gt;
&lt;li&gt;Keeper implements the ZooKeeper protocol, making migration straightforward.&lt;/li&gt;
&lt;li&gt;Embedded deployment simplifies infrastructure by eliminating external coordination servers.&lt;/li&gt;
&lt;li&gt;Migration typically involves verifying replication, updating configuration, restarting nodes, and validating cluster health.&lt;/li&gt;
&lt;li&gt;Keeper generally offers lower latency, lower resource usage, and reduced operational complexity.&lt;/li&gt;
&lt;li&gt;For most new ClickHouse® deployments, ClickHouse Keeper is the recommended coordination service.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Day 65/100 - Implementing Exact-Once Semantics with ClickHouse® and Kafka</title>
      <dc:creator>Kanishga Subramani</dc:creator>
      <pubDate>Wed, 08 Jul 2026 11:34:07 +0000</pubDate>
      <link>https://dev.to/kanishga_subramani_49ad73/day-65100-implementing-exact-once-semantics-with-clickhouser-and-kafka-4lk9</link>
      <guid>https://dev.to/kanishga_subramani_49ad73/day-65100-implementing-exact-once-semantics-with-clickhouser-and-kafka-4lk9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Real-time data pipelines power modern applications such as analytics dashboards, fraud detection systems, IoT platforms, recommendation engines, financial systems, and event-driven applications. Organizations depend on these pipelines to process massive volumes of streaming data with minimal latency while maintaining high accuracy.&lt;/p&gt;

&lt;p&gt;One of the biggest challenges in any streaming architecture is ensuring that every event is processed exactly once.&lt;/p&gt;

&lt;p&gt;If the same event is processed multiple times, duplicate records can inflate business metrics, produce inaccurate analytics, trigger duplicate downstream actions, and reduce trust in data. Conversely, losing events results in incomplete datasets, missing insights, and unreliable reporting.&lt;/p&gt;

&lt;p&gt;While Apache Kafka provides several mechanisms that greatly reduce duplicate message delivery, achieving true end-to-end exactly-once processing requires coordination between producers, consumers, and the destination database.&lt;/p&gt;

&lt;p&gt;ClickHouse® provides powerful capabilities that help build highly reliable Kafka ingestion pipelines by minimizing duplicate records, handling retries safely, and maintaining data consistency at scale.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore exactly-once semantics, understand why duplicate records occur, and learn how to build a reliable Kafka-to-ClickHouse® ingestion pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is Exactly-Once Semantics?
&lt;/h1&gt;

&lt;p&gt;Exactly-once semantics (EOS) is a processing guarantee that ensures every event is processed one and only one time, even when failures, retries, or system restarts occur during data ingestion.&lt;/p&gt;

&lt;p&gt;In practical terms, exactly-once semantics means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every event is successfully processed exactly once.&lt;/li&gt;
&lt;li&gt;Failed operations can be safely retried without creating duplicate records.&lt;/li&gt;
&lt;li&gt;No valid events are unintentionally lost.&lt;/li&gt;
&lt;li&gt;Downstream analytical results remain accurate and consistent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of reliability is especially important for financial transactions, user activity tracking, inventory management, fraud detection, billing systems, and other applications where duplicate or missing data can have significant business consequences.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Do Duplicate Records Occur?
&lt;/h1&gt;

&lt;p&gt;Streaming systems operate across distributed components where temporary failures are inevitable. Even when the application behaves correctly, duplicates can still appear because of retries and recovery operations.&lt;/p&gt;

&lt;p&gt;Some of the most common causes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A consumer crashes after writing data to ClickHouse but before committing Kafka offsets.&lt;/li&gt;
&lt;li&gt;Network interruptions occur during ingestion.&lt;/li&gt;
&lt;li&gt;Producers retry sending messages after temporary failures.&lt;/li&gt;
&lt;li&gt;Consumer applications restart and reprocess previously consumed messages.&lt;/li&gt;
&lt;li&gt;Kafka topics are manually replayed during recovery or historical backfilling.&lt;/li&gt;
&lt;li&gt;Temporary infrastructure failures interrupt normal processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without proper deduplication mechanisms, these scenarios can result in multiple copies of the same event being stored in ClickHouse.&lt;/p&gt;




&lt;h1&gt;
  
  
  Kafka's Role in Exactly-Once Processing
&lt;/h1&gt;

&lt;p&gt;Apache Kafka includes several features designed to improve the reliability of streaming applications.&lt;/p&gt;

&lt;p&gt;Although these capabilities significantly reduce duplicate processing, they work best when combined with database-side safeguards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent Producers
&lt;/h2&gt;

&lt;p&gt;Idempotent producers assign sequence numbers to outgoing messages, allowing Kafka brokers to detect and discard duplicate writes caused by retries.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Preventing duplicate messages during producer retries&lt;/li&gt;
&lt;li&gt;Improving fault tolerance&lt;/li&gt;
&lt;li&gt;Increasing delivery reliability&lt;/li&gt;
&lt;li&gt;Eliminating accidental duplicate writes from the producer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Idempotent producers should be enabled for nearly all production streaming workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kafka Transactions
&lt;/h2&gt;

&lt;p&gt;Kafka transactions allow multiple operations to be grouped into a single atomic unit of work.&lt;/p&gt;

&lt;p&gt;For example, a consumer application can write multiple records while committing offsets within the same transaction.&lt;/p&gt;

&lt;p&gt;This ensures that either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every operation succeeds, or&lt;/li&gt;
&lt;li&gt;None of the operations are committed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transactions prevent partial writes that could otherwise produce inconsistent results across systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Offset Management
&lt;/h2&gt;

&lt;p&gt;Consumer offsets determine which messages have already been processed.&lt;/p&gt;

&lt;p&gt;To avoid data loss, offsets should only be committed after ClickHouse has successfully stored the corresponding records.&lt;/p&gt;

&lt;p&gt;Proper offset management provides two important guarantees:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failed insert operations can be safely retried.&lt;/li&gt;
&lt;li&gt;Successfully processed events are not accidentally skipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Incorrect offset management is one of the most common reasons for duplicate processing in streaming applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building a Kafka-to-ClickHouse® Pipeline
&lt;/h1&gt;

&lt;p&gt;A typical real-time ingestion architecture consists of several components working together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kafka Producer
        │
        ▼
Apache Kafka Topic
        │
        ▼
ClickHouse Kafka Engine
        │
        ▼
Materialized View
        │
        ▼
MergeTree Table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component contributes to creating a scalable, reliable, and low-latency streaming pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  Using the ClickHouse Kafka Engine
&lt;/h1&gt;

&lt;p&gt;ClickHouse provides the Kafka Engine, allowing the database to consume Kafka topics directly without requiring a separate ingestion application.&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;kafka_events&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;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;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;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kafka&lt;/span&gt;
&lt;span class="n"&gt;SETTINGS&lt;/span&gt;
    &lt;span class="n"&gt;kafka_broker_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'localhost:9092'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kafka_topic_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kafka_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'clickhouse-consumer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;kafka_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'JSONEachRow'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Kafka Engine continuously polls Kafka topics and exposes incoming messages as rows inside ClickHouse.&lt;/p&gt;

&lt;p&gt;This greatly simplifies streaming architectures because ClickHouse becomes a native Kafka consumer.&lt;/p&gt;




&lt;h1&gt;
  
  
  Automating Ingestion with Materialized Views
&lt;/h1&gt;

&lt;p&gt;After creating the Kafka Engine table, a Materialized View can automatically move incoming events into a persistent MergeTree table.&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="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;mv_events&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&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;kafka_events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As new messages arrive, the Materialized View continuously inserts them into the destination table without requiring custom ingestion code.&lt;/p&gt;

&lt;p&gt;This enables near real-time analytics with very little operational complexity.&lt;/p&gt;




&lt;h1&gt;
  
  
  Preventing Duplicate Records
&lt;/h1&gt;

&lt;p&gt;Although Kafka minimizes duplicate message delivery, duplicate records may still occur because of retries, consumer failures, or replay operations.&lt;/p&gt;

&lt;p&gt;ClickHouse provides several techniques to reduce or eliminate duplicates.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Use Globally Unique Event IDs
&lt;/h1&gt;

&lt;p&gt;Every event should include a globally unique identifier.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;UUID&lt;/li&gt;
&lt;li&gt;Transaction ID&lt;/li&gt;
&lt;li&gt;Order ID&lt;/li&gt;
&lt;li&gt;Payment ID&lt;/li&gt;
&lt;li&gt;Event ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3d9d34be-3f12-4c89-aef2-d53e5faad0c"&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_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"purchase"&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;p&gt;A unique event identifier makes it possible to recognize duplicate events regardless of how many times they are retried.&lt;/p&gt;

&lt;p&gt;This is one of the most important building blocks for implementing exactly-once processing.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Store Data Using ReplacingMergeTree
&lt;/h1&gt;

&lt;p&gt;One of the most commonly used ClickHouse table engines for retry-safe ingestion is &lt;code&gt;ReplacingMergeTree&lt;/code&gt;.&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;events&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;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_time&lt;/span&gt; &lt;span class="nb"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;version&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;ENGINE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReplacingMergeTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;version&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;event_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When multiple rows share the same primary key (&lt;code&gt;event_id&lt;/code&gt;), ClickHouse keeps the row with the highest version value during background merge operations.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;ReplacingMergeTree&lt;/code&gt; particularly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry scenarios&lt;/li&gt;
&lt;li&gt;Event updates&lt;/li&gt;
&lt;li&gt;Slowly changing dimensions&lt;/li&gt;
&lt;li&gt;Idempotent ingestion pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to note that duplicate removal does not happen immediately after insertion. Instead, deduplication occurs gradually during background merges.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Enable Insert Deduplication
&lt;/h1&gt;

&lt;p&gt;For replicated tables, ClickHouse supports insert deduplication.&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;SET&lt;/span&gt; &lt;span class="n"&gt;insert_deduplicate&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;When enabled, ClickHouse compares insert block checksums.&lt;/p&gt;

&lt;p&gt;If an identical insert block has already been processed, ClickHouse ignores the duplicate instead of inserting it again.&lt;/p&gt;

&lt;p&gt;This provides additional protection against duplicate inserts caused by retries or temporary infrastructure failures.&lt;/p&gt;




&lt;h1&gt;
  
  
  Best Practices for Reliable Kafka-to-ClickHouse® Pipelines
&lt;/h1&gt;

&lt;p&gt;To build robust streaming pipelines, consider the following recommendations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable Kafka idempotent producers.&lt;/li&gt;
&lt;li&gt;Use Kafka transactions whenever appropriate.&lt;/li&gt;
&lt;li&gt;Generate globally unique event IDs for every message.&lt;/li&gt;
&lt;li&gt;Commit Kafka consumer offsets only after successful ClickHouse inserts.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ReplacingMergeTree&lt;/code&gt; for retry-safe ingestion.&lt;/li&gt;
&lt;li&gt;Enable insert deduplication for replicated tables.&lt;/li&gt;
&lt;li&gt;Design tables with stable primary keys.&lt;/li&gt;
&lt;li&gt;Continuously monitor Kafka consumer lag.&lt;/li&gt;
&lt;li&gt;Monitor ingestion failures and retry behavior.&lt;/li&gt;
&lt;li&gt;Regularly test recovery scenarios, including consumer restarts and Kafka topic replays.&lt;/li&gt;
&lt;li&gt;Validate data consistency after infrastructure failures.&lt;/li&gt;
&lt;li&gt;Build monitoring and alerting around ingestion pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following these practices significantly improves data reliability and minimizes operational issues in production environments.&lt;/p&gt;




&lt;h1&gt;
  
  
  Limitations
&lt;/h1&gt;

&lt;p&gt;Although these techniques greatly improve reliability, exactly-once semantics is not achieved automatically.&lt;/p&gt;

&lt;p&gt;There are several important limitations to understand.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ReplacingMergeTree&lt;/code&gt; performs deduplication during background merge operations rather than immediately after insertion. As a result, duplicate records may temporarily exist until merges complete.&lt;/p&gt;

&lt;p&gt;Applications are still responsible for managing Kafka offsets correctly. Incorrect offset commits can lead to either duplicate processing or lost events.&lt;/p&gt;

&lt;p&gt;Replaying Kafka topics without globally unique event identifiers can still introduce duplicate records.&lt;/p&gt;

&lt;p&gt;Finally, achieving true end-to-end exactly-once behavior depends on the combined implementation of Kafka producers, Kafka consumers, ClickHouse, and any additional processing components within the streaming pipeline.&lt;/p&gt;

&lt;p&gt;Exactly-once semantics should therefore be viewed as a system-wide design goal rather than a feature provided by a single technology.&lt;/p&gt;




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

&lt;p&gt;Building reliable real-time data pipelines involves much more than simply moving events from Kafka into ClickHouse®.&lt;/p&gt;

&lt;p&gt;A robust streaming architecture combines Kafka's idempotent producers, transactions, and careful offset management with ClickHouse's Kafka Engine, Materialized Views, unique event identifiers, &lt;code&gt;ReplacingMergeTree&lt;/code&gt;, and insert deduplication capabilities.&lt;/p&gt;

&lt;p&gt;Together, these features allow ingestion pipelines to gracefully handle retries, consumer restarts, infrastructure failures, and replay operations while maintaining accurate analytical results.&lt;/p&gt;

&lt;p&gt;Although achieving true end-to-end exactly-once semantics requires coordination across every stage of the pipeline, following these best practices provides a strong foundation for building scalable, fault-tolerant, and duplicate-resistant real-time analytics systems with ClickHouse® and Kafka.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Exactly-once semantics ensures every event is processed only once, even during retries and failures.&lt;/li&gt;
&lt;li&gt;Duplicate records commonly result from consumer crashes, retries, network interruptions, and replay operations.&lt;/li&gt;
&lt;li&gt;Kafka improves reliability through idempotent producers, transactions, and proper offset management.&lt;/li&gt;
&lt;li&gt;The ClickHouse Kafka Engine enables direct streaming ingestion from Kafka topics.&lt;/li&gt;
&lt;li&gt;Materialized Views automate continuous ingestion into MergeTree tables.&lt;/li&gt;
&lt;li&gt;Globally unique event IDs are essential for reliable deduplication.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ReplacingMergeTree&lt;/code&gt; minimizes duplicate records by keeping the latest version of each event during background merges.&lt;/li&gt;
&lt;li&gt;Insert deduplication adds another layer of protection against repeated insert operations.&lt;/li&gt;
&lt;li&gt;Correct offset management is critical for preventing both duplicate processing and data loss.&lt;/li&gt;
&lt;li&gt;Combining Kafka and ClickHouse® best practices enables reliable, scalable, and duplicate-resistant real-time analytics pipelines.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>clickhouse</category>
      <category>devops</category>
      <category>database</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
