リリース手順書とは、本番環境を変更する作業を「誰が・いつ・何を・どう確認し・うまくいかなければどう戻すか」まで決めて書いた文書です。良い手順書の条件は、①作業者以外の人が読んでも同じ操作ができる、②各ステップに期待結果と判断基準があり「次に進んでよいか」を迷わない、③切り戻しの手順と「切り戻すと決める基準」が最初から書いてある、の3つです。この3つがそろうと、当日の判断は「手順書どおりか」の確認だけになります。

この記事では、構成の早見表、コピペで使える表形式のテンプレート(Markdown)、架空の会員制タスク管理サービスの記入例を丸ごと1本に加えて、上位の解説記事にはないGo/No-Go判定表と切り戻し判断の基準、PostgreSQL 16.13 で「バックアップ取得 → マイグレーション適用 → 動作確認 → 切り戻し」を実際に実行したログを載せます。リリース方式ごとに増える項目、連絡文のテンプレート、抜け漏れチェックリストまで、この1本で手順書が書けるようにまとめました。

リリース手順書の構成早見表

章 書くこと 例 抜けると起きること
1. 概要 リリース名・バージョン、目的、変更内容の要約、対象システム、リリース方式 v2.5.0:タスクの優先度とラベル機能。一斉切替(作業枠60分) 想定外の事象が起きたとき「何のための作業か」から判断できない
2. 体制と連絡先 責任者(判断者)・作業者・確認者・連絡担当、連絡手段、エスカレーション先 判断:佐藤 / 作業:鈴木 / 確認:高橋 / 連絡:田中 深夜に「誰に聞けばいいか」で止まる
3. スケジュール 判定・開始・各作業・確認・切り戻し判断の締切・終了の予定時刻 21:30 判定 → 22:00 開始 → 22:45 切り戻し締切 → 23:00 終了 時間切れに気づかず作業枠を超える
4. 事前準備 前日までの準備(配布物、バックアップ先、権限、告知)と Go/No-Go 判定表 タグ v2.5.0 確定、切り戻しをステージングで検証済み 当日「権限がない」「ファイルがない」で始められない
5. 作業手順 表形式。No・作業・コマンドまたは操作・期待結果・判断基準・所要時間・担当・実施チェック 本記事のテンプレート 手順が記憶頼みになり、順番や確認が抜ける
6. 確認 動作確認の項目、確認用 SQL・URL、合格条件 件数一致、代表クエリが返る、画面でログインと保存 「たぶん大丈夫」で公開し翌朝に気づく
7. 切り戻し 切り戻す基準(時間・影響・データ整合)と切り戻し手順(表形式) 22:45 までに確認が終わらなければ逆マイグレーション 戻し方を当日に考えることになる
8. 連絡テンプレート 開始・完了・切り戻し(・延期)の定型文と宛先 本記事のテンプレート 連絡の内容が人によって変わり、受け手が誤解する
9. 記録 実績時刻、結果、発生した事象と対応、次回への改善 22:03 開始、22:48 完了、ロック待ち1回 次回も同じことでつまずく

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「リリース手順書の書き方|切り戻し付きテンプレートと判定表」

コピペ用テンプレート(Markdown)

まずはこのテンプレートを、Wiki・リポジトリの docs・チケットにそのまま貼って使ってください。作業手順は表形式にし、1行につき1つの操作、必ず「期待結果」と「判断基準」をセットで書くのがコツです。判断基準は「期待結果でなければ次に進まず、○○する」の形にすると、当日の迷いがなくなります。

# リリース手順書:{サービス名} v{X.Y.Z}

## 1. 概要
- リリース名 / バージョン:
- 目的(何ができるようになるか):
- 変更内容の要約(アプリ / DB / インフラ / 設定):
- 対象システム・環境:
- リリース方式:一斉切替 / メンテナンス画面 / Blue/Green / カナリア / フィーチャーフラグ
- 関連チケット・プルリクエスト:

## 2. 体制と連絡先
| 役割 | 氏名 | 連絡手段 | 備考 |
|---|---|---|---|
| リリース責任者(Go/No-Go と切り戻しの判断) | | | |
| 作業者 | | | |
| 確認者(作業者とは別の人) | | | |
| 連絡担当 | | | |
| エスカレーション先 | | | |
- 連絡チャンネル:  - 通話ブリッジ:

## 3. スケジュール
| 時刻 | 内容 | 担当 |
|---|---|---|
| | Go/No-Go 判定 | |
| | 開始連絡・作業開始 | |
| | 動作確認 | |
| | 切り戻し判断の締切(= 枠終了 − 切り戻し所要時間) | |
| | 完了連絡・作業枠終了 | |

## 4. 事前準備(前日まで)
- [ ] 配布物(タグ / イメージ / パッケージ)が確定している
- [ ] マイグレーションと切り戻し手順をステージングで実行し、戻ることを確認した
- [ ] バックアップの取得先と容量、作業者・確認者の本番権限を確認した
- [ ] 利用者向け告知・関係部署への連絡、監視の一時停止と再開の方法を確認した

### Go/No-Go 判定表
| 判定項目 | Go の基準 | 確認方法 | 判定者 | 結果 |
|---|---|---|---|---|
| テスト | | | | Go / No-Go |
| 配布物の凍結 | | | | Go / No-Go |
| 切り戻し検証 | | | | Go / No-Go |
| バックアップ | | | | Go / No-Go |
| 体制 | | | | Go / No-Go |
| 環境の状態 | | | | Go / No-Go |
| 時間 | | | | Go / No-Go |
| 周知 | | | | Go / No-Go |
1つでも No-Go なら延期し、延期の連絡を行う。

