Di artikel lain (lihat: Menggunakan Rsync) telah diperkenalkan rsync, sebuah tool populer untuk melakukan transfer dan sinkronisasi file baik lokal maupun jaringan. Artikel ini berisi sebuah contoh aplikasi penggunaan rsync untuk melakukan backup harian filesystem dengan histori.

Pengertian histori backup

Apa maksudnya backup dengan histori? Artinya, backup yang telah dilakukan pada periode sebelumnya (mis: kemarin, 2 hari lalu, seminggu lalu, dst) tidak dihapus melainkan tetap disimpan. Umumnya, standar di dunia IT dalam backup korporat adalah menyimpan backup data terbaru, 7 histori backup harian, 4 histori backup mingguan, dan beberapa histori backup bulanan (mis: 12). Sehingga periode histori total adalah 12 bulan.

Backup dengan histori lebih berguna karena dapat membantu menyelamatkan data yang rusak atau terhapus tapi tidak segera diketahui. Misalnya Anda secara tak sadar menghapus sebuah direktori yang dirasa tidak dibutuhkan lagi karena proyek sudah selesai. Ternyata sebulan kemudian, proyek diteruskan. Jika Anda tidak menyimpan histori backup, maka setelah Anda menghapus direktori tersebut, pada run backup harian berikutnya data backup terbaru pun tidak akan lagi berisi data direktori tersebut, karena backup terbaru mengikuti perkembangan data terbaru.

Atau, katakanlah Anda menggunakan atau mengembangkan program versi baru. Ternyata setelah beberapa saat baru diketahui bahwa program ini mengandung bug dan sedikit demi sedikit merusak atau menghapus sebagian kecil data. Agar yakin tidak kehilangan data berharga, Anda ingin membandingkan data yang ada sekarang dengan kondisi 6 bulan yang lalu.

Atau, Anda ingin kembali pada kondisi konfigurasi seminggu yang lalu sebelum Anda melakukan instal ulang OS. Jika Anda melakukan backup dengan histori pada direktori /etc, hal ini mudah saja dilakukan. Tinggal mengambil data histori backup yang sesuai.

Menghemat ruang disk

Backup dengan histori memunculkan sebuah masalah: ruang disk yang membengkak. Karena kita menyimpan salinan penuh backup setiap hari. Maka data yang besarnya 100MB dalam seminggu akan membutuhkan ruang backup 7x100MB = 700MB. Dalam sebulan membutuhkan 30x100MB = 3GB. Dalam setahun membutuhkan 365x100MB = 36GB! Bagaimana jika data yang ingin dibackup lebih besar lagi? Bagaimana cara menghemat ruang disk?

Ada dua cara yang dapat dilakukan.

Hard link. Yang pertama adalah dengan menggunakan ‘hard link’. Di sebagian besar filesystem, termasuk filesystem-filesystem yang kerap digunakan di Unix/Linux, sebuah entri direktori diizinkan merujuk ke lokasi data (isi file) yang sama dengan entri direktori lain. Dengan kata lain, sebuah salinan file dapat memiliki lebih dari satu nama/lokasi. Contoh:

$ ln dataku lokasi_lain/datasaya

Perintah ini membuat hard link terhadap dataku ke lokasi_lain/datasaya. Artinya jumlah salinannya hanya 1, tapi dirujuk dengan dua nama file berbeda. Jika kita memodifikasi salah satu, maka dua-duanya akan termodifikasi karena sebetulnya keduanya adalah sama.

Perintah ls -l dapat memperlihatkan jumlah link yang merujuk pada sebuah entri direktori:

$ ls -l dataku
-rw-r--r-- 2 steven steven 149 2007-09-04 17:45 dataku

Angka 2 menunjukkan bahwa ada dua link yang merujuk ke file ini. Jika kita hapus salah satu (atau kita ‘unlink’, inilah sebabnya mengapa perintah delete atau hapus di Unix disebut juga dengan unlink):

$ rm lokasi_lain/datasaya

