web-dev-qa-db-ja.com

plpgsqlストアドプロシージャはクエリを監視して強制終了できますか?

特定のクエリがブロッカーになることを監視する関数を作成し(つまり、クエリによってブロックされ、別のクエリをブロックしている)、それを終了したいと思います。これが私の現在のコードです。クエリをブロックする必要がないように、テスト目的でleft joinを使用するように修正しました。

CREATE OR REPLACE FUNCTION monitor_sql_and_terminate_blocker(IN p_query text, OUT result text)
  RETURNS text AS
$BODY$
DECLARE
  monitor_sql text;
  rec record;
BEGIN

  monitor_sql = '
with blocker as
( 
  select distinct
    waiting.pid  pid
  , other.pid    blocker
  from pg_stat_activity
  join pg_catalog.pg_locks  waiting
    on waiting.pid = pg_stat_activity.procpid 
    and not waiting.granted
  join pg_catalog.pg_locks  other
    on ( ( other."database" = waiting."database" 
           and other.relation = waiting.relation ) 
         or other.transactionid = waiting.transactionid ) 
    and other.pid <> waiting.pid
  where current_query not like ''%<IDLE>%'' 
)
, blockers as
(
  select pid, array_to_string(array_agg(blocker),'','') blocker_list
  from blocker
  group by pid
)
, blocking as
(
  select blocker, array_to_string(array_agg(pid),'','') blocking_list
  from blocker
  group by blocker
)
select
    procpid
from pg_stat_activity
left join blockers on blockers.pid = pg_stat_activity.procpid
left join blocking on blocking.blocker = pg_stat_activity.procpid
where current_query = ''' || p_query || '''
';

LOOP
  FOR rec IN EXECUTE monitor_sql
  LOOP
    RAISE NOTICE 'Terminating procpid %', rec.procpid;
    PERFORM pg_terminate_backend(rec.procpid);
  END LOOP;
  PERFORM pg_sleep(1);
END LOOP;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

これは次のように呼び出されます。

select monitor_sql_and_terminate_blocker('select * from very_large_table')

ただし、それは無限にループするだけで、何もしません。クエリを手動で実行すると、プロセスが検出されてprocpidが返され、手動で終了できます。

これはトランザクション分離のためであり、関数は開始時に実行されていたクエリのみを参照します。クエリの実行中に監視機能を実行すると、クエリは正常に強制終了され、その後何度も強制終了を試み続けます。それを回避するために何ができますか?

私の現在の解決策は、ループをpsqlを実行するシェルスクリプトに移動して、一度実行してから終了するこのコードのバージョンを呼び出すことです。

1
PhilHibbs

[元々、質問はPostgreSQLでタグ付けされていました。 Greenplumについてはわかりませんが、誰かがそのシステムに答えてくれることを願っています。残りはPostgreSQLにのみ適用されます(8.3以降、Greenplumは8.2からフォークされました)。]

ドキュメント に関連するスニペットがあります:

もう1つの重要な点は、サーバープロセスがこれらの統計のいずれかを表示するように求められると、最初にコレクタプロセスによって発行された最新のレポートをフェッチし、次に現在のトランザクションが終了するまで、すべての統計ビューと関数にこのスナップショットを使用し続けることです。 。

これは、一度クエリを実行したトランザクションの出力が凍結されることを意味しますpg_stat_activity。幸いなことに、数文後、これは言われています:

または、pg_stat_clear_snapshot()を呼び出すこともできます。これにより、現在のトランザクションの統計スナップショット(存在する場合)が破棄されます。統計情報を次に使用すると、新しいスナップショットがフェッチされます。

他のセッションでさまざまなステートメントを発行しながら、以下の最小限の例で試してみました。

DO $$ 
DECLARE i text; 
BEGIN 
    LOOP 
        PERFORM pg_stat_clear_snapshot(); -- makes the output up-to-date
        PERFORM pg_sleep(1);  

        FOR i IN SELECT query FROM pg_stat_activity 
        LOOP 
            RAISE WARNING '%', i; 
        END LOOP; 
    END LOOP; 
END;$$;

新しく発行されたクエリや新しいセッションも検出されるので、これが必要だと思います。

3
dezso