Skip to content

ClickHouse リリース 26.4

neutral avatar 400804ae96
ClickHouse
2026年5月8日 · 15分で読む

またひと月が経ち、新しいリリースの時期がやってきました!

ClickHouse 26.4 リリースには、39 件の新機能 🌷 45 件のパフォーマンス最適化 🐇 238 件のバグ修正 🐝 が含まれています。

今回のリリースでは、さらに多くの機能が標準 SQL と互換になり、COUNT DISTINCT が高速化され、EXPLAIN の表示がさらに見やすくなるなど、盛りだくさんの内容となっています!

新しいコントリビューターの皆さん

26.4 のすべての新しいコントリビューターを心から歓迎します! ClickHouse コミュニティの広がりにはいつも圧倒されます。ClickHouse をここまで成長させてくれた皆さんの貢献に、心より感謝いたします。

新しいコントリビューターのお名前は以下のとおりです。

Alexander Kuleshov, Alsu, Anton Frost, Aruj Bansal, Asya, ClickGap AI Bot, Denys Melnyk, Diego Gomes Tome, Dustin Healy, Evgeny Kuzin, Farid Adam, Francisco Garcia Florez, Gagan Dhakrey, Gleb Popov, Groene AI, Ivan Mantova, Jaap Elst, Jack Knudson, James Cunningham, JingYanchao, K, Kc Balusu, Matheus Nerone, Michael Russell, MukundaKatta, Nikita Semenov, Pavel Kravtsov, Peng, RenzoMXD, Sergey Veletskiy, Takumi Hara, Timothy Kurniawan, Wenyu Chen, XiaoBinMu, Yuri Fedoseev, ashrithb, asyablue22, dwagner-decix, egor romanov, groeneai, liuguangliang, manerone, nerve-bot, simonhammes, sourcelliu, xiaobin

ヒント: このリストをどのように生成しているか興味がある方は、こちらをご覧ください。

プレゼンテーションのスライドもご覧いただけます。

SQL 互換性: テーブル式としての VALUES、EXTRACT、SET TIME ZONE

26.4 リリースでは、さらに多くの機能が標準 SQL 構文と互換になりました。ここではその一部を紹介しますが、詳細はプレゼンテーションスライドをご覧ください。

テーブル式としての VALUES

コントリビューター: Desel72

まずは VALUES です。このリリースより前は、次のように呼び出すことができました。

SELECT * 
FROM VALUES((1, 'a'), (2, 'b'), (3, 'c'));
┌─c1─┬─c2─┐
1. │  1 │ a  │
2. │  2 │ b  │
3. │  3 │ c  │
   └────┴────┘

これに対し、今回から以下のようにテーブル式としても呼び出せるようになりました。

SELECT * 
FROM (VALUES (1, 'a'), (2, 'b'), (3, 'c'));
┌─c1─┬─c2─┐
1. │  1 │ a  │
2. │  2 │ b  │
3. │  3 │ c  │
   └────┴────┘

また、カラムにエイリアスを付けられるようにもなりました。これは結合クエリで VALUES を使用する際に便利です。たとえば、これまでは次のように書いていたところを、

SELECT
    c.c2,
    o.c2
FROM VALUES((1, 'Alice'), (2, 'Bob')) AS c
INNER JOIN VALUES((1, 250), (2, 100), (1, 75)) AS o 
ON c.c1 = o.c1;
┌─c2────┬─o.c2─┐
1. │ Alice │  250 │
2. │ Alice │   75 │
3. │ Bob   │  100 │
   └───────┴──────┘

カラムに名前を付けることができ、クエリが理解しやすくなります。

SELECT c.name, o.amount
  FROM (VALUES (1, 'Alice'), (2, 'Bob')) AS c(id, name)
  JOIN (VALUES (1, 250), (2, 100), (1, 75)) AS o(customer_id, amount)
  ON c.id = o.customer_id;
┌─name──┬─amount─┐
1. │ Alice │    250 │
2. │ Alice │     75 │
3. │ Bob   │    100 │
   └───────┴────────┘

EXTRACT

コントリビューター: Alexey Milovidov

