バッチ処理とWebサービスでデータベースを共有している環境において、データの整合性を守るために「バッチ実行中はWebサービスを読み取り専用にする」という設計は、非常に堅実で合理的な判断です。
ステークホルダー(ユーザーやビジネス側)の合意が取れているのであれば、技術的な複雑さを抑えつつ、データ不整合のリスクを確実に排除できる優れた解決策と言えます。
テーブル単位でアクセス可能とすることも可能ですが、実装が複雑になるのでDB単位としています。また、バッチに使用するDBをDumpして使い捨てのDBを作成する方法も考えましたが、バッチ処理で更新される必要があった場合や数10TB等のDBを使い捨てする場合のハードウェアコストとソフトウェアのライセンスコストも発生する場合がありなかなか難しいです。このためバッチ処理中はWebやOffice(Access・Excel)などによるDBデータの変更ができないように読取専用とすることにしました。
1. 読み取り専用化の実現方法の比較
単に「読み取り専用にする」と言っても、いくつかの実装パターンがあります。システムの規模や許容されるダウンタイムに応じて選択してください。
| 実装方法 | メリット | デメリット |
| アプリケーション側での制御 | 特定の更新画面を表示不可にするなど、ユーザーフレンドリーな制御が可能。 | 実装漏れ(隠れた更新処理など)があるとデータが変わってしまう。 |
| DBユーザー権限の切り替え | 接続ユーザーを「参照のみ」のロールに変更する。確実性が高い。 | アプリ側で更新SQLを投げた際にエラーハンドリングが必要。 |
| リードレプリカの活用 | 夜間のみ参照先をレプリカ(読み取り専用機)に向ける。 | インフラ構成の変更が必要。 |
2. 検討しておくべき運用上の注意点
バッチ処理の遅延対策
夜間バッチが予定時間を過ぎて突き抜けてしまった場合、朝になってもWebサービスが読み取り専用のままになってしまいます。
対策: バッチの終了を検知して自動的に読み取り専用を解除する仕組みや、遅延時に管理者に通知する仕組みが必要です。
ユーザーへの周知(UX)
ユーザーが更新しようとした瞬間に「エラー」が出るのは不親切です。
対策: ログイン後やトップページに「〇〇時〜〇〇時はメンテナンスのため、データの更新・登録は行えません」といったアナウンスを表示する工夫が望ましいです。
データの「静止点」の確保
バッチ開始の直前までWebサービスで更新が行われていた場合、バッチ側がその中途半端なデータを読み込んでしまう可能性があります。
対策: バッチ開始直前に数分間の「完全メンテナンス時間(アクセス不可)」を設け、コネクションを一度切断してからバッチを開始するとより安全です。
3. 対応例:DBユーザーの権限を切り替えて「読み取り専用(ReadOnly)」にする方法
DBユーザーの権限を切り替えて「読み取り専用(ReadOnly)」を実現する方法は、アプリケーションのロジックに依存せず、データベース層で確実に更新をブロックできるため、非常に信頼性の高い手法となりえます。
接続先ユーザー(DB User)の構成案
一般的に、以下の2種類のDBユーザーを用意し、時間帯や夜間バッチの開始・終了によってアプリケーションが使用するユーザーを切り替える構成にします。web_rw_user (Read/Write用): 通常運用時に他サービスが使用。SELECT, INSERT, UPDATE, DELETE 権限を持つ。web_ro_user (Read Only用): 夜間バッチ時他サービスが使用。SELECT 権限のみを持つ
DBユーザー作成のSQL例 (PostgreSQL/MySQL等の場合)
-- 参照専用ユーザーの作成例
CREATE USER web_ro_user WITH PASSWORD 'password';
GRANT USAGE ON SCHEMA public TO web_ro_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO web_ro_user;
4. アプリケーションでの切り替え実装パターン
①接続文字列(DataSource)の動的切り替え
アプリケーション内に2つのコネクションプールを持ち、リクエスト時に現在時刻や夜間バッチの動作を判定して使い分ける方法です。
メリット: アプリの再起動が不要
デメリット: 全てのクエリ発行箇所で「どちらの接続を使うか」のロジックが必要(共通フレームワークで吸収するのが一般的)
②環境変数の書き換えと再起動
バッチ開始直前に、DB接続情報の環境変数を書き換えてWebサービスを再起動(またはローリングアップデート)する方法です。
メリット: 実装が極めてシンプル。確実に「更新不可」の状態になる
デメリット: 切り替え時に瞬断が発生する。
実装上の重要なチェックポイン
DBユーザーを切り替える際に必ず直面する課題が2点あります。
① 更新系SQLが発行された際のエラーハンドリング
web_ro_user を使用中にユーザーが更新ボタンを押すと、DBは Permission Denied エラーを返します。
対策: アプリ側でこのエラーをキャッチし、「現在はメンテナンス中のため保存できません」といったユーザーフレンドリーなメッセージに変換する共通処理が必要です。
② フレームワークの「暗黙の書き込み」
Webフレームワーク(Django, Rails, Spring等)は単なるページ表示の際にも「セッション情報の更新」や「最終ログイン日時の更新」のために書き込み処理を自動で行うことがあります。
対策: セッション管理をDBではなくRedis等に逃がすか、特定の管理テーブルだけは web_ro_user でも UPDATE を許可する設定にする必要があります。
5. 切り替えフローの自動化(概要)
①バッチ開始前: スケジューラー(Cron、Airflow、JP1等)が切り替えスクリプトを実行し、Webサービスを web_ro_user 接続に切り替える。
②バッチ実行: DBに対してバッチ処理を実行。
③バッチ終了後: 終了フラグを受けて、Webサービスを web_rw_user 接続に戻す。
6. 主要な3つの実装パターンを提案
「時間帯」という固定の枠ではなく、「バッチプロセスが実際に動いているかどうか」という実態に合わせて動的に切り替える設計を考えました。この方がバッチが早く終わればすぐに通常運用に戻せますし、逆に長引いてもデータ整合性を守れるため、より柔軟で安全な設計と言えます。
この場合、Webサービス側が「今、バッチが動いているか?」を判断するための「信号(フラグ)」をどこに置くかが鍵になります。
① DB内の「ステータス管理テーブル」を参照する方式(推奨)
DB(PostgreSQL)の中に、システムの状態を管理する小さなテーブルを用意する方法です。
- 仕組み:
- バッチ処理の開始直後に、SQLで
UPDATE system_status SET mode = 'BATCH_RUNNING';を実行 - Webサービスは、リクエスト処理の開始時(または一定間隔)にこの値を参照
BATCH_RUNNINGであれば、web_ro_user(読み取り専用)のコネクションを使用する- バッチ終了時に、
UPDATE system_status SET mode = 'NORMAL';に戻す
- バッチ処理の開始直後に、SQLで
- メリット: Webサーバーが複数台あっても、共通のDBを見るだけで一斉にモードを切り替えられる
- 注意点: 毎リクエストでテーブルを見に行くと負荷になるため、アプリ側で数秒間キャッシュするのが一般的です
② 共有ストレージ上の「フラグファイル」を確認する方式
バッチサーバーとWebサーバーが同じファイルシステム(NFSや共有ディスク)を参照できる場合に有効です。
- 仕組み:
- バッチ開始時に
lock.flag等のファイルを作成 - Webサービスは、ファイルが存在するかどうかを
exists()関数等でチェック - ファイルがあれば読み取り専用モードで動作
- バッチ終了時にファイルを削除
- バッチ開始時に
- メリット: DBへの追加負荷がなく、ファイルチェックだけなので高速
- デメリット: サーバーを跨ぐ場合、共有ディレクトリの構成が必要
③ 実行中プロセスを直接監視する方式(同一サーバー限定)
バッチとWebサービスが同じOS上で動いている場合に限定されますが、OSのコマンドで判定が可能です。
- 仕組み:
- Webサービス側で、
ps -ef | grep [バッチのプロセス名]等のコマンドを実行。 - プロセスが存在すれば読み取り専用にする
- Webサービス側で、
- メリット: バッチ側での「フラグを立てる・消す」という実装が不要(異常終了してもプロセスが消えれば自動で復旧する)
- デメリット: バッチとWebが別サーバーになると使えない。また、OSコマンド実行のオーバーヘッドがある
| 項目 | ステータス管理テーブル (DB) | フラグファイル | プロセス監視 |
| 確実性 | 高(DBが動いていれば必ず機能) | 中(ファイルの消し忘れ注意) | 高(実態を直接見る) |
| 拡張性 | 高(複数台構成に強い) | 中(共有ディスクが必要) | 低(同一サーバー限定) |
| 実装負荷 | 低(SQLを2つ追加するだけ) | 低 | 低 |
7. 運用上の考慮
バッチが「異常終了」した時の考慮が必要です。
「バッチが開始フラグを立てたが、エラーで途中で止まった」という場合、Webサービス等が永遠に読み取り専用から戻らなくなるリスクがあります。
対策としてバッチ処理を try-finallyで例外処理。 ブロックで囲み、成功・失敗に関わらず必ず「終了フラグ」を書き込む、あるいは運用者が手動でフラグを強制解除できる運用画面を用意しておくことが必要です。
ご参考まで。