<?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: Reuben</title>
    <description>The latest articles on DEV Community by Reuben (@rtjl).</description>
    <link>https://dev.to/rtjl</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3768719%2F2e2effe7-6374-4164-8b75-6564496e8add.png</url>
      <title>DEV Community: Reuben</title>
      <link>https://dev.to/rtjl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rtjl"/>
    <language>en</language>
    <item>
      <title>Monitoring ClickHouse Inserts with Datadog Agent</title>
      <dc:creator>Reuben</dc:creator>
      <pubDate>Sat, 14 Feb 2026 10:21:37 +0000</pubDate>
      <link>https://dev.to/rtjl/enhanced-monitoring-of-clickhouse-insertion-using-datadog-agent-4h00</link>
      <guid>https://dev.to/rtjl/enhanced-monitoring-of-clickhouse-insertion-using-datadog-agent-4h00</guid>
      <description>&lt;p&gt;Inserts in ClickHouse are highly sensitive and can easily trigger the notorious &lt;a href="https://clickhouse.com/docs/knowledgebase/exception-too-many-parts" rel="noopener noreferrer"&gt;“too-many-parts”&lt;/a&gt; error. In this post, we discuss how to leverage the Datadog agent together with the &lt;a href="https://docs.datadoghq.com/integrations/clickhouse" rel="noopener noreferrer"&gt;Datadog-ClickHouse integration&lt;/a&gt; to improve monitoring of insert operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why
&lt;/h3&gt;

&lt;p&gt;The existing integration provides many &lt;a href="https://docs.datadoghq.com/integrations/clickhouse/?tab=host#metrics" rel="noopener noreferrer"&gt;metrics&lt;/a&gt;, but they lack table-level granularity. For example, you cannot filter metrics by table because there is no table tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clickhouse.query.insert.count{*} by {table} # This won't work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbgj6detrodu5vlqxzq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbgj6detrodu5vlqxzq1.png" alt="List of available tags" width="800" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, the integration supports &lt;a href="https://github.com/DataDog/integrations-core/blob/45c4e5e65c463881955aeb6576a946b95bc0f47f/clickhouse/datadog_checks/clickhouse/data/conf.yaml.example#L104" rel="noopener noreferrer"&gt;custom SQL queries&lt;/a&gt;, which Datadog can ingest as additional metrics. Since these are emitted through the Datadog-ClickHouse integration, they are treated as integration metrics and won't be charged as custom metrics.&lt;/p&gt;

&lt;p&gt;We can leverage this feature to create custom SQL queries, emit them as extra metrics, and use them for visualization and alerting in Datadog.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL Queries
&lt;/h3&gt;

&lt;p&gt;We can use these queries to monitor insert performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Initial Part Size
&lt;/h4&gt;

&lt;p&gt;Tracks the creation of new parts after inserts.&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;query_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;part_type&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;size_in_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;bytes_uncompressed&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="k"&gt;database&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'my-db-name'&lt;/span&gt;
  &lt;span class="k"&gt;AND&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;'my-table-name'&lt;/span&gt;
  &lt;span class="k"&gt;AND&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;'NewPart'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;toIntervalSecond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| query_id        | part_type | rows      | size_in_bytes  | bytes_uncompressed |
|-----------------|-----------|-----------|----------------|--------------------|
| 20260214_001    | Wide      | 1000000   | 524288000      | 1048576000         |
| 20260214_002    | Wide      | 2500000   | 1310720000     | 2621440000         |
| 20260214_003    | Compact   | 500000    | 104857600      | 209715200          |
| 20260214_004    | Wide      | 3000000   | 1572864000     | 3145728000         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;rows (Rows per insert)&lt;/p&gt;

&lt;p&gt;Measures how many rows were inserted in each part.&lt;/p&gt;

&lt;p&gt;Helps evaluate batch efficiency: very small batches → more overhead, slower throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;size_in_bytes (Compressed part size)&lt;/p&gt;

&lt;p&gt;ClickHouse splits data into parts.&lt;/p&gt;