maka jumlah hard link akan berkurang:

$ ls -l dataku
-rw-r--r-- 1 steven steven 149 2007-09-04 17:45 dataku

Sebuah file tidak akan terbebaskan ruang disknya sebelum semua entri direktori yang merujuk padanya hilang (dengan kata lain, sebelum jumlah hard linknya menjadi 0). Ketika semua sudah dihapus:

$ rm dataku

maka tidak ada lagi entri yang merujuk ke isi file, dan file menjadi tak dapat dicapai, dan itu berarti blok yang ditempati oleh isi file dinyatakan bebas dan dapat digunakan untuk isi yang lain.

Dengan hard link, kita dapat mengirit ruang disk. File-file yang tidak berubah dari satu versi backup ke versi backup berikutnya cukup di-hard link satu sama lain.

Level harian, mingguan, bulanan. Cara kedua dalam mengirit ruang disk adalah dengan tidak menyimpan semua histori pada level harian. Umumnya data yang sudah semakin tua semakin tidak relevan, dan tidak perlu disimpan terlalu ‘rapat-rapat’ lagi. Jadi, untuk menyimpan data sepanjang satu tahun, umumnya tidak terlalu dibutuhkan menyimpan 365 buah backup harian, melainkan cukup 7 backup harian, 4 backup mingguan, dan 12 backup bulanan. Periode histori yang tercakup tetap 12 bulan, tapi jumlah salinan histori yang dibutuhkan hanyalah 7+4+12 = 23 buah. Jika hanya ingin menyimpan periode 3 bulan, jumlah salinan histori yang dibutuhkan 7+4+3 = 14 buah.

Bagimana cara merotasi level-level ini, jika kita melakukan backup harian? Di bawah ini ilustrasinya untuk backup s.d. 3 bulan. Setiap angka melambangkan hari, semakin kecil berarti semakin tua. Untuk memperoleh backup terbaru (yaitu yang baru dilakukan hari ini atau paling lambatnya kemarin), Anda bisa mengambil yang angkanya paling besar. Contoh: di hari ke-1, backup terbaru adalah yang berangka 1. Di hari ke-10, backup berangka 10, dst. Sebaliknya, backup tertua yang angkanya paling kecil. Tanda bintang (*) melambangkan calon yang akan dipindahkan ke level backup berikutnya.

Hari 1: backup harian  : 1 backup mingguan: (belum ada) backup bulanan : (belum ada) Hari 2: backup harian  : 2 1 backup mingguan: (belum ada) backup bulanan : (belum ada) Hari 7: backup harian  : 7 6 5 4 3 2 1 backup mingguan: (belum ada) backup bulanan : (belum ada) Hari 8: backup harian  : 8* 7 6 5 4 3 2 1<-- dipindah ke mingguan backup mingguan: 1 backup bulanan : (belum ada) Hari 9: backup harian  : 9 8* 7 6 5 4 3 2<-- dihapus backup mingguan: 1 backup bulanan : (belum ada) Hari 15: backup harian  : 15* 14 13 12 11 10 9 8*<-- dipindah ke mingguan backup mingguan: 8 1 backup bulanan : (belum ada) Hari 16: backup harian  : 16 15* 14 13 12 11 10 9<-- dihapus backup mingguan: 8 1 backup bulanan : (belum ada) Hari 22: backup harian  : 22* 21 20 19 18 17 16 15*<-- dipindah ke mingguan backup mingguan: 15 8 1 backup bulanan : (belum ada) Hari 29: backup harian  : 29* 28 27 26 25 24 23 22*<-- dipindah ke mingguan backup mingguan: 22 15 8 1 backup bulanan : (belum ada) Hari 36: backup harian  : 36* 35 34 33 32 31 30 29*<-- dipindah ke mingguan backup mingguan: 29* 22 15 8 1<-- dipindah ke bulanan backup bulanan : 1
Hari 43: backup harian  : 43* 42 41 40 39 38 37 36*<-- dipindah ke mingguan backup mingguan: 36 29* 22 15 8<-- dihapus backup bulanan : 1
Hari 50: backup harian  : 50* 49 48 47 46 45 44 43*<-- dipindah ke mingguan backup mingguan: 43 36 29* 22 15<-- dihapus backup bulanan : 1
Hari 57: backup harian  : 57* 56 55 54 53 52 51 50*<-- dipindah ke mingguan backup mingguan: 50 43 36 29* 22<-- dihapus backup bulanan : 1
Hari 64: backup harian  : 64* 63 62 61 60 59 58 57*<-- dipindah ke mingguan backup mingguan: 57* 50 43 36 29*<-- dipindah ke bulanan backup bulanan : 29 1
Hari 92: backup harian  : 92* 91 90 89 88 87 86 85*<-- dipindah ke mingguan backup mingguan: 85* 78 71 64 57*<-- dipindah ke bulanan backup bulanan : 57 29 1
Hari 120: backup harian  : 120* 119 118 117 116 115 114 113*<-- dipindah ke mingguan backup mingguan: 113* 106 99 92 85*<-- dipindah ke bulanan backup bulanan : 85 57 29 1<-- dibuang