## 5. 作業手順
| No | 作業 | コマンドまたは操作 | 期待結果 | 判断基準(期待どおりでないとき) | 所要時間 | 担当 | 実施 |
|---|---|---|---|---|---|---|---|
| 1 | | | | | | | [ ] |
| 2 | | | | | | | [ ] |

## 6. 確認
| No | 確認項目 | 方法(SQL / URL / 操作) | 合格条件 | 結果 | 確認者 |
|---|---|---|---|---|---|
| C1 | | | | | |

## 7. 切り戻し
### 切り戻す基準
- 時間:{切り戻し判断の締切時刻} までに「6. 確認」が完了しない
- 影響:{主要機能} が使えない、エラー率が {基準} を超える
- データ整合:件数・合計などが事前確認の値と一致しない
### 切り戻し手順
| No | 作業 | コマンドまたは操作 | 期待結果 | 判断基準 | 所要時間 | 担当 | 実施 |
|---|---|---|---|---|---|---|---|
| R1 | | | | | | | [ ] |

## 8. 連絡テンプレート
(開始 / 完了 / 切り戻し / 延期 の定型文と宛先)

## 9. 記録
| 項目 | 内容 |
|---|---|
| 開始・終了の実績時刻 | |
| 結果(完了 / 切り戻し / 一部完了) | |
| 発生した事象と対応 | |
| 次回への改善 | |

記入例(架空の会員制タスク管理サービス)

DB のマイグレーションを伴うリリースを、テンプレートに沿って丸ごと書いた例です。作業手順のコマンドと期待結果は、後半の「実際に実行したログ」と対応しています。

# リリース手順書:TaskBoard v2.5.0

## 1. 概要
- リリース名 / バージョン:TaskBoard v2.5.0(タスクの優先度・ラベル機能)
- 目的:会員がタスクに優先度(1〜5)とラベルを付けられるようにする
- 変更内容の要約:
  - アプリ:API と画面の追加(v2.4.0 → v2.5.0)
  - DB:tasks に priority 列を追加、labels / task_labels テーブルを追加、
        期限3日以内の未完了タスクの priority を 1 に更新(既存データ移行)
  - 設定:環境変数 FEATURE_LABELS=true を追加
- 対象システム・環境:本番 Web サーバー 2台、PostgreSQL 16.13(DB t67)
- リリース方式:一斉切替(メンテナンス画面あり、作業枠 60 分)
- 関連チケット:TB-412、PR #388(アプリ)、PR #391(マイグレーション)

## 2. 体制と連絡先
| 役割 | 氏名 | 連絡手段 | 備考 |
|---|---|---|---|
| リリース責任者 | 佐藤 | #release-taskboard / 携帯 | Go/No-Go と切り戻しを判断 |
| 作業者 | 鈴木 | 同上 | コマンド実行 |
| 確認者 | 高橋 | 同上 | 確認 SQL と画面確認。作業者と別の人 |
| 連絡担当 | 田中 | 同上 | 開始・完了・切り戻しの連絡 |
| エスカレーション先 | 開発部長 | 携帯 | 23:00 を超える見込みのとき |
- 連絡チャンネル:#release-taskboard(時刻つきで逐次投稿) - 通話ブリッジ:22:00〜常時接続

## 3. スケジュール(2026-10-08 木)
| 時刻 | 内容 | 担当 |
|---|---|---|
| 21:30 | Go/No-Go 判定 | 佐藤 |
| 21:50 | 開始連絡 | 田中 |
| 22:00 | メンテナンス画面へ切替、アプリ停止 | 鈴木 |
| 22:05 | 事前確認 SQL、バックアップ取得 | 鈴木 / 高橋 |
| 22:20 | マイグレーション適用、アプリ v2.5.0 起動 | 鈴木 |
| 22:30 | 動作確認(SQL・画面) | 高橋 |
| 22:45 | 切り戻し判断の締切(切り戻し所要 15 分) | 佐藤 |
| 22:50 | メンテナンス画面解除、完了連絡 | 鈴木 / 田中 |
| 23:00 | 作業枠終了 | 全員 |

## 4. 事前準備(前日まで)
- [x] タグ v2.5.0(コミット 8c1d2f0)を確定。10/6 以降の変更なし
- [x] ステージングでマイグレーション適用 → 逆マイグレーションを実行し、元に戻ることを確認(10/6、鈴木)
- [x] バックアップ先 /backup の空き容量 120GB(DB サイズ 8GB)。鈴木・高橋の DB 接続(ロール t67)を確認
- [x] 利用者向け告知(10/1 掲示)、サポート窓口へ連絡(10/7)、監視のメンテナンスモード(22:00〜23:00)を予約

### Go/No-Go 判定表(21:30 実施)
| 判定項目 | Go の基準 | 確認方法 | 判定者 | 結果 |
|---|---|---|---|---|
| テスト | ステージングの受け入れテスト 48 件が全件合格 | テスト報告書 | 高橋 | Go |
| 配布物の凍結 | タグ v2.5.0 のコミットが PR #388 のマージ結果と一致 | git tag -v | 鈴木 | Go |
| 切り戻し検証 | ステージングで逆マイグレーションを実行済み | 実施記録 10/6 | 鈴木 | Go |
| バックアップ | 取得先の空き容量が DB サイズの 3 倍以上 | df -h | 鈴木 | Go |
| 体制 | 4 名全員が 21:30 に集合、連絡先が最新 | 体制表 | 佐藤 | Go |
| 環境の状態 | 未対応のアラートがなく、1 分以上のトランザクションがない | 監視画面、事前確認 SQL | 高橋 | Go |
| 時間 | 作業+確認+切り戻し(30+15+15 分)が枠 60 分に収まる | スケジュール | 佐藤 | Go |
| 周知 | 告知が掲示され、サポート窓口が把握している | 掲示の記録 | 田中 | Go |

