PostgreSQLデータベースが最後に変更された日時を特定する


10

私はバックアップがどのように行われるかを変更することを検討しており、postgreqlクラスター内のどのデータベースが最近変更されていないかを判断する方法があるかどうか疑問に思っていますか?

pg_dumpallを使用する代わりに、pg_dumpを使用して、最後のバックアップ以降に変更されたデータベースのみをダンプします(一部のデータベースは頻繁に更新されません)。変更がない場合、現在のバックアップは、まだ良い。

特定のデータベースが最後に更新/変更された時期を判別する方法を知っている人はいますか?

ありがとう...

更新:

1つの特定のクラスターでのデータベースの作成(データベース内のdbオブジェクトの作成はもちろん)を制御できないため、場所全体にトリガーを記述する必要がないことを望んでいました。

さらに掘り下げてみると、$ PGDATA / global / pg_databaseファイルの内容(特に2番目のフィールド)と$ PGDATA / baseの下のディレクトリ名の間に相関関係があるようです。

実際、pg_databaseファイルの2番目のフィールドはデータベースoidであり、各データベースには$ PGDATA / baseの下に独自のサブディレクトリがある(サブディレクトリ名はoidである)と思います。あれは正しいですか?もしそうなら、バックアップを必要とするトリガーとして$ PGDATA / base / *の下のファイルからのファイルのタイムスタンプを使用することは理にかなっていますか?

...またはより良い方法はありますか?

再度、感謝します...



現在のバックアップが適切であると思い込まないでください。常に定期的に新しいバックアップを取りたいと考えています。
mrdenny、2011

Sonu Singh-データベースの追加を制御することはできません。このクラスターへのテーブルはもちろんのこと、トリガーは機能しません-(私の知る限り)トリガーはddlの変更をキャッチしません。mrdenny♦-正解です。ただし、定期的な完全バックアップの間に冗長な増分バックアップを生成しないようにしたいと思います。

回答:


9

select datname, xact_commit from pg_stat_database;@Jack Douglasの提案どおりに使用しても動作しない(どうやらautovacuumが原因)が、select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_database動作しているように見えます。DMLとDDLの両方の変更により、tup_ *列の値は変更されますが、aは変更されvacuumません(vacuum analyze一方で...)。

これが他のユーザーに役立つ可能性がある場合は、私が配置したバックアップスクリプトを含めます。これはPg 8.4.xでは機能しますが、使用されているPgのバージョンによっては8.2.xでは機能しません-YMMV。

#!/usr/bin/env perl
=head1 Synopsis

pg_backup -- selectively backup a postgresql database cluster

=head1 Description

Perform backups (pg_dump*) of postgresql databases in a cluster on an
as needed basis.

For some database clusters, there may be databases that are:

 a. rarely updated/changed and therefore shouldn't require dumping as 
    often as those databases that are frequently changed/updated.

 b. are large enough that dumping them without need is undesirable.

The global data is always dumped without regard to whether any 
individual databses need backing up or not.

=head1 Usage

pg_backup [OPTION]...

General options:

  -F, --format=c|t|p    output file format for data dumps 
                          (custom, tar, plain text) (default is custom)
  -a, --all             backup (pg_dump) all databases in the cluster 
                          (default is to only pg_dump databases that have
                          changed since the last backup)
  --backup-dir          directory to place backup files in 
                          (default is ./backups)
  -v, --verbose         verbose mode
  --help                show this help, then exit

Connection options:

  -h, --host=HOSTNAME   database server host or socket directory
  -p, --port=PORT       database server port number
  -U, --username=NAME   connect as specified database user
  -d, --database=NAME   connect to database name for global data

=head1 Notes

This utility has been developed against PostgreSQL version 8.4.x. Older 
versions of PostgreSQL may not work.

`vacuum` does not appear to trigger a backup unless there is actually 
something to vacuum whereas `vacuum analyze` appears to always trigger a 
backup.

=head1 Copyright and License

Copyright (C) 2011 by Gregory Siems

This library is free software; you can redistribute it and/or modify it 
under the same terms as PostgreSQL itself, either PostgreSQL version 
8.4 or, at your option, any later version of PostgreSQL you may have 
available.

=cut

use strict;
use warnings;
use Getopt::Long;
use Data::Dumper;
use POSIX qw(strftime);

my %opts = get_options();

my $connect_options = '';
$connect_options .= "--$_=$opts{$_} " for (qw(username host port));

