メインコンテンツへスキップ
メインコンテンツへスキップ

クエリパフォーマンス - 時系列

ストレージの最適化に続く次のステップは、クエリパフォーマンスの向上です。 このセクションでは、2 つの重要な手法、ORDER BY キーの最適化と materialized view の使用を取り上げます。 これらの手法により、クエリ時間を数秒から数ミリ秒まで短縮できることを見ていきます。

ORDER BY キーの最適化

他の最適化に取り組む前に、ClickHouse が可能な限り高速に結果を返せるよう、まず ORDER BY キーを最適化する必要があります。 最適なキーの選択は、主に実行するクエリに依存します。たとえば、ほとんどのクエリが projectsubproject カラムでフィルタするとします。 この場合、時間でも検索を行うため、これらのカラムに加えて time カラムも ORDER BY キーに含めるのが有効です。

wikistat と同じカラム型を持ちつつ、(project, subproject, time)ORDER BY とするテーブルの別バージョンを作成してみましょう。

CREATE TABLE wikistat_project_subproject
(
    `time` DateTime,
    `project` String,
    `subproject` String,
    `path` String,
    `hits` UInt64
)
ENGINE = MergeTree
ORDER BY (project, subproject, time);

ここで複数のクエリを比較して、ソートキー式がパフォーマンスにどの程度重要かを見てみましょう。なお、ここでは前回行ったデータ型やコーデックの最適化は適用していないため、クエリのパフォーマンス差はすべてソート順のみを原因とするものです。

クエリ(time)(project, subproject, time)
SELECT project, sum(hits) AS h
FROM wikistat
GROUP BY project
ORDER BY h DESC
LIMIT 10;
2.381 秒1.660 秒
SELECT subproject, sum(hits) AS h
FROM wikistat
WHERE project = 'it'
GROUP BY subproject
ORDER BY h DESC
LIMIT 10;
2.148 秒0.058 秒
SELECT toStartOfMonth(time) AS m, sum(hits) AS h
FROM wikistat
WHERE (project = 'it') AND (subproject = 'zero')
GROUP BY m
ORDER BY m DESC
LIMIT 10;
2.192 秒0.012 秒
SELECT path, sum(hits) AS h
FROM wikistat
WHERE (project = 'it') AND (subproject = 'zero')
GROUP BY path
ORDER BY h DESC
LIMIT 10;
2.968 秒0.010 秒

マテリアライズドビュー

別の方法として、マテリアライズドビューを使用して、よく実行されるクエリの結果を集計・保存することができます。以降は、元のテーブルではなく、これらの結果に対してクエリを実行します。ここでは、次のクエリがかなり頻繁に実行されるケースを想定します。

SELECT path, SUM(hits) AS v
FROM wikistat
WHERE toStartOfMonth(time) = '2015-05-01'
GROUP BY path
ORDER BY v DESC
LIMIT 10
┌─path──────────────────┬────────v─┐
│ -                     │ 89650862 │
│ Angelsberg            │ 19165753 │
│ Ana_Sayfa             │  6368793 │
│ Academy_Awards        │  4901276 │
│ Accueil_(homonymie)   │  3805097 │
│ Adolf_Hitler          │  2549835 │
│ 2015_in_spaceflight   │  2077164 │
│ Albert_Einstein       │  1619320 │
│ 19_Kids_and_Counting  │  1430968 │
│ 2015_Nepal_earthquake │  1406422 │
└───────────────────────┴──────────┘

10 rows in set. Elapsed: 2.285 sec. Processed 231.41 million rows, 9.22 GB (101.26 million rows/s., 4.03 GB/s.)
Peak memory usage: 1.50 GiB.

マテリアライズドビューを作成する

次のマテリアライズドビューを作成します。

CREATE TABLE wikistat_top
(
    `path` String,
    `month` Date,
    hits UInt64
)
ENGINE = SummingMergeTree
ORDER BY (month, hits);
CREATE MATERIALIZED VIEW wikistat_top_mv 
TO wikistat_top
AS
SELECT
    path,
    toStartOfMonth(time) AS month,
    sum(hits) AS hits
FROM wikistat
GROUP BY path, month;

宛先テーブルのバックフィル

この宛先テーブルは wikistat テーブルに新しいレコードが挿入されたときにのみデータが投入されるため、バックフィルを行う必要があります。

これを行う最も簡単な方法は、INSERT INTO SELECT ステートメントを使用し、ビューの SELECT クエリ (変換処理) を利用して、マテリアライズドビューのターゲットテーブルに直接挿入することです。

INSERT INTO wikistat_top
SELECT
    path,
    toStartOfMonth(time) AS month,
    sum(hits) AS hits
FROM wikistat
GROUP BY path, month;

生データセットのカーディナリティによっては (ここでは 10 億行あります!) 、この方法はメモリを多く消費する可能性があります。代わりに、必要なメモリを最小限に抑えられる別の手法を使うこともできます。

  • Null テーブルエンジンを持つ一時テーブルを作成する
  • 通常使用しているマテリアライズドビューのコピーをその一時テーブルに接続する
  • INSERT INTO SELECT クエリを使用して、生データセットからすべてのデータをその一時テーブルにコピーする
  • 一時テーブルと一時マテリアライズドビューを削除する。

この方法では、生データセットの行がブロック単位で一時テーブル (これらの行は一切保存されません) にコピーされ、各行ブロックごとに部分状態が計算されてターゲットテーブルに書き込まれます。ターゲットテーブルでは、これらの状態がバックグラウンドでインクリメンタルにマージされます。

CREATE TABLE wikistat_backfill
(
    `time` DateTime,
    `project` String,
    `subproject` String,
    `path` String,
    `hits` UInt64
)
ENGINE = Null;

次に、wikistat_backfill から読み取り、wikistat_top に書き込むマテリアライズドビューを作成します。

CREATE MATERIALIZED VIEW wikistat_backfill_top_mv 
TO wikistat_top
AS
SELECT
    path,
    toStartOfMonth(time) AS month,
    sum(hits) AS hits
FROM wikistat_backfill
GROUP BY path, month;

最後に、最初の wikistat テーブルから wikistat_backfill テーブルにデータを投入します。

INSERT INTO wikistat_backfill
SELECT * 
FROM wikistat;

そのクエリの実行が完了したら、バックフィル用テーブルとマテリアライズドビューを削除できます。

DROP VIEW wikistat_backfill_top_mv;
DROP TABLE wikistat_backfill;

これで、元のテーブルではなくマテリアライズドビューに対してクエリを実行できます。

SELECT path, sum(hits) AS hits
FROM wikistat_top
WHERE month = '2015-05-01'
GROUP BY ALL
ORDER BY hits DESC
LIMIT 10;
┌─path──────────────────┬─────hits─┐
│ -                     │ 89543168 │
│ Angelsberg            │  7047863 │
│ Ana_Sayfa             │  5923985 │
│ Academy_Awards        │  4497264 │
│ Accueil_(homonymie)   │  2522074 │
│ 2015_in_spaceflight   │  2050098 │
│ Adolf_Hitler          │  1559520 │
│ 19_Kids_and_Counting  │   813275 │
│ Andrzej_Duda          │   796156 │
│ 2015_Nepal_earthquake │   726327 │
└───────────────────────┴──────────┘

10 rows in set. Elapsed: 0.004 sec.

ここでのパフォーマンス向上は劇的です。 以前はこのクエリの結果を計算するのに 2 秒強かかっていましたが、今ではわずか 4 ミリ秒で済みます。