Xác định khi nào cơ sở dữ liệu PostgreSQL được thay đổi lần cuối


10

Tôi đang xem xét việc thay đổi cách thức sao lưu được thực hiện và tự hỏi liệu có cách nào để xác định cơ sở dữ liệu nào trong cụm postgreql không được thay đổi gần đây không?

Thay vì sử dụng pg_dumpall, tôi muốn sử dụng pg_dump và chỉ kết xuất những cơ sở dữ liệu đã thay đổi kể từ lần sao lưu cuối cùng (một số cơ sở dữ liệu không được cập nhật thường xuyên) - ý tưởng là nếu không có gì thay đổi thì nên sao lưu hiện tại vẫn tốt

Có ai biết một cách để xác định khi nào một cơ sở dữ liệu cụ thể được cập nhật / thay đổi lần cuối không?

Cảm ơn...

Cập nhật:

Tôi đã hy vọng không phải viết các trình kích hoạt khắp nơi vì tôi không có quyền kiểm soát việc tạo cơ sở dữ liệu trong một cụm cụ thể (chứ đừng nói đến việc tạo các đối tượng db trong cơ sở dữ liệu).

Đi sâu hơn, có vẻ như có mối tương quan giữa nội dung của tệp $ PGDATA / global / pg_database (cụ thể là trường thứ hai) và tên thư mục trong $ PGDATA / cơ sở.

Đi ra ngoài một chi, tôi đoán rằng trường thứ hai của tệp pg_database là cơ sở dữ liệu và mỗi cơ sở dữ liệu có thư mục con riêng dưới $ PGDATA / cơ sở (với oid cho tên thư mục con). Đúng không? Nếu vậy, có hợp lý không khi sử dụng dấu thời gian của tệp từ các tệp trong $ PGDATA / base / * làm trình kích hoạt cho việc cần sao lưu?

...đây có phải là cách tốt hơn không?

Cảm ơn lần nữa ...



Đừng bao giờ cho rằng bản sao lưu hiện tại là tốt. Bạn luôn muốn có bản sao lưu mới trong lịch trình thường xuyên của bạn.
mrdenny

Sonu Singh - Tôi không thể kiểm soát việc bổ sung cơ sở dữ liệu, hãy để các bảng vào cụm này để các trình kích hoạt không hoạt động - cộng với (theo hiểu biết của tôi) các trình kích hoạt sẽ không bắt được các thay đổi ddl. mrdenny ♦ - Đúng. Tuy nhiên, tôi muốn tránh tạo các bản sao lưu gia tăng dự phòng giữa các bản sao lưu đầy đủ định kỳ.

Câu trả lời:


9

Mặc dù sử dụng select datname, xact_commit from pg_stat_database;theo đề xuất của @Jack Douglas không hoạt động tốt (rõ ràng là do tự động lưu), select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_databasecó vẻ như hoạt động. Cả hai thay đổi DML và DDL sẽ thay đổi giá trị của các cột tup_ * trong khi a vacuumkhông ( vacuum analyzemặt khác ...).

Trong trường hợp khả năng này có thể hữu ích cho những người khác, tôi đang bao gồm tập lệnh sao lưu mà tôi đã đặt. Điều này hoạt động cho PG 8.4.x nhưng không phải cho 8.2.x-- YMMV tùy thuộc vào phiên bản của PG được sử dụng.

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

Cập nhật: tập lệnh đã được đưa lên github tại đây .


Khá đẹp mã, cảm ơn đã chia sẻ. BTW, nó có thể là github'ed, bạn không nghĩ vậy sao? :-)
poige

2

Có vẻ như bạn có thể sử dụng pg_stat_databaseđể lấy số lượng giao dịch và kiểm tra xem điều này có thay đổi từ lần chạy dự phòng này sang lần chạy tiếp theo không:

select datname, xact_commit from pg_stat_database;

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

Nếu ai đó đã gọi cho pg_stat_resetbạn, bạn không thể chắc chắn liệu db có thay đổi hay không, nhưng bạn có thể cho rằng điều đó không đủ khả năng xảy ra, tiếp theo là chính xác số lượng giao dịch phù hợp với lần đọc cuối cùng của bạn.

--BIÊN TẬP

xem câu hỏi SO này để biết tại sao điều này có thể không hoạt động. Không chắc chắn tại sao điều này có thể xảy ra nhưng cho phép đăng nhập có thể làm sáng tỏ ....


Nếu ai đó đã gọi pg_stat_resetthì xác suất của giá trị xact_commit khớp với giá trị trước đó là khá thấp, phải không? Vì vậy, điều đó chắc chắn sẽ bắt được sự tồn tại của các thay đổi DML. Bây giờ tất cả những gì tôi cần là nắm bắt nếu đã có thay đổi DDL.
gsiems

DDL là giao dịch trong postgres - Tôi cũng mong muốn số lượng cam kết sẽ tăng trong trường hợp đó. Chưa được kiểm tra ...
Jack nói hãy thử topanswers.xyz

Thưa bạn, là chính xác. Tôi đã quên về create table ...PG DDL là giao dịch và một bài kiểm tra nhanh xuất hiện để tăng xact_commit.
gsiems

1
Thử nghiệm thêm cho thấy xact_commit tăng ngay cả khi không có hoạt động người dùng nào đang diễn ra-- có lẽ là tự động?
gsiems

Điều này chắc chắn không hoạt động cho mục đích sao lưu. xact_commit tăng rất thường xuyên, ngay cả khi không có ai kết nối với cơ sở dữ liệu.
mivk

1

Từ việc đào bới xung quanh các tài liệu và nhóm tin của postgres:

txid_current()sẽ cung cấp cho bạn một cái mới xid- nếu bạn gọi lại chức năng vào một ngày sau đó, nếu bạn nhận được một xidmức cao hơn, bạn biết rằng không có giao dịch nào được cam kết giữa hai cuộc gọi. Bạn có thể nhận được thông báo sai mặc dù - ví dụ: nếu người khác gọitxid_current()


Cảm ơn vì đã góp ý. Tôi không tin điều này sẽ hoạt động tuy nhiên vì txid_cản () dường như hoạt động ở cấp cụm chứ không phải ở cấp cơ sở dữ liệu.
gsiems

Tôi đã tìm một số tài liệu về điều đó và không thể tìm thấy - bạn có một liên kết không?
Jack nói hãy thử topanswers.xyz

1
Không có liên kết. Tôi đã kiểm tra bằng cách chuyển đổi giữa các cơ sở dữ liệu và chạy "select current_database (), txid_cản ();" và so sánh kết quả.
gsiems

0

Chọn dấu thời gian trên các tệp của bạn chứa dữ liệu DB và xem chúng có thay đổi không. Nếu họ đã có một văn bản.

Chỉnh sửa sau WAL-gợi ý: Bạn chỉ nên làm điều này sau khi xóa phần ghi nổi bật.


2
Điều đó không đáng tin cậy. Có thể có những thay đổi chưa được ghi (xóa) cho các tệp dữ liệu, tức là chúng chỉ được ghi vào WAL.
a_horse_with_no_name

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.