my $shared_dump_args = ($opts{verbose})
    ? $connect_options . ' --verbose '
    : $connect_options;

my $backup_prefix = (exists $opts{host} && $opts{host} ne 'localhost')
    ? $opts{backup_dir} . '/' . $opts{host} . '-'
    : $opts{backup_dir} . '/';

do_main();


########################################################################
sub do_main {
    backup_globals();

    my $last_stats_file = $backup_prefix . 'last_stats';

    # get the previous pg_stat_database data
    my %last_stats;
    if ( -f $last_stats_file) {
        %last_stats = parse_stats (split "\n", slurp_file ($last_stats_file));
    }

    # get the current pg_stat_database data
    my $cmd = 'psql ' . $connect_options;
    $cmd .= " $opts{database} " if (exists $opts{database});
    $cmd .= "-Atc \"
        select date_trunc('minute', now()), datid, datname, 
            xact_commit, tup_inserted, tup_updated, tup_deleted 
        from pg_stat_database 
        where datname not in ('template0','template1','postgres'); \"";
    $cmd =~ s/\ns+/ /g;
    my @stats = `$cmd`;
    my %curr_stats = parse_stats (@stats);

    # do a backup if needed
    foreach my $datname (sort keys %curr_stats) {
        my $needs_backup = 0;
        if ($opts{all}) {
            $needs_backup = 1;
        }
        elsif ( ! exists $last_stats{$datname} ) {
            $needs_backup = 1;
            warn "no last stats for $datname\n" if ($opts{debug});
        }
        else {
            for (qw (tup_inserted tup_updated tup_deleted)) {
                if ($last_stats{$datname}{$_} != $curr_stats{$datname}{$_}) {
                    $needs_backup = 1;
                    warn "$_ stats do not match for $datname\n" if ($opts{debug});
                }
            }
        }
        if ($needs_backup) {
            backup_db ($datname);
        }
        else {
            chitchat ("Database \"$datname\" does not currently require backing up.");
        }
    }

    # update the pg_stat_database data
    open my $fh, '>', $last_stats_file || die "Could not open $last_stats_file for output. !$\n";
    print $fh @stats;
    close $fh;
}

sub parse_stats {
    my @in = @_;
    my %stats;
    chomp @in;
    foreach my $line (@in) {
        my @ary = split /\|/, $line;
        my $datname = $ary[2];
        next unless ($datname);
        foreach my $key (qw(tmsp datid datname xact_commit tup_inserted tup_updated tup_deleted)) {
            my $val = shift @ary;
            $stats{$datname}{$key} = $val;
        }
    }
    return %stats;
}

sub backup_globals {
    chitchat ("Backing up the global data.");

    my $backup_file = $backup_prefix . 'globals-only.backup.gz';
    my $cmd = 'pg_dumpall --globals-only ' . $shared_dump_args;
    $cmd .= " --database=$opts{database} " if (exists $opts{database});

    do_dump ($backup_file, "$cmd | gzip");
}

sub backup_db {
    my $database = shift;
    chitchat ("Backing up database \"$database\".");

    my $backup_file = $backup_prefix . $database . '-schema-only.backup.gz';
    do_dump ($backup_file, "pg_dump --schema-only --create --format=plain $shared_dump_args $database | gzip");

    $backup_file = $backup_prefix . $database . '.backup';
    do_dump ($backup_file, "pg_dump --format=". $opts{format} . " $shared_dump_args $database");
}

sub do_dump {
    my ($backup_file, $cmd) = @_;

    my $temp_file = $backup_file . '.new';
    warn "Command is: $cmd > $temp_file" if ($opts{debug});

    chitchat (`$cmd > $temp_file`);
    if ( -f $temp_file ) {
        chitchat (`mv $temp_file $backup_file`);
    }
}

sub chitchat {
    my @ary = @_;
    return unless (@ary);
    chomp @ary;
    my $first   = shift @ary;
    my $now     = strftime "%Y%m%d-%H:%M:%S", localtime;
    print +(join "\n                  ", "$now $first", @ary), "\n";
}

