Bestimmen Sie, wann eine PostgreSQL-Datenbank zuletzt geändert wurde

10

Ich möchte ändern, wie Sicherungen durchgeführt werden, und frage mich, ob es eine Möglichkeit gibt, festzustellen, welche Datenbanken in einem Postgreql-Cluster kürzlich nicht geändert wurden.

Anstatt pg_dumpall zu verwenden, möchte ich pg_dump verwenden und nur die Datenbanken sichern, die sich seit der letzten Sicherung geändert haben (einige Datenbanken werden nicht sehr oft aktualisiert). Die Idee ist, dass sich die aktuelle Sicherung ändern sollte, wenn sich nichts geändert hat sei immer noch gut

Kennt jemand eine Möglichkeit, um festzustellen, wann eine bestimmte Datenbank zuletzt aktualisiert / geändert wurde?

Vielen Dank...

Aktualisieren:

Ich hatte gehofft, nicht überall Trigger schreiben zu müssen, da ich keine Kontrolle über die Erstellung von Datenbanken in einem bestimmten Cluster habe (geschweige denn die Erstellung von Datenbankobjekten innerhalb einer Datenbank).

Wenn Sie weiter graben, scheint es eine Korrelation zwischen dem Inhalt der Datei $ PGDATA / global / pg_database (insbesondere dem zweiten Feld) und den Verzeichnisnamen unter $ PGDATA / base zu geben.

Ich gehe davon aus, dass das zweite Feld der Datei pg_database die Datenbank-OID ist und dass jede Datenbank ein eigenes Unterverzeichnis unter $ PGDATA / base hat (mit der OID für den Namen des Unterverzeichnisses). Ist das korrekt? Wenn ja, ist es sinnvoll, die Dateizeitstempel aus den Dateien unter $ PGDATA / base / * als Auslöser für die Notwendigkeit einer Sicherung zu verwenden?

... oder gibt es einen besseren Weg?

Danke noch einmal...

gsiems
quelle
Gehen Sie niemals davon aus, dass das aktuelle Backup gut ist. Sie möchten immer neue Backups in Ihrem regulären Zeitplan erstellen.
Mrdenny
Sonu Singh - Ich kann das Hinzufügen von Datenbanken nicht kontrollieren, geschweige denn Tabellen zu diesem Cluster, damit Trigger nicht funktionieren - plus (meines Wissens) Trigger fangen keine ddl-Änderungen ab. mrdenny ♦ - Richtig. Ich möchte jedoch vermeiden, redundante inkrementelle Sicherungen zwischen den regelmäßigen vollständigen Sicherungen zu generieren.

Antworten:

9

Während die Verwendung, select datname, xact_commit from pg_stat_database;wie von @Jack Douglas vorgeschlagen, nicht ganz funktioniert (anscheinend aufgrund von Autovakuum), select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_databasescheint sie zu funktionieren. Sowohl DML- als auch DDL-Änderungen ändern die Werte von tup_ * -Spalten, während a vacuumdies nicht tut ( vacuum analyzeandererseits ...).

Für den Fall, dass dies für andere nützlich sein könnte, füge ich das von mir eingerichtete Sicherungsskript hinzu. Dies funktioniert für Pg 8.4.x, jedoch nicht für 8.2.x-- YMMV, abhängig von der verwendeten Pg-Version.

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

Update: Das Skript wurde setzen auf GitHub hier .

gsiems
quelle
Ganz netter Code, danke fürs Teilen. Übrigens, es könnte github'ed sein, meinst du nicht auch? :-)
Poige
2

Es sieht so aus, als könnten pg_stat_databaseSie eine Transaktionsanzahl abrufen und prüfen, ob sich dies von einem Sicherungslauf zum nächsten ändert:

select datname, xact_commit from pg_stat_database;

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

Wenn jemand angerufen pg_stat_resethat, können Sie nicht sicher sein, ob sich eine Datenbank geändert hat oder nicht, aber Sie halten es möglicherweise für unwahrscheinlich genug, dass dies passieren würde, gefolgt von genau der richtigen Anzahl von Transaktionen, die Ihrer letzten Lesung entsprechen.

--BEARBEITEN

In dieser SO-Frage erfahren Sie, warum dies möglicherweise nicht funktioniert. Ich bin mir nicht sicher, warum dies passieren könnte, aber das Aktivieren der Protokollierung könnte etwas Licht ins Dunkel bringen.

Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
Wenn jemand angerufen hat, pg_stat_resetist die Wahrscheinlichkeit, dass der xact_commit-Wert mit dem vorherigen übereinstimmt, ziemlich gering, nein? Das scheint also die Existenz von DML-Änderungen zu erfassen. Jetzt muss ich nur noch abfangen, ob DDL-Änderungen vorgenommen wurden.
Gsiems
DDL ist in Postgres transaktional - ich würde erwarten, dass die Anzahl der Commits auch in diesem Fall zunimmt. Nicht überprüft ...
Jack sagt, versuchen Sie es mit topanswers.xyz
Sie, Sir, sind richtig. Ich hatte vergessen, dass Pg DDL eine Transaktion ist, und ein schneller create table ...Test scheint xact_commit zu erhöhen.
Gsiems
1
Weitere Tests zeigen, dass der xact_commit zunimmt, obwohl keine Benutzeraktivität stattfindet - Autovakuum vielleicht?
Gsiems
Dies funktioniert definitiv nicht für Backup-Zwecke. xact_commit steigt sehr häufig an, auch wenn niemand mit der Datenbank verbunden ist.
Mivk
1

Vom Stöbern in den Postgres-Dokumenten und Newsgroups:

txid_current()erhalten Sie eine neue xid- wenn Sie die Funktion zu einem späteren Zeitpunkt erneut aufrufen, wenn Sie xideine höhere erhalten, wissen Sie, dass keine Transaktionen zwischen den beiden Aufrufen festgeschrieben wurden. Es kann jedoch zu Fehlalarmen kommen - z. B. wenn jemand anderes anrufttxid_current()

Jack sagt, versuchen Sie es mit topanswers.xyz
quelle
Vielen Dank für den Vorschlag. Ich glaube nicht, dass dies funktionieren wird, da txid_current () eher auf Clusterebene als auf Datenbankebene zu arbeiten scheint.
Gsiems
Ich habe nach einem Dokument gesucht und konnte es nicht finden - haben Sie einen Link?
Jack sagt, versuchen Sie topanswers.xyz
1
Keine Verbindung. Ich habe getestet, indem ich zwischen Datenbanken gewechselt und "select current_database (), txid_current ();" und Vergleichen der Ergebnisse.
Gsiems
0

Erinnern Sie sich an den Zeitstempel Ihrer Dateien, die die DB-Daten enthalten, und prüfen Sie, ob sie sich geändert haben. Wenn ja, gab es ein Schreiben.

Nach WAL-Hinweis bearbeiten: Dies sollte erst nach dem Leeren der ausstehenden Schreibvorgänge erfolgen.

Nils
quelle
2
Das ist nicht zuverlässig. Es kann Änderungen geben, die noch nicht in die Datendateien geschrieben (gelöscht) wurden, dh sie wurden nur in die WAL geschrieben.
a_horse_with_no_name