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


22

一連の関連する値を持つ時系列データセット(間違っている場合は修正してください)と思われるものがあります。

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

タイムスタンプ| スピード| 走行距離| 温度| 等

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

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

また、データが10時間以上のデータセットというまれな可能性で毎秒追跡されると仮定すると、N秒ごとにサンプリングしてデータセットを切り捨てることが一般的に推奨されますか?

回答:


31

時系列データを保存するための「最良の方法」は実際には存在せず、正直に多くの要因に依存しています。ただし、主に次の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億8,000万行を読み取ることになります。一度に1つまたは2つの測定タイプのみにアクセスする必要がある限り、このようなクエリの速度を直線的に向上させることで、いくつかの利点が得られます。

Arrays / HStore /およびTOAST

おそらく、この部分について心配する必要はありませんが、重要な場合は知っています。あなたがアクセスする必要がある場合は、巨大な時系列データの量を、そしてあなたが一つの巨大なブロックでそれのすべてにアクセスする必要が知っている、あなたはの使用になります構造を使用することができますTOASTテーブルの圧縮、本質的に大きいのあなたのデータを格納し、セグメント。これにより、すべてのデータにアクセスすることが目標である限り、データへの迅速なアクセスが可能になります。

1つの実装例は

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

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

別の可能性は

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

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

ユースケース:これはおそらく、PostgreSQLに慣れている人に任せた方がよい実装であり、アクセスパターンがバルクアクセスパターンである必要があると確信している場合に限ります。

結論は?

わあ、これは予想よりずっと長くなった、ごめんなさい。:)

基本的に、多くのオプションがありますが、2番目または3番目を使用することで、より一般的なケースに合うため、おそらく最大の価値が得られます。

PS:最初の質問は、すべてのデータが収集された後、データを一括読み込みすることを意味していました。PostgreSQLインスタンスにデータをストリーミングしている場合、データの取り込みとクエリのワークロードの両方を処理するために、さらに作業を行う必要がありますが、それについては後ほど説明します。;)


うわー、詳細な回答に感謝、クリス!オプション2または3の使用を検討します。
guest8215

頑張って!
クリス

できれば、この答えを1000回投票するでしょう。詳細な説明をありがとう。
kikocorreoso

1

その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の世界でハイパーテーブルと呼ばれるものに変えます。
  • 簡単に言えば、それは一定の時間間隔の小さなテーブルに連続的に分割される大きなテーブルです。たとえば、各ミニテーブルがチャンクと呼ばれる日
  • このミニテーブルは、クエリを実行すると明確ではありませんが、クエリに含めることも除外することもできます

    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か月を超える古いチャンクを削除することもできます

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

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

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

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

  • クエリを最適化して、チャンクを含めたり、除外したり、最後のN個のチャンクのみを操作したりできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.