Визначте, коли востаннє була змінена база даних PostgreSQL


10

Я дивлюсь на те, як змінюються резервні копії, і мені цікаво, чи є спосіб визначити, які бази даних в кластері postgreql нещодавно не були змінені?

Замість використання pg_dumpall я хотів би використовувати pg_dump і скидати лише ті бази даних, які змінилися після останньої резервної копії (деякі бази даних не оновлюються дуже часто) - ідея полягає в тому, що якщо нічого не змінилося, то поточна резервна копія повинна все одно будь хороший.

Хтось знає спосіб визначити, коли конкретна база даних востаннє оновлена ​​/ змінена?

Дякую...

Оновлення:

Я сподівався, що мені не доведеться писати тригери в усіх місцях, оскільки я не маю контролю над створенням баз даних в одному конкретному кластері (не кажучи вже про створення db-об'єктів у базі даних).

Покопившись далі, схоже, що існує вміст файлу $ PGDATA / global / pg_database (зокрема друге поле) та назви каталогів у $ PGDATA / base.

Виходячи на кінцівку, я думаю, що друге поле файлу pg_database - це oid бази даних і що кожна база даних має власний підкаталог під $ PGDATA / base (з oid для імені підкаталогу). Це правильно? Якщо так, чи розумно використовувати часові позначки з файлів під $ PGDATA / base / * як тригер для необхідності резервного копіювання?

... чи є кращий спосіб?

Знову дякую...



Ніколи не вважайте, що поточне резервне копіювання добре. Ви завжди хочете робити нові резервні копії за своїм звичайним графіком.
mrdenny

Сону Сінг - Я не можу контролювати додавання баз даних, не кажучи вже про таблиці до цього кластеру, щоб тригери не спрацювали - плюс (наскільки мені відомо) тригери не сприйматимуть змін DDL. mrdenny ♦ - Правильно. Однак я хотів би уникати надмірних поступових резервних копій між періодичними повними резервними копіями.

Відповіді:


9

Хоча використання, select datname, xact_commit from pg_stat_database;як запропонував @Jack, Дуглас не дуже працює (мабуть, через автовакуум), select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_databaseале, здається, працює. Зміни DML та DDL змінять значення стовпців tup_ *, а а - vacuumні ( vacuum analyzeз іншого боку ...).

Не маючи шансу, що це може бути корисно для інших, я включаю резервний сценарій, який я створив. Це працює для Pg 8.4.x, але не для 8.2.x-- YMMV залежно від використовуваної версії Pg.

#!/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'ed, ти не вважаєш так? :-)
poige

2

Схоже, ви можете використати pg_stat_databaseдля підрахунку транзакцій і перевірити, чи не змінюється це зміна від однієї резервної копії до наступної:

select datname, xact_commit from pg_stat_database;

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

Якщо хтось зателефонував pg_stat_resetвам, ви не можете бути впевнені, змінився чи ні, але ви можете вважати малоймовірним, що це станеться, після чого слід точно вказати кількість транзакцій, щоб відповідати вашому останньому читанню.

--EDIT

перегляньте це питання, чому це може не спрацювати. Не впевнений, чому це може статися, але включення журналу може пролити трохи світла ....


Якщо хтось подзвонив, pg_stat_resetто ймовірність значення xact_commit, що відповідає попередньому, була б досить низькою, ні? Таким чином, це, безумовно, схоже на існування змін у DML. Тепер все, що мені потрібно - це зловити, чи відбулися зміни DDL.
gsiems

DDL є транзакційним в постгресі - я очікую, що кількість комісій збільшиться і в цьому випадку. Не перевірено, хоча ...
Джек каже спробувати topanswers.xyz

Ви, пане, правильно. Я забув, що Pg DDL є транзакційним, і швидкий create table ...тест, схоже, збільшує xact_commit.
gsiems

1
Подальше тестування показує, що xact_commit збільшується, навіть якщо активність користувача не відбувається - можливо, autovacuum?
gsiems

Це безумовно не працює для резервного копіювання. xact_commit збільшується дуже часто, навіть коли ніхто не підключений до бази даних.
mivk

1

З розкопок по документах та групах новин по пошті:

txid_current()дасть вам нове xid- якщо ви знову зателефонуєте на цю функцію пізніше, якщо ви отримаєте xidодну вище, ви знаєте, що між двома дзвінками не було здійснено жодних транзакцій. Однак ви можете отримати помилкові позитиви - наприклад, якщо хтось інший телефонуєtxid_current()


Дякую за пропозицію. Я не вірю, що це буде працювати, однак, як видається, tidd_current () працює на рівні кластера, а не на рівні бази даних.
gsiems

Я шукав цього документа і не зміг знайти - у вас є посилання?
Джек каже, спробуйте topanswers.xyz

1
Немає посилання. Я перевіряв, перемикаючись між базами даних та виконуючи "select current_database (), txid_current ();" та порівняння результатів.
gsiems

0

Помітьте позначку часу на ваші файли, що містять дані БД, і подивіться, чи вони змінилися. Якщо вони це зробили, написали.

Редагування після підказки WAL: Ви повинні зробити це лише після промивання видатних записів.


2
Це не покладається на надійне. Можуть бути зміни, які ще не записані (розмиті) у файли даних, тобто вони були записані лише у WAL.
a_horse_with_no_name

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.