日付の処理で使用する EXTRACT 演算子が PostgreSQL スタイルの単位に対応しました。以下のクエリのように使用できます。

SELECT
  EXTRACT(EPOCH      FROM now())   AS epoch,
  EXTRACT(DOW        FROM today()) AS dayOfWeek,
  EXTRACT(DOY        FROM today()) AS dayOfYear,
  EXTRACT(ISODOW     FROM today()) AS isoDOW,
  EXTRACT(ISOYEAR    FROM today()) AS isoYear,
  EXTRACT(WEEK       FROM today()) AS isoWeek,
  EXTRACT(CENTURY    FROM today()) AS century,
  EXTRACT(DECADE     FROM today()) AS decade,
  EXTRACT(MILLENNIUM FROM today()) AS millennium;
Row 1:
──────
epoch:      1777992683
dayOfWeek:  2
dayOfYear:  125
isoDOW:     2
isoYear:    2026
isoWeek:    19
century:    21
decade:     202
millennium: 3

SET TIME ZONE

コントリビューター: phulv94

タイムゾーンを設定するための新しい標準 SQL エイリアスも追加されました。まず、現在のタイムゾーンを確認してみます。

SELECT timezone(), formatDateTime(now(), '%Y-%m-%d %H:%M:%S %z');
┌─timezone()────┬─formatDateTim⋯H:%M:%S %z')─┐
1. │ Europe/London │ 2026-05-05 15:May:47 +0100 │
   └───────────────┴────────────────────────────┘

次に、アムステルダムに設定します。

SET TIME ZONE 'Europe/Amsterdam';

上記のクエリを再実行すると、以下のようになります。

┌─timezone()───────┬─formatDateTim⋯H:%M:%S %z')─┐
1. │ Europe/Amsterdam │ 2026-05-05 16:May:59 +0200 │
   └──────────────────┴────────────────────────────┘

その他の互換性の向上

これだけではありません。NATURAL JOIN のサポート、そのまま使える OVERLAY の互換性、複合 INTERVAL リテラルのサポートなども追加されています!

LIKE でのテキストインデックスの利用

コントリビューター: Elmi Ahmadov

ClickHouse 25.4 から、LIKE/ILIKE のクエリパターンが %<スペースを含まない英数字>% であり、テキストインデックスのトークナイザーが splitByNonAlpha の場合、ClickHouse は転置インデックスを利用してこれらのクエリを高速化します。一致するパターンを見つけるためにテーブル全体をフルスキャンするのではなく、転置インデックスの辞書をスキャンすることでこれを実現しています。

おなじみの HackerNews データセットを使って、この仕組みを確認してみましょう。まず clickhousectl を使い、ノート PC 上で ClickHouse 26.4 を起動します。

chctl local install 26.4

次にサーバーを起動します。

chctl local server start --version 26.4

そして ClickHouse クライアントを使用して接続します。

chctl local client --name default -mn

続いて、HackerNews テーブルを作成します。

CREATE TABLE hackernews
(
    `id` Int64,
    `deleted` Int64,
    `type` String,
    `by` String,
    `time` DateTime64(9),
    `text` String,
    `dead` Int64,
    `parent` Int64,
    `poll` Int64,
    `kids` Array(Int64),
    `url` String,
    `score` Int64,
    `title` String,
    `parts` Array(Int64),
    `descendants` Int64
)
ORDER BY time;

データを挿入します。

INSERT INTO hackernews 
SELECT *
FROM url('https://datasets-documentation.s3.eu-west-3.amazonaws.com/hackernews/hacknernews.csv.gz', 'CSVWithNames')

次に、splitByNonAlpha トークナイザーを使用して text カラムにテキストインデックスを追加します。

ALTER TABLE hackernews
ADD INDEX text_tokens_idx text 
TYPE text(tokenizer='splitByNonAlpha') 
GRANULARITY 1;

そのインデックスをマテリアライズします。

ALTER TABLE hackernews
(MATERIALIZE INDEX text_tokens_idx)
SETTINGS mutations_sync = 1;

この最適化は 26.4 で既に有効になっていますが、use_text_index_like_evaluation_by_dictionary_scan 設定を使って制御できます。次のクエリは、Kubernetes について言及している Hacker News の投稿数をカウントします。

