1

実行中のプロセスに関する情報をログに記録するシステムがあります。実行中の各プロセスには、並行して実行される場合と実行されない場合がある一連のステップが含まれています。システムは、プロセスとそのステップに関する情報を 2 つの別々のテーブルに記録します。

CREATE TABLE pid (
  pid         integer,
  start_time  timestamp,
  end_time    timestamp,
  elapsed     bigint,
  aborted     integer,
  label       char(30)
);

CREATE TABLE pid_step (
  pid         integer,
  step        integer,
  start_time  timestamp,
  end_time    timestamp,
  elapsed     bigint,
  mem         bigint,
  ...
);

このpid_step表には、各ステップに関するリソース使用量の統計が多数含まれています。ここではmem、そのステップに割り当てられたメモリのバイト数を記録する列として簡略化しています。おそらく5秒間隔で、プロセスラベルごとにメモリ割り当てをサンプリングしたいので、それをプロットできます。次のような結果が必要です。

tick                    label  mem
----------------------- ------ -----------
2014-11-04 05:37:40.0   foo      328728576
2014-11-04 05:37:40.0   bar         248436
2014-11-04 05:37:40.0   baz        1056144
2014-11-04 05:37:45.0   foo     1158807552
2014-11-04 05:37:45.0   bar         632822
2014-11-04 05:37:45.0   baz         854398

ログには、5 秒間隔でのリソース使用量のサンプルではなく、各プロセスとステップの開始と終了のタイムスタンプしか表示されないため、5 秒間隔ごとに実行されていたプロセス ステップを特定する最も効率的な方法を見つける必要があります (目盛り)。次に、割り当てられたメモリを集約します。私は 3 つの別々の試行を行いましたが、パフォーマンスのレベルは異なりますが、すべて同じ結果が得られました。簡潔にするために、各クエリとその説明プランを要点 ( https://gist.github.com/anonymous/3b57f70015b0d234a2de ) に入れますが、それぞれのアプローチについて説明します。

  1. これは私の最初の試みであり、間違いなく最も直感的で保守が簡単です。個別のプロセス ラベルを で交差結合して、ラベルgenerate_seriesごとに 5 秒の目盛りを生成し、pidおよびpid_stepテーブルで左結合します。左結合は「ゼロ フィル」効果を作成し、関連付けられたデータを持たないティックを削除しないようにします。残念ながら、このアプローチは最悪のパフォーマンスを示します (以下のベンチマーク リンクを参照)。これは、between t2.start_time and t2.end_time述語が結合条件ではなく結合フィルターとして処理されるハッシュ結合の使用によるものだと思います。

  2. これは私の 2 回目の試みであり、パフォーマンスははるかに優れていますが、直感的で保守しにくいものです。「ゼロ フィル」アプローチは、クエリ 1 と同じです。ただし、 と の左結合を実行する前pidpid_step、最大プロセス経過時間とプロセス ステップの開始時間と終了時間に基づいて、関連付けられたデータを持つティックを事前に計算します。 . これにより、ティック述語とラベル述語の両方を結合条件として表現でき、結合フィルターを使用しないソート/マージ結合が可能になります。

  3. これは私の最後の試みであり、クエリ 2 とほぼ同じ直感性と保守性で最高のパフォーマンスを発揮します。ここでの最適化は、最大プロセス経過時間よりも小さいことが保証されている最大プロセス ステップ経過時間を使用することです。 CTE t3 の開始時にネストされた小さなループ。

理想的には、SQL をクエリ 1 と同じくらいシンプルで保守しやすいものにしたいのですが、クエリ 3 と同じように実行できます。パフォーマンスを向上させるインデックスやクエリ 1 のわずかな書き直しの方法でできることはありますか?

ベンチマーク結果: http://i.imgur.com/yZxdQlM.png

4

1 に答える 1

0

これは、 PostgreSQL範囲の力を使用したソリューションです(SQLFiddle

CREATE TABLE pid (
  pid         integer PRIMARY KEY,
  label       char(30)
);

CREATE TABLE pid_step (
  pid         integer,
  step        serial,
  start_time  timestamp,
  end_time    timestamp,
  mem         bigint,
  PRIMARY KEY (pid, step)
);

サンプリング方法は良い考えですが、私の意見では最適化です。これが私の解決策です:

ある日のデータをプロットしたいとしましょう。この日を、それぞれが 5 秒続くいくつかのタイム スライスに分割します。1 つのプロセスと 1 つのタイム スライスについて、この 5 秒間に実行されたすべてのステップの平均メモリを取得したいと考えています。したがって、5 秒ごとにサンプリングする (データ スパイクを隠すことができる) 代わりに、この 5 秒間の関連データの集計を表示しています。集計は、使用可能な PostgreSQL 集計関数であればどれでもかまいません。

最初のステップは、これらのタイム スライスを生成することです (範囲データ型を使用せずに既に行ったように)。

-- list of time ranges of 5 seconds interval
-- inclusive lower bound, exclusive upper bound
SELECT 
  tsrange(tick, tick + '5 seconds'::interval, '[)') as time_range
FROM generate_series(
  '2001-02-16 21:28:30'::timestamp, 
  '2001-02-16 22:28:30'::timestamp, 
  '5 seconds'::interval
) AS tick

下限は包括的で上限は排他的であるため、これらのスライスは互いに重なり合わないことに注意してください。

ここが難しい部分です。このデータの範囲列を削除および作成してstart_time、テーブル スキーマを変更したくありません。end_time幸いなことに、PostgreSQLでは式のインデックスを使用できます。

-- create index on range (inclusive on upper and lower) 
CREATE INDEX pid_step_tstzrange_index ON pid_step 
USING gist (tsrange(start_time, end_time, '()'));

このインデックスを使用すると、さまざまなPostgreSQL 範囲演算子をわずかな処理コストで使用できるようになりました。唯一の注意点は、このインデックスを使用するには、クエリでまったく同じ関数を使用する必要があることです。

すでにお察しのとおり、インデックスはタイム スライスとステップを結合するために使用されます。これは、ステップの「仮想」範囲がタイム スライスと重なる場合に結合する必要があるためです。

最後のクエリは次のとおりです。

WITH

time_range AS (
  -- list of time ranges of 5 seconds interval
  -- inclusive lower bound, exclusive upper bound
  SELECT 
    tsrange(tick, tick + '5 seconds'::interval, '[)') as time_range
  FROM generate_series(
    '2001-02-16 21:28:30'::timestamp, 
    '2001-02-16 22:28:30'::timestamp, 
    '5 seconds'::interval
  ) AS tick
),

-- associate each pid_step with the matching time_range
-- aggregate the average memory usage for each pid for each time slice
avg_memory_by_pid_by_time_range AS (
  SELECT 
    time_range,
    pid,
    avg(mem) avg_memory
  FROM 
    time_range
    JOIN pid_step 
      ON tsrange(pid_step.start_time, pid_step.end_time, '()') && time_range.time_range
  GROUP BY
    time_range,
    pid
)

-- embellish the result with some additional data from pid
SELECT 
  lower(time_range) AS tick,
  pid.label AS label,
  trunc(avg_memory) AS mem
FROM
  avg_memory_by_pid_by_time_range
  JOIN pid ON avg_memory_by_pid_by_time_range.pid = pid.pid
ORDER BY
  lower(time_range),
  pid.label
;

本番データでのパフォーマンスが引き続き優れていることを願っています (クエリ計画の方程式には多くの詳細が関係しています)。

于 2014-11-27T01:35:46.863 に答える