&lt;p&gt;Large parts improve compression, but too many parts can lead to the notorious "too-many-parts" error if inserts are frequent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;bytes_uncompressed (Uncompressed part size)&lt;/p&gt;

&lt;p&gt;Used to calculate compression ratio (bytes_uncompressed / size_in_bytes).&lt;/p&gt;

&lt;p&gt;Helps monitor storage efficiency and disk I/O impact, especially for large inserts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Insert queries
&lt;/h4&gt;

&lt;p&gt;Tracks insert execution performance for completed queries.&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;query_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;arrayJoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;query_duration_ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;read_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;written_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;written_bytes&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;query_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'QueryFinish'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;query_kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Insert'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-table-name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'my-db-name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;toIntervalSecond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| query_id          | table          | query_duration_ms | read_rows | read_bytes | written_rows | written_bytes | memory_usage |
|-------------------|----------------|-------------------|-----------|------------|--------------|---------------|--------------|
| 20260214_101523_1 | my-table-name  | 120               | 0         | 0          | 500000       | 157286400     | 10485760     |
| 20260214_101524_2 | my-table-name  | 115               | 0         | 0          | 600000       | 188743680     | 12582912     |
| 20260214_101525_3 | my-table-name  | 130               | 0         | 0          | 450000       | 141557760     | 9437184      |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;query_duration_ms&lt;/p&gt;

&lt;p&gt;Time taken for each insert query.&lt;/p&gt;

&lt;p&gt;Essential to detect slow inserts, which can indicate bottlenecks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;written_rows / written_bytes&lt;/p&gt;

&lt;p&gt;Actual data inserted per query.&lt;/p&gt;

&lt;p&gt;Use this with query_duration_ms to calculate insert throughput (rows/sec or bytes/sec).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;read_rows / read_bytes&lt;/p&gt;

&lt;p&gt;Shows any reads caused by the insert (e.g., merges).&lt;/p&gt;

&lt;p&gt;Important for understanding I/O impact on the system during large inserts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;memory_usage&lt;/p&gt;

&lt;p&gt;Memory consumed by the insert query.&lt;/p&gt;

&lt;p&gt;Helps detect inserts that could cause OOM or memory spikes, especially for huge batches.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Datadog Config
&lt;/h3&gt;

&lt;p&gt;This configuration enables the Datadog Agent to monitor ClickHouse inserts by querying system tables and emitting metrics. &lt;/p&gt;

&lt;p&gt;It tracks new parts (rows, size_in_bytes, bytes_uncompressed) to monitor batching and compression, and insert query performance (query_duration_ms, read_rows, written_rows, memory_usage) to monitor throughput and resource usage. &lt;/p&gt;

&lt;p&gt;Metrics are collected at regular intervals, allowing real-time monitoring and alerts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;custom_queries&lt;/code&gt; section is where you define the SQL queries that emit metrics for both initial parts and insert query performance. All the metrics discussed later are generated based on these custom queries.&lt;/p&gt;

