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

概要

ClickHouse と OLTP データベースにおけるデータ更新の違い

更新処理の扱いに関しては、ClickHouse と OLTP データベースは、その基盤となる設計思想と想定ユースケースの違いにより、大きく異なります。たとえば PostgreSQL は、行指向で ACID 準拠のリレーショナルデータベースであり、堅牢でトランザクション性のある UPDATE および DELETE 操作をサポートし、Multi-Version Concurrency Control (MVCC) などの仕組みによってデータの一貫性と完全性を保証します。これにより、高い同時実行性の環境でも安全かつ信頼性の高い更新が可能になります。

一方、ClickHouse は、読み取り中心のアナリティクスと高スループットな追記専用処理に最適化されたカラム指向データベースです。インプレースの UPDATEDELETE をネイティブにサポートしていますが、大量の I/O を招かないよう注意して使用する必要があります。別の方法として、テーブルを再構成し、DELETEUPDATE を追記型の操作に変換して、非同期および/または読み取り時に処理させることもできます。これにより、リアルタイムなデータ操作よりも、高スループットなデータインジェストと効率的なクエリパフォーマンスに重点を置くという設計方針が反映されます。

ClickHouse のデータを更新する方法

ClickHouse でデータを更新する方法はいくつかあり、それぞれに利点とパフォーマンス特性があります。データモデルと、更新する予定のデータ量に基づいて適切な方法を選択する必要があります。

どちらの操作においても、ある期間にわたってバックグラウンドで処理されるミューテーション数よりも、送信されるミューテーション数が継続的に多い場合、適用待ちの非マテリアライズされたミューテーションのキューは増え続けます。その結果、最終的には SELECT クエリのパフォーマンスが低下します。

要約すると、更新操作は慎重に発行する必要があり、system.mutations テーブルを使ってミューテーションのキューを綿密に監視すべきです。OLTP データベースのように頻繁に更新を発行しないでください。頻繁な更新が必要な要件がある場合は、ReplacingMergeTree を参照してください。

MethodSyntaxWhen to use
Update mutationALTER TABLE [table] UPDATEデータを即座にディスク上で更新する必要がある場合に使用します(例: コンプライアンス目的)。SELECT パフォーマンスに悪影響を与えます。
Lightweight updatesUPDATE [table] SET ... WHERE少量のデータ(テーブルの約 10% 程度まで)を更新する場合に使用します。カラム全体を書き換えることなく、即時可視性のためのパッチパーツを作成します。SELECT クエリにオーバーヘッドを追加しますが、レイテンシは予測可能です。現在は実験的機能です。
On-the-fly updatesALTER TABLE [table] UPDATESET apply_mutations_on_fly = 1; を設定して有効化して使用します。少量のデータを更新する場合に利用します。以降のすべての SELECT クエリで更新後のデータを即座に返しますが、ディスク上では当初は内部的に更新済みとしてマークされるだけです。
ReplacingMergeTreeENGINE = ReplacingMergeTree大量のデータを更新する場合に使用します。このテーブルエンジンは、マージ時のデータ重複排除に最適化されています。
CollapsingMergeTreeENGINE = CollapsingMergeTree(Sign)個々の行を頻繁に更新する場合、または時間とともに変化するオブジェクトの最新状態を保持する必要があるシナリオで使用します。たとえば、ユーザーアクティビティや記事の統計を追跡する場合などです。

更新ミューテーション

更新ミューテーションは ALTER TABLE ... UPDATE コマンドを使って発行できます。例えば、次のように実行します。

ALTER TABLE posts_temp
        (UPDATE AnswerCount = AnswerCount + 1 WHERE AnswerCount = 0)

これらは非常に I/O 負荷が高く、WHERE 式に一致するすべてのパーツを書き換えます。この処理にはアトミック性がなく、パーツはミューテーション後のパーツが準備でき次第、順次置き換えられます。そのため、ミューテーションの実行中に開始された SELECT クエリでは、すでにミューテーション済みのパーツのデータと、まだミューテーションされていないパーツのデータが混在して見えることになります。ユーザーは systems.mutations テーブルを通じて進行状況を追跡できます。これらは I/O 負荷の高い操作であり、クラスターの SELECT パフォーマンスに影響を与える可能性があるため、使用は必要最小限に留めるべきです。

