Featured image of post シンプルなバックアップから始める

シンプルなバックアップから始める

Perlで再帰的ファイルコピーを実装し、シンプルなバックアップツールを作成。動作ベンチマークで課題を特定し、改善への道筋を示します。

「大切なデータを失ってしまった…」 そんな経験はありませんか? バックアップは重要だと分かっていても、既存のツールは帯に短し襷に長し…。

「なら、自分で作ってしまえばいいじゃない!」

今日から始まる新シリーズ「Perlで作るファイルバックアップツール」では、実用的なツールをゼロから作り上げます。単なるコピープログラムではありません。Template Method や Strategy といったデザインパターンを駆使し、プロの現場でも通用する「変更に強い」アーキテクチャを学びます。

初回の今日は、まずは「動くもの」を作るところからスタートです。

目次 | 次回: タイムスタンプ比較で差分検出

今回の目標

  • 指定したディレクトリを別の場所に再帰的にコピーするツールを作る
  • ファイル操作の基本(File::Copy, File::Findなど)を確認する
  • ベンチマークを取り、現状の課題を洗い出す

単純なコピーの実装

まずは、CPANモジュールを使わずに、標準モジュールだけで実装してみましょう。Perlには File::CopyFile::Path などの便利なコアモジュールがあります。

再帰的コピーのロジック

ディレクトリを丸ごとコピーするには、ディレクトリツリーを再帰的に辿る必要があります。ここでは File::Find を使っても良いですが、よりモダンで扱いやすい Path::Tiny を使った実装を紹介したいところですが…今回は「基本を知る」ために、あえて標準的な手法と少しの現代的な書き方(Moo はまだ使いません)で書いてみます。

といっても、車輪の再発明は避けたいので、File::Copy::Recursive のような挙動を自前で書くイメージを持ちつつ、今回はシンプルに Path::Tiny を使ってサクッと実装してしまいましょう。おっと、標準モジュールと言いましたが、Path::Tiny は現代のPerl開発ではほぼ標準と言っても過言ではないので、これを使います。ない場合は cpanm Path::Tiny でインストールしてください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env perl
use strict;
use warnings;
use Path::Tiny;
use File::Copy qw(copy);
use Time::HiRes qw(gettimeofday tv_interval);

# 使い方: perl simple_backup.pl <source_dir> <dest_dir>
my $source_dir = path($ARGV[0] or die "Usage: $0 <source_dir> <dest_dir>");
my $dest_dir   = path($ARGV[1] or die "Usage: $0 <source_dir> <dest_dir>");

die "Source directory not found!" unless $source_dir->is_dir;

print "Start backup from $source_dir to $dest_dir\n";
my $t0 = [gettimeofday];

my $count = 0;
my $iterator = $source_dir->iterator({ recurse => 1 });

while (my $path = $iterator->()) {
    next if $path->is_dir; # ディレクトリは作成時に自動処理されるか、ファイルコピー時に親が作られる

    my $rel_path = $path->relative($source_dir);
    my $to_path  = $dest_dir->child($rel_path);

    # 親ディレクトリの作成
    $to_path->parent->mkpath unless $to_path->parent->exists;

    # コピー実行
    # print "Copying $rel_path...\n";
    copy($path, $to_path) or die "Failed to copy $path: $!";
    $count++;
}

my $elapsed = tv_interval($t0);
printf "Backup completed! Copied %d files in %.2f seconds.\n", $count, $elapsed;

とてもシンプルですね。Path::Tiny のおかげで、イテレータを使った再帰処理も直感的に書けます。

ベンチマーク:単純コピーの弱点

さて、このツールは「動きます」。しかし、バックアップツールとして常用するには大きな問題があります。それは 「毎回すべてのファイルをコピーしている」 という点です。

実際に試してみましょう。適当なディレクトリ(例えば写真フォルダやプロジェクトフォルダなど、数千ファイルあるもの)を用意して、2回連続で実行してみます。

1
2
3
4
5
6
7
$ perl simple_backup.pl ./my_photos ./backup_test
Start backup from ./my_photos to ./backup_test
Backup completed! Copied 1500 files in 5.23 seconds.

$ perl simple_backup.pl ./my_photos ./backup_test
Start backup from ./my_photos to ./backup_test
Backup completed! Copied 1500 files in 5.18 seconds.

2回目もほぼ同じ時間がかかっていますね。ファイルに変更がなくても、すべて上書きコピーしているからです。これでは、ファイル数が増えれば増えるほど時間がかかり、ディスクI/Oも無駄に消費してしまいます。

欠点の整理

  1. 遅い: 変更されていないファイルまでコピーしている
  2. 無駄: ディスクへの書き込み負荷が高い
  3. 拡張性がない: 「圧縮したい」「特定のファイルを除外したい」といった要望が出たとき、この while ループの中にif文を継ぎ足していくことになり、コードがスパゲッティ化しやすい

次回予告

「動くけれど、実用的ではない」状態からスタートしました。次回は、バックアップの基本中の基本である 「差分バックアップ」 を実装することで、見違えるほど高速化させてみます。

そこで登場するのが、ファイルのメタデータ(タイムスタンプ)の活用です。

お楽しみに!

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。