Program backup-with-history

Berikut ini implementasi sistem backup harian dengan histori menggunakan rsync, dalam sekitar 100 baris Perl. Skrip ini dapat diperoleh juga online dihttp://people.masterwebnet.com/steven/files/backup-with-history.

  1|#!/usr/bin/perl -w   |  3|use strict;  4|use Cwd qw(abs_path);  5|use POSIX;  6|use Time::Local;   |  8|# --- config  9|my $HISTORIES = [ -7, 4, 3 ];   | 11|# --- subs 12|sub esc { 13|    local $_ = shift; 14|    s/'/'"'"'/g; 15|    "'$_'"; 16|}   | 18|# --- main 19|@ARGV == 2 or die "Usage: $0 <src> <dest>\n"; 20|my ( $src, $dst ) = @ARGV; 21|for ( $src, $dst ) {s!/$!!} 22|( -d $src ) or die "Src haruslah direktori yang sudah ada: $src\n"; 23|system "mkdir -p " . esc("$dst/current"); 24|( -d "$dst/current" ) or die "Gagal membuat creating $dst/current\n";   | 26|print "Membuat backup terbaru $src ke $dst/ ...\n"; 27|system "nice -n19 rsync -av --del --force " 28|    . "--link-dest " 29|    . esc( abs_path("$dst") . "/current" ) . " " 30|    . esc("$src/") . " " 31|    . esc("$dst/.work/") . "\n"; 32|warn "Perintah rsync tidak sukses, sebaiknya Anda periksa\n" if $?;   | 34|chdir($dst) or die "Tidak bisa chdir ke $dst: $!\n"; 35|system "touch .current.timestamp"; 36|my $now = time; 37|my @st  = stat(".current.timestamp"); 38|my $tstamp 39|    = POSIX::strftime( "%Y-%m-%d\@%H:%M:%S+00", gmtime( $st[9] || $now ) ); 40|rmdir "current" or rename "current", "hist.$tstamp"; 41|rename ".work", "current";   | 43|print "Menghapus histori backup yang sudah terlalu lama ...\n"; 44|for my $level ( 1 .. @$HISTORIES ) { 45|    my $is_highest_level  = $level == @$HISTORIES; 46|    my $prefix            = "hist" . ( $level == 1 ?  : $level ); 47|    my $prefix_next_level = "hist" . ( $level + 1 ); 48|    my $n                 = $HISTORIES->[ $level - 1 ]; 49|    my $moved             = 0;   | 51|    if ( $n > 0 ) { 52|        print "Hanya menyimpan $n buah histori backup L$level ...\n"; 53|        my @f = reverse sort grep { !/\.work$/ } glob "$prefix.*"; 54|        my $any_tagged = ( grep {/t$/} @f ) ? 1 : 0; 55|        for my $f ( @f[ $n .. @f - 1 ] ) { 56|            my ( $st, $tagged ) = $f =~ /[^.]+\.(.+?)(t)?$/; 57|            my $f2 = "$prefix_next_level.$st"; 58|            if (   !$is_highest_level 59|                && !$moved 60|                && ( $tagged || !$any_tagged ) ) 61|            { 62|                print "Memindahkan level histori backup: $f -> $f2\n"; 63|                system "mv " . esc($f) . " " . esc($f2); 64|                $moved++; 65|                if ( $f ne $f[0] ) { 66|                    my $e3 = esc( $f[0] ); 67|                    system "mv $e3 ${e3}t"; 68|                } 69|            } 70|            else { 71|                print "Menghapus histori backup: $f ...\n"; 72|                system "nice -n19 rm -rf " . esc($f); 73|            } 74|        } 75|    } 76|    else { 77|        $n = -$n; 78|        print "Hanya menyimpan histori backup L$level sd $n hari ...\n"; 79|        my @f = reverse sort grep { !/\.work$/ } glob "$prefix.*"; 80|        my $any_tagged = ( grep {/t$/} @f ) ? 1 : 0; 81|        for my $f (@f) { 82|            my ( $st, $tagged ) = $f =~ /[^.]+\.(.+?)(t)?$/; 83|            my $f2 = "$prefix_next_level.$st"; 84|            my $t; 85|            $st =~ /(\d\d\d\d)-(\d\d)-(\d\d)\@(\d\d):(\d\d):(\d\d)\+00/; 86|            $t = timegm( $6, $5, $4, $3, $2 - 1, $1 ) if $1; 87|            $st && $t or do { 88|                print "Histori backup salah format: $f, diabaikan\n"; 89|                next; 90|            }; 91|            if ( $t > $now ) { 92|                print "Histori backup di masa depan? $f, diabaikan\n"; 93|                next; 94|            } 95|            my $delta = ( $now - $t ) / 86400; 96|            if ( $delta > $n ) { 97|                if (   !$is_highest_level 98|                    && !$moved 99|                    && ( $tagged || !$any_tagged ) ) 100|                { 101|                    print "Memindahkan level histori backup: $f -> $f2\n"; 102|                    system "mv " . esc($f) . " " . esc($f2); 103|                    $moved++; 104|                    if ( $f ne $f[0] ) { 105|                        my $e3 = esc( $f[0] ); 106|                        system "mv $e3 ${e3}t"; 107|                    } 108|                } 109|                else { 110|                    print "Menghapus history backup: $f ...\n"; 111|                    system "nice -n19 rm -rf " . esc($f); 112|                } 113|            } 114|        } 115|    } 116|}   | 118|