SELECT count()
FROM hackernews
WHERE text LIKE '%Kubernetes%'
SETTINGS use_text_index_like_evaluation_by_dictionary_scan=0;
┌─count()─┐
1. │   20070 │
   └─────────┘

1 row in set. Elapsed: 0.832 sec. Processed 18.25 million rows, 6.29 GB (21.93 million rows/s., 7.56 GB/s.)
Peak memory usage: 88.18 MiB.

1 row in set. Elapsed: 0.624 sec. Processed 18.25 million rows, 6.29 GB (29.23 million rows/s., 10.08 GB/s.)
Peak memory usage: 87.93 MiB.

1 row in set. Elapsed: 0.638 sec. Processed 18.25 million rows, 6.29 GB (28.60 million rows/s., 9.86 GB/s.)
Peak memory usage: 86.01 MiB.

次に、最適化を使用して実行します。

SELECT count()
FROM hackernews
WHERE text LIKE '%Kubernetes%'
SETTINGS use_text_index_like_evaluation_by_dictionary_scan=1;
┌─count()─┐
1. │   20070 │
   └─────────┘

1 row in set. Elapsed: 0.208 sec. Processed 18.25 million rows, 18.25 MB (87.53 million rows/s., 87.53 MB/s.)
Peak memory usage: 2.07 MiB.

1 row in set. Elapsed: 0.225 sec. Processed 18.25 million rows, 18.25 MB (80.98 million rows/s., 80.98 MB/s.)
Peak memory usage: 2.07 MiB.

1 row in set. Elapsed: 0.234 sec. Processed 18.25 million rows, 18.25 MB (77.83 million rows/s., 77.83 MB/s.)
Peak memory usage: 2.07 MiB.

処理された行数は同じですが、最適化を使用したクエリの最短実行時間は 208 ミリ秒で、624 ミリ秒と比べて 3 倍強高速になっています。

クエリプランを比較すると、最適化を使用した方がスキャンするグラニュールが 1,000 個以上少ないことがわかります。

転置インデックスを使用しない場合:

┌─explain─────────────────────────────────────────────────────────┐
 1. │ Output: count()                                                 │
 2. │                                                                 │
 3. │ Aggregating                                                     │
 4. │ └──Filter ((WHERE + Change column names to column identifiers)) │
 5. │    └──ReadFromMergeTree (default.hackernews)                    │
 6. │          Indexes:                                               │
 7. │            PrimaryKey                                           │
 8. │              Condition: true                                    │
 9. │              Parts: 6/6                                         │
10. │              Granules: 3533/3533                                │
11. │            Skip                                                 │
12. │              Name: text_tokens_idx                              │
13. │              Description: text GRANULARITY 100000000            │
14. │              Condition: (mode: All; tokens: [])                 │
15. │              Parts: 6/6                                         │
16. │              Granules: 3533/3533                                │
17. │            Ranges: 6                                            │
    └─────────────────────────────────────────────────────────────────┘

転置インデックスを使用する場合:

┌─explain──────────────────────────────────────────────┐
 1. │ Output: count()                                      │
 2. │                                                      │
 3. │ Aggregating                                          │
 4. │ └──Filter                                            │
 5. │    └──ReadFromMergeTree (default.hackernews)         │
 6. │          Indexes:                                    │
 7. │            PrimaryKey                                │
 8. │              Condition: true                         │
 9. │              Parts: 6/6                              │
10. │              Granules: 3533/3533                     │
11. │            Skip                                      │
12. │              Name: text_tokens_idx                   │
13. │              Description: text GRANULARITY 100000000 │
14. │              Condition: (mode: All; tokens: [])      │
15. │              Parts: 6/6                              │
16. │              Granules: 2247/3533                     │
17. │            Ranges: 190                               │
    └──────────────────────────────────────────────────────┘

COUNT DISTINCT の高速化

コントリビューター: Jiebin Sun

