More Related Content Similar to Webアプリを並行開発する際のマイグレーション戦略 (20) More from Takayuki Shimizukawa (20) Webアプリを並行開発する際のマイグレーション戦略2. @shimizukawa (清水川)
● BeProud 取締役 / IT Architect
○ 受託開発(Webアプリ / 機械学習 / 数理最適化)
○ 自社サービス (connpass / PyQ / TRACERY)
○ Python研修(Python基礎、Django、Pandas、その他)
● 一般社団法人PyCon JP Association 会計理事
○ PyCon JP 年次イベントの見守り
○ Python Boot Camp 主催
● Sphinx (コミッター休業中)
おまえ誰よ / Who are you
2
3. 1版: 2010/5/28
2版: 2018/2/26
3版: 2021/7/30
1版: 2013/09/12
2版: 2017/10/20
2018/02/23
2020/02/27
1版: 2012/03/27
2版: 2015/02/27
3版: 2018/06/12
執筆・翻訳した書籍
3
NEW
年
8. 🏃SQL(DDL)によるスキーマ定義の変更≤
● SQL = Structured Query Language = スキーマの管理や操作を行う言語
● DDL = Data Definition Language データ定義言語
○ CREATE, DROP, ALTER, …
8
CREATE TABLE "customers" (
"id" bigserial NOT NULL PRIMARY KEY,
"name" varchar(128) NOT NULL,
...
);
「難しい・・SQL覚えたくない・・」(大分後で覚えました)
「DBにデータを格納するには、 DB用の言語を使う必要があるんだね」
9. 🏃Django ORMによる作成・変更
from django.db import models
class Customer(models.Model):
name = models.CharField(max_length=128)
...
9
$ python manage.py makemigrations
$ python manage.py migrate
「Pythonなら読める・・分かるぞ!」
「DBにテーブルが作成された! SQL知らなくても大丈夫だ!」( ※ダメです)
11. 🏃Djangoのmigrationsファイル
↓開発が進んだ時のmigrations
11
$ python manage.py showmigrations
app1
[X] 0001_initial
[X] 0002_setup_rls_role
[X] 0003_grant_all
[X] 0004_setup_rls_policy
[X] 0005_datamigration
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
もうちょっと進んだ時のマイグレーショングラフ↑
12. 🏃マイグレーションの種類
スキーママイグレーション
● テーブル構造を変更するDDLを発行する
データマイグレーション
● データ変更を行うDMLを発行する
● スキーマ変更に合わせて、データの移動や
設定を行う
12
users
id name birthday
1 佐藤 19xx/xx/xx
2 鈴木 19xx/xx/xx
3 清水川 1975/05/22
users
id name birthday age
1 佐藤 19xx/xx/xx NULL
2 鈴木 19xx/xx/xx NULL
3 清水川 1975/05/22 NULL
スキーママイグレーション
データマイグレーション
users
id name birthday age
1 佐藤 19xx/xx/xx NULL
2 鈴木 19xx/xx/xx NULL
3 清水川 1975/05/22 NULL
users
id name birthday age
1 佐藤 19xx/xx/xx 4x
2 鈴木 19xx/xx/xx 4x
3 清水川 1975/05/22 46
15. 🔥事例1. 開発中、スキーマ変更が競合
並行開発中に、テーブル追加や変更が各ブランチで行われた
● 競合1
○ ブランチA: カラムのデータ型を変更
○ ブランチB: カラムの削除
● 競合2
○ 各ブランチで、同じテーブルを作成していた
○ それぞれスキーマが微妙に異なる
(コミュニケーションの問題ではない)
● 意図的な競合
○ ブランチAで作ったテーブル、
ブランチBの開発でも使いたい!
(とりあえずコピーして入れちゃう)
15
roles
id name
1 管理者
2 担当者
スキーママイグレーションが
並列で作られ、
それぞれ微妙に異なる
role
id name
1 管理者
2 担当者
roles
id name description
1 admin 管理者
2 user 担当者
「現場では、あるあるです」
17. 🔥事例3. マイグレーションエラーからの二次災害
17
● 二次災害1
○ ロールバック実装していなかった
○ エラー発生時に、ロールバックする手順がない!
● 二次災害2
○ 1つのマイグレーションファイルで色々変えすぎ
○ 途中で失敗し、マイグレーションの前提とテーブル状態が不一致
○ 再実行も、ロールバックもできなくなる
手動でデータやスキーマを元に戻すことに...
「この時は検証環境でしたが、泣きながらやりましたね ....本番じゃなくて良かった ...」
18. 🔥事例4. 長時間のマイグレーション
● 原因: 本番環境のデータ件数が大きすぎた
● 結果1: カラム追加に時間がかかる
○ 行指向DBでは、列追加時間は行数に比例する
○ ALTER TABLEの途中経過も見えない
● 結果2: データマイグレーションに時間がかかる
○ 行毎の処理は行数に比例する
○ 1hの予定を軽々オーバー
18
users
id name birthday age
1 佐藤 19xx/xx/xx 4x
2 鈴木 19xx/xx/xx 4x
3 清水川 1975/05/22 46
...
99999999
寿限無 20xx/xx/xx xx
データマイグレーション
参考: PostgreSQL: How to update large tables - in Postgres | Codacy | Tips
Postgresの場合、行の更新時に行数分の
INSERT & DELETE & INDEXING が行われる
users
id name birthday age
1 佐藤 19xx/xx/xx NULL
2 鈴木 19xx/xx/xx NULL
3 清水川 1975/05/22 NULL
...
99999999
寿限無 20xx/xx/xx NULL
「まずは、進行状況をログ出力しましょう」
「1分のマイグレーションも、
600テナントあれば、
10時間かかります」
19. ● 住所データを users.address から addresses.location に移動した
● 別チームが開発している出荷システムが住所を参照していて、影響を受
けることが分かった
● リリースタイミングを合わせる必要がでてきた
🔥事例5. スキーマ変更が他システムに影響
19
users
id name
1 佐藤
2 鈴木
3 清水川
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
出荷システム 出荷システム
参照
参照エラー
「多チーム、多システムの開発では、あたりまえに発生します」
21. 1人でも競合💥 - ブランチ作成
21
● 一般的な開発では、機能毎にブランチを作り開発します
● 実装途中でもcommit&pushしたいので、1人でもブランチで開発します
(完成したらマージ)
● 実装中に別の機能のためにスキーマ変更すると、競合💥します
○ ちょっと良い機能を思い付いた
○ バグ修正のために必要になった
● 大抵は2並列程度なのでなんとかなる
○ 片方mainに入れたら、もう片方はrebaseする
並列なしブランチ運用
2並列ブランチ運用
rebase &merge 運用
23. 受入待ち行列問題 👤👤👤👤👤
23
● ブランチで並行開発すると、各機能に依存関係はない
● だから、受入確認も機能毎に順番にやりましょう
○ でも受入環境は大抵1つ(複数作るとお金も手間も増える)
○ 確認待ち: 機能A, 機能B, 機能C, 機能B修正版, 機能A修正版, ….
○ 👷開発者: 受入環境に機能Cをリリースするので1時間ください
○ 👤受入担当者: じゃあ続きはまた明日見ます
機能A,B,Cまとめて確認したい。
そうだ、受入(acceptance)ブランチを作ろう!
24. 受入ブランチは競合💥 of 競合💥
● 機能A,B,Cを一旦acceptanceにマージ
○ スキーマ変更が競合したら acceptanceで修正
● 実装ブランチで再修正したら、もう一度マージ
● 機能BのDB修正で機能CのDB操作がエラーに!
○ どっちのブランチで直す?
○ とりあえず受入ブランチ上で直すしかない!
● DBマイグレーションでエラーが!
○ マイグレーショングラフがカオスで追跡に丸 1日...
○ 毎週そんなことが起こるのが、受入ブランチ
24
「受入ブランチ捨てたいけど、どうしても避けられない・・」
��
��
30. users.address に住所を格納しているが、 1ユーザーに複
数の住所を持たせたくなった。
● スキーマ変更1
○ CREATE TABLE addresses
● データ変更
○ addresses.user_id = users.id
addresses.location = users.address
● スキーマ変更2
○ ALTER TABLE users DROP address
● コード変更
○ addresses.location にデータ保存
○ addresses.location を参照
データ移動を伴うマイグレーションの例
30
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
users
id name
1 佐藤
2 鈴木
3 清水川
addresses
id user_id location
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
address
東京都
北海道
秋田県
スキーマ変更 1
データ変更
スキーマ変更 2
「普通、ガッ!とやっちゃうよね」 1回のリリースで、ガッ!とマイグレーション
R W
リリース1
31. 危険なロールバックの例
リリース後に致命的なバグが!今すぐリリース前の状態
に戻して!
● スキーマ変更2
○ ALTER TABLE users ADD address -- NOT NULL
問題が起きるかも!
● データ変更
○ users.id = addresses.user_id -- 既に複数の住所
があるかも!
users.address = addresses.location
● スキーマ変更1
○ DROP TABLE addresses
● コード変更
○ users.address にデータ保存
○ users.address を参照
31
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
4 3 東京都
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
4 3 東京都
users
id name
1 佐藤
2 鈴木
3 清水川
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県?東京都?
address
NULL
NULL
NULL
データ変更のロールバック
スキーマ変更 2のロールバック
カラム追加
「多重度の変更や、データ形式の変更があると、逆データマイグレーションが不可能な場合もあるよ」
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 NULL
スキーマ変更 1
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
4 3 東京都
テーブル削除
R W
ロールバック
33. 新旧スキーマの並行運用
● スキーマ変更
○ CREATE TABLE addresses
● データ変更
○ addresses.user_id = users.id
addresses.location = users.address
● コード変更
○ (新)users.addresses.location と
(旧)users.address の両方を更新
○ (新)users.addresses.location を参照
● ロールバック発生!
○ addresses を削除する
○ 参照を users.address に戻す
33
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
addresses
id user_id location
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
スキーマ変更 1
データ変更
スキーマ変更 1のロールバック
addresses
id user_id location
1 1 東京都
2 2 北海道
3 3 秋田県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
R W
W
R W
「新旧両方に保存しておいて、
逆データマイグレーションを避けよう」
リリース1
リリース2
35. 課題
● リリース1
○ 新カラム追加
○ アプリは新旧双方にデータを格納し、新カラムを参照
○ データマイグレーション実行
● リリース2
○ 旧カラムへのデータ格納コードを削除
○ 旧カラム削除
35
マイグレーションエラーの可能性
マイグレーションに数時間以上かかる可能性
旧カラム参照が残っているとシステムエラーが
発生する可能性
「トラブル事例🔥の伏線を回収できてませんね」
36. ● 新カラム追加リリース
● 新旧双方突っ込むアプリをリリース (参照は旧カラム
● 必要であればデーターマイグレーション実行
● 新カラム参照アプリをリリース
● 旧カラムデーター突っ込まないアプリをリリース
● しばらくして旧カラム削除リリース
@Surgo 手順の例
36
https://twitter.com/Surgo/status/1382689002193571842
37. [改善版] 新旧スキーマの並行運用
● R1. 新カラム追加リリース
○ コードは変えていないため、影響がない
● R2. 新旧双方突っ込むアプリをリリース(参照は旧カラム
○ 新しいカラムはまだ参照されないため、影響がない
● R3. 必要であればデータマイグレーション実行
○ リリース時間外に行ってOK
● R4. 新カラム参照アプリをリリース
○ もし不具合があっても、安全に
R3に戻せる
● R5. 旧カラムにデータを突っ込まないアプリをリリース
○ もし旧カラム参照が残っていたら、修正をリリースする
● R6. しばらくして旧カラム削除リリース
○ 数ヶ月なにもないなら、問題ないはず
37
マイグレーションエラーの影響が無い
マイグレーションに時間がかかっても問題ない
旧カラム参照が残っていてもシス
テムエラーになりにくい
38. [改善版] 新旧スキーマの並行運用
38
addresses
id user_id location
1 4 沖縄県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
addresses
id user_id location
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
4 田中 沖縄県
スキーマ変更のみ、使用しない
データ追加のみ、参照しない
R
W
W
リリース1
● R1. 新カラム追加リリース
○ コードは変えていないため、影響がない
● R2. 新旧双方突っ込むアプリをリリース
○ 参照は旧カラム
○ 新しいカラムはまだ参照されないため、影響
がない
リリース2
R W
「図ではテーブルを追加してますが、カラム追加のほうが難易度が高く、有り難みがある手法です」
39. addresses
id user_id location
1 4 沖縄県
2 1 東京都
3 2 北海道
4 3 秋田県
[改善版] 新旧スキーマの並行運用
39
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
4 田中 沖縄県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
4 田中 沖縄県
既存のデータを新テーブルへ変換
参照を新テーブルに変更
R W
W
リリース3
● R3. 必要であればデータマイグレーション実
行
○ リリース時間外に行ってOK
● R4. 新カラム参照アプリをリリース
○ もし不具合があっても、安全に
R3に戻せる
リリース4
R W
「マイグレーションをリリース時間外に行って良いなんて!」(伏線回収)
addresses
id user_id location
1 4 沖縄県
2 1 東京都
3 2 北海道
4 3 秋田県
W
40. addresses
id user_id location
1 4 沖縄県
2 1 東京都
3 2 北海道
4 3 秋田県
[改善版] 新旧スキーマの並行運用
40
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
4 田中 沖縄県
users
id name address
1 佐藤 東京都
2 鈴木 北海道
3 清水川 秋田県
4 田中 沖縄県
旧テーブルへの書き込みを終了
旧テーブル、カラムの削除
リリース5
● R5. 旧カラムにデータを突っ込まないアプリ
をリリース
○ もし旧カラム参照が残っていたら、修正をリ
リースする
● R6. しばらくして旧カラム削除リリース
○ 数ヶ月待ちましょう
○ 数ヶ月何もないなら、問題ないはず
リリース6
「旧カラム参照、さすがにもう残ってないでしょう」(伏線回収)
W
addresses
id user_id location
1 4 沖縄県
2 1 東京都
3 2 北海道
4 3 秋田県
R W
R W
44. towncrierを導入しましょう
書き方
● /changelog/ 配下に 2190.migration.rst のようなチケット番号
付きファイル名で changelogの断片を書く
読み方
● /changelog/2190.migration.rst があるから、いまリリースす
るとマイグレーションが実行されるのか
● 2190.migration.rst にマイグレーションの概要が書いてあるか
ら安心
リリース時
● towncrierコマンドで /changelog 以下の断片をCHANGELOG.rst
に統合し、/changelog 配下が空になる
● 今日のリリース内容は CHANGELOG.rstで把握できる
「まとめておきました
: towncrier - 清水川のScrapbox 」 44
47. 参考資料📚
Django
● 58:DBのスキーママイグレーションとデータマイグレーションを分ける — 自走プログラマー【抜粋版】
● 59:データマイグレーションはロールバックも実装する — 自走プログラマー【抜粋版】
RDB
● PostgreSQL: How to update large tables - in Postgres | Codacy | Tips
● つらくないマルチテナンシーを求めて : 全て見せます! SmartHR データベース移行プロジェクトの裏側
その他
● towncrier - 清水川のScrapbox
● https://twitter.com/shimizukawa/status/1382600507361939456
● https://twitter.com/Surgo/status/1382689002193571842
47
48. Thanks! -- Review頂いたみなさん
BeProud メンバー(トーク練習でコメントくれた方々)
● kashew
● nakagami
● takanory
● kameko
● HayaoSuzuki
● 他、社内勉強会に参加された同僚のみなさん
Special Thanks!!
● @Surgo
48