ตรวจสอบว่าฐานข้อมูล PostgreSQL มีการเปลี่ยนแปลงล่าสุด


10

ฉันกำลังดูการเปลี่ยนแปลงวิธีการสำรองข้อมูลและสงสัยว่ามีวิธีการตรวจสอบว่าฐานข้อมูลใดในคลัสเตอร์ postgreql ที่ยังไม่ได้เปลี่ยนไปเมื่อเร็ว ๆ นี้

แทนที่จะใช้ pg_dumpall ฉันต้องการใช้ pg_dump และถ่ายโอนข้อมูลเฉพาะฐานข้อมูลที่มีการเปลี่ยนแปลงนับตั้งแต่การสำรองข้อมูลครั้งล่าสุด (บางฐานข้อมูลไม่ได้รับการอัปเดตบ่อยมาก) - แนวคิดว่าถ้าไม่มีการเปลี่ยนแปลงใด ๆ การสำรองข้อมูลปัจจุบันควร ยังดีอยู่

ไม่มีใครทราบวิธีการตรวจสอบเมื่อมีการปรับปรุง / เปลี่ยนแปลงฐานข้อมูลเฉพาะล่าสุด?

ขอบคุณ ...

ปรับปรุง:

ฉันหวังว่าจะไม่ต้องเขียนทริกเกอร์ให้ทั่วเพราะฉันไม่สามารถควบคุมการสร้างฐานข้อมูลในคลัสเตอร์ใดคลัสเตอร์หนึ่ง (ให้สร้างวัตถุ db ภายในฐานข้อมูล)

ขุดเพิ่มเติมดูเหมือนว่ามีความสัมพันธ์ระหว่างเนื้อหาของไฟล์ $ PGDATA / global / pg_database (โดยเฉพาะฟิลด์ที่สอง) และชื่อไดเรกทอรีภายใต้ $ PGDATA / ฐาน

ออกไปบนกิ่งฉันเดาว่าฟิลด์ที่สองของไฟล์ pg_database เป็นฐานข้อมูล oid และแต่ละฐานข้อมูลมีไดเรกทอรีย่อยของตนเองภายใต้ $ PGDATA / ฐาน (มี oid สำหรับชื่อไดเรกทอรีย่อย) ถูกต้องหรือไม่ ถ้าเป็นเช่นนั้นมีเหตุผลหรือไม่ที่จะใช้การประทับเวลาของไฟล์จากไฟล์ภายใต้ $ PGDATA / base / * เป็นตัวกระตุ้นให้ต้องการการสำรองข้อมูล

... หรือมีวิธีที่ดีกว่า

ขอบคุณอีกครั้ง...



ไม่เคยคิดว่าการสำรองข้อมูลปัจจุบันดี คุณต้องการสำรองข้อมูลใหม่ตามกำหนดเวลาปกติของคุณเสมอ
mrdenny

Sonu Singh - ฉันไม่สามารถควบคุมการเพิ่มฐานข้อมูล, ให้ตารางเพียงอย่างเดียวในคลัสเตอร์นี้ดังนั้นทริกเกอร์จะไม่ทำงาน - และทริกเกอร์ (ความรู้ของฉัน) จะไม่จับการเปลี่ยนแปลงของ ddl 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 แต่ไม่ใช่สำหรับ 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 ที่นี่


รหัสค่อนข้างดีขอบคุณสำหรับการแบ่งปัน 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คุณจะไม่แน่ใจว่ามีการเปลี่ยนแปลงฐานข้อมูลหรือไม่ แต่คุณอาจคิดว่ามันไม่น่าเป็นไปได้ที่จะเกิดขึ้นตามด้วยจำนวนธุรกรรมที่ถูกต้องเพื่อให้ตรงกับการอ่านครั้งสุดท้ายของคุณ

--EDIT

ดูคำถาม SO นี้ว่าทำไมสิ่งนี้อาจไม่ทำงาน ไม่แน่ใจว่าทำไมสิ่งนี้ถึงเกิดขึ้นได้ แต่การเปิดใช้งานการบันทึกอาจทำให้บางโรง ...


หากมีคนโทร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

จากการขุดเอกสารและกลุ่มข่าวภายหลัง:

txid_current()จะให้ใหม่xid- ถ้าคุณเรียกใช้ฟังก์ชันอีกครั้งในภายหลังถ้าคุณได้รับxidหนึ่งสูงกว่าคุณรู้ว่าไม่มีการทำธุรกรรมระหว่างสองสาย คุณอาจได้รับผลบวกปลอมเช่น - ถ้ามีคนอื่นโทรมาtxid_current()


ขอบคุณสำหรับคำแนะนำ ฉันไม่เชื่อว่าสิ่งนี้จะทำงานได้เป็น txid_current () ดูเหมือนจะทำงานในระดับคลัสเตอร์มากกว่าระดับฐานข้อมูล
gsiems

ฉันค้นหาเอกสารบางอย่างเกี่ยวกับเรื่องนั้นและหาไม่พบคุณมีลิงค์หรือไม่
แจ็คบอกว่าลอง topanswers.xyz

1
ไม่มีลิงก์ ฉันทดสอบโดยสลับระหว่างฐานข้อมูลและเรียกใช้ "select current_database (), txid_current ();" และเปรียบเทียบผลลัพธ์
gsiems

0

เตือนการประทับเวลาของไฟล์ที่มีฐานข้อมูลและดูว่ามีการเปลี่ยนแปลงหรือไม่ หากพวกเขาทำนั่นก็คือการเขียน

แก้ไขหลังจาก WAL-hint: คุณควรทำสิ่งนี้หลังจากล้างการเขียนที่โดดเด่นเท่านั้น


2
นั่นไม่น่าเชื่อถือ อาจมีการเปลี่ยนแปลงที่ยังไม่ได้เขียน (ล้างข้อมูล) ไปยัง datafiles นั่นคือมีการเขียนไปยัง WAL เท่านั้น
a_horse_with_no_name

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.