こんにちは、Habr!
この記事では、かなりよく知られている列データベースKDBに保存するアイデアと、このデータにアクセスする方法の例を説明します。 この基地は2001年から存在しており、現在、同様のシステムを比較してサイトの高い場所を占めています(たとえば、 こちらを参照 )
なんで?
保管時系列
過去20年間の為替レートの1秒ごとの変動がある場合、リレーショナルデータベースは、蓄積および処理するための最速かつ最も効果的なソリューションではありません(つまり、200通貨で120 * 10 ^ 9行を少し超える)。 この場合、高速な列データベースを使用するのが最も論理的です。つまり、KDBが役立ちます。
同様に、上記の例のように番号を保存せず、シリアル化されたオブジェクトの場合。 この場合、各行のサイズが大きいという複雑さが、多数の行を格納するタスクに追加されます。
計算
大量のデータを取得した後、タスクは多くの場合、このデータの分析に関与し始めます-それらの間の相関関係を見つけ、集計などを作成します。ネットワーク上でデータを駆動しないように、できるだけデータの近くで(理想的にはデータベース自体で)実行されます。
生産準備完了
直接的な技術的な問題をすべて解決すると、次のようになります。
- バックアップ
- レプリケーション(+サービスのアクティブ/アクティブ作業)
- シャーディング
- コアテクノロジーとプログラミング言語(Java、.Net、Rなど)のサポート
1台のサーバーでどのように機能しますか?
KDBの物理データは最小限のオーバーヘッドで保存されます。 したがって、整数を含む列は、ディスク上の単一のファイルに格納される整数のシーケンスにすぎません。
物理ストレージ
前述のように、KDBは列データベースです。 各列は個別に保存されます。 実際には、列は単なる別のファイルであり、それ以上のものではありません。 つまり、列a、b、c、およびdを含むテーブルtは、ディスク上の「t」フォルダーを表します。このフォルダーには、a、b、c、dの4つのファイルがあります。 さらに、小さなメタデータファイル。 テーブルをコピーする必要がある場合は、ファイルをコピーするだけで済みます(そしてそれらにメタデータを生成させる)。 データの一部を新しいサーバーに転送する必要がある場合は、ファイルをコピーするだけです。
読者なら誰でも知っているように、数百万のオブジェクトを単一のファイルに保存することは非常に非効率的です。 この場合、再ソートのタスクでさえ、すでに困難で高価になります(結局のところ、すべてをメモリに入れることはできません-それがたくさんあります)。 ここからKDBで(すべての適切な列データベースのように)テーブル全体が最初にパーティションに分割されます 。 ドキュメントを参照してください。 セクションはデータベース全体に割り当てられ、ほとんどの場合は単なる日付です。
後者はすでにファイル構造を少し複雑にします。 2つのテーブル(t1とt2)があり、それらに日付列がある場合(データをフォルダに分割します)、ディスクは次の構造になります。
\ 2017.01.01 \ t1 \ t2 \ 2017.01.02 \ t1 \ t2
つまり、日付のあるフォルダーにはテーブルのあるフォルダーがあり、列のあるファイルが含まれています。
データは常に変更または削除の可能性なしにディスクに保存されます。 追加できるのはデータのみです。 すなわち 日付dのデータを更新または破棄する必要がある場合は、それらをすべてメモリに入れ( select from t where date = d
)、必要なすべての操作を行い、日付d1に保存してから、ディスク上のフォルダーの名前を変更します。
ディスク上のファイルを分割する方法を学習した後、圧縮(gzipやgoogle snappyなど )を使用してストレージをさらに最適化できます。 有効な列ベースは、ファイルシステムによって圧縮する(つまり、非圧縮データをRAMキャッシュに保存する)か、データをまったく圧縮しない(およびIOを増やす)か、アプリケーション層に既にあるデータを圧縮する(そして失う)ため、独自にこれを実行できる必要があります隣接する行を圧縮する機能)。
KDBは、効率的なデータストレージに加えて、データをメモリにすばやく読み込む機能を提供します。 これを行うには、テーブルを順序付ける必要があります。つまり、次のいずれかを選択する必要があります。
- 各セクションのデータはそのまま保持されます。 つまり、日付(パーティション)列と、b、c、dを含むテーブルtがある場合、クエリ
select v from t where date=2017.01.01 and k=12
を実行するにはselect v from t where date=2017.01.01 and k=12
すべてのデータをロードする必要があります特定の日付の列kおよびv。 または、リレーショナルデータベースの言語と言えば、インデックススキャンを実行する必要があります。 - 列の1つがソートされます。 上記の例を続けて、列kでデータを並べ替えると、
select v from t where date=2017.01.01 and k=12
のselect v from t where date=2017.01.01 and k=12
がはるかに高速に動作しselect v from t where date=2017.01.01 and k=12
はデータの一部のみをメモリにロードし、対数で検索します。 重要なこと-この属性から、テーブルはディスク上で成長しません。 追加のデータは必要ありません。 - 列の1つは一意です。 この場合、KDBは値のハッシュテーブルを追加で作成します。これにより
select v from t where date=2017.01.01 and k=12
インデックスシークが可能になります。 明らかに、この場合、ハッシュテーブルは近くに保存され、貴重なスペースを占有します。 - いくつかの列がグループ化されています。 本質的に、これはリレーショナルデータベースのプライマリキーインデックスとほぼ同じです。 このようなテーブルでは、同じ列値のタプルが一緒に格納され、さらにハッシュテーブルが個別に格納されます。これにより、目的の値にすぐにアクセスできます。 つまり
select v from t where date=2017.01.01 and k=12
場合、フォームselect v from t where date=2017.01.01 and k=12
クエリでは、インデックスシークが発生し、KDBは即座にディスク上の目的の値にジャンプします。 ただし、フォームのクエリは、select v from t where date=2017.01.01 and k<12 and k > 10
。ハッシュテーブルはデータを並べ替えないため、インデックススキャンを実行します。 ただし、追加のテーブルとソートされた列を使用すると、問題は簡単に解決されます。
RDBおよびHDB
注意深い読者は、上記の2つのステートメントを組み合わせるのが多少難しいことに気付くでしょう。KDBのデータはソートして保存 でき、ディスク上のテーブルの中央に挿入することはできません 。 末尾にのみ追加できます。 これら2つのステートメントを結合する(そしてパフォーマンスを失わない)ために、KDBは次のアプローチを使用します。
- すべての履歴データはHDB(履歴DB)に保存されます。 それらはディスク上に簡潔かつ整然と格納されており、すばやくメモリに読み込んで分析できます。
- 最終日のすべてのデータはRDB(リアルタイムDB)に格納されます 。そのタスクは、アプリケーションから可能な限り迅速にデータを収集することです。 この場合、数値はRAMに保存できます(20年の最後の日が多くのスペースを占めることはほとんどありません)。これにより、数値が並べ替えられていなくても、すばやくアクセスできます。 データストリームが十分に大きい場合、ディスクにフラッシュされるときに、RAMから自然に数値を削除できます。
完全に表面的な場合、RDBアルゴリズムは次のようになります。
- アプリケーションからデータを取得します
- N秒/分ごとに1回-データをディスクにリセットし、リセットを転送するユーザー定義関数を呼び出します。 彼女:
2.1。 または、到着したばかりのデータをメモリ内のオブジェクトに追加します(フィルタリング、集計など)
2.2。 何もしません(結局のところ、履歴を分析するために常に現在の日が必要というわけではありません) - 一日の終わりに-RDBに蓄積されたすべてのデータを取り除き、それらをソート/グループ化し、HDBにダンプします
Q
KDBについて言えば、KDBのすべてのクエリ(およびすべての機能)が構築されているQ言語に言及することは間違いありません。 選択関数が多かれ少なかれ明確な場合(上記の例を参照してくださいselect v from t where date=2017.01.01 and k=12
ください)、残りのものはもう少し珍しく見えます。
Qのアイデアは、才能の姉妹であることわざの簡潔さに関連付けることができます。
したがって、新しい変数を作成します。
tv: select v from t where date=2017.01.01 and k=12;
リクエストを簡素化する-条件をリストする必要はありません:
tv: select v from t where date=2017.01.01,k=12;
グループ化と集約を追加します。
tv: select count by v from t where date=2017.01.01,k=12;
列の名前を変更します。
tv: select c: count by v from t where date=2017.01.01,k=12;
最初のリクエストに戻る
tv: select v from t where date=2017.01.01,k=12;
そして、列の名前を変更します
tv: select v from t where date=2017.01.01,k=12; tv: `v1 xcol tv;
列をソートします。
tv: select v from t where date=2017.01.01,k=12; tv: `v1 xcol tv; tv: `v1 xasc tv;
または、より便利な方法として、リクエストをより馴染みのある1行にまとめます。
tv: `v1 xasc `v1 xcol select v from t where date=2017.01.01,k=12;
リクエストを関数でラップします(式の先頭の「:」記号は、上記の例のように、割り当てではなく戻りを意味します)。
f: {[] tv: `v1 xasc `v1 xcol select v from t where date=2017.01.01,k=12; :tv; }
パラメーターを追加します。
f: {[i_d; i_k] tv: `v1 xasc `v1 xcol select v from t where date=i_d,k=i_k; :tv; }
そして、関数を呼び出します(最後に ";"を記述しません-これは有用な副作用としてコンソールへの出力を与えます):
f: {[i_d; i_k] tv: `v1 xasc `v1 xcol select v from t where date=i_d,k=i_k; :tv; }; f[2017.01.01; 12]
辞書に引数を渡すので、将来、他の関数から引数を転送する方が便利になります(明示的な戻り値なし、つまり「:」なしで、最後の式の結果はラムダの結果と見なされます)。
f: {[d] i_d: d[`date]; i_k: d[`key]; `v1 xasc `v1 xcol select v from t where date=i_d,k=i_k; }; f[(`date`key)!(2017.01.01;12)]
最後の例では、一度にいくつかのことを行いました。
- 辞書は式
(`date`key)!(2017.01.01;12)
を使用して発表されました(`date`key)!(2017.01.01;12)
- 辞書を関数に渡しました
-
i_d: d[`date]
辞書から変数を読み取りますi_d: d[`date]
;
次に-データがない場合のスローエラーを追加します。
f: {[d] i_d: d[`date]; i_k: d[`key]; r: `v1 xasc `v1 xcol select v from t where date=i_d,k=i_k; $[0 = count r;'`no_data;:r]; }; f[(`date`key)!(2017.01.01;12)]
そのため、リクエストに応じてテーブルにデータが存在しない場合、関数は「no_data」という単語を含む例外をスローします。
構造$[1=0;`true;`false]
は、条件が最初に進む条件付き遷移であり、条件がtrueの場合に実行される式です。 最後にelseブロックがあります 。 ただし、実際には、これはパターンマッチングよりも可能性が高く、次の構成でも許容されます。 $[a=0;`0; a=1;`2; `unknown]
$[a=0;`0; a=1;`2; `unknown]
$[a=0;`0; a=1;`2; `unknown]
。 つまり、すべての奇数のポジション(最後のポジションを除く)には条件があり、すべての偶数のポジションに-満たされる必要があるものがあります。 そして最後に-elseブロック。
例からわかるように、言語は論理的です(簡潔ではありますが)。 Qには:
- ラムダス
- 条件付きジャンプ
- サイクル
- テーブルを結合するための特別な指示(複雑な結合、ピボットテーブルを含む)
- モジュールを追加する機能(たとえば、GPU分析で同時にカウントする)
そして結論として
- 大量のデータを扱う場合-KDBが役立ちます
- 時系列分析タスクがある場合-KDBがお手伝いします
- 大量のデータストリームをすばやく記録(およびその後の分析)するタスクがある場合-KDBが役立ちます