## 5. 作業手順
| No | 作業 | コマンドまたは操作 | 期待結果 | 判断基準(期待どおりでないとき) | 所要時間 | 担当 | 実施 |
|---|---|---|---|---|---|---|---|
| 1 | 開始連絡 | #release-taskboard に「開始」定型文を投稿 | 投稿される | — | 1分 | 田中 | [ ] |
| 2 | メンテナンス画面へ切替、アプリ停止 | LB でメンテナンス画面を有効化 → systemctl stop taskboard(2台) | トップがメンテナンス画面。systemctl status が inactive | 2回で止まらなければ責任者へ | 3分 | 鈴木 | [ ] |
| 3 | 事前確認 SQL | psql -f 05_precheck.sql | version=20260915_002_add_due_on、tasks_count=1000、long_transactions=0 | version が違えば中止。long_transactions が 0 でなければ終了を待つ | 2分 | 高橋 | [ ] |
| 4 | バックアップ取得 | pg_dump -Fc -f backup_before_v2.5.0_日時.dump → ls -lh → pg_restore --list | ファイルが作られ、--list に tasks の TABLE DATA がある | 取れなければ中止(切り戻し手段がないため) | 5分 | 鈴木 | [ ] |
| 5 | 配布物の配置 | v2.5.0 を 2 台に配置、.env に FEATURE_LABELS=true を追加 | 配置先のバージョン表示が 2.5.0 | 配置できない台があれば両方 v2.4.0 のまま中止 | 5分 | 鈴木 | [ ] |
| 6 | マイグレーション適用 | psql -v ON_ERROR_STOP=1 --single-transaction -f 10_migrate_v2.5.0.sql | 終了コード 0。UPDATE 216、INSERT 0 1 で終わる | 終了コード 3 なら何も適用されていない。原因が 5 分で分からなければ R1 へ | 3分 | 鈴木 | [ ] |
| 7 | 動作確認 SQL | psql -f 20_verify.sql | C1〜C6 がすべて期待値 | 1 つでも違えば R1 へ | 3分 | 高橋 | [ ] |
| 8 | アプリ v2.5.0 起動 | systemctl start taskboard(2台)→ ヘルスチェック | /healthz が 200、version=2.5.0 | 1 台でも起動しなければ R1 へ | 3分 | 鈴木 | [ ] |
| 9 | 画面での動作確認 | 確認用アカウントでログイン → タスク作成 → 優先度とラベルを設定して保存 | 一覧に優先度とラベルが表示される(C7) | 保存できなければ R1 へ | 5分 | 高橋 | [ ] |
| 10 | 切り戻し判断 | 22:45 時点で No.7〜9 が完了しているか | 完了している | 未完了なら R1 へ | — | 佐藤 | [ ] |
| 11 | メンテナンス画面解除、完了連絡 | LB のメンテナンス画面を無効化 → 定型文を投稿 | トップが通常画面 | 通常画面にならなければ LB 設定を再確認 | 3分 | 鈴木 / 田中 | [ ] |
| 12 | 監視再開、記録 | メンテナンスモード解除 → 実績時刻を「9. 記録」に記入 | 15 分間エラー率 1% 未満 | 基準を超えたら責任者へ | 15分 | 高橋 | [ ] |

## 6. 確認
| No | 確認項目 | 方法 | 合格条件 | 結果 | 確認者 |
|---|---|---|---|---|---|
| C1 | マイグレーションの記録 | SELECT version ... LIMIT 1 | 20261001_003_priority_labels | | 高橋 |
| C2 | priority 列の定義 | information_schema.columns | smallint / NOT NULL / default 3 | | 高橋 |
| C3 | tasks の件数 | SELECT count(*) FROM tasks | 事前確認と同じ 1000 | | 高橋 |
| C4 | priority の分布 | GROUP BY priority | 1 と 3 のみ。NULL なし | | 高橋 |
| C5 | 初期ラベル | SELECT count(*) FROM labels | 3 | | 高橋 |
| C6 | 代表クエリ | 未完了タスクを priority 順に 3 件 | 3 行返る | | 高橋 |
| C7 | 画面 | ログイン → 作成 → 優先度・ラベル設定 → 保存 | 一覧に反映 | | 高橋 |

## 7. 切り戻し
### 切り戻す基準
- 時間:22:45 までに C1〜C7 が完了しない
- 影響:ログイン・タスク作成・保存のいずれかができない
- データ整合:C3 が 1000 と一致しない、C4 に NULL や 1・3 以外の値がある
### 切り戻し手順(所要 15 分)
| No | 作業 | コマンドまたは操作 | 期待結果 | 判断基準 | 所要時間 | 担当 | 実施 |
|---|---|---|---|---|---|---|---|
| R1 | 切り戻し連絡 | 定型文を投稿 | 投稿される | — | 1分 | 田中 | [ ] |
| R2 | アプリ停止 | systemctl stop taskboard(2台) | inactive | — | 2分 | 鈴木 | [ ] |
| R3 | DB を戻す(切替後に会員の書き込みがない場合) | psql -v ON_ERROR_STOP=1 --single-transaction -f 30_rollback_reverse.sql | 終了コード 0、\dt に labels がない、version=20260915_002_add_due_on | エラーなら R3' へ | 2分 | 鈴木 | [ ] |
| R3' | DB を戻す(書き込みが進んだ場合) | 新しい DB に pg_restore → 確認 → ALTER DATABASE で名前を入れ替え | 入れ替え後の t67 で version=…002、tasks_count=1000 | 復元先の確認が通らなければ責任者へ | 8分 | 鈴木 | [ ] |
| R4 | アプリ v2.4.0 起動、FEATURE_LABELS を削除 | systemctl start taskboard(2台) | /healthz が 200、version=2.4.0 | — | 3分 | 鈴木 | [ ] |
| R5 | 動作確認、メンテナンス画面解除 | C3・C7 相当を v2.4.0 で確認 → LB 解除 | 通常画面でログインと保存ができる | — | 3分 | 高橋 | [ ] |