コア数の多いマシンにおける uniqExact(COUNT(DISTINCT ...) で使用)に対して、いくつかの改善が行われました。

  • マージフェーズ中に ClickHouse が冗長なスレッドを生成しなくなりました。uniqExact は 256 個のバケットを持つ 2 レベルのハッシュテーブルを使用しますが、これまでは状況に関係なく最大 max_threads 個のスレッドを生成していたため、その多くは何の処理も行わずに即座に終了していました。
  • N 個の中間ハッシュテーブル(集計スレッドごとに 1 個)をマージする際、スレッドプールが N 回初期化されていたため、合計 O(N × threads) 回のスレッド生成と深刻なロック競合が発生していました。今回から、N 個のハッシュテーブルすべてが 1 回のパスでマージされるようになりました。各スレッドがすべてのハッシュテーブルにわたる 1 つのバケットを一度に処理するため、スレッドプールの初期化が O(N) から O(1) に削減されます。

ベンチマークの一部では、288 コアマシンで 3 〜 15 倍の高速化が確認されました。

ただし、これはコア数の多いマシン向けの最適化です。手元の Mac M2 Max(12 コア)で HackerNews データセットを使って試してみたところ、改善は見られませんでした。

EXPLAIN のさらなる美化

コントリビューター: Kirill Kopnev

EXPLAIN PLAN pretty=1 が式を人間が読みやすい形式で出力するようになり、トップレベルの出力カラムとステップごとの出力カラムを表示し、JOIN に推定行数と局所性(locality)のラベルを付けるようになりました。

以下のクエリでどのように動作するか見てみましょう。

EXPLAIN pretty = 1
SELECT by, count()
FROM hackernews
WHERE (text LIKE '%OpenAI%') AND (text LIKE '%Google%')
GROUP BY ALL
ORDER BY count() DESC, by
LIMIT 10;

26.3

┌─explain────────────────────────────────────────────────────────────────────────────┐
1. │ Expression (Project names)                                                         │
2. │ └──Limit (preliminary LIMIT)                                                       │
3. │    └──Sorting (Sorting for ORDER BY)                                               │
4. │       └──Expression ((Before ORDER BY + Projection))                               │
5. │          └──Aggregating                                                            │
6. │             └──Expression (Before GROUP BY)                                        │
7. │                └──Expression ((WHERE + Change column names to column identifiers)) │
8. │                   └──ReadFromMergeTree (default.hackernews)                        │
   └────────────────────────────────────────────────────────────────────────────────────┘

26.4

┌─explain─────────────────────────────────────────────────────┐
 1. │ Output: by, count()                                         │
 2. │                                                             │
 3. │ Expression (Project names)                                  │
 4. │ └──Limit (preliminary LIMIT)                                │
 5. │    └──Sorting (Sorting for ORDER BY)                        │
 6. │       └──Expression ((Before ORDER BY + Projection))        │
 7. │          └──Aggregating                                     │
 8. │             └──Expression (Before GROUP BY)                 │
 9. │                └──Expression                                │
10. │                   └──ReadFromMergeTree (default.hackernews) │
    └─────────────────────────────────────────────────────────────┘

JSONAllValues + テキストインデックス

コントリビューター: Anton Popov

ClickHouse 26.4 では JSONAllValues が追加され、JSON カラムのすべてのリーフ値を Array(String) として返すようになりました。この上にテキストインデックスを作成することで、JSON サブカラムに対するより効率的なフィルタリングが可能になります。

StatsBomb データセットを使って、この仕組みを確認してみましょう。以下を実行して、データの一部を手元のマシンに取得します。

git clone --filter=blob:none --sparse https://github.com/statsbomb/open-data.git
cd open-data
git sparse-checkout set data/events

clickhouse-local を使用して以下のテーブルを作成します。

CREATE TABLE events (
      match_id UInt32,
      json JSON(id String, index UInt32),
      INDEX vals JSONAllValues(json) TYPE text(tokenizer = 'ngrams') GRANULARITY 1
  )
  ENGINE = MergeTree
  ORDER BY (match_id, json.index);

続いてデータを挿入します。

INSERT INTO events
SELECT
  toUInt32(replaceRegexpOne(_file, '\\.json$', '')) AS match_id,
  json
