web-dev-qa-db-ja.com

時系列データを保存する方法

時系列データセット(間違っている場合は修正してください)と思われる値が関連付けられていると思います。

例としては、車をモデル化し、旅行中にそのさまざまな属性を追跡します。例えば:

タイムスタンプ|スピード|移動距離|温度|等

このデータを保存してWebアプリケーションがフィールドを効率的にクエリして最大値、最小値を見つけ、各データセットを経時的にプロットできるようにするための最良の方法は何でしょうか?

データダンプを解析して結果をキャッシュし、保存する必要がないという素朴なアプローチを始めました。しかし、少し試してみたところ、このソリューションはメモリの制約のために長期間拡張できないようであり、キャッシュをクリアする場合、すべてのデータを再解析して再キャッシュする必要があります。

また、データが毎秒追跡され、10時間以上のデータセットが発生する可能性はまれであると仮定すると、N秒ごとにサンプリングしてデータセットを切り捨てることをお勧めしますか?

22
guest82

時系列データを保存するための「最善の方法」は実際にはありません。正直なところ、それはいくつかの要因に依存します。ただし、主に次の2つの要素に焦点を当てます。

(1)このプロジェクトは、スキーマを最適化するための努力に値するほど深刻ですか?

(2)クエリのアクセスパターンはどのようなものですか本当にはどのようになりますか?

これらの質問を念頭に置いて、いくつかのスキーマオプションについて説明します。

フラットテーブル

フラットテーブルを使用するオプションには、質問(1)との関係があり、これが深刻なプロジェクトまたは大規模プロジェクトでない場合、スキーマについてあまり考えないほうがはるかに簡単で、次のようにフラットテーブルを使用するだけです。

_CREATE flat_table(
  trip_id integer,
  tstamp timestamptz,
  speed float,
  distance float,
  temperature float,
  ,...);
_

これがあなたの時間の多くを保証しない小さなプロジェクトである場合にのみ、私がこのコースを勧める多くのケースはありません。

寸法と事実

したがって、問題のハードルをクリアし(1)、より高いパフォーマンススキーマが必要な場合、これは最初に検討するオプションの1つです。 。これにはいくつかの基本的な標準化が含まれますが、測定された「事実」の量から「次元」の量が抽出されます。

基本的に、旅行に関する情報を記録するテーブルが必要です。

_CREATE trips(
  trip_id integer,
  other_info text);
_

タイムスタンプを記録するテーブル、

_CREATE tstamps(
  tstamp_id integer,
  tstamp timestamptz);
_

最後に、ディメンションテーブルへの外部キー参照(つまり、meas_facts(trip_id)参照trips(trip_id)meas_facts(tstamp_id)参照tstamps(tstamp_id)を使用して、測定されたすべてのファクト)

_CREATE meas_facts(
  trip_id integer,
  tstamp_id integer,
  speed float,
  distance float,
  temperature float,
  ,...);
_

これは最初はそれほど役に立たないように思えるかもしれませんが、たとえば数千の同時トリップがある場合、それらはすべて、毎秒1秒に1回、測定を行っている可能性があります。その場合、tstampsテーブルの単一のエントリを使用するだけでなく、旅行ごとにタイムスタンプを再記録する必要があります。

使用例:このケースは、データを記録している同時トリップが多数あり、すべての測定値にアクセスしても構わない場合に適していますすべて一緒にタイプします。

Postgresは行ごとに読み取るため、たとえば、指定された時間範囲でのspeedの測定が必要な場合は常に、_meas_facts_テーブルから行全体を読み取る必要があります。これにより、クエリが確実に遅くなりますただし、操作しているデータセットが大きすぎない場合は、違いに気付くこともありません。

測定した事実を分割する

最後のセクションをもう少し拡張するには、測定を個別のテーブルに分割します。たとえば、速度と距離のテーブルを示します。

_CREATE speed_facts(
  trip_id integer,
  tstamp_id integer,
  speed float);
_

そして

_CREATE distance_facts(
  trip_id integer,
  tstamp_id integer,
  distance float);
_

もちろん、これが他の測定にどのように拡張されるかを確認できます。

使用例:したがって、クエリの速度が大幅に向上することはありません。1つの測定値についてクエリを実行すると、線形速度が向上するだけかもしれません。タイプ。これは、速度に関する情報を検索する場合、_speed_facts_の行に存在する余分な不要な情報すべてではなく、_meas_facts_テーブルから行を読み取るだけでよいためです。テーブル。