## 8. 連絡テンプレート
(本記事「連絡文のテンプレート」の 3 種を使用。宛先:#release-taskboard、サポート窓口)

## 9. 記録
| 項目 | 内容 |
|---|---|
| 開始・終了の実績時刻 | |
| 結果 | |
| 発生した事象と対応 | |
| 次回への改善 | |

Go/No-Go 判定表と、切り戻し判断の基準

Go/No-Go 判定は「始めてよいか」、切り戻し判断は「続けてよいか」を決めるものです。どちらも基準を事前に文章にしておくと、当日の判断が「気持ち」ではなく「事実」で決まります。

Go/No-Go 判定表(汎用)

判定項目 Go の基準 確認方法 判定者
テスト ステージングで受け入れテストが全件合格。残った既知の問題は「リリース可」と判断済み テスト報告書、チケット QA・確認者
配布物の凍結 リリース対象のタグ・イメージが確定し、それ以降の変更が入っていない タグのコミット ID を照合 開発リード
切り戻し検証 切り戻し手順をステージングで実際に実行し、元に戻ることを確認済み 実施記録(日付・実施者) 開発リード
バックアップ 直前バックアップが取得できる(容量・権限・所要時間を確認済み) df、前回の所要時間 DB 担当
体制 責任者・作業者・確認者・連絡担当が全員そろい、連絡先が最新 体制表 リリース責任者
環境の状態 未対応のアラートがない。長時間トランザクションや大きなバッチが動いていない 監視画面、pg_stat_activity 運用担当
時間 「作業+確認+切り戻し」の合計が作業枠に収まる スケジュール リリース責任者
周知 利用者向け告知と関係部署への連絡が済んでいる 告知の記録 連絡担当

1つでも No-Go なら延期です。「たぶん間に合う」「後で確認する」は No-Go に含めます。延期の連絡文を用意しておけば(後述)、決めるのは1分で済みます。

切り戻し判断の基準(時間・影響・データ整合)

観点 切り戻す 継続する(前方修正で対応する)
時間 「切り戻し判断の締切(= 作業枠の終了 − 切り戻しの所要時間)」までに確認が終わらない 締切前に確認が完了した
影響 主要機能(ログイン・作成・保存など)が使えない。エラー率・レイテンシが基準を超えている 軽微な表示の問題で、回避策があり翌営業日の修正で足りる
データ整合 件数・合計が事前確認の値と一致しない。NULL・重複・想定外の値が出た 確認 SQL がすべて期待値どおり
原因の見通し 5〜10 分で原因が特定できない 原因が分かり、修正が小さく、確認手順を再利用できる

切り戻しの方式は「切替後に書き込みが進んだかどうか」で決めます。進んでいなければ逆マイグレーション(速くて確実)、進んでいれば「新しい書き込みをどう扱うか」を責任者が決めたうえでバックアップから復元します。この判断も、当日ではなく手順書を書く時点で決めておきます。

flowchart TD
  A[Go/No-Go 判定] -->|1つでも No-Go| B[延期して連絡]
  A -->|すべて Go| C[作業手順を実施]
  C --> D{各ステップの期待結果どおり?}
  D -->|はい| E[確認 C1〜Cn]
  D -->|いいえ| F{判断基準: 時間・影響・データ整合}
  E -->|締切までに全項目合格| G[完了連絡・監視再開・記録]
  E -->|不合格 or 締切超過| F
  F -->|書き込みが進んでいない| H[逆マイグレーション → 旧アプリ起動]
  F -->|書き込みが進んだ| I[新DBへ復元 → 確認 → 入れ替え → 旧アプリ起動]
  H --> J[切り戻し連絡・記録]
  I --> J

DB マイグレーションを伴うリリースを実際に実行したログ

ここからは、記入例の No.3〜7 と切り戻し R3・R3' を、PostgreSQL 16.13 の検証用データベース(t67)で実際に実行した結果です。題材は記入例と同じ「tasks に priority 列とラベル機能を追加する v2.5.0」で、tasks には 1,000 行入っています。時刻はすべて JST です。

使うファイル

マイグレーションは 1 ファイル 1 トランザクションで書き、先頭で lock_timeout を設定します。

-- 10_migrate_v2.5.0.sql
-- 実行: psql -v ON_ERROR_STOP=1 --single-transaction -f 10_migrate_v2.5.0.sql
SET lock_timeout = '3s';   -- ロック待ちで固まらないよう、3秒で諦めてエラーにする

ALTER TABLE tasks
  ADD COLUMN priority smallint NOT NULL DEFAULT 3
  CHECK (priority BETWEEN 1 AND 5);

CREATE TABLE labels (
  id    serial PRIMARY KEY,
  name  text NOT NULL UNIQUE,
  color text NOT NULL
);

CREATE TABLE task_labels (
  task_id  bigint NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
  label_id int    NOT NULL REFERENCES labels(id),
  PRIMARY KEY (task_id, label_id)
);

INSERT INTO labels (name, color) VALUES
  ('急ぎ', '#d9534f'), ('レビュー待ち', '#f0ad4e'), ('あとで', '#5bc0de');

-- 既存データの移行: 期限が3日以内の未完了タスクは優先度を 1 にする
UPDATE tasks SET priority = 1
 WHERE status = 'open' AND due_on <= date '2026-10-01' + 3;

INSERT INTO schema_migrations (version) VALUES ('20261001_003_priority_labels');
-- 20_verify.sql(動作確認。期待結果はコメントに書いておく)
-- C1 → 20261001_003_priority_labels
SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;
-- C2 → priority / smallint / NO / 3
SELECT column_name, data_type, is_nullable, column_default
  FROM information_schema.columns
 WHERE table_name = 'tasks' AND column_name = 'priority';
-- C3 → 1000(事前確認の値と一致)
SELECT count(*) AS tasks_count FROM tasks;
-- C4 → 1 と 3 のみ。NULL なし
SELECT priority, count(*) FROM tasks GROUP BY priority ORDER BY priority;
-- C5 → 3
SELECT count(*) AS labels_count FROM labels;
-- C6 → 3 行返る
SELECT t.id, t.title, t.priority FROM tasks t
 WHERE t.status = 'open' ORDER BY t.priority, t.due_on LIMIT 3;
-- 30_rollback_reverse.sql(逆マイグレーション。適用と逆の順番で消す)
SET lock_timeout = '3s';
DROP TABLE task_labels;
DROP TABLE labels;
ALTER TABLE tasks DROP COLUMN priority;
DELETE FROM schema_migrations WHERE version = '20261001_003_priority_labels';

No.3 事前確認:今の版・件数・長いトランザクション

### STEP 3 事前確認(Go/No-Go の材料)  [2026-10-01 07:07:54 JST]
         version
-------------------------
 20260915_002_add_due_on
 tasks_count
-------------
        1000
 long_transactions
-------------------
                 0
 db_size
---------
 7967 kB

ここで取った「version」「件数」が、後の確認と切り戻しの比較対象になります。長いトランザクション(pg_stat_activity で xact_start が 1 分より前)が 0 件であることも、DDL がロック待ちにならない根拠として記録します。

No.4 バックアップ取得:ファイルと中身を確認する

pg_dump -h 127.0.0.1 -U t67 -d t67 -Fc -f backup_before_v2.5.0_$(date +%Y%m%d_%H%M).dump
ls -lh backup_before_v2.5.0_*.dump
pg_restore --list backup_before_v2.5.0_*.dump | grep 'TABLE DATA'
### STEP 4 バックアップ取得  [2026-10-01 07:07:54 JST]
real    0m0.104s
-rw-r--r-- 1 root root 14K Oct  1 07:07 backup_before_v2.5.0_20261001_0707.dump
3416; 0 90499 TABLE DATA public members t67
3418; 0 90506 TABLE DATA public schema_migrations t67
3419; 0 90512 TABLE DATA public tasks t67

「取った」だけでなく「中身がある」まで確認するのが手順書の書き方です。pg_restore --list で対象テーブルの TABLE DATA が並んでいれば、復元に使えるファイルだと分かります。ファイル名に日時を入れておくと、切り戻し時にどれを使うか迷いません。

No.6 マイグレーション適用:1 トランザクションで、エラーなら何も残さない

psql -h 127.0.0.1 -U t67 -d t67 -v ON_ERROR_STOP=1 --single-transaction -f 10_migrate_v2.5.0.sql
echo "exit code: $?"
### STEP 6 マイグレーション適用  [2026-10-01 07:07:54 JST]
SET
ALTER TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 3
UPDATE 216
INSERT 0 1
real    0m0.077s
exit code: 0

--single-transaction は、psql の公式ドキュメントにあるとおり「最初のコマンドの前に BEGIN、最後の後に COMMIT を発行し、ON_ERROR_STOP が設定されていれば途中でエラーになったときに ROLLBACK を送る」オプションです。手順書の期待結果には「終了コード 0」「最後の行が INSERT 0 1」のように、機械的に照合できるものを書きます。psql は正常終了で 0、スクリプト内のエラーで ON_ERROR_STOP が有効なら 3 を返します。

No.7 動作確認 SQL:C1〜C6 を期待値と照合する

### STEP 7 動作確認 SQL  [2026-10-01 07:07:54 JST]
           version
------------------------------
 20261001_003_priority_labels
 column_name | data_type | is_nullable | column_default
-------------+-----------+-------------+----------------
 priority    | smallint  | NO          | 3
 tasks_count
-------------
        1000
 priority | count
----------+-------
        1 |   216
        3 |   784
 labels_count
--------------
            3
 id  |         title         | priority
-----+-----------------------+----------
  14 | 週次レポート作成 #14  |        1
 994 | 週次レポート作成 #994 |        1
  42 | 週次レポート作成 #42  |        1

件数 1,000 が事前確認と一致し、priority は 1(216 件)と 3(784 件)だけ、NULL なし。UPDATE の件数 216 と分布の 216 が一致していることも、データ移行が想定どおりだった根拠になります。

切り戻し R3:逆マイグレーション(書き込みが進んでいない場合)

### 切り戻し A: 逆マイグレーション  [2026-10-01 07:07:54 JST]
SET
DROP TABLE
DROP TABLE
ALTER TABLE
DELETE 1
real    0m0.125s
exit code: 0
 Schema |       Name        | Type  | Owner
--------+-------------------+-------+-------
 public | members           | table | t67
 public | schema_migrations | table | t67
 public | tasks             | table | t67
         version
-------------------------
 20260915_002_add_due_on
 tasks_count
-------------
        1000

逆マイグレーションは「適用と逆の順番で消す」だけなので数秒で終わり、テーブル一覧・version・件数が事前確認の値に戻りました。PostgreSQL は DDL もトランザクションで扱えるため、途中でエラーになっても中途半端な状態は残りません(公式ドキュメントのとおり DROP COLUMN は列を見えなくするだけで、領域は後から回収されます)。

切り戻し R3':バックアップからの復元(書き込みが進んだ場合)

切替後に新アプリが task_labels へ 50 行書き込み、tasks の 10 行を更新した状態を作ってから、復元を試しました。まず、同じ DB に --clean で上書き復元すると何が起きるかです。

pg_restore -h 127.0.0.1 -U t67 -d t67 --clean --if-exists backup_before_v2.5.0_20261001_0707.dump
pg_restore: error: could not execute query: ERROR:  cannot drop table public.tasks because other objects depend on it
DETAIL:  constraint task_labels_task_id_fkey on table public.task_labels depends on table public.tasks
HINT:  Use DROP ... CASCADE to drop the dependent objects too.
Command was: DROP TABLE IF EXISTS public.tasks;
pg_restore: error: could not execute query: ERROR:  relation "tasks" already exists
pg_restore: error: COPY failed for table "tasks": ERROR:  duplicate key value violates unique constraint "tasks_pkey"
pg_restore: warning: errors ignored on restore: 5
exit code: 1
 Schema |       Name        | Type  | Owner
--------+-------------------+-------+-------
 public | labels            | table | t67
 public | members           | table | t67
 public | schema_migrations | table | t67
 public | task_labels       | table | t67
 public | tasks             | table | t67
         version
-------------------------
 20260915_002_add_due_on
 tasks_count | updated_rows
-------------+--------------
        1000 |           10

バックアップは「リリース前」の状態なので、リリースで新しく作った task_labels を知りません。その task_labels が tasks を参照しているため DROP TABLE tasks が通らず、schema_migrations だけが戻って tasks は新しいまま、という「版の記録と実体がずれた状態」になりました。pg_restore はエラーがあっても既定では続行し、最後に件数を報告するだけです(--exit-on-error で止められます)。

手順書には「別の新しい DB に復元し、確認してから名前を入れ替える」と書いておきます。入れ替え前の DB は t67_old として残るので、新しい書き込みの調査にも使えます。

psql -h 127.0.0.1 -U t67 -d postgres -c "CREATE DATABASE t67_restore OWNER t67;"
pg_restore -h 127.0.0.1 -U t67 -d t67_restore --exit-on-error backup_before_v2.5.0_20261001_0707.dump
# 復元先を確認してから、アプリ停止中(接続なし)に名前を入れ替える
psql -h 127.0.0.1 -U t67 -d postgres -v ON_ERROR_STOP=1 \
  -c "ALTER DATABASE t67 RENAME TO t67_old;" \
  -c "ALTER DATABASE t67_restore RENAME TO t67;"
### B-2 別の新しいDBに復元し、確認してから名前を入れ替える  [2026-10-01 07:09:48 JST]
CREATE DATABASE
real    0m0.060s
exit code: 0
--- 復元先の確認(切り替え前)
         version
-------------------------
 20260915_002_add_due_on
 tasks_count | updated_rows
-------------+--------------
        1000 |            0
--- 名前を入れ替えて切り替え(アプリ停止中・接続なしの状態で実施)
ALTER DATABASE
ALTER DATABASE
 current_database |         version         | tasks_count
------------------+-------------------------+-------------
 t67              | 20260915_002_add_due_on |        1000

ALTER DATABASE ... RENAME は、その DB に接続しているセッションがあると実行できません。切り戻し手順で R2「アプリ停止」が R3' より前にあるのは、このためです。

切り戻し方式 向いている状況 所要時間の目安 手順書に書くこと
逆マイグレーション 切替後に書き込みが進んでいない。スキーマ変更が可逆(列追加・テーブル追加) 分単位 逆順の SQL、version の戻り先、確認 SQL
バックアップから復元 書き込みが進んだが、その書き込みを捨ててよい(責任者が判断) DB サイズに比例 復元先 DB の作り方、入れ替え手順、旧 DB の保持期間
前方修正 原因が明確で修正が小さく、確認手順を再利用できる 修正内容による 「前方修正を選ぶ条件」と、修正版の確認手順
データの部分復元 特定テーブルだけ戻したい テーブルサイズに比例 pg_restore -t テーブル名 の対象と、外部キーの順序

つまずく箇所①:DEFAULT なしの NOT NULL 列追加はエラーになり、全体が巻き戻る

DEFAULT 3 を付け忘れると、既存の 1,000 行に入れる値がないためエラーになります。

CREATE TABLE
psql:10_migrate_v2.5.0_ng.sql:9: ERROR:  column "priority" of relation "tasks" contains null values
exit code: 3
 Schema |       Name        | Type  | Owner
--------+-------------------+-------+-------
 public | members           | table | t67
 public | schema_migrations | table | t67
 public | tasks             | table | t67

先に成功した CREATE TABLE labels も一緒に巻き戻り、\dt に labels は残っていません。--single-transaction と ON_ERROR_STOP を組み合わせているからで、この 2 つがないと「labels だけできていて priority がない」状態で止まり、再実行すると今度は relation "labels" already exists で止まります。手順書の判断基準に「終了コード 3 なら何も適用されていないので、原因を直して再実行できる」と書けるのは、この組み合わせのおかげです。

なお PostgreSQL 16 の公式ドキュメントによると、揮発性でない DEFAULT を付けた列追加はテーブルの書き換えを伴わず、既存行にはメタデータ上の既定値が使われます。1,000 行でも 1 億行でも ADD COLUMN ... DEFAULT 3 は一瞬で終わるため、所要時間の見積もりにも効きます(型変更や揮発性の DEFAULT は書き換えが起きます)。

つまずく箇所②:ロック待ちで固まる → lock_timeout で「エラーにして気づく」

別のセッションが tasks を読んでいる途中(トランザクションが開いたまま)で、マイグレーションを実行した例です。ALTER TABLE は ACCESS EXCLUSIVE ロックを取るため、読み取りのロックが解放されるまで待たされます。

SET
psql:10_migrate_v2.5.0.sql:7: ERROR:  canceling statement due to lock timeout
real    0m3.059s
exit code: 3
--- 塞いでいるセッションを探す
 pid  | state  |    xact_age     |                     query
------+--------+-----------------+-----------------------------------------------
 7754 | active | 00:00:04.080177 | BEGIN; SELECT count(*) FROM tasks; SELECT pg_

lock_timeout がないと、この ALTER は相手が終わるまで無言で待ち続け、しかも待っている間は後続のすべてのクエリ(SELECT も含む)がその後ろに並びます。3 秒で諦める設定にしておくと、「待ちが発生した」事実が終了コード 3 として判断基準に乗り、pg_stat_activity で相手を確認してから再実行できます。これが記入例の No.3 で長時間トランザクションを確認している理由です。

手順書に書く SQL の工夫 効果
SET lock_timeout = '3s' を先頭に ロック待ちで固まらず、エラーとして検知できる
psql -v ON_ERROR_STOP=1 --single-transaction 途中でエラーになったら全部巻き戻る。再実行が安全
schema_migrations に版を記録 事前確認・動作確認・切り戻しの比較対象になる
確認 SQL のコメントに期待値 確認者が「期待どおりか」を読むだけで判定できる
バックアップ名に日時 切り戻しで使うファイルを迷わない
CREATE INDEX CONCURRENTLY などは別ファイルに トランザクション内で実行できない文があると --single-transaction 全体がエラーになるため

リリース方式の選択表と、方式ごとに手順書へ増える項目

方式が変わると、手順書に書く「切替」「確認」「切り戻し」の中身が変わります。小さなサービスで DB のスキーマ変更を伴うなら一斉切替(メンテナンス枠)が最も手順が短く、利用者を止めたくないなら Blue/Green やカナリアを選びます。

方式 向いている場面 停止時間 切り戻しの速さ 手順書に増える項目
一斉切替(メンテナンス枠) DB スキーマ変更、小〜中規模、深夜の作業枠が取れる あり 中(逆マイグレーション) メンテナンス開始・終了の告知、アクセス遮断と解除、作業枠内の時間管理
メンテナンス画面 一斉切替の派生。利用者に状況を案内したい あり 中 メンテナンス画面の切替と戻し、ヘルスチェックの除外、CDN・ブラウザキャッシュの扱い
Blue/Green 2 系統を用意し、確認後に切替。すぐ戻したい ほぼなし 速い(切替を戻す) 両系統のデプロイ確認、切替方法(DNS / ロードバランサー)、旧系統の保持期間、DB の前方・後方互換の確認
カナリア 一部の利用者で確認してから広げる なし 速い(比率を 0 に) 段階(例:1% → 10% → 50% → 100%)ごとの判断基準と観測時間、対象の選び方、比較する指標
フィーチャーフラグ コードは先に配布し、機能は後で有効化する なし 最速(フラグを戻す) フラグ名・既定値・切替手順と権限、フラグを削除する予定日

Blue/Green・カナリア・フィーチャーフラグでは、新旧のアプリが同じ DB を同時に使う時間があります。そのため DB の変更は「追加してから、後で削除する」の 2 段階(expand / contract)に分け、旧アプリが新しい列を無視しても動くこと(後方互換)を確認項目に加えます。今回の例の priority 列は DEFAULT 3 があるので、旧アプリ(v2.4.0)が priority を知らずに INSERT しても動きます。

連絡文のテンプレート(開始・完了・切り戻し)

連絡文は「今どの状態か」「利用者への影響」「次の連絡はいつか」の 3 点を必ず入れます。定型文にしておくと、慌てているときほど効きます。

【リリース開始】TaskBoard v2.5.0
- 日時:2026-10-08 22:00 開始(予定終了 23:00)
- 内容:タスクの優先度・ラベル機能の追加
- 影響:22:00〜22:50 はメンテナンス画面を表示し、サービスを利用できません
- 状況:Go/No-Go 判定はすべて Go。作業を開始します
- 次の連絡:完了時(遅くとも 23:00)。遅れる場合は 22:45 に連絡します
- 連絡先:#release-taskboard(担当:田中)
【リリース完了】TaskBoard v2.5.0
- 完了時刻:2026-10-08 22:48
- 結果:すべての確認項目(C1〜C7)が合格。メンテナンス画面を解除しました
- 利用者への案内:優先度とラベルが設定できるようになりました。表示が更新されない場合はページを再読み込みしてください
- 監視:23:05 まで強化監視を継続します
【切り戻し】TaskBoard v2.5.0
- 判断時刻:2026-10-08 22:45(判断者:佐藤)
- 理由:確認項目 C7(画面での保存)が完了せず、切り戻し判断の締切に達したため
- 対応:v2.4.0 に戻し、DB は逆マイグレーションで 22:05 時点の状態に戻しました
- 影響:メンテナンス画面中のため、22:00〜23:00 の間に行われた操作はありません
- 今後:原因を確認のうえ、再リリース日を 10/9 中に連絡します
- 次の連絡:23:00 に状況を更新します

延期の連絡は、開始の連絡文の「状況」を「Go/No-Go 判定で{項目}が No-Go のため、{日付}に延期します。利用者への影響はありません」に置き換えるだけで作れます。

よくある抜け漏れチェックリスト

手順書レビューで実際によく見つかる抜けを、事前準備の項目として並べました。「該当なし」も明示的に書くと、レビューで「忘れたのか、ないのか」が分かります。

項目 確認すること 抜けると起きること
キャッシュ アプリ内キャッシュ、CDN、ブラウザキャッシュの削除・無効化と、その戻し 新しい画面が一部の利用者に届かない、以前の JS が新 API を呼ぶ
cron・定期ジョブ 作業中に動くジョブの一時停止と再開。停止中に飛ばしたジョブの扱い マイグレーション中にバッチが走りロック待ちになる、再開を忘れて翌日のジョブが動かない
環境変数・設定・シークレット 追加・変更・削除する値と、置く場所(全台)、切り戻し時の値 1 台だけ設定が違い、断続的なエラーになる
DNS の TTL 切替を DNS で行うなら、事前に TTL を短くする時期と戻す時期 切替・切り戻しが利用者に届くまで TTL の分だけかかる
外部連携の切替 Webhook の URL、コールバック先、API キー、相手側の設定変更のタイミング 相手側が旧 URL に送り続ける、切り戻し時に相手側も戻す必要がある
監視の一時停止と再開 メンテナンスモードの予約と解除、解除後に確認するアラート 作業中の通知で関係者が驚く、再開を忘れて障害に気づかない
証明書・ドメイン 新しいホストや系統の証明書、SAN の追加 Blue/Green の新系統で証明書エラー
権限 作業者の本番アクセス、DB ロールの権限(CREATE、ALTER)、配置先の書き込み権限 当日「権限がない」で止まる
旧バージョンの残り 旧プロセス・旧コンテナ・旧ジョブが残っていないか 旧アプリが新スキーマに書き込み続ける
通知メール・プッシュ 作業中に送られる通知の抑止と、抑止解除後の再送の扱い 利用者に大量の通知が届く
時刻・タイムゾーン スケジュールと記録の時刻にタイムゾーンを明記 サーバー時刻(UTC)との取り違え
作業ログの保存 ターミナルのログ(script コマンドなど)を残す場所 記録に実績を書けない

よくある質問

Q. 手順書はどのくらい細かく書けばよいですか? A. 「作業者以外の人が、その手順書だけで同じ操作ができる」が目安です。コマンドは省略せずに書き、GUI の操作は画面名とボタン名まで書きます。背景の説明や設計の理由は概要に 1〜2 行で十分です。

Q. 切り戻し手順は毎回検証する必要がありますか? A. DB のスキーマ変更やデータ移行を含むリリースは、ステージングで「適用 → 切り戻し → 再適用」まで実行してから Go にします。アプリの入れ替えだけで前回と同じ切り戻し手順なら、検証記録を流用できます。検証した日付と実施者を Go/No-Go 判定表に書きます。

Q. リリース手順書と、デプロイの自動化(CI/CD)は両立しますか? A. 両立します。自動化されている部分は「パイプライン名と起動方法、成功の判定方法、エラー時の止め方」を書き、人が判断する部分(Go/No-Go、切り戻し判断、連絡)を手順書に残します。自動化が進むほど、手順書は「判断と連絡の書類」になっていきます。

Q. 作業枠が短くて切り戻しの時間が取れません。 A. 「作業+確認+切り戻し」が枠に収まらなければ、その枠では No-Go です。対策は、枠を延ばす、作業を分割する(DB の列追加を前日に先行させる)、フィーチャーフラグで切替を後回しにする、の 3 つです。切り戻しの時間を削るのではなく、作業の側を小さくします。

まとめ

  • リリース手順書の条件は、他の人が同じ操作をできる・各ステップに期待結果と判断基準がある・切り戻しの手順と基準が最初からあるの 3 つ
  • 構成は 概要・体制と連絡先・スケジュール・事前準備・作業手順・確認・切り戻し・連絡テンプレート・記録 の 9 章
  • 作業手順は No・作業・コマンド・期待結果・判断基準・所要時間・担当・実施チェック の表形式で、1 行 1 操作
  • Go/No-Go は 1 つでも No-Go なら延期。切り戻し判断は 時間(締切)・影響・データ整合 の 3 観点で事前に決める
  • DB マイグレーションは ON_ERROR_STOP + --single-transaction + lock_timeout で、エラーになっても何も残らず再実行できる形にする
  • バックアップからの復元は 同じ DB へ上書きせず、新しい DB に復元して確認してから入れ替える
  • 連絡文は 状態・影響・次の連絡時刻 の 3 点。抜け漏れは キャッシュ・cron・環境変数・DNS TTL・外部連携・監視 から確認する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「リリース手順書の書き方|切り戻し付きテンプレートと判定表」

株式会社RJCでは、リリース手順書のように「当日の判断を事前に文章にしておく」進め方を、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?