Cara menggunakan skrip ini:

$ backup-with-history DIR DIRBACKUP

DIR adalah direktori yang ingin dibackup, misalnya /home/steven. DIRBACKUP adalah lokasi backup yang diinginkan, mis: /backup/home-steven. Sebaiknya /backup berada pada harddisk yang berbeda, karena tujuan backup lokal adalah menghindari kehilangan data akibat kerusakan harddisk. Selesai proses backup, skrip akan membentuk direktori DIRBACKUP/current/ yang berisi backup terbaru, dan serangkaian direktori DIRBACKUP/hist.*, hist2.*, dan hist3.* yang berisi histori backup harian, mingguan, bulanan.

Untuk melakukan proses backup itu sendiri intinya adalah pada baris 27-31, yaitu mengandalkan rsync. Rsync sudah akan menangani transfer dan sinkronisasi struktur direktori beserta metadata setiap file (permission, kepemilikan, tanggal, dsb). Dengan opsi –link-dest, rsync juga akan membandingkan data yang ada dengan backup terakhir (current/) dan melakukan hardlinking jika memungkinkan. Sisa dari skrip sebagian besar adalah penanganan rotasi histori backup.

Skrip ini masih sederhana. Bisa Anda tambah agar dapat dikonfigurasi (mis: opsi –exclude rsync, dll). Selamat mencoba!