&lt;p&gt;Config File: &lt;code&gt;/etc/datadog-agent/conf.d/clickhouse.d/conf.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;init_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;instances&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;datadog&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;
    &lt;span class="na"&gt;custom_queries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Query to monitor initial parts&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;SELECT&lt;/span&gt;
              &lt;span class="s"&gt;query_id,&lt;/span&gt;
              &lt;span class="s"&gt;part_type,&lt;/span&gt;
              &lt;span class="s"&gt;rows,&lt;/span&gt;
              &lt;span class="s"&gt;size_in_bytes,&lt;/span&gt;
              &lt;span class="s"&gt;bytes_uncompressed&lt;/span&gt;
            &lt;span class="s"&gt;FROM system.part_log&lt;/span&gt;
            &lt;span class="s"&gt;WHERE&lt;/span&gt;
              &lt;span class="s"&gt;database = 'my-db-name'&lt;/span&gt;
              &lt;span class="s"&gt;AND table = 'my-table-name'&lt;/span&gt;
              &lt;span class="s"&gt;AND event_type = 'NewPart'&lt;/span&gt;
              &lt;span class="s"&gt;AND event_time &amp;gt; (now() - toIntervalSecond(60))&lt;/span&gt;
          &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_id&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;part_type&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rows&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;size_in_bytes&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bytes_uncompressed&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
          &lt;span class="na"&gt;collection_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
          &lt;span class="c1"&gt;# Hardcoded way to inject tags&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table:my-table-name"&lt;/span&gt;
          &lt;span class="na"&gt;metric_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse.my_custom_queries.insert.initial_parts&lt;/span&gt;
        &lt;span class="c1"&gt;# Query to monitor insert queries&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;SELECT&lt;/span&gt;
              &lt;span class="s"&gt;query_id,&lt;/span&gt;
              &lt;span class="s"&gt;arrayJoin(tables) AS table, -- expand array to one row per table&lt;/span&gt;
              &lt;span class="s"&gt;query_duration_ms,&lt;/span&gt;
              &lt;span class="s"&gt;read_rows,&lt;/span&gt;
              &lt;span class="s"&gt;read_bytes,&lt;/span&gt;
              &lt;span class="s"&gt;written_rows,&lt;/span&gt;
              &lt;span class="s"&gt;written_bytes,&lt;/span&gt;
              &lt;span class="s"&gt;memory_usage&lt;/span&gt;
            &lt;span class="s"&gt;FROM&lt;/span&gt;
              &lt;span class="s"&gt;system.query_log&lt;/span&gt;
            &lt;span class="s"&gt;WHERE&lt;/span&gt;
              &lt;span class="s"&gt;type = 'QueryFinish'&lt;/span&gt;
              &lt;span class="s"&gt;AND query_kind = 'Insert'&lt;/span&gt;
              &lt;span class="s"&gt;AND has(tables, 'my-table-name')&lt;/span&gt;
              &lt;span class="s"&gt;AND has(databases, 'my-db-name')&lt;/span&gt;
              &lt;span class="s"&gt;AND event_time &amp;gt; (now() - toIntervalSecond(60))&lt;/span&gt;
          &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_id&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
            &lt;span class="c1"&gt;# We inject the table name as a tag using the result from the SQL query&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;table&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_duration_ms&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read_rows&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read_bytes&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;written_rows&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;written_bytes&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory_usage&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
          &lt;span class="na"&gt;collection_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
          &lt;span class="na"&gt;metric_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse.my_custom_queries.insert.queries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Metrics Generated
&lt;/h4&gt;

&lt;p&gt;These are the metrics generated by the Datadog Agent based on the &lt;code&gt;custom_queries&lt;/code&gt; configuration.&lt;/p&gt;

&lt;h5&gt;
  
  
  From the initial parts query
&lt;/h5&gt;

&lt;p&gt;We get 3 metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.initial_parts.rows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.initial_parts.size_in_bytes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.initial_parts. bytes_uncompressed&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those metric will have the tags, &lt;code&gt;query_id&lt;/code&gt;, &lt;code&gt;part_type&lt;/code&gt; &amp;amp; &lt;code&gt;table&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw20zhtcq4vvybc8rcnsf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw20zhtcq4vvybc8rcnsf.png" alt="Tags from the new metric produced" width="799" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  From the insert queries query
&lt;/h5&gt;

&lt;p&gt;We get 6 metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.query_duration_ms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.read_rows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.read_bytes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.written_rows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.written_bytes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clickhouse.my_custom_queries.insert.queries.memory_usage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those metric will have the tags, &lt;code&gt;query_id&lt;/code&gt; &amp;amp; &lt;code&gt;table&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsc4ip1ob6m25qzqdp1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsc4ip1ob6m25qzqdp1u.png" alt="Example tags from the new metrics" width="799" height="886"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These metrics help monitor insert performance, including query duration, resource usage, and data throughput.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using the metrics in Datadog
&lt;/h4&gt;

