web-dev-qa-db-ja.com

マテリアライズドビューが常に最新であることを確認するにはどうすればよいですか?

関係するテーブルを変更するたびにREFRESH MATERIALIZED VIEWを呼び出す必要がありますよね?これについては、ウェブ上であまり議論されていないことに驚きました。

これを行うにはどうすればよいですか?

ここでの答えの上半分は私が探しているものだと思います: https://stackoverflow.com/a/23963969/16814

これには危険がありますか?ビューの更新が失敗した場合、呼び出しの更新、挿入などのトランザクションはロールバックされますか? (これは私が欲しいものです...私は思う)

41
John Bachir

関係するテーブルを変更するたびにREFRESH MATERIALIZED VIEWを呼び出す必要がありますか?

はい、PostgreSQL自体は決して自動的に呼び出しません。何らかの方法で呼び出す必要があります。

これを行うにはどうすればよいですか?

これを実現する多くの方法。例を示す前に、 REFRESH MATERIALIZED VIEW command はAccessExclusiveモードでビューをブロックするため、作業中はSELECTも実行できないことに注意してください。表。

ただし、バージョン9.4以降を使用している場合は、CONCURRENTLYオプションを指定できます。

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;

これはExclusiveLockを取得し、SELECTクエリをブロックしませんが、オーバーヘッドが大きくなる可能性があります(変更されたデータの量に依存します。それでも、2つのREFRESHコマンドを同時に実行することはできません。

手動で更新する

これは考慮すべきオプションです。特に、データの読み込みまたはバッチ更新の場合(たとえば、大量の情報/データのみを長時間読み込むシステム)、データを変更または処理するための操作を最後に行うのが一般的であるため、_ [最後に$ var] _操作。

REFRESH操作のスケジュール

最初に広く使用されているオプションは、何らかのスケジューリングシステムを使用して更新を呼び出すことです。たとえば、cronジョブで同様の設定を行うことができます。

*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"

そして、マテリアライズドビューは30分ごとに更新されます。

考慮事項

このオプションは、特にREFRESHオプションを使用した場合に非常に優れていますが、常に100%最新ではないデータを受け入れることができる場合のみです。 CONCURRENTLYの有無にかかわらず、CONCURRENTLYコマンドはクエリ全体を実行する必要があるため、内部クエリの実行に必要な時間を考慮する必要があることに留意してください。 REFRESHをスケジュールします。

トリガーでリフレッシュする

別のオプションは、次のように、トリガー関数でREFRESH MATERIALIZED VIEWを呼び出すことです。

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
    RETURN NULL;
END;
$$;

次に、ビューの変更を含むテーブルで、次のことを行います。

CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();

考慮事項

パフォーマンスと同時実行性にいくつかの重大な落とし穴があります。

  1. INSERT/UPDATE/DELETE操作では、クエリを実行する必要があります(MVを検討している場合は時間がかかる可能性があります)。
  2. REFRESHを使用しても、1つのCONCURRENTLYは別のREFRESHをブロックするため、関連するテーブルに対するINSERT/UPDATE/DELETEはシリアル化されます。

唯一の状況は、良いアイデアとして、変更が本当にまれかどうかだと思うことができます。

LISTEN/NOTIFYを使用して更新する

前のオプションの問題は、同期であり、各操作で大きなオーバーヘッドを課すことです。それを改善するために、以前のようなトリガーを使用できますが、それは NOTIFY operation のみを呼び出します:

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, 'my_mv';
    RETURN NULL;
END;
$$;

したがって、接続を維持し、 LISTEN operation を使用してREFRESHを呼び出す必要性を識別するアプリケーションを構築できます。これをテストするために使用できる素敵なプロジェクトの1つは pgsidekick です。このプロジェクトでは、シェルスクリプトを使用してLISTENを実行できるため、REFRESHを次のようにスケジュールできます。

pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"

または、pglaterpgsidekick内)を使用して、REFRESHを頻繁に呼び出さないようにします。たとえば、次のトリガーを使用してREFRESHにすることができますが、1分(60秒)以内です。

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, '60 REFRESH MATERIALIZED VIEW CONCURRENLTY my_mv';
    RETURN NULL;
END;
$$;

したがって、REFRESHは60秒以内に呼び出されません。また、NOTIFYが60秒以内に何度も呼び出されると、REFRESHは1回だけトリガーされます。

考慮事項

Cronオプションとして、これは少し古いデータでむき出しにできる場合にのみ有効ですが、これにはREFRESHが本当に必要な場合にのみ呼び出されるという利点があるため、オーバーヘッドが少なくなり、データは必要に応じてより近く更新されます。

OBS:私はまだコードと例を試していません。だから誰かが間違いを見つけたり、タイプミスをしたり、それを試して動作する(またはしない)場合は、お知らせください。

102
MatheusOl

MatheusOlによる前回の回答について3つのことを指摘しましょう-pglaterテクノロジーです。

  1. Long_options配列の最後の要素として、 https://linux.die.net/man/3/getopt_long 「配列の最後の要素はゼロで埋める必要があります。」というフレーズだから、それを読む必要があります-

    static struct option long_options[] =     {
          //......
          {"help", no_argument, NULL, '?'},
          {0, 0, 0, 0} 
    };
    
  2. Malloc/freeについて-free(forchar listen = malloc(...) ;) 不足している。とにかく、mallocが原因でpglaterプロセスがCentOSでクラッシュしました(ただし、Ubuntuではそうではありません-理由はわかりません)。したがって、char配列を使用し、charポインターに配列名を割り当てることをお勧めします(charとchar **の両方に)。多くの場合、それを行う際に型変換を強制する必要があります(ポインターの割り当て)。

    char block4[100];
    ...
    password_Prompt = block4;
    ...
    char block1[500];
    const char **keywords = (const char **)&block1;
    ...
    char block3[300];
    char *listen = block3;
    sprintf(listen, "listen %s", id);
    PQfreemem(id);
    res = PQexec(db, listen);
    
  3. 次の表を使用して、mdがmature_durationであるタイムアウトを計算します。これは、最新のrefresh(lr)の時点と現在の時刻との時間差です。

    md> = callback_delay(cd)==>タイムアウトの場合:0

    when md + PING_INTERVAL> = cd ==> timeout:cd-md [= cd-(now-lr)]

    md + PING_INTERVAL <cd ==>タイムアウトの場合:PI

このアルゴリズムを実装するには(3番目のポイント)、次のように「lr」を初期化する必要があります-

res = PQexec(db, command);
latest_refresh = time(0);
if (PQresultStatus(res) == PGRES_COMMAND_OK) {
1
Park JongBum