Skip to content

Musings of an Anonymous Geek

Made with only the finest 1's and 0's

Menu
  • About
  • Search Results
Menu

ClickHouse Cheat Sheet 2024

Posted on January 17, 2024January 17, 2024 by jonesy

For the past 4 months, ClickHouse has been my life, full time. I’ve been vetting it for production use and learning all about it in the process. Since my memory has always been notoriously poor, I take a lot of notes (in fact, that was the original reason for this blog’s existence).

So, while I’d love to have time to do some longer-form writing about ClickHouse, what I have now in terms of notes could be helpful to probably a lot of people as ClickHouse gains in popularity by the minute.

If there are specific topics within the realm of ClickHouse that you want to see covered, you can certainly let me know, but you should know that, operationally, ClickHouse was complex enough that we just this week opened a ClickHouse Cloud account, so my knowledge of it operationally will probably start to atrophy starting with the 24.x releases (up to this week, I built and ran various testing clusters myself, in AWS EC2, and EKS).

So, without further ado, here’s the Cheat Sheet.

NUMBER_OF_COLUMNS_DOESNT_MATCH

If you get seemingly inexplicable ‘NUMBER_OF_COLUMNS_DOESNT_MATCH’ errors whenever you’re doing a select (by itself or as part of an INSERT…SELECT, or whatever), and it seems like the column numbers do match, remove the parentheses around whatever follows SELECT. So, instead of SELECT (x, y, z) FROM foo it should be SELECT x,y,z FROM foo.

Remote and RemoteSecure Table Functions

If you have an ClickHouse server you manage, or an instance in their cloud offering, you can use clickhouse local and the remote table function to move data into your server from some other source.

For example, I have a cloud instance. I also have an event warehouse in S3, where events are stored in Parquet files. I can use the following command to do an INSERT...SELECT into my (remote) cloud database from the data in the Parquet files in S3:

clickhouse local --verbose --query "INSERT INTO TABLE FUNCTION remoteSecure('my-cloud-host:9440', 'mydb.mytable', 'myclouduser', 'mycloudpassword') (col1, col2, col3) SELECT col1, col2, col3 FROM s3('https://s3-endpoint/events/some-event/2023/**/*.parquet', 'Parquet');"

As a bonus trick, note the wildcard use in the s3 function. The supported wildcards are similar to those used in the bash shell (and maybe zsh – I’m not as familiar). Read all about those here.

Also, note that remoteSecure is just a secure version of the remote function! The syntax is the same! On cloud instances I believe only remoteSecure is supported, which makes sense.

ClickHouse Can’t Parse Some Date Formats

I’ve had issues with:

  • Dates with a timezone offset going into a DateTime column, and
  • Dates with microsecond precision going into a DateTime column

There are multiple solutions to this, but one I’ve used that could work depending on your requirements is to wrap the column with the parseDateTimeBestEffortOrZero function, like this:

INSERT INTO events (source, timestamp) SELECT source, parseDateTimeBestEffortOrZero(timestamp) FROM mytable

By default, ClickHouse has a fast, cheap, and simplified time parsing algorithm. The ‘BestEffort’ functions support a wide array of formats, including ISO8601 and RFC 822, and more. See here.

Here’s how it deals with a couple of formats I had to deal with:

SELECT parseDateTimeBestEffortOrZero('2011-11-04 00:05:23.283+00:00') AS time

┌────────────────time─┐
│ 2011-11-04 00:05:23 │
└─────────────────────┘

SELECT parseDateTimeBestEffortOrZero('2023-10-26T10:11:18.964768') AS time

┌────────────────time─┐
│ 2023-10-26 10:11:18 │
└─────────────────────┘

In my case, I didn’t require the additional information lost by using the function, so it worked fine. If you need to preserve it, there are more options than I can cover here, but look at DateTime64 datatype for storing subsecond precision, and look at all of the other datetime parsing functions available on this page!

My ClickHouse Server Is Out of Disk!

