Bir PostgreSQL veritabanının en son ne zaman değiştirildiğini belirleme


10

Yedeklemelerin nasıl yapıldığını değiştirmeye bakıyorum ve bir postgreql kümesindeki hangi veritabanlarının son zamanlarda değiştirilmediğini belirlemenin bir yolu olup olmadığını merak ediyorum?

Pg_dumpall kullanmak yerine, pg_dump kullanmak ve sadece son yedeklemeden bu yana değişen veritabanlarını dökmek istiyorum (bazı veritabanları çok sık güncellenmez) - fikir şu ki, hiçbir şey değişmediyse mevcut yedeklemenin hala iyi ol.

Herkes belirli bir veritabanının en son ne zaman güncellendiğini / değiştirildiğini belirlemenin bir yolunu biliyor mu?

Teşekkürler...

Güncelleme:

Ben belirli bir küme (yalnız bir veritabanı içinde db nesneleri oluşturma) veritabanları oluşturma üzerinde hiçbir kontrole sahip olduğu gibi tüm bu yerde tetikler yazmak zorunda umuyordum.

Daha da ileri giderek, $ PGDATA / global / pg_database dosyasının içeriği (özellikle ikinci alan) ile $ PGDATA / base altındaki dizin adları arasında bir korelasyon var gibi görünüyor.

Bir uzuv çıkıyor, pg_database dosyasının ikinci alanının veritabanı oid olduğunu ve her veritabanı $ PGDATA / base (alt dizin adı için oid ile) altında kendi alt dizini olduğunu tahmin ediyorum. Bu doğru mu? Öyleyse, yedeklemeye ihtiyaç duymak için $ PGDATA / base / * altındaki dosyalardan dosya zaman damgalarını kullanmak mantıklı mıdır?

... yoksa daha iyi bir yol var mı?

Tekrar teşekkürler...



Asla mevcut yedeklemenin iyi olduğunu varsaymayın. Her zaman düzenli programınıza göre yeni yedekler almak istersiniz.
mrdenny

Sonu Singh - Veritabanlarının eklenmesini kontrol edemiyorum, bu kümeye tablolar bırakın, böylece tetikleyiciler çalışmaz - artı (bildiklerime göre) tetikleyiciler ddl değişikliklerini yakalamaz. mrdenny ♦ - Doğru. Ancak, periyodik tam yedeklemeler arasında artımlı yedekler oluşturmaktan kaçınmak istiyorum.

Yanıtlar:


9

select datname, xact_commit from pg_stat_database;@Jack tarafından önerildiği gibi kullanıldığında Douglas oldukça işe yaramaz (görünüşe göre otovakum nedeniyle), select datname, tup_inserted, tup_updated, tup_deleted from pg_stat_databaseişe yarıyor gibi görünüyor. DML ve DDL değişikliklerinin hiçbiri tup_ * sütunlarının değerlerini değiştirmezken vacuum( vacuum analyzediğer yandan ...).

Bunun başkaları için yararlı olabileceği ihtimaline karşı, yerine koyduğum yedek komut dosyasını ekliyorum. Bu, Pg 8.4.x için çalışır ancak kullanılan Pg sürümüne bağlı olarak 8.2.x-- YMMV için çalışmaz.

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

Güncelleme: script burada github'a konuldu .


Oldukça hoş bir kod, paylaşım için teşekkürler. BTW, github'ed olabilir, öyle değil mi? :-)
poige

2

Kullanabileceğiniz gibi görünüyor pg_stat_databasebir işlem sayısını elde etmek ve check eğer bir yedek vadede diğerine bu değişiklikler:

select datname, xact_commit from pg_stat_database;

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

Birisi pg_stat_resetaradıysa, bir db'nin değişip değişmediğinden emin olamazsınız, ancak bunun gerçekleşmesi için yeterince olası olmadığını ve ardından son okumanızla eşleşen doğru sayıda işlem olduğunu düşünebilirsiniz.

--DÜZENLE

bunun neden işe yaramayabileceğine dair bu SO sorusuna bakın . Bunun neden olabileceğinden emin değilim ancak günlüğe kaydetmeyi etkinleştirmek biraz ışık tutabilir ...


Birisi pg_stat_resetaradıysa, o zaman bir önceki kime karşılık gelen xact_commit değerinin olasılığı oldukça düşük, değil mi? Bu kesinlikle DML değişikliklerinin varlığını yakalamak için görünüyor. Şimdi tek ihtiyacım olan DDL değişiklikleri varsa yakalamak.
gsiems

DDL postgres'te işlem yapıyor - bu durumda da taahhüt sayısının artmasını beklerdim. Yine de kontrol edilmedi ...
Jack, topanswers.xyz denemek diyor

Siz Efendim, haklısınız. Pg DDL işlemsel olduğunu unuttum ve hızlı bir create table ...test xact_commit artırmak gibi görünüyor.
gsiems

1
Daha fazla test, devam eden kullanıcı etkinliği olmamasına rağmen xact_commit'in arttığını gösteriyor - belki de otomatik vakum?
gsiems

Bu kesinlikle yedekleme amacıyla çalışmaz. xact_commit, veritabanına hiç kimse bağlı olmasa bile çok sık artar.
mivk

1

Postgres dokümanları ve haber gruplarını araştırmaktan:

txid_current()size yeni bir değer verecektir xid- işlevi daha sonraki bir tarihte tekrar ararsanız, xidbir üst seviyeye yükselirseniz, iki arama arasında herhangi bir işlem yapılmadığını bilirsiniz. Yine de yanlış pozitifler alabilirsiniz - örneğin, başka biri aradığındatxid_current()


Öneri için teşekkürler. Ancak txid_current () veritabanı düzeyi yerine küme düzeyinde çalışmak gibi görünüyor bu işe yaramaz inanmıyorum.
gsiems

Bununla ilgili bir doktora baktım ve bulamadım - bağlantın var mı?
Jack diyor ki topanswers.xyz

1
Bağlantı yok. Ben veritabanları arasında geçiş yaparak ve "select current_database (), txid_current ();" ve sonuçların karşılaştırılması.
gsiems

0

DB verilerini içeren dosyalarınızdaki zaman damgasını kaldırın ve değişip değişmediğine bakın. Eğer yapmışlarsa, bir yazı vardı.

WAL ipucundan sonra düzenle: Bunu yalnızca seçkin yazma işlemlerini temizledikten sonra yapmalısınız.


2
Bu güvenilir değil. Veri dosyalarına henüz yazılmayan (temizlenmeyen) değişiklikler olabilir, yani yalnızca WAL'a yazılmıştır.
a_horse_with_no_name

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.