したがって、1つの測定タイプのみに関する大量のデータを読み取る必要がある場合、いくつかの利点が得られます。 1秒間隔で10時間のデータを使用するという提案されたケースでは、36,000行しか読み取らないため、これを実行しても大きなメリットはありません。ただし、すべてが約10時間である5,000のトリップの速度測定データを表示する場合は、1億8000万行を読み取ることになります。一度に1つまたは2つの測定タイプのみにアクセスする必要がある限り、このようなクエリの線形速度の向上は、いくつかの利点をもたらす可能性があります。

配列/ HStore /&TOAST

あなたはおそらくこの部分について心配する必要はありませんが、私はそれが重要である場合を知っています。 [〜#〜] huge [〜#〜]時系列の量にアクセスする必要がある場合データ、および1つの巨大なブロックでそのすべてにアクセスする必要があることがわかっている場合、 TOAST Tables を使用する構造を使用できます。これは、本質的に、より大きな圧縮されたセグメントにデータを格納します。これにより、すべてのデータにアクセスすることが目標である限り、データにすばやくアクセスできます。

実装例の1つは、

_CREATE uber_table(
  trip_id integer,
  tstart timestamptz,
  speed float[],
  distance float[],
  temperature float[],
  ,...);
_

このテーブルでは、tstartは配列の最初のエントリのタイムスタンプを格納し、後続の各エントリは次の1秒の読み取り値になります。これには、アプリケーションソフトウェアの各配列値に関連するタイムスタンプを管理する必要があります。

別の可能性は

_CREATE uber_table(
  trip_id integer,
  speed hstore,
  distance hstore,
  temperature hstore,
  ,...);
_

ここで、(タイムスタンプ、測定)の(キー、値)のペアとして測定値を追加します。

使用例:これはおそらくPostgreSQLに慣れている人に任せた方がいい実装であり、アクセスパターンを確認する必要がある場合のみバルクアクセスパターン。

結論は?

すごい、これは思ったよりずっと長くなりました。 :)

基本的に、いくつかのオプションがありますが、2番目または3番目の方がより一般的なケースに適合するため、2番目または3番目を使用することで、費用を最大化できます。

追記:最初の質問は、すべてのデータが収集された後、データを一括でロードすることを暗示していました。 PostgreSQLインスタンスにデータをストリーミングする場合は、データの取り込みとクエリのワークロードの両方を処理するために、さらにいくつかの作業を行う必要がありますが、それは別の時間に残します。 ;)

31
Chris

その2019とこの質問は更新された回答に値します。

  • アプローチが最良であるかどうかは、ベンチマークとテストに任せますが、ここにアプローチがあります。
  • timescaledbというデータベース拡張を使用します
  • これは標準のPostgreSQLにインストールされている拡張機能であり、時系列を適切に保存しているときに発生したいくつかの問題を適切に処理します

あなたの例をとると、最初にPostgreSQLで簡単なテーブルを作成します

ステップ1

CREATE TABLE IF NOT EXISTS trip (
    ts TIMESTAMPTZ NOT NULL PRIMARY KEY,
    speed REAL NOT NULL,
    distance REAL NOT NULL,
    temperature REAL NOT NULL
) 

ステップ2

  • これを、timescaledbの世界で hypertable と呼ばれるものに変換します。
  • 簡単な言葉で言えば、ある時間間隔の小さなテーブルに連続的に分割される大きなテーブルです。たとえば、各ミニテーブルがチャンクと呼ばれる日です。
  • このミニテーブルは、クエリを実行するときに明確ではありませんが、クエリに含めたり除外したりできます。

    SELECT create_hypertable( 'trip'、 'ts'、chunk_time_interval => interval '1 hour'、if_not_exists => TRUE);

  • 上記で行ったのは、トリップテーブルを取得し、列「ts」に基づいて1時間ごとにミニチャンクテーブルに分割します。 10:00から10:59のタイムスタンプを追加すると、それらは1つのチャンクに追加されますが、11:00が新しいチャンクに挿入され、これは無限に続きます。

  • データを無期限に保存したくない場合は、3か月以上経過したチャンクをDROPして、

    SELECT drop_chunks(interval '3 months'、 'trip');

  • 次のようなクエリを使用して、日付までに作成されたすべてのチャンクのリストを取得することもできます

    SELECT chunk_table、table_bytes、index_bytes、total_bytes FROM chunk_relation_size( 'trip');

  • これにより、日付までに作成されたすべてのミニテーブルのリストが表示され、このリストから必要な場合は、最後のミニテーブルでクエリを実行できます

  • チャンクを含める、除外する、または最後のN個のチャンクのみを操作するようにクエリを最適化できます。

1
PirateApp