Определите, когда база данных PostgreSQL была в последний раз изменена

10

Я смотрю на изменение способа создания резервных копий и мне интересно, есть ли способ определить, какие базы данных в кластере postgreql не были недавно изменены?

Вместо использования pg_dumpall я бы хотел использовать pg_dump и создавать дамп только тех баз данных, которые изменились с момента последнего резервного копирования (некоторые базы данных обновляются не очень часто). Идея заключается в том, что если ничего не изменилось, то текущая резервная копия должна все еще быть хорошим.

Кто-нибудь знает способ определить, когда конкретная база данных последний раз обновлялась / изменялась?

Спасибо...

Обновить:

Я надеялся, что мне не придется писать триггеры повсюду, поскольку я не могу контролировать создание баз данных в одном конкретном кластере (не говоря уже о создании объектов БД в базе данных).

Более подробно, похоже, что существует корреляция между содержимым файла $ PGDATA / global / pg_database (в частности, второе поле) и именами каталогов в $ PGDATA / base.

Если исходить из конечности, я бы предположил, что второе поле файла pg_database - это oid базы данных, и что каждая база данных имеет свой собственный подкаталог в каталоге $ PGDATA / base (с oid для имени подкаталога). Это верно? Если да, то разумно ли использовать временные метки файлов из файлов в каталоге $ PGDATA / base / * в качестве триггера для необходимости резервного копирования?

... или есть лучший способ?

Еще раз спасибо...

gsiems
источник
Никогда не думайте, что текущая резервная копия хороша. Вы всегда хотите делать новые резервные копии по вашему регулярному расписанию.
Мрденни
Сону Сингх - я не могу контролировать добавление баз данных, не говоря уже о таблицах в этом кластере, чтобы триггеры не работали - плюс (насколько мне известно) триггеры не будут ловить изменения ddl. мрденный ♦ - правильно. Однако я бы хотел избежать создания избыточных инкрементных резервных копий между периодическими полными резервными копиями.

Ответы:

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_ *, а a 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 здесь .

gsiems
источник
Довольно хороший код, спасибо, что поделились. Кстати, это может быть github'ed, вы так не думаете? :-)
Пой
2

Похоже, вы можете использовать, pg_stat_databaseчтобы получить количество транзакций и проверить, если это изменится от одного резервного копирования к следующему:

select datname, xact_commit from pg_stat_database;

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

Если кто-то звонил pg_stat_resetвам, вы не можете быть уверены, изменился ли БД или нет, но вы можете считать, что это маловероятно, что это произойдет, а затем точно такое количество транзакций, которое соответствует вашему последнему чтению.

--РЕДАКТИРОВАТЬ

Посмотрите этот ТАК вопрос, почему это может не сработать. Не уверен, почему это может произойти, но включение ведения журнала может пролить некоторый свет ....

Джек говорит, попробуйте topanswers.xyz
источник
Если кто-то позвонил, pg_stat_resetвероятность того, что значение xact_commit совпадет с предыдущим, будет довольно низкой, нет? Так что, безусловно, выглядит, чтобы поймать наличие изменений DML. Теперь все, что мне нужно, это поймать, были ли изменения в DDL.
gsiems
DDL является транзакционным в postgres - я бы ожидал, что в этом случае также увеличится число коммитов. Не проверено, хотя ...
Джек говорит попробуйте topanswers.xyz
Вы, сэр, правы. Я забыл, что Pg DDL является транзакционным, и быстрый create table ...тест действительно увеличивает xact_commit.
gsiems
1
Дальнейшее тестирование показывает увеличение xact_commit, даже несмотря на то, что нет никаких действий пользователя - возможно, автовакуум?
gsiems
Это определенно не работает в целях резервного копирования. xact_commit увеличивается очень часто, даже когда никто не подключен к базе данных.
mivk
1

От копания в документах и ​​группах новостей postgres:

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

Джек говорит, попробуйте topanswers.xyz
источник
Спасибо за предложение. Я не верю, что это сработает, так как txid_current () работает на уровне кластера, а не на уровне базы данных.
gsiems
Я искал какой-то документ по этому вопросу и не смог найти - у вас есть ссылка?
Джек говорит, попробуй topanswers.xyz
1
Нет ссылки. Я проверил, переключаясь между базами данных и запустив «select current_database (), txid_current ();» и сравнивая результаты.
gsiems
0

Запомните отметку времени в ваших файлах, содержащих DB-данные, и посмотрите, изменились ли они. Если они сделали, была запись.

Изменить после WAL-подсказки: Вы должны делать это только после сброса ожидающих записей.

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