AFAIK this will only apply to self-hosted instances. If your server’s disk is full, you have a couple of options:

  • EBS volumes can be extended & filesystem resized without data loss or downtime (source)
  • A new, bigger disk can be attached to the EC2 instance. Then, add the old and new disks to a new storage policy in the clickhouse server’s config.xml file. Then, move all of the data to the new disk. Once done, the old disk can be removed from the storage policy. (source)

Making Wide Tables Without Using NULLable Columns

NULLable columns can impact performance in clickhouse and their best practices docs explicitly recommend avoiding them. Instead, you can create default values. This is how I was able to combine what were separate tables for each event type in our system into a single ‘events’ table, even though these events don’t all follow the exact same schema. Here’s an example excerpted from a CREATE TABLE statement:

    `headers` Map(String, String) DEFAULT map('00000', '00000'),
    `client_id` UUID DEFAULT '00000000-0000-0000-0000-000000000000',
    `destination` String DEFAULT '-',

The Infamous “TOO MANY PARTS” Error

If you get a “TOO MANY PARTS” error either in the server log or in a client when doing an insert query to the server, run this query. It’ll tell you, for each table, how many parts were created, and the average rows per part, over the last 10 minutes.

SELECT  toStartOfTenMinutes(event_time) AS time,
  concat(database, '.', table) as table,
  count() AS new_parts,
  rount(avg(rows)) AS avg_rows_per_part 
FROM system.part_log
WHERE (event_date >= today()) AND (event_type = 'NewPart')
GROUP BY time, table
ORDER BY time ASC, table ASC;

If you see thousands of parts created but the average row size is like, say, 4, then that’s unhealthy. Remember that ClickHouse is optimized to perform fewer, bigger inserts, and performs poorly when doing a high volume of very small inserts, because all of the inserts create parts that have to be merged, and merges are resource intensive. The fewer inserts, the fewer parts, the fewer merges, the happier the server is.

Moving Internal Data Off The Root Volume

ClickHouse stores internal log tables, tables from the system database, and metadata, all under /var/lib/clickhouse by default. Also by default, Amazon Linux puts /var/ on the root volume. I wanted it off of the root volume, and thought it made sense to put it on the volume where all of the other data was stored.

This is possible, but it’s not a documented process and it took me a little debugging to get it done. Here’s what I did:

First, In config.xml (or wherever you keep the storage config), at the top level, set the following settings to whatever path you want (I used /data/lib/clickhouse):

<path>/data/lib/clickhouse/</path>
<tmp_path>/data/lib/clickhouse/tmp/</tmp_path>
<user_files_path>/data/lib/clickhouse/user_files/</user_files_path>
<format_schema_path>/data/lib/clickhouse/format_schemas/</format_schema_path>

<user_directories>
    <local_directory>
        <path>/data/lib/clickhouse/access/</path>
    </local_directory>
</user_directories>

Also in config.xml, make sure there isn’t a metadata path associated with an existing disk in your <storage_configuration>. It would likely be pointing at /var/lib by default.

At this point I copied everything to the new location: cp -R /var/lib/clickhouse /data/lib/.

In addition, you need to make 100% sure that the clickhouse user has ownership and full permissions over everything it had access to before that you’ve now moved. I did this: cd /data/lib; chown -R clickhouse:clickhouse clickhouse and that worked fine for me.

ClickHouse Silently Fails To Start

There have been plenty of times while learning about ClickHouse that it failed to start, but it was always very good about putting an error in the error log (by default, /var/log/clickhouse-server/clickhouse-server.err.log). One time, it failed to start, and there was no error in the log, and nothing to see using journalctl or systemctl status either.

If this happens, you should go find the command systemd (or your init script) uses to start it, copy it, and run it verbatim yourself, and you’ll likely see the missing error. In my case, ClickHouse failed to start because it was unable to access its pid file. That’s weird, because ClickHouse creates the pid file itself.

After some spelunking around I figured out that there was, in fact, a permissions issue (it’s always permissions or DNS folks!). What’s weird is that the permissions issue was in a directory on another completely separate physical disk, but it was causing this issue. So if this happens to you, go look at permissions under your /var/lib/clickhouse directory. The user that ClickHouse runs as should own everything recursively underneath /var/lib/clickhouse.

