{"id":91438,"date":"2020-08-13T19:42:36","date_gmt":"2020-08-13T17:42:36","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks"},"modified":"2020-08-13T19:42:36","modified_gmt":"2020-08-13T17:42:36","slug":"effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks","title":{"rendered":"Effective Use of ClickHouse. Alexey Milovidov (Yandex)","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/8e42c1fffffcc964eacef240ea90bbc5.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Since ClickHouse is a specialized system, it is important to consider the features of its architecture when using it. In this report, Alexey will discuss examples of common mistakes when using ClickHouse that can lead to inefficient performance. Practical examples will illustrate how the choice of a specific data processing schema can significantly change performance.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>Hello everyone! My name is Alexey, and I work with ClickHouse.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/7f5fc01663be58d3d25e518a4169b8cf.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>First of all, I'm happy to inform you that today I won't be explaining what ClickHouse is. Honestly, I'm tired of doing that. I explain it every time, and probably everyone already knows. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/fae6bd07d098200643d591b8e5bf51e8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Instead, I will talk about the potential pitfalls, i.e., how ClickHouse can be misused. In fact, there\u2019s no need to be afraid, because we develop ClickHouse as a system that is simple, convenient, and works out of the box. Just install it and you're good to go, no problems. <\/p>\n<p><\/p>\n<p>However, it is still necessary to keep in mind that this is a specialized system, and it\u2019s easy to encounter an unusual usage scenario that will take the system out of its comfort zone.<\/p>\n<p><\/p>\n<p>So, what are the pitfalls? I will mainly discuss obvious things. It's all clear to everyone; everyone understands everything and can be pleased that they are so smart, and those who don\u2019t understand will learn something new. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/bffacf353ff31b361460178e911f0e16.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>The first simplest example, which, unfortunately, is often encountered, is a large number of inserts with small batches, i.e., a large number of small inserts.<\/p>\n<p><\/p>\n<p>If we consider how ClickHouse executes inserts, you can send a data stream of up to a terabyte in one request. This is not a problem. <\/p>\n<p><\/p>\n<p>Now let's look at what typical performance would be. For example, we have a table of data from Yandex.Metrica. Hits. 105 columns or so. 700 bytes uncompressed. And we will insert batches of one million rows, which is ideal. <\/p>\n<p><\/p>\n<p>Inserting into a MergeTree table results in about half a million rows per second. Excellent. For a replicated table, it will be slightly less, around 400,000 rows per second. <\/p>\n<p><\/p>\n<p>If quorum inserts are enabled, the performance drops a bit, but it's still decent, at 250,000 rows per second. Quorum insertion is an undocumented feature in ClickHouse*.<\/p>\n<p><\/p>\n<p>* as of 2020, <noindex><a rel=\"nofollow\" href=\"https:\/\/clickhouse.tech\/docs\/en\/operations\/settings\/settings\/#settings-insert_quorum\">it has already been documented.<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/668498fc82a0f6e851d05e6e1ea67033.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>What happens if things are done poorly? Inserting one row at a time into the MergeTree table results in 59 rows per second. That's 10,000 times slower. In ReplicatedMergeTree, it's 6 rows per second. And if a quorum is involved, it goes down to 2 rows per second. To me, this is just ridiculous. How can performance be so terrible? I even have a t-shirt that says ClickHouse shouldn't lag. But it does happen sometimes. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/4c771eddc4e8453f60ef91123ae5d109.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Actually, this is our shortcoming. We could easily have made it work properly, but we didn't. We didn't do it because it wasn't necessary for our scenario. We already had batches. We received batches and everything worked smoothly. But, of course, various scenarios are possible. For instance, when you have a bunch of servers generating data. They insert data not that frequently, but still, it leads to frequent inserts. And we need to somehow avoid that. <\/p>\n<p><\/p>\n<p>From a technical standpoint, the essence is that when you perform an insert in ClickHouse, the data doesn't go into any memtable. We don't even have a real log-structured MergeTree; it's just a MergeTree because there's neither log nor memTable. We write data directly to the filesystem, already laid out in columns. If you have 100 columns, you will have to write more than 200 files into a separate directory. This is quite cumbersome. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/24688fe3d39d81b92f12ac1b8b21425b.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>And the question arises: \"How to do it correctly?\" if the situation is such that data must somehow be written to ClickHouse.<\/p>\n<p><\/p>\n<p>Method 1. This is the simplest approach. Use a distributed queue, like Kafka. Just pull the data from Kafka, batching it every second. Everything will work fine; you're writing, and everything functions normally. <\/p>\n<p><\/p>\n<p>The downside is that Kafka is just another cumbersome distributed system. I can understand if your company already has Kafka. That's good and convenient. But if you don't have it, you should think twice before bringing another distributed system into your project. Therefore, it's worth considering alternatives. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/4f687221e8c7443abf02fb002c4eba5b.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Method 2. Here's a retro alternative that is very simple. You have a server that generates your logs. It simply writes your logs to a file. And every second, for example, you rename this file, open a new one. An additional script, either via cron or some daemon, takes the oldest file and writes it to ClickHouse. If you write logs every second, everything will work beautifully. <\/p>\n<p><\/p>\n<p>But the downside of this method is that if your server generating the logs disappears, then the data will also disappear.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/b4c28b6df87386c42b6908bdfccb6b90.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Method 3. There's another interesting method that completely avoids temporary files. For example, you have some advertising rotator or another interesting daemon that generates data. You can accumulate a batch of data directly in memory, in the buffer. When enough time has passed, you set this buffer aside, create a new one, and in a separate thread, you insert what has been accumulated into ClickHouse.<\/p>\n<p><\/p>\n<p>On the other hand, data will also disappear upon a kill -9 command. If your server crashes, you will lose that data. There's also the problem that if you fail to write to the database, the data will continue to accumulate in memory. Eventually, you'll either run out of memory or simply lose the data. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/dc375b22fcb26cfa111d0bdb4563aa88.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Method 4. Another interesting method. You have some server process. It can send data to ClickHouse immediately, but do this in a single connection. For example, it sends an HTTP request with transfer-encoding: chunked containing the insert. You can generate chunks not too infrequently; you can even send each line, although there will be overhead on the framing of this data. <\/p>\n<p><\/p>\n<p>However, in this case, the data will be sent to ClickHouse immediately. And ClickHouse will buffer them on its own. <\/p>\n<p><\/p>\n<p>But issues arise as well. Now, you will lose data, especially if your process crashes and, if the ClickHouse process crashes, because this will result in an incomplete insert. In ClickHouse, inserts are atomic up to a specified threshold of rows. This is, in principle, an interesting method that can also be used.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/6a18a974b9600ade377a870390e95d3d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Method 5. Here\u2019s another interesting approach. This is a community-developed server for batch data processing. I haven't looked into it myself, so I can't guarantee anything. Moreover, no guarantees are provided for ClickHouse itself either. This is also open source, but on the other hand, you may have gotten used to a certain quality standard that we strive to maintain. For this tool though, I'm not sure; go check GitHub and look at the code. They might have written something decent. <\/p>\n<p><\/p>\n<p>* as of 2020, it should also be added to consideration <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/en\/company\/vk\/blog\/430168\/\">KittenHouse<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/ae6a04af63ae4e7fa160c002fdb6d506.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Method 6. Another way is to use Buffer tables. The advantage of this method is that it is very easy to start using. You create a Buffer table and insert into it. <\/p>\n<p><\/p>\n<p>The downside is that the problem is not entirely solved. If with a MergeTree insert you need to group data at one batch per second, then with inserts into a buffer table, you need to group at least a few thousand per second. If it exceeds 10,000 per second, it will still be problematic. And if you insert in batches, you've seen that you get hundreds of thousands of rows per second. And this is already with fairly heavy data. <\/p>\n<p><\/p>\n<p>Also, buffer tables do not have a log. If something goes wrong with your server, the data will be lost. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/9bb16bf5e8145cc8368b11e54c86c837.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>And as a bonus, recently we introduced the ability to fetch data from Kafka in ClickHouse. There is a table engine \u2013 Kafka. You simply create it. And materialized views can be attached to it. In this case, it will automatically pull data from Kafka and insert it into the tables you need. <\/p>\n<p><\/p>\n<p>What\u2019s especially exciting about this feature is that we didn't create it. It's a community feature. And when I say \"community feature,\" I say it without any disdain. We read the code, conducted a review, it should work fine. <\/p>\n<p><\/p>\n<p>* as of 2020, similar support appeared for <noindex><a rel=\"nofollow\" href=\"https:\/\/clickhouse.tech\/docs\/en\/engines\/table-engines\/integrations\/rabbitmq\/\">RabbitMQ<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/15dc61dfd435f5f8640395b7de480fe2.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>What else could be inconvenient or unexpected when inserting data? If you perform an insert values query and write some computed expressions in values. For example, now() \u2013 this is also a computed expression. In this case, ClickHouse is forced to run the interpreter for these expressions on each row, and performance will drop dramatically. It's better to avoid this.<\/p>\n<p><\/p>\n<p>* Currently, the issue has been fully resolved; there is no longer any performance regression when using expressions in VALUES.<\/p>\n<p><\/p>\n<p>Another example where you may encounter some issues is when you have data across multiple partitions in a single batch. By default, ClickHouse partitions data by month. If you insert a batch of a million rows containing data over several years, you will have several dozen partitions. This is equivalent to having batches that are several dozen times smaller since they are initially divided by partitions.<\/p>\n<p><\/p>\n<p>* Recently, support for a compact format of parts and in-memory chunks with a write-ahead log has been added to ClickHouse in experimental mode, which almost completely resolves the issue.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/b8c6cecdba53559dad31ed11713a980d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Now let's consider the second type of problem \u2013 data typing. <\/p>\n<p><\/p>\n<p>Data typing can be strict or string-based. String-based means you simply declared that all your fields are of the string type. This is not ideal. It's not how it should be done. <\/p>\n<p><\/p>\n<p>Let's figure out how to do it correctly in cases when you want to state that a certain field is a string, and let ClickHouse handle it while I don\u2019t worry too much. However, it is still worth making some effort. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/a0fdcf6423933293318411242fe2ede7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>For example, we have an IP address. In one case, we saved it as a string, for instance, 192.168.1.1. In another case, it will be a number of UInt32 type. 32 bits are sufficient for IPv4 addresses.<\/p>\n<p><\/p>\n<p>Firstly, surprisingly, the data will compress roughly the same way. There will be a difference, of course, but not a major one. So, there are no significant issues with disk I\/O. <\/p>\n<p><\/p>\n<p>However, there is a significant difference in processor time and query execution time. <\/p>\n<p><\/p>\n<p>Let's calculate the number of unique IP addresses if they are stored as numbers. This yields 137 million rows per second. If it's the same as strings, then it's 37 million rows per second. I don't know why such a coincidence occurred. I executed these queries myself. Nevertheless, it is approximately four times slower. <\/p>\n<p><\/p>\n<p>If we consider the difference in disk space, there is also a difference. It is about one quarter, since there are quite a few unique IP addresses. If there were strings with a small number of different values, they would compress down to a similar volume using a dictionary. <\/p>\n<p><\/p>\n<p>And a fourfold difference in travel time is not trivial. You may not care, but when I see such a discrepancy, it makes me sad.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/f74dbfdab5a9e26fa044a5870a8cc00d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Let's consider different scenarios. <\/p>\n<p><\/p>\n<p>1. One scenario is when you have a few unique values. In this case, we use a straightforward approach that you probably know and can apply to any DBMS. This makes sense not only for ClickHouse. You simply store numerical identifiers in the database. You can convert them to strings and back on the application side. <\/p>\n<p><\/p>\n<p>For example, let's say you have a region. And you are trying to store it as a string. It might read: Moscow and MO. When I see 'Moscow,' that's fine, but when it includes MO, it becomes quite sad. That's how many bytes. <\/p>\n<p><\/p>\n<p>Instead, we simply store the number UInt32 and 250. We have 250 in Yandex, but it may differ for you. Just to clarify, ClickHouse has built-in capabilities for working with geodatabases. You can store a directory of regions, including hierarchical ones, meaning it will include both Moscow and MO, and everything you need. Conversion can be performed at the query level. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/cc6e871136dab8f8a35246a5af512cda.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>The second option is quite similar, but already has support within ClickHouse. This is the Enum data type. You simply list all the necessary values inside the Enum. For example, device type, where you specify: desktop, mobile, tablet, TV. Just four options. <\/p>\n<p><\/p>\n<p>The downside is that you need to periodically alter the structure. If you've added just one option, you perform an ALTER TABLE. In reality, altering a table in ClickHouse is free. It's particularly free for Enum because the data on disk remains unchanged. However, the ALTER command still acquires a lock on the table and must wait for all SELECTs to finish. Only after that does the ALTER execute, meaning some inconvenience does exist.<\/p>\n<p><\/p>\n<p>* In recent versions of ClickHouse, ALTER is made fully non-blocking.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/d7284fa5be583ab063bb6de0b60f54d4.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another quite unique option for ClickHouse is connecting external dictionaries. You can store numbers in ClickHouse while keeping your reference tables in any system you find convenient. For instance, you can use MySQL, Mongo, Postgres. You could even build your own microservice to serve this data via HTTP. At the ClickHouse level, you write a function that transforms this data from numbers to strings. <\/p>\n<p><\/p>\n<p>This is a specialized yet highly effective way to perform a join with an external table. There are two variants. In one variant, this data will be fully cached, entirely present in memory, and updated periodically. In the other variant, if the data does not fit into memory, it can be partially cached. <\/p>\n<p><\/p>\n<p>Here\u2019s an example. There\u2019s Yandex.Direct. There, there's an advertising campaign and banners. There are probably around ten million advertising campaigns, which can fit into memory. But there are billions of banners that cannot fit. We use a cachable dictionary from MySQL.<\/p>\n<p><\/p>\n<p>The only issue is that the cachable dictionary will work properly if the hit rate is close to 100%. If it\u2019s lower, then when processing requests for each batch of data, you will actually need to fetch the missing keys and retrieve the data from MySQL. I can still vouch for ClickHouse that it doesn\u2019t lag; I won\u2019t comment on other systems.<\/p>\n<p><\/p>\n<p>As a bonus, dictionaries provide a very simple way to update data in ClickHouse retroactively. For instance, if you had a report on advertising campaigns, and the user simply changed the advertising campaign, then in all previous data, in all reports, these details would also be updated. If you write the rows directly to the table, updating them would be impossible. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/a5a2919d6be7d00c0ad5875b9a8f0f31.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another method, when you don't know where to get identifiers for your rows, is to simply hash them. The simplest option is to use a 64-bit hash. <\/p>\n<p><\/p>\n<p>The only problem is that with a 64-bit hash, collisions are almost guaranteed. If there are a billion rows, the probability becomes significant. <\/p>\n<p><\/p>\n<p>It would not be very wise to hash the names of advertising campaigns in this way. If the advertising campaigns of different companies get mixed up, it would lead to confusion. <\/p>\n<p><\/p>\n<p>There is a simple trick. Although it\u2019s not very suitable for serious data, if you have something not too critical, just add the client identifier to the dictionary key. This way, you'll have collisions, but only within a single client. This method is used for the link map in Yandex.Metrica. We have URLs there, and we store hashes. We know that collisions exist, of course. However, when a page is displayed, the chance that one user has conflicting URLs on the same page and notices them can be disregarded. <\/p>\n<p><\/p>\n<p>As a bonus, for many operations, just hashes are sufficient, and the strings themselves do not need to be stored anywhere. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/abbb73aaba29d10e3b7e7650bf68e6a6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another example is when the strings are short, like domain names. They can be stored as they are. For instance, the browser language 'ru' is 2 bytes. Of course, I feel very bad about losing bytes, but don't worry, 2 bytes are fine. Please store them as they are, no need to overthink. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/901eaed0029da6ea59630b57ccf80c25.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>On the other hand, there are cases where there are a lot of strings, with many unique ones and potentially unlimited ones. A typical example is search phrases or URLs. Search phrases, including those with typos. Let's take a look at how many unique search phrases occur in a day. It turns out that they are almost half of all events. In this case, you might think that data should be normalized, identifiers calculated and stored in a separate table. But that\u2019s not necessary. Just store those strings as they are. <\/p>\n<p><\/p>\n<p>It's better not to invent anything, because if you store them separately, you'll have to perform a join. And that join, in the best case, will require random access to memory, if it fits in memory. If it doesn't fit, then there will be real issues. <\/p>\n<p><\/p>\n<p>But if the data is stored in place, it is simply read in the necessary order from the file system, and everything works fine.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/15f1d632083def16ccbc939579c1cf6b.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>If you have URLs or any other complex long strings, you should consider that you can calculate some kind of summary in advance and store it in a separate column. <\/p>\n<p><\/p>\n<p>For URLs, for example, you can store the domain separately. If you actually need the domain, just use that column, while the URLs will be stored and you won't even need to touch them. <\/p>\n<p><\/p>\n<p>Let's see what difference it makes. ClickHouse has a specialized function that calculates the domain. It's very fast, and we've optimized it. Honestly, it doesn't even comply with the RFC, but it still calculates everything we need. <\/p>\n<p><\/p>\n<p>In one case, we'll simply extract the URLs and compute the domain. That takes 166 milliseconds. If we take a ready-made domain, it takes only 67 milliseconds, almost three times faster. The speed improvement is not because we need to perform calculations, but because we read less data. <\/p>\n<p><\/p>\n<p>For some reason, one of the slower queries shows a higher speed in gigabytes per second. That's because it reads more gigabytes. These are completely unnecessary data. The query appears to run faster, but it takes longer to complete.<\/p>\n<p><\/p>\n<p>If we look at the amount of data on disk, the URL is 126 megabytes, while the domain is only 5 megabytes. That's 25 times smaller. Yet, the query executes only 4 times faster. This is because the data is hot. If it were cold, it would likely be 25 times faster due to disk I\/O. <\/p>\n<p><\/p>\n<p>By the way, if we assess how much smaller the domain is compared to the URL, it's about four times smaller. Yet, on disk, the data takes up 25 times less space. Why? Due to compression. Both the URL and the domain are compressed. However, URLs often contain a lot of extraneous data. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/c93d2128a41561ed7abb25d77414bc2d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Of course, it's essential to use the right data types designed specifically for the required values or those that fit. If you're working with IPv4, store it as UInt32. For IPv6, use FixedString(16), because an IPv6 address is 128 bits, meaning you should store it in binary format. <\/p>\n<p><\/p>\n<p>What if you occasionally have IPv4 addresses and sometimes IPv6? You can store both. One column for IPv4, another for IPv6. There's also the option to map IPv4 to IPv6. That would work too, but if you frequently need the IPv4 address in your queries, it would be better to put it in a separate column. <\/p>\n<p><\/p>\n<p>* Now, ClickHouse has separate data types for IPv4 and IPv6, which store data as efficiently as numbers but represent it as conveniently as strings.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/d7d6342ebd6b8b4f3526b3cd8a06cf26.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>It's also important to note that you should preprocess the data in advance. For example, you might receive some raw logs. And while it may be tempting to just shove them into ClickHouse immediately, it\u2019s better to perform the necessary calculations first.<\/p>\n<p><\/p>\n<p>For instance, browser version. In a nearby department, which I won\u2019t name, the browser version is stored like this, i.e., as a string: 12.3. Then, to create a report, they split this string into an array and take the first element. Naturally, everything slows down. I asked why they do it this way. They told me they don\u2019t like premature optimization. But I dislike premature pessimization.<\/p>\n<p><\/p>\n<p>So, in this case, it's better to split it into 4 columns. Don\u2019t be afraid, because it\u2019s ClickHouse. ClickHouse is a columnar database. The more neat, small columns you have, the better. If you have 5 BrowserVersion values, create 5 columns. It\u2019s perfectly fine. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/f9a67221bf8508933d7bd336b16f9e04.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Now let\u2019s consider what to do if you have many very long strings or arrays. You shouldn\u2019t store them in ClickHouse at all. Instead, you can save only some identifier in ClickHouse. Store those long strings in some other system. <\/p>\n<p><\/p>\n<p>For example, in one of our analytics services, there are certain event parameters. If an event has many parameters, we simply store the first 512 that come our way. Because 512 isn\u2019t a big deal. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/755271e8788ba4bd638f493bf320c820.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>If you can\u2019t decide on your data types, you can also write data to ClickHouse, but in a temporary table of type Log, which is special for temporary data. After that, you can analyze the value distribution, see what you have, and establish the correct types.<\/p>\n<p><\/p>\n<p>* Currently, ClickHouse has a data type <noindex><a rel=\"nofollow\" href=\"https:\/\/clickhouse.tech\/docs\/en\/sql-reference\/data-types\/lowcardinality\/\">LowCardinality<\/a><\/noindex> that allows efficient storage of strings with lower resource costs.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/7e9e53d86a4189783c28ce56a669198c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Now, let\u2019s look at another interesting case. Sometimes, things work strangely for people. I come in and see this. It immediately suggests that some very experienced, smart admin, with great experience setting up MySQL version 3.23, was involved. <\/p>\n<p><\/p>\n<p>Here we see a thousand tables, each of which contains the remainder of some unclear division by a thousand. <\/p>\n<p><\/p>\n<p>In principle, I respect the experience of others, including understanding the hardships that may have been endured to gain that experience. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/3a5e21ef2076966fcc627670485c5b3a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>The reasons are more or less clear. These are old stereotypes that may have accumulated from working with other systems. For example, MyISAM tables do not have clustered primary keys. Such a method of data separation may be a desperate attempt to gain the same functionality. <\/p>\n<p><\/p>\n<p>Another reason is that performing operations like ALTER on large tables is difficult. Everything gets locked. However, in modern versions of MySQL, this issue is not as critical. <\/p>\n<p><\/p>\n<p>Or, for example, micro-sharding, but more on that later.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/321b1d9caa8db7aca5b96e08c61d05b5.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>In ClickHouse, this is unnecessary because, firstly, the primary key is clustered, and the data is ordered by the primary key. <\/p>\n<p><\/p>\n<p>Sometimes, I get asked: \u2018How does the performance of range queries in ClickHouse change with the size of the table?\u2019. I say it doesn\u2019t change at all. For example, if you have a table with a billion rows and read a range of one million rows, everything works fine. If the table has a trillion rows and you read one million rows, it will be almost the same. <\/p>\n<p><\/p>\n<p>And, secondly, manual partitioning is not required. If you go and check what\u2019s on the file system, you will see that the table is a significant entity. Inside it, there\u2019s something like partitions. That is, ClickHouse does everything for you, and you don\u2019t have to suffer.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/652f3742ad3a44f172d0d7302abd09f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Alter operations in ClickHouse are free if it's ALTER ADD\/DROP COLUMN. <\/p>\n<p><\/p>\n<p>And you shouldn\u2019t create small tables because whether you have 10 rows or 10,000 rows in a table is entirely irrelevant. ClickHouse is a system that optimizes throughput, not latency, so processing 10 rows doesn\u2019t make sense. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/144144b4740e9c5daec80c8dadc445f9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>It\u2019s correct to use one large table. Get rid of old stereotypes, and everything will be fine. <\/p>\n<p><\/p>\n<p>As a bonus, our latest version introduced the ability to create arbitrary partitioning keys for performing various maintenance operations on individual partitions. <\/p>\n<p><\/p>\n<p>For example, you may need many small tables, such as when there is a need to process some intermediate data; you receive chunks and need to perform transformations on them before writing to the final table. For this case, there is a wonderful table engine \u2013 StripeLog. It's somewhat like TinyLog, but better. <\/p>\n<p><\/p>\n<p>* there is now also a <noindex><a rel=\"nofollow\" href=\"https:\/\/clickhouse.tech\/docs\/en\/sql-reference\/table-functions\/input\/\">table function input<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/d62f0b3d29876c29c6c0ba236b200186.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another anti-pattern is micro-sharding. For instance, you need to shard your data and you have 5 servers, but tomorrow there will be 6 servers. And you wonder how to rebalance these data. Instead, you break it not into 5 shards, but into 1,000 shards. Then you map each of these micro-shards to a separate server. This means you could end up with, for example, 200 ClickHouse instances on one server, each on separate ports or separate databases. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/3e550ef7e338b84e4394eb9fa4538769.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>But this is not very good in ClickHouse. Because even one ClickHouse instance tries to use all available server resources to process a single query. For instance, if you have a server with, say, 56 CPU cores, and you execute a query that runs for one second, it will utilize 56 cores. However, if you have 200 ClickHouse instances on one server, it will start 10,000 threads. Overall, this will lead to significant issues.<\/p>\n<p><\/p>\n<p>Another reason is that the workload distribution across these instances will be uneven. Some will finish earlier, some later. If all this were happening in one instance, ClickHouse would manage to distribute the data across the threads correctly by itself. <\/p>\n<p><\/p>\n<p>And yet another reason is that you will have inter-processor communication over TCP. Data will need to be serialized and deserialized, and managing such a large number of micro-shards would be highly inefficient.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/18cacc17d22ae1cb2977c6be74769896.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another anti-pattern, although it is hard to call it that, is having a large amount of pre-aggregation.<\/p>\n<p><\/p>\n<p>In general, pre-aggregation is beneficial. If you had a billion rows, aggregated them down to 1,000 rows, and now the query runs instantly, that\u2019s great. This is indeed feasible. For this, ClickHouse even has a special table type, AggregatingMergeTree, which performs incremental aggregation as data is inserted. <\/p>\n<p><\/p>\n<p>However, there are cases when you think we are going to aggregate data like this and also aggregate data like that. And in some nearby department, I wouldn't want to say which one, they use SummingMergeTree tables to sum by primary key, using about 20 different columns as the primary key. For the sake of privacy, I changed the names of some columns, but that's more or less how it is.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/54e3a02ab4bbce7c63ded595780ae1f6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>And such problems arise. Firstly, the volume of data doesn't decrease significantly. For example, it decreases by three times. Three times would be a good price to allow for unlimited analytics opportunities that arise when your data is unaggregated. If the data is aggregated, you get just miserable statistics instead of analytics. <\/p>\n<p><\/p>\n<p>And what is particularly annoying? The fact that those people from the neighboring department come and sometimes ask to add another column to the primary key. That is, we aggregated the data like this, but now we want a little more. But in ClickHouse, there is no way to alter the primary key. So we have to write some scripts in C++. And I don't like scripts, even if they are in C++.<\/p>\n<p><\/p>\n<p>And if you look at the purpose for which ClickHouse was created, unaggregated data is precisely the scenario it was born for. If you are using ClickHouse for unaggregated data, then you are doing everything right. If you are aggregating, then it's sometimes forgivable. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/09fafd4c8a3ea8c22e586cb7a5cf6564.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Another interesting case is infinite loop queries. I sometimes log into some production server and look at show processlist. And every time I discover that something terrible is happening. <\/p>\n<p><\/p>\n<p>For example, like this. It is immediately clear that everything could have been executed in one query. Just write url in and the list.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/ae03b659f178962f50e0640614517296.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Why is having many such queries in an infinite loop bad? If the index is not used, you will have many passes over the same data. But if the index is used, for example, if you have a primary key by ru and you write url = something. And you think that it will read one url exactly from the table, everything will be fine. But in reality, no. Because ClickHouse processes everything in batches. <\/p>\n<p><\/p>\n<p>When it needs to read a certain range of data, it reads a bit more because the index in ClickHouse is sparse. This index does not allow finding a single individual row in the table, only some range. Data is compressed in blocks. To read one row, you need to take the whole block and decompress it. If you run a lot of queries, you will have many overlaps like this, and a lot of work will be duplicated.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/95eef43c5b869f8b145c61ce3c616cfd.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>As a bonus, it's worth noting that in ClickHouse, you shouldn't be afraid to pass even megabytes or even hundreds of megabytes in the IN clause. From our experience, if we pass a bunch of values in an IN clause in MySQL, for example, passing 100 megabytes of some numbers, MySQL consumes 10 gigabytes of memory, and nothing else happens; everything works poorly. <\/p>\n<p><\/p>\n<p>Secondly, in ClickHouse, if your queries use the index, it is never slower than a full scan, i.e., if you need to read almost the entire table, it will proceed sequentially and read the whole table. In general, it will take care of itself.<\/p>\n<p><\/p>\n<p>However, there are some complexities. For instance, the IN clause with a subquery does not use the index. But that's our problem, and we need to fix it. There's nothing fundamental about it. We'll fix it. <\/p>\n<p><\/p>\n<p>Another interesting thing is that if you have a very long query and distributed query processing is going on, that very long query will be sent to each server without compression. For example, 100 megabytes and 500 servers. Accordingly, you will transmit 50 gigabytes over the network. It will be sent, and then it will all execute successfully.<\/p>\n<p><\/p>\n<p>* is already in use; everything is fixed, as promised.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/5fcde9bf04fff0645be2530daa12743f.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>It's also quite common for queries to come from APIs. For example, if you created your own service. If your service is in demand, you opened an API, and literally two days later, you see that something strange is happening. Everything is overloaded, and some terrible queries are coming in that should never have happened. <\/p>\n<p><\/p>\n<p>The solution here is simple. If you opened an API, you will have to restrict it. For example, introduce quotas. There are no other reasonable options. Otherwise, someone will immediately write a script, and there will be problems. <\/p>\n<p><\/p>\n<p>ClickHouse has a special feature \u2013 quota counting. You can pass your own quota key, for example, a user\u2019s internal identifier. Quotas will be counted independently for each of them. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/532438d0f116919e62f04e32ebbd3c45.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Now here\u2019s another interesting thing. This is manual replication. <\/p>\n<p><\/p>\n<p>I know many cases where, despite ClickHouse having built-in replication support, people replicate ClickHouse manually.<\/p>\n<p><\/p>\n<p>What's the principle? You have a data processing pipeline that works independently, for example, across different data centers. You write the same data in the same way to ClickHouse. However, practice shows that the data will still diverge due to some peculiarities in your code. I hope it\u2019s not in yours. <\/p>\n<p><\/p>\n<p>And periodically, you will still have to synchronize manually. For instance, once a month, admins perform an rsync.<\/p>\n<p><\/p>\n<p>In reality, it's much easier to use the built-in replication in ClickHouse. But there could be some contraindications because it requires using ZooKeeper. I won't say anything bad about ZooKeeper; it's a working system, but sometimes people avoid it due to a Java phobia since ClickHouse is a well-designed system written in C++, which works excellently. And ZooKeeper is in Java. Thus, they might prefer to use manual replication instead. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/3b5b68996daef6b8920f067b189f25e0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>ClickHouse is a practical system. It considers your needs. If you have manual replication, you can create a Distributed table that looks at your manual replicas and performs failover between them. There\u2019s even a special option that allows you to avoid flaps, even if your replicas systematically diverge.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/70f6630f8b14756163923a7db8dd7391.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Further issues may arise if you use primitive table engines. ClickHouse is like a constructor with a variety of table engines available. For all serious cases, as noted in the documentation, use tables from the MergeTree family. All others are just for specific cases or testing.<\/p>\n<p><\/p>\n<p>In the MergeTree table, it's not mandatory to have a specific date and time. You can still use it. If there's no date and time, specify the default as the year 2000. This will work without demanding resources. <\/p>\n<p><\/p>\n<p>In the new version of the server, you can even specify to have custom partitioning without a partition key. It will be the same. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/6d4046d11afdd1e5962d49a1612b666a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>On the other hand, primitive table engines can be used. For example, load the data once and see, manipulate, and delete it. You can use Log.<\/p>\n<p><\/p>\n<p>Or storing small volumes for intermediate processing \u2013 that\u2019s StripeLog or TinyLog.<\/p>\n<p><\/p>\n<p>Memory can be used if the data volume is small and you just want to manipulate something in memory. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/8a3d4e0e3a8c26dd3a2b2f64123e2bf9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>ClickHouse does not particularly like over-normalized data. <\/p>\n<p><\/p>\n<p>Here\u2019s a typical example. This is a huge number of URLs. You put them in a separate table. Then you decided to do a JOIN with them, but it usually won\u2019t work because ClickHouse supports only Hash JOIN. If there isn\u2019t enough memory for the large amount of data that needs to be joined, the JOIN won\u2019t be possible.* <\/p>\n<p><\/p>\n<p>If the data has high cardinality, don\u2019t worry, store it in a denormalized form, URLs directly in place in the main table. <\/p>\n<p><\/p>\n<p>* Now ClickHouse also has merge join, and it works when the intermediate data doesn\u2019t fit into memory. But this is inefficient, and the recommendation remains valid.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/e8160a8db17c18d48f810092c55b094c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Here are a couple more examples, but I\u2019m already doubting whether they are antipatterns or not. <\/p>\n<p><\/p>\n<p>ClickHouse has one known drawback. It doesn\u2019t support updates.* In some sense, this is even good. If you have important data, such as accounting, then no one will be able to send them because there are no updates.<\/p>\n<p><\/p>\n<p>* Support for update and delete in batch mode has been added for a long time now.<\/p>\n<p><\/p>\n<p>But there are some special methods that allow updates to happen in the background. For example, ReplaceMergeTree tables. They perform updates during background merges. You can force this using optimize table. But don\u2019t do this too often as it will completely rewrite the partition. <\/p>\n<p><\/p>\n<p>Distributed JOINs in ClickHouse are also poorly handled by the query planner. <\/p>\n<p><\/p>\n<p>Poorly, but sometimes it\u2019s okay.<\/p>\n<p><\/p>\n<p>Using ClickHouse only to read data back with select*.<\/p>\n<p><\/p>\n<p>I wouldn't recommend using ClickHouse for heavy computations. However, that's not entirely true, because we're already moving away from that recommendation. Recently, we added the capability to apply machine learning models in ClickHouse \u2013 Catboost. This worries me because I think, \"What a nightmare. How many cycles per byte does that produce!?\" I really hate to use cycles on bytes. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Effective Use of ClickHouse. Alexey Milovidov (Yandex)\" src=\"\/wp-content\/uploads\/2020\/08\/f686d5d352ccb444cd06e2ae68897137.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>But don't be afraid, install ClickHouse, everything will be fine. If anything, we have a community. By the way, the community is you. And if you have any issues, you can at least pop into our chat, and hopefully you'll get help. <\/p>\n<p><\/p>\n<p>Questions<\/p>\n<p><\/p>\n<p><em>Thank you for the talk! Where can I complain about ClickHouse crashing?<\/em><\/p>\n<p><\/p>\n<p>You can complain to me personally right now. <\/p>\n<p><\/p>\n<p><em>I recently started using ClickHouse. I immediately crashed the CLI interface.<\/em><\/p>\n<p><\/p>\n<p>You\u2019re lucky. <\/p>\n<p><\/p>\n<p><em>A little later I crashed the server with a small select.<\/em><\/p>\n<p><\/p>\n<p>You have a talent. <\/p>\n<p><\/p>\n<p><em>I opened a bug on GitHub, but it was ignored.<\/em> <\/p>\n<p><\/p>\n<p>Let's see. <\/p>\n<p><\/p>\n<p><em>Alexey tricked me into giving a presentation by promising to tell how you compress data internally.<\/em><\/p>\n<p><\/p>\n<p>It's very simple. <\/p>\n<p><\/p>\n<p><em>I realized that just yesterday. More specifics.<\/em> <\/p>\n<p><\/p>\n<p>There are no awful tricks. It's just block compression. LZ4 is used by default, but you can enable ZSTD*. Blocks range from 64 kilobytes to 1 megabyte.<\/p>\n<p><\/p>\n<p>* There is also support for specialized compression codecs that can be used in conjunction with other algorithms.<\/p>\n<p><\/p>\n<p><em>Are there just raw data in the blocks?<\/em><\/p>\n<p><\/p>\n<p>Not exactly raw. There are arrays. If you have a numeric column, the numbers are packed sequentially into an array. <\/p>\n<p><\/p>\n<p><em>Got it.<\/em> <\/p>\n<p><\/p>\n<p><em>Alexey, the example with uniqExact over IP addresses, i.e., that uniqExact is calculated slower over strings than over numbers, and so on. What if we pull a fast one and cast during reading? I mean, you mentioned that it doesn\u2019t differ much on disk. If we read strings from the disk and cast them, will our aggregates be faster or not? Or will we still not see a significant gain here? It seems to me you tested this, but for some reason didn\u2019t mention it in the benchmark.<\/em> <\/p>\n<p><\/p>\n<p>I think it will be slower than without casting. In this case, the IP address needs to be parsed from the string. In ClickHouse, of course, IP address parsing is also optimized. We made a big effort, but you have the numbers written in ten-thousandth form. Very inconvenient. On the other hand, the uniqExact function will work slower on strings not only because they are strings, but also because a different algorithm specialization is chosen. Strings are simply processed differently.<\/p>\n<p><\/p>\n<p><em>But what if we take a more primitive data type? For example, we wrote the user ID that we have in as a string, and then cast it, will that be easier or not?<\/em><\/p>\n<p><\/p>\n<p>I doubt it. I think it will even be worse, because parsing numbers is indeed a serious problem. It seems to me that this colleague even had a talk about how difficult it is to parse numbers in ten-thousandth form, or maybe not. <\/p>\n<p><\/p>\n<p><em>Alexey, thank you very much for the presentation! And also, thank you for ClickHouse! I have a question about the plans. Is there a plan to add a feature for partially updating dictionaries?<\/em><\/p>\n<p><\/p>\n<p>That is, partial reload?<\/p>\n<p><\/p>\n<p><em>Yes, yes. Like the ability to specify a MySQL field, that is, update after so that only these data are loaded if the dictionary is very large.<\/em><\/p>\n<p><\/p>\n<p>Very interesting feature. And it seems to me that someone suggested it in our chat. Maybe it was even you. <\/p>\n<p><\/p>\n<p><em>I don\u2019t think so.<\/em><\/p>\n<p><\/p>\n<p>Great, so now it\u2019s two requests. And we can start working on it at our own pace. But I want to warn you right away that this feature is quite simple to implement. I mean, you basically just need to write the version number in the table and then specify: version less than this one. And this means that we will most likely suggest doing this to enthusiasts. Are you an enthusiast? <\/p>\n<p><\/p>\n<p><em>Yes, but unfortunately, not in C++.<\/em><\/p>\n<p><\/p>\n<p>Do your colleagues know how to code in C++? <\/p>\n<p><\/p>\n<p><em>I will find someone.<\/em><\/p>\n<p><\/p>\n<p>Great.<\/p>\n<p><\/p>\n<p>* the feature was added two months after the presentation \u2013 it was developed by the person who asked the question and sent his <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/ClickHouse\/ClickHouse\/pull\/1771\">pull request<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><em>Thank you!<\/em><\/p>\n<p><\/p>\n<p><em>Hello! Thank you for the presentation! You mentioned that ClickHouse utilizes all available resources very well. The presenter next to you from Luxoft talked about his solution for the Russian Post. He said they really liked ClickHouse, but they didn't use it instead of their main competitor because it consumed all the CPU. They couldn't integrate it into their architecture, into their ZooKeeper with Docker containers. Is there a way to limit ClickHouse so that it doesn't consume everything available to it?<\/em><\/p>\n<p><\/p>\n<p>Yes, it can be done very easily. If you want it to consume fewer cores, just write <code>set max_threads = 1<\/code>. And that's it, it will execute queries on a single core. Moreover, different users can be given different settings. So there are no problems. And please let the colleagues from Luxoft know that it's unfortunate they didn't find this setting in the documentation. <\/p>\n<p><\/p>\n<p><em>Hello, Alexey! I would like to ask a question. I've heard several times that many are starting to use ClickHouse as a log storage. In your presentation, you mentioned that this shouldn't be done, i.e., long strings shouldn't be stored. What do you think about this?<\/em><\/p>\n<p><\/p>\n<p>First of all, logs are generally not long strings. Of course, there are exceptions. For example, a service written in Java throws an exception, it gets logged. And so in an endless loop, and it eventually fills up the hard drive space. The solution is very simple. If the strings are very long, just cut them off. And what does long mean? Tens of kilobytes is bad*.<\/p>\n<p><\/p>\n<p>* In the latest versions of ClickHouse, \"adaptive index granularity\" has been introduced, which largely addresses the issue of storing long strings.<\/p>\n<p><\/p>\n<p><em>And is a kilobyte normal?<\/em><\/p>\n<p><\/p>\n<p>Normal. <\/p>\n<p><\/p>\n<p><em>Hello! Thank you for the presentation! I already asked this in the chat, but I don't remember if I got a response. Is there any plan to expand the WITH section like CTE?<\/em><\/p>\n<p><\/p>\n<p>Not yet. The WITH section is somewhat informal. It's just a small feature for us.<\/p>\n<p><\/p>\n<p><em>I understand. Thank you!<\/em><\/p>\n<p><\/p>\n<p><em>Thank you for the presentation! Very interesting! A global question. Is there any plan to implement, perhaps, some kind of placeholders for data deletion modification?<\/em><\/p>\n<p><\/p>\n<p>Definitely. This is our first task in our queue. We are currently actively considering how to do everything correctly. It's time to start typing*.<\/p>\n<p><\/p>\n<p>* pressed keys on the keyboard and did everything.<\/p>\n<p><\/p>\n<p><em>Will this affect system performance or not? Will the insertion be as fast as it is now?<\/em><\/p>\n<p><\/p>\n<p>It's possible that the deletes and updates themselves will be very heavy, but this won't affect the performance of selects and inserts.<\/p>\n<p><\/p>\n<p><em>And one small question. In the presentation, you talked about the primary key. Accordingly, we have partitioning, which is monthly by default, correct? And when we set a date range that falls within a month, it only reads that partition, right?<\/em><\/p>\n<p><\/p>\n<p>Yes.<\/p>\n<p><\/p>\n<p><em>Here's a question. If we can't designate any primary key, is it correct to use the 'Date' field for it so that the underlying data has less disruption, allowing it to be more orderly? If you have no range queries and can't choose any primary key, should we include the date in the primary key?<\/em><\/p>\n<p><\/p>\n<p>Yes.<\/p>\n<p><\/p>\n<p>Perhaps, it makes sense to include a field in the primary key where the data will compress better if it is sorted by that field. For example, the user ID. If a user frequently visits the same site, then you would include the user ID and the timestamp. This way, your data will compress better. Regarding the date, if you truly never have range queries by dates, then you don't need to include the date in the primary key. <\/p>\n<p><\/p>\n<p><em>Okay, thank you very much!<\/em><\/p>\n<p>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/514840\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0422\u0430\u043a \u043a\u0430\u043a ClickHouse \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439, \u043f\u0440\u0438 \u0435\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0432\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0435\u0433\u043e \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b. \u0412 \u044d\u0442\u043e\u043c \u0434\u043e\u043a\u043b\u0430\u0434\u0435 \u0410\u043b\u0435\u043a\u0441\u0435\u0439 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 \u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a \u043f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 ClickHouse, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043a \u043d\u0435\u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u0435. \u041d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u0438\u0437 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043e, \u043a\u0430\u043a \u0432\u044b\u0431\u043e\u0440 \u0442\u043e\u0439 \u0438\u043b\u0438 \u0438\u043d\u043e\u0439 \u0441\u0445\u0435\u043c\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0430 \u043f\u043e\u0440\u044f\u0434\u043a\u0438. \u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":91439,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-91438","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u042d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 ClickHouse. \u0410\u043b\u0435\u043a\u0441\u0435\u0439 \u041c\u0438\u043b\u043e\u0432\u0438\u0434\u043e\u0432 (\u042f\u043d\u0434\u0435\u043a\u0441) | ProHoster\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-08-13T17:42:36+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-08-13T17:42:36+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Effective use of ClickHouse. Alexey Milovidov (Yandex) | ProHoster","description":"","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u042d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 ClickHouse. \u0410\u043b\u0435\u043a\u0441\u0435\u0439 \u041c\u0438\u043b\u043e\u0432\u0438\u0434\u043e\u0432 (\u042f\u043d\u0434\u0435\u043a\u0441) | ProHoster","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/effektivnoe-ispolzovanie-clickhouse-aleksej-milovidov-yandeks","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-08-13T17:42:36+00:00","article:modified_time":"2020-08-13T17:42:36+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"91438","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 12:27:23","updated":"2022-09-28 17:25:41","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/91438","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=91438"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/91438\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/91439"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=91438"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=91438"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=91438"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}