Kdb

cdp







こんにちは、Habr!







この記事では、かなりよく知られている列データベースKDBに保存するアイデアと、このデータにアクセスする方法の例を説明します。 この基地は2001年から存在しており、現在、同様のシステムを比較してサイトの高い場所を占めています(たとえば、 こちらを参照







なんで?



保管時系列



過去20年間の為替レートの1秒ごとの変動がある場合、リレーショナルデータベースは、蓄積および処理するための最速かつ最も効果的なソリューションではありません(つまり、200通貨で120 * 10 ^ 9行を少し超える)。 この場合、高速な列データベースを使用するのが最も論理的です。つまり、KDBが役立ちます。







同様に、上記の例のように番号を保存せず、シリアル化されたオブジェクトの場合。 この場合、各行のサイズが大きいという複雑さが、多数の行を格納するタスクに追加されます。







計算



大量のデータを取得した後、タスクは多くの場合、このデータの分析に関与し始めます-それらの間の相関関係を見つけ、集計などを作成します。ネットワーク上でデータを駆動しないように、できるだけデータの近くで(理想的にはデータベース自体で)実行されます。







生産準備完了



直接的な技術的な問題をすべて解決すると、次のようになります。









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は、効率的なデータストレージに加えて、データをメモリにすばやく読み込む機能を提供します。 これを行うには、テーブルを順序付ける必要があります。つまり、次のいずれかを選択する必要があります。









RDBおよびHDB



注意深い読者は、上記の2つのステートメントを組み合わせるのが多少難しいことに気付くでしょう。KDBデータはソートして保存 でき、ディスク上のテーブルの中央に挿入することはできません末尾にのみ追加できます。 これら2つのステートメントを結合する(そしてパフォーマンスを失わない)ために、KDBは次のアプローチを使用します。









完全に表面的な場合、RDBアルゴリズムは次のようになります。







  1. アプリケーションからデータを取得します
  2. N秒/分ごとに1回-データをディスクにリセットし、リセットを転送するユーザー定義関数を呼び出します。 彼女:

    2.1。 または、到着したばかりのデータをメモリ内のオブジェクトに追加します(フィルタリング、集計など)

    2.2。 何もしません(結局のところ、履歴を分析するために常に現在の日が必要というわけではありません)
  3. 一日の終わりに-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)]
      
      





最後の例では、一度にいくつかのことを行いました。







  1. 辞書は式(`date`key)!(2017.01.01;12)



    を使用して発表されました(`date`key)!(2017.01.01;12)



  2. 辞書を関数に渡しました
  3. 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には:









そして結論として






All Articles