sub get_options {
    Getopt::Long::Configure('bundling');

    my %opts = ();
    GetOptions(
        "a"             => \$opts{all},
        "all"           => \$opts{all},
        "p=s"           => \$opts{port},
        "port=s"        => \$opts{port},
        "U=s"           => \$opts{username},
        "username=s"    => \$opts{username},
        "h=s"           => \$opts{host},
        "host=s"        => \$opts{host},
        "F=s"           => \$opts{format},
        "format=s"      => \$opts{format},
        "d=s"           => \$opts{database},
        "database=s"    => \$opts{database},
        "backup-dir=s"  => \$opts{backup_dir},
        "help"          => \$opts{help},
        "v"             => \$opts{verbose},
        "verbose"       => \$opts{verbose},
        "debug"         => \$opts{debug},
        );

    # Does the user need help?
    if ($opts{help}) {
        show_help();
    }

    $opts{host}         ||= $ENV{PGHOSTADDR} || $ENV{PGHOST}     || 'localhost';
    $opts{port}         ||= $ENV{PGPORT}     || '5432';
    $opts{host}         ||= $ENV{PGHOST}     || 'localhost';
    $opts{username}     ||= $ENV{PGUSER}     || $ENV{USER}       || 'postgres';
    $opts{database}     ||= $ENV{PGDATABASE} || $opts{username};
    $opts{backup_dir}   ||= './backups';

    my %formats = (
        c       => 'custom',
        custom  => 'custom',
        t       => 'tar',
        tar     => 'tar',
        p       => 'plain',
        plain   => 'plain',
    );
    $opts{format} = (defined $opts{format})
        ? $formats{$opts{format}} || 'custom'
        : 'custom';

    warn Dumper \%opts if ($opts{debug});
    return %opts;
}

sub show_help {
    print `perldoc -F $0`;
    exit;
}

sub slurp_file { local (*ARGV, $/); @ARGV = shift; <> }

__END__

更新:スクリプトはここの githubに置かれています


共有してくれてありがとう。ところで、それはgithubされている可能性がありますね。:-)
poige

2

を使用pg_stat_databaseしてトランザクション数を取得し、これが1つのバックアップ実行から次のバックアップ実行に変更されるかどうかを確認できるようです。

select datname, xact_commit from pg_stat_database;

  datname  | xact_commit 
-----------+-------------
 template1 |           0
 template0 |           0
 postgres  |      136785

誰かが電話pg_stat_resetをかけた場合、データベースが変更されたかどうかはわかりませんが、それが起こる可能性は低いと考えて、最後の読み取りと一致する正確な数のトランザクションを続けます。

-編集

これが機能しない理由については、このSOの質問を参照してください。なぜこれが発生するのかはわかりませんが、ロギングを有効にすると多少の影響が出る可能性があります...


誰かが呼び出した場合pg_stat_reset、xact_commit値が前のwhoと一致する確率はかなり低くなりますか?したがって、DMLの変更の存在を確実に把握できるようになります。今必要なのは、DDLの変更があったかどうかをキャッチすることだけです。
gsiems 2011

DDLはpostgresでトランザクション対応です-その場合もコミット数が増えると思います。...にもかかわらずチェックなし
ジャックはtopanswers.xyz試し言う

あなたは正しいです。Pg DDLがトランザクションであることを忘れていましたが、簡単なcreate table ...テストではxact_commitが増加するようです。
gsiems 2011

1
さらなるテストにより、ユーザーアクティビティが行われていないにも関わらずxact_commitが増加していることがわかります-おそらくautovacuum?
gsiems 2011

これは間違いなくバックアップの目的では機能しません。xact_commitは、誰もデータベースに接続していない場合でも、非常に頻繁に増加します。
mivk

1

postgresのドキュメントとニュースグループを掘り下げて:

txid_current()新しいものを提供しますxid-後でもう一度関数を呼び出す場合、xid1つ高い値を取得すると、2つの呼び出し間でトランザクションがコミットされないことがわかります。ただし、他の誰かが電話した場合など、誤検知が発生する可能性があります。txid_current()


提案ありがとうございます。txid_current()がデータベースレベルではなくクラスターレベルで動作しているように見えるので、これはうまくいきません。
gsiems 2011

そのドキュメントを探しましたが見つかりませんでした-リンクはありますか?
ジャックはtopanswers.xyzしようと言う

1
リンクなし。データベースを切り替えて「select current_database()、txid_current();」を実行してテストしました。結果を比較します。
gsiems 2011

0

DBデータを含むファイルにタイムスタンプを記録し、変更されているかどうかを確認します。彼らがした場合、書き込みがありました。

WALヒントの後で編集する:これは、未処理の書き込みをフラッシュした後でのみ行う必要があります。


2
それは信頼できません。データファイルにまだ書き込まれていない(フラッシュされている)変更がある可能性があります。つまり、変更はWALにのみ書き込まれました。
a_horse_with_no_name 2011

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