--- machine_translated: true machine_translated_rev: 72537a2d527c63c07aa5d2361a8829f3895cf2bd toc_priority: 62 toc_title: "\u30AF\u30EA\u30C3\u30AF\u30CF\u30A6\u30B9\u5EFA\u7BC9\u306E\u6982\u8981" --- # クリックハウス建築の概要 {#overview-of-clickhouse-architecture} ClickHouseは真の列指向のDBMSです。 データは、列によって、および配列(列のベクトルまたはチャンク)の実行中に格納されます。 可能であれば、個々の値ではなく配列に対して演算が送出されます。 それは呼ばれます “vectorized query execution,” そしてそれは実際のデータ処理の費用を下げるのを助けます。 > この考え方は、新しいものではない。 それはにさかのぼります `APL` プログラミング言語とその子孫: `A +`, `J`, `K`,and `Q`. 配列プログラミングは科学データ処理で使用されます。 どちらもこの考え方は関係データベースで新しいものではありません。 `Vectorwise` システム クエリ処理の高速化には、ベクトル化されたクエリ実行とランタイムコード生成という二つの方法があります。 後者は、すべての間接および動的ディスパッチを削除します。 ずれのアプローチは厳重によります。 ランタイムコード生成は、多くの操作を融合し、CPU実行単位とパイプラインを完全に利用する場合に優れています。 Vectorizedクエリを実行できる実用的では一時的ベクトルを明記のことは、キャッシュを読みます。 一時データがL2キャッシュに収まらない場合、これが問題になります。 しかし、ベクトル化されたクエリの実行は、CPUのSIMD機能をより簡単に利用します。 A [研究論文](http://15721.courses.cs.cmu.edu/spring2016/papers/p5-sompolski.pdf) 書面による友人達しないことであり、両アプローチ。 ClickHouse用vectorizedクエリを実行して初期支援のためにランタイムコード。 ## 列 {#columns} `IColumn` interfaceは、メモリ内の列(実際には列のチャンク)を表すために使用されます。 このイ 元の列を変更するのではなく、新しい変更された列を作成します。 例えば、 `IColumn :: filter` 法を受け入れフィルタのバイトマスクです。 それはのために使用されます `WHERE` と `HAVING` 関係演算子。 追加の例: `IColumn :: permute` サポートする方法 `ORDER BY` は、 `IColumn :: cut` サポートする方法 `LIMIT`. 各種 `IColumn` 実装 (`ColumnUInt8`, `ColumnString` というように)は、列のメモリレイアウトを担当しています。 メモリレイアウトは、通常、連続した配列です。 整数型の列の場合は、次のように連続した配列にすぎません `std :: vector`. のために `String` と `Array` すべての配列要素に対して一つ、連続して配置され、各配列の先頭へのオフセットに対して二つのベクトルです。 また `ColumnConst` これはメモリに一つの値を格納しますが、列のように見えます。 ## フィールド {#field} それにもかかわらず、個々の価値を扱うことも可能です。 個々の値を表すために、 `Field` が使用される。 `Field` ただの識別された組合である `UInt64`, `Int64`, `Float64`, `String` と `Array`. `IColumn` は、 `operator[]` n番目の値をaとして取得するメソッド `Field` そして、 `insert` aを追加するメソッド `Field` 列の最後まで。 これらの方法は、一時的な処理を必要とするため、あまり効率的ではありません `Field` 個々の値を表すオブジェクト。 次のようなより効率的な方法があります `insertFrom`, `insertRangeFrom`、というように。 `Field` テーブルの特定のデータ型に関する十分な情報がありません。 例えば, `UInt8`, `UInt16`, `UInt32`,and `UInt64` すべてとして表されます `UInt64` で `Field`. ## 漏れやすい抽象化 {#leaky-abstractions} `IColumn` は方法のための共通の関係変容のデータもあるんですが、そのすべて満たす。 例えば, `ColumnUInt64` 二つの列の合計を計算する方法がありません。 `ColumnString` 部分文字列検索を実行するメソッドがありません。 これらの無数のルーチンは `IColumn`. 列のさまざまな関数は、次のようにして、一般的で非効率的な方法で実装できます `IColumn` 抽出する方法 `Field` 値、または特定のデータの内部メモリレイアウトの知識を使用して特殊な方法で `IColumn` 実装。 で実施する鋳造機能を特定 `IColumn` 内部表現を直接入力して処理します。 例えば, `ColumnUInt64` は、 `getData` 内部配列への参照を返すメソッドは、別のルーチンが直接その配列を読み取るか、または塗りつぶします。 我々は持っている “leaky abstractions” さまざまなルーチンの効率的な専門化を可能にする。 ## データ型 {#data_types} `IDataType` 列または個々の値のチャンクをバイナリ形式またはテキスト形式で読み書きするためのものです。 `IDataType` テーブル内のデータ型に直接対応します。 例えば、次のものがあります `DataTypeUInt32`, `DataTypeDateTime`, `DataTypeString` など。 `IDataType` と `IColumn` 互いに緩やかに関連しているだけです。 異なるデータ型は、同じメモリで表すことができます `IColumn` 実装。 例えば, `DataTypeUInt32` と `DataTypeDateTime` で表される。 `ColumnUInt32` または `ColumnConstUInt32`. また、同じデータ型で表現することができな `IColumn` 実装。 例えば, `DataTypeUInt8` で表すことができる `ColumnUInt8` または `ColumnConstUInt8`. `IDataType` 貨物のメタデータを指すものとします。 例えば, `DataTypeUInt8` 何も保存しません(vptrを除きます)。 `DataTypeFixedString` ちょうど店 `N` (固定サイズの文字列のサイズ)。 `IDataType` はヘルパーの方法のための様々なデータフォーマット たとえば、クォート可能な値をシリアル化したり、JSONの値をシリアル化したり、XML形式の一部として値をシリアル化したりするメソッドがあります。 データ形式への直接の対応はありません。 たとえば、異なるデータ形式 `Pretty` と `TabSeparated` 同じを使用できます `serializeTextEscaped` からのヘルパーメソッド `IDataType` インタフェース ## ブロック {#block} A `Block` メモリ内のテーブルのサブセット(チャンク)を表すコンテナです。 それは単なるトリプルのセットです: `(IColumn, IDataType, column name)`. クエリの実行中、データは次の方法で処理されます `Block`s.私達にaがあれば `Block`,我々はデータを持っている(で `IColumn` そのタイプに関する情報があります `IDataType`)それはその列をどのように扱うかを教えてくれます。 これは、テーブルの元の列名か、一時的な計算結果を取得するために割り当てられた人工的な名前のいずれかです。 ブロック内の列に対して関数を計算するとき、その結果を含む別の列をブロックに追加します。 後で、不要な列はブロックから削除できますが、変更はできません。 共通の部分式を排除するのに便利です。 ブロックの作成のための各処理チャンクのデータです。 同じタイプの計算では、列名と型は異なるブロックで同じままであり、列データのみが変更されることに注意してください。 ブロックサイズが小さいと、shared_ptrsと列名をコピーするための一時的な文字列のオーバーヘッドが高くなるため、ブロックヘッダーからブロックデータを分割 ## ブロックの流れ {#block-streams} ブロックストリームのための加工データです。 を使用していま流のブロックからデータを読み込むためのどこかに、データ変換、または書き込みデータをどこかということです。 `IBlockInputStream` は、 `read` 利用可能な状態で次のブロックを取得するメソッド。 `IBlockOutputStream` は、 `write` どこかにブロックをプッシュする方法。 ストリームは: 1. テーブルへの読み書き。 のテーブルだけを返しますストリームを読み取りまたは書き込みブロックとなります。 2. データ形式の実装。 たとえば、端末にデータを出力する場合は `Pretty` 書式設定すると、ブロックをプッシュするブロック出力ストリームを作成し、書式設定します。 3. データ変換の実行。 あなたが持っているとしよう `IBlockInputStream` いを作ろうというストリームです。 作成する `FilterBlockInputStream` ストリームで初期化します その後、ブロックを引っ張るときから `FilterBlockInputStream` で引きブロックからストリーム、フィルタでは、フィルタを返しますブロックします。 クエリの実行パイプラインで表現しました。 より洗練された変換があります。 例えば、 `AggregatingBlockInputStream` で読み込みのすべてのデータからのフレームワークを利用して、集合体で、それを返しますストリームの集約トへすでに使用されています。 別の例: `UnionBlockInputStream` コンストラクタ内の多くの入力ソースと多数のスレッドを受け入れます。 複数のスレッドを起動し、複数のソースから並列に読み込みます。 > ブロックストリームは “pull” 制御フローへのアプローチ:最初のストリームからブロックをプルすると、ネストされたストリームから必要なブロックがプルされ、実行パイプライン全体 どちらも “pull” また “push” 制御フローは暗黙的であり、複数のクエリの同時実行(多くのパイプラインをマージする)などのさまざまな機能の実装を制限するため、最良の解決策で この制限は、コルーチンまたはお互いを待つ余分なスレッドを実行するだけで克服できます。 つまり、ある計算単位から別の計算単位の外部にデータを渡すロジックを見つけると、制御フローを明示的にすると、より多くの可能性があります。 これを読む [記事](http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/) より多くの思考のため。 クエリ実行パイプラインでは、各ステップで一時データが作成されます。 一時データがCPUキャッシュに収まるように、ブロックサイズを十分に小さくしておきます。 その前提では、一時データの書き込みと読み込みは、他の計算と比較してほとんど自由です。 これは、パイプライン内の多くの操作を一緒に融合させることです。 パイプラインをできるだけ短くし、一時データの多くを削除することができますが、これは利点ですが、欠点もあります。 たとえば、分割パイプラインを使用すると、中間データのキャッシュ、同時に実行される類似クエリからの中間データの盗み、類似クエリのパイプライン ## 形式 {#formats} データフォーマットにて実施しブロックわれている。 そこには “presentational” 次のように、クライアントへのデータ出力にのみ適した形式になります `Pretty` のみを提供する形式 `IBlockOutputStream`. そして入出力フォーマットが、のようなあります `TabSeparated` または `JSONEachRow`. 行ストリームもあります: `IRowInputStream` と `IRowOutputStream`. ブロックではなく、個々の行でデータをプル/プッシュすることができます。 また、行指向形式の実装を簡素化するためにのみ必要です。 ラッパー `BlockInputStreamFromRowInputStream` と `BlockOutputStreamFromRowOutputStream` 行指向のストリームを通常のブロック指向のストリームに変換できます。 ## I/O {#io} バイト指向の入出力には、次のようなものがあります `ReadBuffer` と `WriteBuffer` 抽象クラス。 それらはC++の代わりに使用されます `iostream`心配しないでください:すべての成熟したC++プロジェクトは、 `iostream`正当な理由のためのs。 `ReadBuffer` と `WriteBuffer` 単なる連続したバッファであり、そのバッファ内の位置を指すカーソルです。 実装にはない独自のメモリにバッファです。 以下のデータでバッファを埋める仮想メソッドがあります `ReadBuffer`)またはバッファをどこかにフラッシュする( `WriteBuffer`). 仮想メソッドはまれに呼び出されます。 の実装 `ReadBuffer`/`WriteBuffer` 圧縮を実装するために、ファイルとファイル記述子とネットワークソケッ (`CompressedWriteBuffer` is initialized with another WriteBuffer and performs compression before writing data to it), and for other purposes – the names `ConcatReadBuffer`, `LimitReadBuffer`,and `HashingWriteBuffer` 自分のために話す。 Read/WriteBuffersはバイトのみを扱います。 からの関数があります `ReadHelpers` と `WriteHelpers` 入力/出力のフォーマットに役立つヘッダファイル。 たとえば、小数の形式で数値を書くヘルパーがあります。 結果セットを書きたいときに何が起こるかを見てみましょう `JSON` 標準出力にフォーマットします。 結果セットを取得する準備ができています `IBlockInputStream`. 作成する `WriteBufferFromFileDescriptor(STDOUT_FILENO)` stdoutにバイトを書き込む。 作成する `JSONRowOutputStream`、それで初期化されます `WriteBuffer`、行を書き込むには `JSON` 標準出力に。 作成する `BlockOutputStreamFromRowOutputStream` その上に、それを次のように表します `IBlockOutputStream`. それから電話する `copyData` データを転送するには `IBlockInputStream` に `IBlockOutputStream` そして、すべてが動作します。 内部的には, `JSONRowOutputStream` さまざまなJSON区切り文字を書き、 `IDataType::serializeTextJSON` を参照するメソッド `IColumn` 引数として行番号を指定します。 その結果, `IDataType::serializeTextJSON` からメソッドを呼び出します `WriteHelpers.h`:例えば, `writeText` 数値型および `writeJSONString` のために `DataTypeString`. ## テーブル {#tables} その `IStorage` インタフェースです。 異なる実装のインタフェースの異なるテーブルエンジンです。 例としては `StorageMergeTree`, `StorageMemory`、というように。 これらのクラスのインスタ キー `IStorage` メソッドは `read` と `write`. また、 `alter`, `rename`, `drop`、というように。 その `read` このメソッドは、次の引数を受け入れます。 `AST` 考慮すべきクエリ、および返すストリームの必要な数。 一つまたは複数を返します `IBlockInputStream` クエリの実行中にテーブルエンジン内で完了したデータ処理のステージに関するオブジェクトと情報。 ほとんどの場合、readメソッドは、指定された列をテーブルから読み取るだけで、それ以降のデータ処理は行いません。 すべてのデータ処理が行われるクエリの通訳や外部の責任 `IStorage`. しかし、顕著な例外があります: - ASTクエリは `read` 法により処理し、テーブルエンジンを使用できる指の利用と読みの少ないデータを表示します。 - 時々のテーブルエンジンを処理できるデータそのものである場合でも特定の段階にある。 例えば, `StorageDistributed` リモートサーバーにクエリを送信し、異なるリモートサーバーからのデータをマージできるステージにデータを処理するように依頼し、その前処理されたデータを返すこ クエリの通訳を仕上げ加工のデータです。 テーブルの `read` メソッドは、複数の `IBlockInputStream` 並列データ処理を可能にするオブジェクト。 これらの複数のブロックの入力ストリームでテーブルから行なった。 次に、これらのストリームを、独立して計算できるさまざまな変換(式の評価やフィルタリングなど)でラップして、 `UnionBlockInputStream` それらの上に、並列に複数のストリームから読み込みます。 また、 `TableFunction`これらは一時的なものを返す関数です `IStorage` で使用するオブジェクト `FROM` クエリの句。 テーブルエンジンの実装方法の簡単なアイデアを得るには、次のような単純なものを見てください `StorageMemory` または `StorageTinyLog`. > の結果として `read` 方法, `IStorage` ツづゥツ。 `QueryProcessingStage` – information about what parts of the query were already calculated inside storage. ## パーサー {#parsers} 手書きの再帰降下パーサーは、クエリを解析します。 例えば, `ParserSelectQuery` クエリのさまざまな部分に対して基になるパーサを再帰的に呼び出すだけです。 パーサーは `AST`. その `AST` ノードによって表されます。 `IAST`. > パーサジェネレータは、使用しない歴史的な理由があります。 ## 通訳者 {#interpreters} インタプリタは、クエリ実行パイプラインの作成を担当します。 `AST`. 以下のような簡単な通訳があります `InterpreterExistsQuery` と `InterpreterDropQuery` またはより洗練された `InterpreterSelectQuery`. クエリの実行パイプラインの組み合わせたブロック入力または出力ストリーム. たとえば、 `SELECT` クエリは `IBlockInputStream` 結果セットを読み取るために、INSERTクエリの結果は次のようになります。 `IBlockOutputStream` に挿入するためのデータを書き込むために、および解釈の結果 `INSERT SELECT` クエリは `IBlockInputStream` これは、最初の読み取り時に空の結果セットを返しますが、データをコピーします `SELECT` に `INSERT` 同時に。 `InterpreterSelectQuery` 用途 `ExpressionAnalyzer` と `ExpressionActions` クエリ分析と変換のための機械。 ここでは、ほとんどのルールベースのクエリの最適化が行われます。 `ExpressionAnalyzer` モジュラー変換やクエリを可能にするために、さまざまなクエリ変換と最適化を別々のクラスに抽出する必要があります。 ## 関数 {#functions} 通常の関数と集計関数があります。 集計関数については、次の節を参照してください。 Ordinary functions don't change the number of rows – they work as if they are processing each row independently. In fact, functions are not called for individual rows, but for `Block`ベクトル化されたクエリ実行を実装するためのデータ。 いくつかのその他の機能があります。 [ブロックサイズ](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock),and [runningAccumulate](../sql-reference/functions/other-functions.md#function-runningaccumulate)、それはブロック処理を悪用し、行の独立性に違反します。 ClickHouseには強い型指定があるため、暗黙的な型変換はありません。 関数が特定の型の組み合わせをサポートしていない場合、例外がスローされます。 ものの機能で作業する過負荷のもとに多くの異なる組み合わせます。 例えば、 `plus` 関数(実装するには `+` 演算子)数値型の任意の組み合わせに対して動作します: `UInt8` + `Float32`, `UInt16` + `Int8`、というように。 また、一部の可変引数関数は、以下のような任意の数の引数を受け入れることができます。 `concat` 機能。 実施の機能が少し不便での機能を明示的に派遣サポートされているデータの種類と対応 `IColumns`. 例えば、 `plus` 関数は、数値型の組み合わせごとにC++テンプレートのインスタンス化によって生成されたコード、および定数または非定数左と右の引数を持っていま テンプレートコードの膨張を避けるために、実行時コード生成を実装するのに最適な場所です。 また、fused multiply-addのようなfused関数を追加したり、一つのループ反復で多重比較を行うこともできます。 ベクトル化されたクエリの実行により、関数は短絡されません。 たとえば、 `WHERE f(x) AND g(y)`,両側が計算されます,でも行について,とき `f(x)` がゼロである(場合を除く `f(x)` はゼロ定数式である)。 しかし、の選択性が `f(x)` 条件は高く、計算の `f(x)` よりもはるかに安いです `g(y)`、マルチパス計算を実装する方が良いでしょう。 最初に計算します `f(x)` 次に、結果によって列をフィルター処理し、次に計算します `g(y)` フィルター処理された小さなデータのチャンクのみ。 ## 集計関数 {#aggregate-functions} 集計関数はステートフル関数です。 渡された値をある状態に蓄積し、その状態から結果を得ることができます。 それらはと管理されます `IAggregateFunction` インタフェース 状態はかなり単純にすることができます( `AggregateFunctionCount` ただの単一です `UInt64` 値)または非常に複雑な(の状態 `AggregateFunctionUniqCombined` 線形配列、ハッシュテーブル、およびaの組み合わせです `HyperLogLog` 確率データ構造)。 状態は `Arena` (メモリプール)高カーディナリティの実行中に複数の状態を処理する `GROUP BY` クエリ。 たとえば、複雑な集約状態では、追加のメモリを割り当てることができます。 これは、作成し、状態を破壊し、適切にその所有権と破壊命令を渡すためにいくつかの注意が必要です。 集約状態をシリアル化および逆シリアル化して、分散クエリの実行中にネットワーク経由で渡したり、十分なRAMがないディスクに書き込んだりできま それらはあるテーブルで貯えることができます `DataTypeAggregateFunction` データの増分集計を可能にする。 > 集計関数状態のシリアル化されたデータ形式は、現在バージョン管理されていません。 集約状態が一時的にのみ格納されていればokです。 しかし、我々は持っている `AggregatingMergeTree` テーブルエンジンが増えた場合の集約、人々に基づき使用されている。 これは、将来集計関数のシリアル化形式を変更するときに下位互換性が必要な理由です。 ## サーバ {#server} サーバを実装し複数の複数のインタフェース: - 外部クライアント用のHTTPインターフェイス。 - TCPインタフェースのネイティブClickHouseクライアントとクロス-サーバー通信中に分散クエリを実行します。 - インターフェース転送データレプリケーション. 内部的には、コルーチンやファイバーのない原始的なマルチスレッドサーバーです。 サーバーは、単純なクエリの割合が高いのではなく、比較的低い複雑なクエリの割合を処理するように設計されているため、それぞれが分析のために膨大 サーバーは初期化します `Context` クエリ実行に必要な環境を持つクラス:使用可能なデータベース、ユーザーとアクセス権、設定、クラスター、プロセスリスト、クエリログなどのリスト。 通訳者はこの環境を利用します。 古いクライアントは新しいサーバーと話すことができ、新しいクライアントは古いサーバーと話すことができます。 しかし、我々は永遠にそれを維持したくない、と我々は約一年後に古いバージョンのサポートを削除しています。 !!! note "注" ほとんどの外部アプリケーションでは、HTTPインターフェイスを使用することをお勧めします。 TCPプロトコルは、データのブロックを渡すために内部形式を使用し、圧縮されたデータにはカスタムフレーミングを使用します。 まだ公表したCライブラリのためのこのプロトコールすることが必要なことから、リンクのClickHouseコードベース、るのは現実的ではありません。 ## 分散クエリの実行 {#distributed-query-execution} サーバーにクラスターセットアップがほぼ独立しています。 を作成することができます `Distributed` クラスター内のサーバーの表。 その `Distributed` table does not store data itself – it only provides a “view” すべての地方のテーブルに複数のノードのクラスター Aから選択すると `Distributed` テーブルは、そのクエリを書き換え、負荷分散設定に従ってリモートノードを選択し、それらにクエリを送信します。 その `Distributed` テーブル要求をリモートサーバー処理クエリーだけで最大のペースでの中間結果から異なるサーバできます。 その後、中間結果を受信してマージします。 のテーブルを配布してできる限りの仕事へのリモートサーバーを送信しない多くの中間データのネットワーク. INまたはJOIN句にサブクエリがあり、それぞれが `Distributed` テーブル。 これらのクエリの実行には、さまざまな戦略があります。 分散クエリ実行用のグローバルクエリプランはありません。 各ノードには、ジョブの一部のローカルクエリプランがあります。 リモートノードに対してクエリを送信し、結果をマージします。 しかし、基数の多いグループBYsを持つ複雑なクエリや、結合のための大量の一時データを持つクエリでは、これは不可能です。 そのような場合には、 “reshuffle” 追加の調整が必要なサーバー間のデータ。 ClickHouseはそのようなクエリの実行をサポートしていません。 ## マージツリー {#merge-tree} `MergeTree` 主キ 主キーは、列または式の任意のタプルにすることができます。 Aのデータ `MergeTree` テーブルは “parts”. 各パートはデータを主キーの順序で格納するため、データは主キータプルによって辞書順に並べ替えられます。 すべてのテーブル列は別々に格納されます `column.bin` これらの部分のファイル。 ファイルは圧縮ブロックで構成されます。 各ブロックは、平均値のサイズに応じて、通常64KBから1MBの非圧縮データです。 ブロックは、列の値が連続して次々に配置されています。 列の値は各列で同じ順序になるため(主キーによって順序が定義されます)、多くの列で反復処理すると、対応する行の値が取得されます。 主キー自体は次のとおりです “sparse”. すべての行に対処するのではなく、いくつかの範囲のデータのみを扱います。 別の `primary.idx` fileには、N番目の行ごとに主キーの値があります。 `index_granularity` (通常、N=8192)。 また、各列について、我々は持っている `column.mrk` ファイル “marks,” これは、データファイル内のN番目の行ごとにオフセットされます。 各マークは、ファイル内の圧縮ブロックの先頭までのオフセットと、圧縮解除ブロック内のデータの先頭までのオフセットのペアです。 通常、圧縮されたブロックはマークで整列され、解凍されたブロックのオフセットはゼロです。 データのための `primary.idx` 常にメモリ内に存在し、 `column.mrk` ファイ 我々は一部から何かを読むつもりですときに `MergeTree` 私たちは `primary.idx` データと要求されたデータを含む可能性のある範囲を見つけて、次に `column.mrk` これらの範囲の読み取りを開始する場所のデータと計算オフセット。 希薄さのために、余分なデータが読み取られることがあります。 ClickHouseは、単純なポイントクエリの高負荷には適していません。 `index_granularity` キーごとに行を読み取り、圧縮されたブロック全体を列ごとに解凍する必要があります。 私たちは、インデックスの顕著なメモリ消費なしに単一のサーバーごとに数兆行を維持できる必要があるため、インデックスを疎にしました。 また、主キーは疎であるため、一意ではありません。 テーブルに同じキーを持つ多くの行を持つことができます。 あなたが `INSERT` データの束に `MergeTree`、その束は主キーの順序でソートされ、新しい部分を形成します。 が背景のスレッドを定期的に、選択部分と統合して単一のソート部の部品点数が比較的低い。 それが呼び出される理由です `MergeTree`. もちろん、マージは “write amplification”. すべてのパーツは不変であり、作成および削除のみが行われますが、変更は行われません。 SELECTが実行されると、テーブルのスナップショット(パーツのセット)が保持されます。 マージ後もしばらくの間、古いパーツを保持して、障害後の回復を容易にするため、マージされたパーツが壊れている可能性があることがわかったら、ソースパーツ `MergeTree` LSMツリーではありません。 “memtable” と “log”: inserted data is written directly to the filesystem. This makes it suitable only to INSERT data in batches, not by individual row and not very frequently – about once per second is ok, but a thousand times a second is not. We did it this way for simplicity's sake, and because we are already inserting data in batches in our applications. > MergeTreeテーブルには、一つの(プライマリ)インデックスしか持つことができません。 たとえば、複数の物理的順序でデータを格納したり、事前に集計されたデータと元のデータを含む表現を許可したりすることもできます。 バックグラウンドマージ中に追加の作業を行っているMergeTreeエンジンがあります。 例としては `CollapsingMergeTree` と `AggregatingMergeTree`. この処理として特別支援しました。 なぜなら、ユーザーは通常、バックグラウンドマージが実行される時間とデータを制御することができないからです。 `MergeTree` テーブルは、ほとんどの場合、完全にマージされた形式ではなく、複数の部分に格納されます。 ## 複製 {#replication} ClickHouseでのレプリケーションは、テーブルごとに構成できます。 きも複製されない一部の複製のテーブルと同じサーバです。 また、二要素複製のテーブルと三要素複製のテーブルなど、さまざまな方法でテーブルをレプリケートすることもできます。 レプリケーションは `ReplicatedMergeTree` ストレージエンジン のパス `ZooKeeper` ストレージエンジンのパラメータとして指定します。 同じパスを持つすべてのテーブル `ZooKeeper` 互いのレプリカになる:彼らはデータを同期し、一貫性を維持する。 レプリカは、テーブルを作成または削除するだけで動的に追加および削除できます。 複製を使用して非同期マルチマスタースキームです。 次のセッションを持つ任意のレプリカにデータを挿入できます `ZooKeeper` データを複製、その他すべてのレプリカは非同期的に. ClickHouseは更新をサポートしていないため、複製は競合しません。 挿入のクォーラム確認がないため、あるノードに障害が発生すると、挿入されただけのデータが失われる可能性があります。 複製のメタデータはZooKeeperに格納されます。 実行するアクションを示すレプリケーションログがあります。 アクションは次のとおりです。 各レプリカコピー、複製のログをキューにその行動からのキューに挿入します 例えば、挿入では、 “get the part” actionを作成し、ログイン、レプリカのダウンロードいます。 マージはレプリカ間で調整され、バイトが同一の結果が得られます。 すべての部品を合併した場合と同様にすべてのレプリカ. では達成を補い、一つのレプリカのリーダーとして、レプリカを始めと融合し、書き込みます “merge parts” ログへのアクション。 圧縮された部分のみがノード間で転送され、クエリは転送されません。 合併処理され、各レプリカ多くの場合、自主的に下げるネットワークコストを回避することによるネットワークが増幅。 大合併をさらにネットワークする場合に限り重複製に遅れて波及してきています。 さらに、各レプリカは、その状態をパーツとそのチェックサムのセットとしてZooKeeperに格納します。 ローカルファイルシステム上の状態がZooKeeperの参照状態から乖離すると、レプリカは他のレプリカから欠落している部分と壊れている部分をダウンロード ローカルファイルシステムに予期しないデータや壊れたデータがある場合、ClickHouseはそれを削除しませんが、別のディレクトリに移動して忘れます。 !!! note "注" ClickHouseクラスターは独立したシャードで構成され、各シャードはレプリカで構成されます。 クラスターは **弾性ではない** したがって、新しいシャードを追加した後、データはシャード間で自動的に再調整されません。 代わりに、クラスタ負荷は不均一に調整されることになっています。 この実装はより多くの制御を提供し、数十のノードなどの比較的小さなクラスタでもokです。 しかし、運用環境で使用している数百のノードを持つクラスターでは、このアプローチは重大な欠点になります。 を実行すべきである"と述べていテーブルエンジンで広がる、クラスターを動的に再現れる可能性がある地域分割のバランスとクラスターの動します。 {## [元の記事](https://clickhouse.com/docs/en/development/architecture/) ##}