FROM file('open-data/data/events/*.json', JSONAsObject);
12188949 rows in set. Elapsed: 1275.404 sec. Processed 12.19 million rows, 10.48 GB (9.56 thousand rows/s., 8.22 MB/s.)
Peak memory usage: 1.87 GiB.

インデックスの仕組みを理解するために、JSONAllValues 関数が何を返すのか確認してみましょう。

SELECT JSONAllValues(json) FROM events LIMIT 1
FORMAT Vertical;
JSONAllValues(json): ['[36.4,21.7]','1.013174','000000b5-8156-429d-9088-e62a6ac2ea0d','2529','[36.8,20]','60','2','4','From Throw In','10958','Chris Smalling','5','Left Center Back','123','39','Manchester United','[\'5fbbde9b-74ab-48e9-9873-ef956db384de\',\'fd43cc18-c37b-438a-8a40-a8bb50e59469\']','18','39','Manchester United','00:15:18.727','43','Carry']

このデータセットのレコード数は 1,200 万件強にとどまり、インデックスの効果を確認するにはやや不十分なため、データを何回か複製します。

ALTER TABLE events
ATTACH PARTITION ID 'all'
FROM events;
0 rows in set. Elapsed: 3.892 sec.
0 rows in set. Elapsed: 7.957 sec.
0 rows in set. Elapsed: 15.894 sec.
0 rows in set. Elapsed: 33.655 sec.
0 rows in set. Elapsed: 68.870 sec.

これでレコード数が大幅に増えました。

SELECT count()
FROM events;
┌───count()─┐
1. │ 390046368 │ -- 390.05 million
   └───────────┘

次のクエリは、リオネル・メッシに関連する行数を返します。

SELECT count()
FROM events
WHERE json.player.name = 'Lionel Andrés Messi Cuccittini'
SETTINGS use_skip_indexes = 1;

use_skip_indexes = 0 を設定することでテキストインデックスを無効にできます。このクエリを実行すると、以下の結果が得られます。

┌─count()─┐
1. │ 4268960 │ -- 4.27 million
   └─────────┘

インデックスなしで 3 回実行してみます。

1 row in set. Elapsed: 1.505 sec. Processed 390.05 million rows, 13.87 GB (259.20 million rows/s., 9.22 GB/s.)
Peak memory usage: 48.23 MiB.

1 row in set. Elapsed: 1.666 sec. Processed 390.05 million rows, 13.87 GB (234.12 million rows/s., 8.32 GB/s.)
Peak memory usage: 48.23 MiB.

1 row in set. Elapsed: 1.668 sec. Processed 390.05 million rows, 13.87 GB (233.88 million rows/s., 8.32 GB/s.)
Peak memory usage: 48.23 MiB.

次にインデックスありで 3 回実行します。

1 row in set. Elapsed: 1.139 sec. Processed 80.64 million rows, 3.23 GB (70.80 million rows/s., 2.84 GB/s.)
Peak memory usage: 69.25 MiB.

1 row in set. Elapsed: 1.096 sec. Processed 80.64 million rows, 3.23 GB (73.61 million rows/s., 2.95 GB/s.)
Peak memory usage: 68.93 MiB.

1 row in set. Elapsed: 1.087 sec. Processed 80.64 million rows, 3.23 GB (74.21 million rows/s., 2.97 GB/s.)
Peak memory usage: 74.13 MiB.

処理された行数を見ると、インデックスによってスキャンするデータ量が 5 分の 1 近くに削減されていることがわかります。最短実行時間は、インデックスなしの 1,505 ミリ秒に対してインデックスありでは 1,087 ミリ秒となっており、約 50% の向上が見られます。

今すぐ始める

実際のデータで ClickHouse の動作を試してみませんか?ClickHouse Cloud なら数分で利用開始でき、300 ドル分の無料クレジットも進呈されます。

サインアップ

この記事をシェア

  • Y Combinator icon
  • X icon
  • Bluesky icon
  • Facebook icon
  • LinkedIn icon

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!

Aditya Chidurala, Bentsi Leviav and Alex Francoeur · 2026年9月17日
Aditya Chidurala, José Muñoz and Alex Francoeur · 2026年9月16日

Follow us

XBlueskySlackGithubTelegramMeetupRSS