&lt;p&gt;Once the metrics are emitted, they can be visualized and monitored for insert performance.&lt;/p&gt;

&lt;h5&gt;
  
  
  Number of insert queries
&lt;/h5&gt;

&lt;p&gt;Track how many insert queries are executed per table over time using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count_nonzero(sum:clickhouse.my_custom_queries.insert.queries.written_rows{table:default.nyc_taxi, db:default} by {query_id})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzs5ksco05e2bazp2knr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzzs5ksco05e2bazp2knr.png" alt="Number of insert queries" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the &lt;code&gt;count_nonzero&lt;/code&gt; function to visualize how many queries&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5&gt;
  
  
  Number of rows in initial parts
&lt;/h5&gt;

&lt;p&gt;Monitor how many rows in the initial parts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max:clickhouse.my_custom_queries.insert.initial_parts.rows{table:nyc_taxi} by {query_id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft1v4sj505urmb95g879.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fft1v4sj505urmb95g879.png" alt="Number of rows in each initial part" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Monitoring compression ratio
&lt;/h5&gt;

&lt;p&gt;Compression ratio helps evaluate storage efficiency for inserts.&lt;/p&gt;

&lt;p&gt;Metric A&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max:clickhouse.my_custom_queries.insert.initial_parts.bytes_uncompressed{table:nyc_taxi} by {query_id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Metric B&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max:clickhouse.my_custom_queries.insert.initial_parts.size_in_bytes{table:nyc_taxi} by {query_id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Formula&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compression Ratio: A / B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztpv8xbu57u54rbkduzt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztpv8xbu57u54rbkduzt.png" alt="Visualization of uncompressed, compressed &amp;amp; compression ratio" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Using the Datadog Agent with custom SQL queries, you can gain table-level visibility into ClickHouse inserts, monitor batching efficiency, part sizes, compression ratios, and query performance in real time.&lt;/p&gt;

&lt;p&gt;This approach is also easily extensible. You can define additional custom queries to monitor specific SQL statements.&lt;/p&gt;

&lt;p&gt;For example, if your application executes an API like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /userActionsCount?user-id&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which translates to a ClickHouse query such as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_actions&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;user_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;event_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can track its performance by querying the &lt;code&gt;system.query_log&lt;/code&gt; table for that specific query pattern and sending metrics to Datadog&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;SELECT&lt;/span&gt;
      &lt;span class="s"&gt;query_id,&lt;/span&gt;
      &lt;span class="s"&gt;query_duration_ms,&lt;/span&gt;
      &lt;span class="s"&gt;read_rows,&lt;/span&gt;
      &lt;span class="s"&gt;read_bytes,&lt;/span&gt;
      &lt;span class="s"&gt;memory_usage&lt;/span&gt;
    &lt;span class="s"&gt;FROM system.query_log&lt;/span&gt;
    &lt;span class="s"&gt;WHERE&lt;/span&gt;
      &lt;span class="s"&gt;type = 'QueryFinish'&lt;/span&gt;
      &lt;span class="s"&gt;AND query LIKE 'SELECT user_id, COUNT(*) AS total_actions FROM user_activity WHERE user_id = % AND event_date &amp;gt;= today() - 7 GROUP BY user_id'&lt;/span&gt;
      &lt;span class="s"&gt;AND event_time &amp;gt; (now() - toIntervalSecond(60))&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_id&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;query_duration_ms&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read_rows&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read_bytes&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;memory_usage&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gauge&lt;/span&gt;
  &lt;span class="na"&gt;collection_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api:user_actions_count&lt;/span&gt;  &lt;span class="c1"&gt;# all metrics will have this tag&lt;/span&gt;
  &lt;span class="na"&gt;metric_prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;clickhouse.my_custom_queries.application_queries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you monitor the performance of API-generated queries, detect bottlenecks, and set up alerts, giving full observability over both inserts and application-level queries.&lt;/p&gt;

</description>
      <category>clickhouse</category>
      <category>datadog</category>
    </item>
  </channel>
</rss>
