PostgreSQL 데이터베이스가 마지막으로 변경된시기 확인


10

백업 수행 방법을 변경하고 postgreql 클러스터에서 최근에 변경되지 않은 데이터베이스를 결정하는 방법이 있는지 궁금합니다.

pg_dumpall을 사용하는 대신 pg_dump를 사용하고 마지막 백업 이후 변경된 데이터베이스 만 덤프하고 싶습니다 (일부 데이터베이스는 자주 업데이트되지 않음). 아무것도 변경되지 않으면 현재 백업이 여전히 좋다.

누구든지 특정 데이터베이스가 마지막으로 업데이트 / 변경된시기를 결정하는 방법을 알고 있습니까?

감사...

최신 정보:

하나의 특정 클러스터에서 데이터베이스 생성을 제어 할 수 없으므로 데이터베이스 내에서 트리거를 작성할 필요가 없었습니다.

더 자세히 살펴보면 $ PGDATA / global / pg_database 파일의 내용 (특히 두 번째 필드)과 $ PGDATA / base 아래의 디렉토리 이름간에 상관 관계가있는 것 같습니다.

사지로 나가서 pg_database 파일의 두 번째 필드는 데이터베이스가 아니며 각 데이터베이스에는 $ PGDATA / base 아래에 하위 디렉토리 이름이있는 자체 하위 디렉토리가 있다고 생각합니다. 그 맞습니까? 그렇다면 $ PGDATA / base / *에있는 파일의 파일 타임 스탬프를 백업이 필요한 트리거로 사용하는 것이 합리적입니까?

... 또는 더 좋은 방법이 있습니까?

다시 한번 감사드립니다 ...



현재 백업이 양호하다고 가정하지 마십시오. 항상 정기적으로 새 백업을 수행하려고합니다.
mrdenny

Sonu Singh-이 클러스터에 테이블은 물론 데이터베이스 추가를 제어 할 수 없으므로 트리거가 작동하지 않습니다. mrdenny ♦-맞습니다. 그러나 주기적 전체 백업간에 중복 증분 백업이 생성되는 것을 피하고 싶습니다.

답변:


9

select datname, xact_commit from pg_stat_database;@Jack Douglas가 제안한대로 사용하는 것은 (자동 진공으로 인해) 제대로 작동하지 않지만 작동하는 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에 배치되었습니다 .


공유해 주셔서 감사합니다. BTW, 그것은 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를했다면 DB가 변경되었는지 여부를 확신 할 수 없지만 DB가 변경 될 가능성이 충분하지 않다고 생각한 다음 마지막 판독 값과 일치하는 정확한 수의 트랜잭션이 이어질 수 있습니다.

--편집하다

이것이 작동하지 않는 이유에 대해서는 이 SO 질문 을 참조하십시오 . 왜 이런 일이 발생하는지 모르지만 로깅을 사용하도록 설정하면 약간의 빛이 비칠 수 있습니다.


누군가 전화 pg_stat_reset하면 이전 whould와 일치하는 xact_commit 값의 확률이 매우 낮습니다. 따라서 DML 변경 사항이 있는지 확인해야합니다. 이제 필요한 것은 DDL 변경 사항이 있는지 파악하는 것입니다.
gsiems

DDL은 postgres에서 트랜잭션입니다.이 경우 커밋 수가 증가 할 것으로 기대합니다. 하지만 확인하지 ...
잭 topanswers.xyz 시도라고

당신은 정확합니다. Pg DDL이 트랜잭션 처리되는 것을 create table ...잊었고 xact_commit을 증가 시키는 빠른 테스트가 나타납니다.
gsiems

1
추가 테스트에 따르면 xact_commit은 사용자 활동이 없는데도 자동 진공이 증가하고 있습니까?
gsiems

이것은 백업 목적으로는 작동하지 않습니다. 데이터베이스에 아무도 연결되어 있지 않아도 xact_commit은 매우 자주 증가합니다.
mivk 2016 년

1

postgres 문서와 뉴스 그룹을 파헤쳐 서 :

txid_current()당신에게 새로운을 줄 것이다 xid- 당신은 나중에 다시 함수를 호출하는 경우, 당신이 얻는 경우에 xid더 높은 한, 당신은 어떤 트랜잭션이 두 통화 사이에 최선을 다하고 없다는 것을 알고있다. 하지만 다른 사람이 전화를하는 경우 오 탐지가 발생할 수 있습니다.txid_current()


제안 해 주셔서 감사합니다. txid_current ()가 데이터베이스 수준이 아닌 클러스터 수준에서 작동하는 것처럼 보이지만 이것이 작동하지는 않습니다.
gsiems

나는 그것에 대한 문서를 찾았고 찾을 수 없었습니다-링크가 있습니까?
잭 topanswers.xyz 시도라고

1
링크가 없습니다. 데이터베이스를 전환하고 "select current_database (), txid_current ();"를 실행하여 테스트했습니다. 그리고 결과를 비교하는 것.
gsiems

0

DB 데이터가 포함 된 파일의 타임 스탬프를 수정하고 변경 된지 확인하십시오. 그들이했다면 글이 있었다.

WAL 힌트 후 편집 : 미해결 쓰기를 비운 후에 만이 작업을 수행해야합니다.


2
신뢰할만한 것은 아닙니다. 데이터 파일에 아직 기록되지 않은 변경 사항이있을 수 있습니다. 즉, WAL에만 기록 된 것입니다.
a_horse_with_no_name

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.