Age-based Tiered Storage In ClickHouse

When I first read about tiered storage in ClickHouse, the example they used seemed really strange to me: they configured ClickHouse to move data to another volume when the first volume was 80% full. When I think of tiered storage I immediately think of hot/cold storage volumes, and aging out data to cold storage after some specific period of time. You can totally do that with ClickHouse.

ClickHouse uses ‘storage policies’ to let you inform clickhouse of the disks available to it & the volumes those disks make up. Using a storage policy (and the also-cool TTL clause), you can configure multiple volumes, ‘hot’ and ‘cold’, for example, and then ClickHouse will migrate the data between volumes for you. It can also recompress data or do other operations at the same time. Here’s what I did:

First, set up a <storage_configuration> w/ the hot & cold disks:

     <disks>
        <hot>
            <type>local</type>
            <path>/data/</path>
        </hot>
        <cold>
           <type>s3</type>
           <endpoint>https://mys3endpoint</endpoint>
        </cold>
    </disks>

Now, add the hot/cold policy:

<policies>
    <tiered_storage> <!-- policy name --> 
        <volumes>
            <hot_volume> <!-- volume name -->
                <disk>hot</disk>
            </hot_volume>
            <cold_volume> <!-- volume name -->
                 <disk>cold</disk>
            </cold_volume>
        </volumes>
    </tiered_storage>
</policies>

Now, when you create a new table, you can reference the tiered_storage policy in the SETTINGS, and use a TTL clause to tell ClickHouse what to move and when to move it. For example:

CREATE TABLE default.foo
(
   source_id UUID,
   timestamp DateTime
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + toIntervalDay(45) TO VOLUME 'cold'
SETTINGS storage_policy = 'tiered_storage';

ClickHouse will use the order of the volumes in the storage policy to determine priority, so by default, because we defined the ‘hot’ volume first, newly-inserted rows will go there. However, after timestamp + toIntervalDay(45) it’ll be moved to the ‘cold’ volume.

Hope This Helps!

ClickHouse is a very deep product, and each individual feature has a lot of nuance to it, as does the overall behavior of the service in general. This is obviously not the totality of what I learned, but if you’re just starting out with ClickHouse, I hope this saves you some of the hours I spent getting this knowledge. Good luck!

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to share on Tumblr (Opens in new window) Tumblr
  • Click to share on Facebook (Opens in new window) Facebook

Recent Posts

  • Auditing Your Data Migration To ClickHouse Using ClickHouse Local
  • ClickHouse Cheat Sheet 2024
  • User Activation With Django and Djoser
  • Python Selenium Webdriver Notes
  • On Keeping A Journal and Journaling
  • What Geeks Could Learn From Working In Restaurants
  • What I’ve Been Up To
  • PyCon Talk Proposals: All You Need to Know And More
  • Sending Alerts With Graphite Graphs From Nagios
  • The Python User Group in Princeton (PUG-IP): 6 months in

Categories

  • Apple
  • Big Ideas
  • Books
  • CodeKata
  • Database
  • Django
  • Freelancing
  • Hacks
  • journaling
  • Leadership
  • Linux
  • LinuxLaboratory
  • Loghetti
  • Me stuff
  • Other Cool Blogs
  • PHP
  • Productivity
  • Python
  • PyTPMOTW
  • Ruby
  • Scripting
  • Sysadmin
  • Technology
  • Testing
  • Uncategorized
  • Web Services
  • Woodworking

Archives

  • January 2024
  • May 2021
  • December 2020
  • January 2014
  • September 2012
  • August 2012
  • February 2012
  • November 2011
  • October 2011
  • June 2011
  • April 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • September 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • February 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007
  • February 2007
  • January 2007
  • December 2006
  • November 2006
  • September 2006
  • August 2006
  • July 2006
  • June 2006
  • April 2006
  • March 2006
  • February 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • March 2005
  • February 2005
  • January 2005
  • December 2004
  • November 2004
  • October 2004
  • September 2004
  • August 2004
© 2025 Musings of an Anonymous Geek | Powered by Minimalist Blog WordPress Theme