Tentukan kapan database PostgreSQL terakhir diubah

10

Saya sedang mencari cara mengubah cadangan dilakukan dan saya bertanya-tanya apakah ada cara untuk menentukan database mana dalam cluster postgreql yang belum diubah baru-baru ini?

Alih-alih menggunakan pg_dumpall, saya ingin menggunakan pg_dump dan hanya membuang database yang telah berubah sejak cadangan terakhir (beberapa database tidak sering diperbarui) - gagasannya adalah jika tidak ada yang berubah maka cadangan saat ini harus tetap baik.

Adakah yang tahu cara menentukan kapan database tertentu terakhir diperbarui / diubah?

Terima kasih...

Memperbarui:

Saya berharap tidak harus menulis pemicu di semua tempat karena saya tidak memiliki kendali atas pembuatan database dalam satu cluster tertentu (apalagi penciptaan objek db dalam database).

Menggali lebih jauh, sepertinya ada korelasi antara isi file $ PGDATA / global / pg_database (khususnya bidang kedua) dan nama direktori di bawah $ PGDATA / base.

Akan mengambil risiko, saya kira bidang kedua dari file pg_database adalah database oid dan bahwa setiap database memiliki subdirektori sendiri di bawah $ PGDATA / base (dengan oid untuk nama subdirektori). Apakah itu benar? Jika demikian, apakah masuk akal untuk menggunakan cap waktu file dari file di bawah $ PGDATA / base / * sebagai pemicu untuk memerlukan cadangan?

... atau ada cara yang lebih baik?

Terima kasih lagi...

gsiems
sumber
Jangan pernah berasumsi bahwa cadangan saat ini baik. Anda selalu ingin mengambil cadangan baru pada jadwal reguler Anda.
mrdenny
Sonu Singh - Saya tidak bisa mengendalikan penambahan basis data, apalagi tabel ke kluster ini sehingga pemicu tidak akan berfungsi - ditambah (setahu saya) pemicu tidak akan menangkap perubahan ddl. mrdenny ♦ - Benar. Namun, saya ingin menghindari menghasilkan cadangan tambahan yang berlebihan antara cadangan penuh berkala.

Jawaban:

9

Sementara menggunakan select datname, xact_commit from pg_stat_database;seperti yang disarankan oleh @Jack Douglas tidak cukup bekerja (tampaknya karena autovacuum), select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_databasetampaknya berfungsi. Perubahan DML dan DDL akan mengubah nilai kolom tup_ * sementara a vacuumtidak ( vacuum analyzedi sisi lain ...).

Jika hal ini bermanfaat bagi orang lain, saya menyertakan skrip cadangan yang telah saya tempatkan. Ini berfungsi untuk Pg 8.4.x tetapi tidak untuk 8.2.x-- YMMV tergantung pada versi Pg yang digunakan.

#!/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__

Pembaruan: skrip telah diletakkan di github di sini .

gsiems
sumber
Kode yang cukup bagus, terima kasih sudah berbagi. BTW, bisa saja github'ed, bukan begitu? :-)
poige
2

Sepertinya Anda dapat menggunakan pg_stat_databaseuntuk mendapatkan jumlah transaksi dan memeriksa apakah ini berubah dari satu pencadangan ke yang berikutnya:

select datname, xact_commit from pg_stat_database;

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

Jika seseorang menelepon pg_stat_resetAnda, Anda tidak dapat memastikan apakah db telah berubah atau tidak, tetapi Anda mungkin menganggapnya tidak cukup bahwa itu akan terjadi, diikuti dengan jumlah transaksi yang tepat untuk mencocokkan pembacaan terakhir Anda.

--EDIT

lihat pertanyaan SO ini mengapa ini tidak berhasil. Tidak yakin mengapa ini bisa terjadi tetapi mengaktifkan logging mungkin memberi sedikit cahaya ....

Jack mengatakan coba topanswers.xyz
sumber
Jika seseorang memang menelpon pg_stat_resetmaka probabilitas nilai xact_commit yang cocok dengan yang sebelumnya akan sangat rendah, bukan? Jadi yang pasti terlihat menangkap adanya perubahan DML. Sekarang yang saya butuhkan adalah menangkap jika ada perubahan DDL.
gsiems
DDL bersifat transaksional dalam postgres - Saya perkirakan jumlah komit akan meningkat dalam kasus itu juga. Tidak dicentang ...
Jack bilang coba topanswers.xyz
Anda Tuan, benar. Saya lupa tentang Pg DDL yang transaksional dan create table ...tes cepat tampaknya menambah xact_commit.
gsiems
1
Pengujian lebih lanjut menunjukkan peningkatan xact_commit meskipun tidak ada aktivitas pengguna yang terjadi - autovacuum mungkin?
gsiems
Ini jelas tidak berfungsi untuk tujuan cadangan. xact_commit meningkat sangat sering, bahkan ketika tidak ada yang terhubung ke database.
mivk
1

Dari menggali di sekitar postgres doc dan newsgroup:

txid_current()akan memberi Anda yang baru xid- jika Anda memanggil fungsi lagi di kemudian hari, jika Anda mendapatkan yang xidlebih tinggi, Anda tahu bahwa tidak ada transaksi yang dilakukan antara kedua panggilan. Anda mungkin mendapatkan hasil positif palsu - misalnya jika orang lain menelepontxid_current()

Jack mengatakan coba topanswers.xyz
sumber
Terima kasih atas sarannya. Saya tidak percaya ini akan berfungsi sebagai txid_current () tampaknya beroperasi pada level cluster daripada level database.
gsiems
Saya mencari beberapa dokumen tentang itu dan tidak dapat menemukan - apakah Anda memiliki tautan?
Jack bilang coba topanswers.xyz
1
Tidak ada hubungan. Saya diuji dengan beralih antara database dan menjalankan "pilih current_database (), txid_current ();" dan membandingkan hasilnya.
gsiems
0

Ingat stempel waktu pada file Anda yang berisi data-DB dan lihat apakah sudah berubah. Jika mereka melakukannya ada tulisan.

Sunting setelah WAL-hint: Anda harus melakukan ini hanya setelah menyiram tulisan yang beredar.

Nils
sumber
2
Itu tidak bisa diandalkan andal. Mungkin ada perubahan yang belum ditulis (memerah) ke filefile, yaitu mereka hanya ditulis ke WAL.
a_horse_with_no_name