導入(問題提起)
「バックアップは取っていたつもりだったが、いざ障害が起きたら復旧できなかった」——現場で最も多い失敗です。Excelでのファイル共有運用からWebシステム開発に移行すると、利用者数が増え、更新頻度が上がり、万一の停止コストも跳ね上がります。だからこそ、機能追加より先に“止まらない設計”と“戻せる設計”を入れておく必要があります。本稿では小さな会社でも実現できるバックアップとディザスタリカバリ(DR: Disaster Recovery)の現実解を、実務的な手順とPythonを使った実装の勘所まで含めて詳しく解説します。SEO観点の主要キーワード(Webシステム開発/業務システム/Python/Excel Web化)も押さえつつ、検索意図「どう設計すれば安全に運用できるか」に応えます。
課題の詳細説明
バックアップ/DRは「取る」だけでは不十分で、「戻せる」「一貫性が保たれる」「想定時間内に戻せる」までを満たす必要があります。小規模の業務システムでは以下のつまずきが典型です。
- 取得しているのにリストア検証をしていない(実際は壊れたスナップショット)
- 同一サーバ・同一ディスクに保管しており、ハード障害や暗号化マルウェアで同時に失われる
- 世代が1つだけで、誤削除や論理破壊の発生時に「壊れた状態」しか残らない
- アプリとDBの整合性(トランザクション一貫性)を考慮していないため、復旧後にデータ不整合が生じる
- 手順が属人化し、担当者不在で復旧に着手できない/時間超過する
- Excel Web化の過渡期で、ファイルとDBの両方が情報源となり復旧対象が増える
これらは全て、事前の方針(RPO/RTO)と標準化された運用(世代・保管・訓練)で回避できます。
解決方法
まず“方針”を定め、その方針を満たす“仕組み”を最小コストで実装します。
1) 目的指標(RPO/RTO)の合意
- RPO(Recovery Point Objective): 許容できるデータ損失量(例:最大15分)
- RTO(Recovery Time Objective): 許容復旧時間(例:60分以内に主要機能再開)
2) 世代管理と保管場所の分離
- 日次×7、週次×4、月次×6(業務特性に応じて調整)
- 別サーバ/クラウド保管(暗号化・署名)で“同時消失”を避ける
3) 一貫性の確保
- DB論理バックアップ(ダンプ)とアプリ設定(.env、INI、YAML等)の同時取得
- 重要バッチの停止ウィンドウでスナップショットを取得し、トランザクション整合を担保
4) 自動化と手順化
- スクリプト化(取得・保存・世代ローテーション・通知)
- 復旧手順書(Runbook)の整備と四半期ごとのDR訓練
5) 監査性と安全性
- 取得/復旧の実行ログ、ハッシュ検証(整合性チェック)
- 復旧オペレーション時はRBAC(ロールベースアクセス制御)で権限制御、二要素認証で重要操作を保護
具体例
小規模の業務システムでの“現実解”を、段階別に示します。
- ステップA:SQLite開始のPython Webアプリ
- 取得対象:SQLiteファイル、/asset配下のユーザアップロード、環境設定(.env) - 頻度:日次フル、時間毎差分(必要なら) - 保管:ローカルとは別のNAS/クラウドストレージ(S3等)へ暗号化転送 - 検証:テスト用環境でDBを差し替え、サンプル画面まで起動確認
- ステップB:MySQL/PostgreSQL運用
- 論理バックアップ(mysqldump/pg_dump)+物理スナップショットの併用 - PITR(Point-In-Time Recovery)用にバイナリログ/WALを保管 - ジョブ失敗時の通知(メール/Slack)とデッドレターフォルダ
- ステップC:複数拠点・API連携
- アプリケーション設定と外部連携の資格情報(APIキー)を秘匿ストアに分離 - DRサイト(別リージョン)で最小構成を常設し、RTO短縮
技術的な解説
Pythonでの最小バックアップ基盤はシンプルに構築できます。以下はBottleを用いた業務システムの例です。
データモデル(監査・世代管理)
-- バックアップ実行ログ
CREATE TABLE IF NOT EXISTS backup_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
job_name TEXT NOT NULL,
started_at TEXT NOT NULL,
finished_at TEXT,
status TEXT CHECK(status IN ('SUCCESS','FAILED')),
artifact_path TEXT,
artifact_hash TEXT,
message TEXT
);
-- 復旧(リストア)ログ
CREATE TABLE IF NOT EXISTS restore_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
requested_by TEXT,
backup_job_id INTEGER,
started_at TEXT,
finished_at TEXT,
status TEXT,
notes TEXT,
FOREIGN KEY(backup_job_id) REFERENCES backup_jobs(id)
);
取得スクリプト例(擬似コード)
import hashlib, shutil, os, datetime as dt
def sha256sum(path):
h = hashlib.sha256()
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
h.update(chunk)
return h.hexdigest()
def backup_sqlite(db_path, dst_dir):
ts = dt.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
basename = f"appdb-{ts}.sqlite"
dst = os.path.join(dst_dir, basename)
shutil.copy2(db_path, dst)
return dst, sha256sum(dst)
Bottleの管理用ルート例(RBAC保護)
from bottle import route, request, abort
def require_role(role):
def deco(fn):
def wrapper(*args, **kwargs):
user = request.get_header('X-User')
roles = request.get_header('X-Roles','').split(',')
if role not in roles:
abort(403, 'forbidden')
return fn(*args, **kwargs)
return wrapper
return deco
@route('/admin/backup/run', method='POST')
@require_role('admin')
def run_backup():
# バックアップ起動→結果ログ化→通知、の流れ
pass
世代ローテーションとクラウド退避
- 命名規則で時系列ソート可能に(例:appdb-YYYYMMDDTHHMMSSZ.sqlite)
- 世代数を超えた古いファイルを自動削除
- S3等へサーバサイド暗号化で送信し、バケット側でライフサイクル管理
導入の流れ
- 現状把握:データ源(DB/ファイル/設定)とRPO/RTOの決定
- 設計:世代・保管・検証・権限・通知の方針を定義
- 実装:取得スクリプト、管理用エンドポイント、ジョブスケジューラ(Windowsタスク/cron)
- 訓練:四半期に一度、テスト環境で“手順どおり復旧できるか”を確認
- 運用:ジョブ失敗の監視、ログの定期レビュー、改善サイクル
まとめ
- RPO/RTOを最初に決めると、設計は自ずと決まる
- 「複数世代・別ロケーション・定期検証」が三本柱
- RBACと監査ログで“安全に復旧を実行できる状態”を保つ
- Excel Web化の過渡期はファイルとDBの両面を対象にする
問い合わせ導線
バックアップとDR設計・実装は、お問い合わせからご相談ください。Webシステム開発の初期段階から、業務システムの運用まで伴走します。
Webシステム開発のご相談は monou まで