update mutations の詳細については、こちらを参照してください。

Lightweight updates

Lightweight updates は、従来の mutation のように列全体を書き換えるのではなく、「patch parts」と呼ばれる特別なデータパーツ(更新された列と行のみを含む)を使って行を更新する ClickHouse の機能です。Lightweight UPDATE の主な特性は次のとおりです。

主な特性:

  • 標準的な UPDATE 構文を使用し、マージを待たずに直ちに patch parts を作成する
  • 更新された値は、patch の適用によって SELECT クエリから即座に参照可能だが、物理的にマテリアライズされるのはその後のマージ時のみ
  • 予測可能なレイテンシで(テーブル全体のおおよそ 10% 程度までの)小規模な更新向けに設計されている
  • patch を適用する必要がある SELECT クエリにはオーバーヘッドが増えるが、列全体の書き換えは回避できる

詳細は "The Lightweight UPDATE Statement" を参照してください。

オンザフライ更新

オンザフライ更新は、行を即時に更新し、その後の SELECT クエリが自動的に更新後の値を返すようにする仕組みを提供します(その分のオーバーヘッドが発生し、クエリは遅くなります)。これにより、通常のミューテーションが持つ原子性に関する制約を実質的に解消できます。以下に例を示します。

SET apply_mutations_on_fly = 1;

SELECT ViewCount
FROM posts
WHERE Id = 404346

┌─ViewCount─┐
│   26762   │
└───────────┘

1 row in set. Elapsed: 0.115 sec. Processed 59.55 million rows, 238.25 MB (517.83 million rows/s., 2.07 GB/s.)
Peak memory usage: 113.65 MiB.

-カウントを増分
ALTER TABLE posts
        (UPDATE ViewCount = ViewCount + 1 WHERE Id = 404346)

SELECT ViewCount
FROM posts
WHERE Id = 404346

┌─ViewCount─┐
│       26763   │
└───────────┘

1 row in set. Elapsed: 0.149 sec. Processed 59.55 million rows, 259.91 MB (399.99 million rows/s., 1.75 GB/s.)

オンザフライ更新の場合でも、データの更新には依然として mutation が使用されます。ただし、その結果は即座にはマテリアライズされず、SELECT クエリの実行時に適用されます。バックグラウンドで非同期プロセスとして適用される点は同じであり、通常の mutation と同等の大きなオーバーヘッドが発生するため、I/O 集約型の処理となり、慎重に使用する必要があります。この操作で使用できる式にも制限があります(詳細を参照してください)。

オンザフライ更新についての詳細はこちらを参照してください。

CollapsingMergeTree

更新はコストが高い一方で、挿入を活用して更新を実現できるという発想に基づき、 CollapsingMergeTree テーブルエンジンは sign 列と組み合わせて使用することで、sign1-1 のペアになっている行を 折りたたみ(削除)ることによって、特定の行を更新するよう ClickHouse に指示できます。 sign 列に -1 が挿入された場合、その行全体が削除されます。 sign 列に 1 が挿入された場合、ClickHouse はその行を保持します。 更新対象の行は、テーブル作成時の ORDER BY () 句で使用されるソートキーに基づいて識別されます。

CREATE TABLE UAct
(
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8 -- CollapsingMergeTreeテーブルエンジンで使用される特殊なカラム
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID

INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1)
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1) -- sign = -1 はこの行の状態を更新することを通知する
INSERT INTO UAct VALUES (4324182021466249494, 6, 185, 1) -- 行は新しい状態に置き換えられる

SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0

┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │         6 │      185 │
└─────────────────────┴───────────┴──────────┘
注記

上記の更新手法では、ユーザーがクライアント側で状態を保持する必要があります。 これは ClickHouse の観点からは最も効率的ですが、大規模な環境で運用する場合には複雑になり得ます。

より包括的な解説については、 CollapsingMergeTree のドキュメントを参照することを推奨します。

参考資料