Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
365日24時間稼働必須サービスの 完全無停止DB移行
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Kyuden Masahiro
March 24, 2018
Technology
23
11k
365日24時間稼働必須サービスの 完全無停止DB移行
Rails Developers Meetup 2018: Day 1 発表資料
https://techplay.jp/event/639872
Kyuden Masahiro
March 24, 2018
Tweet
Share
More Decks by Kyuden Masahiro
See All by Kyuden Masahiro
Red-Black Tree for Ruby
kyuden
1
2k
Why Rails 5.1
kyuden
1
750
Rails Authorization
kyuden
21
14k
One Night Vue.js
kyuden
14
3.8k
Other Decks in Technology
See All in Technology
オープンウェイトのLLMリランカーを契約書で評価する / searchtechjp
sansan_randd
3
480
MySQLのJSON機能の活用術
ikomachi226
0
120
toCプロダクトにおけるAI機能開発のしくじりと学び / ai-product-failures-and-learnings
rince
6
4.7k
ZOZOにおけるAI活用の現在 ~開発組織全体での取り組みと試行錯誤~
zozotech
PRO
3
1.9k
Claude Codeベストプラクティスまとめ
minorun365
56
31k
入社1ヶ月でデータパイプライン講座を作った話
waiwai2111
1
200
Databricks Free Edition講座 データサイエンス編
taka_aki
0
250
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
10
73k
3分でわかる!新機能 AWS Transform custom
sato4mi
1
270
AI推進者の視点で見る、Bill OneのAI活用の今
sansantech
PRO
2
290
2026年はチャンキングを極める!
shibuiwilliam
8
1.7k
GSIが複数キー対応したことで、俺達はいったい何が嬉しいのか?
smt7174
3
110
Featured
See All Featured
Designing Powerful Visuals for Engaging Learning
tmiket
0
210
Everyday Curiosity
cassininazir
0
120
Code Review Best Practice
trishagee
74
20k
Rails Girls Zürich Keynote
gr2m
96
14k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
320
Abbi's Birthday
coloredviolet
1
4.6k
Side Projects
sachag
455
43k
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
190
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
170
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
150
Transcript
365 日 24 時間稼働必須サービスの 完全無停止 DB 移行 〜 MongoDB to
Amazon Aurora 〜
Hi! I’m kyuden • Github: kyuden • Twitter: @kyuden_ •
Sorcery gem commiter • https://github.com/Sorcery/sorcery • Banken gem creator • https://github.com/kyuden/banken • WEB+DB Press Ruby 連載 (vol96~101)
どんなサービス ?
collection の規模感 RubyKaigi 2017
Ruby biz Grand prix 2017
Our Team
はじまりはじまり
昨年 7 月に 38 億レコード ( ドキュメント ) を 不整合データなし
ダウンタイムゼロで MongoDB から Amazon Aurora に データ移行した
昨年 7 月に 38 億レコード ( ドキュメント ) を 不整合データなし
ダウンタイムゼロで MongoDB から Amazon Aurora に データ移行した
このトークで主に話すこと • 具体的なデータ移行方法 • 移行のために作ったツールの設計 / 内部実装
移行対象のコレクション • node_values • 翻訳データが格納されたコレクション • 約 12 億ドキュメント •
page_node_values • どのページにどの翻訳データがあるかが格納されたコレク ション • 大まかに言うと page と node_values のジャンクションテー ブル ( ジャンクションコレクション ) • 約 26 億ドキュメント
制約 • そもそもダウンタイムゼロである必要はあったのか • 仮にダウンタイムがあっても翻訳データはキャッシュされ ているので 10000+ の Web サイト
/ サービスは翻訳可能 • しかし、ダウンタイムがあるとその間は翻訳の作成 / 更新 / 削除は不可能 • ユーザは日本だけでなく世界中に存在 • たとえば、 EC サイトなどは頻繁に新しいページが公開され るが、その間新しい翻訳がなされないと元言語以外を使用 するユーザからの売上は確実に減少する • ビジネスサイドと話し合いをした結果、数分であればダウ ンタイムの許可は取れそう • しかし、ダウンタイムゼロにこしたことはないし、エンジ ニアとしはチャレンジングなのでやりたかった
なぜ MongoDB から移行するのか • そもそもスキーマレスである必要がなかった • 厳密な整合性求められるケースが増えてきた • Mongos 突然の死
( 不安定 ) • クエリが激烈に重くなり調べてみるとある Mongo サーバー だけインデックスがはられていない • Mongoid の機能不足 • 小さなチームにはメンテナンスコストが高すぎた • Etc • ちゃんと話そうとすると時間足りないので省略。別の機会 にでも。なぜ Aurora なのかも同じく省略
移行手順
Step0: アプリケーションコードの修正 両方の DB を使えるようアプリケーションコードを修正する • すべての DB アクセスを Abstracter
クラス経由に書き換える • ユーザごとにどちらの DB を使用するかのフラグを持たせる • `use_mongo?` はフラグを参照している • フラグは MongoDB にある users collection の field
移行ステップと対応するフラグ名一覧 Write Read Read Write 2 Read Write 1 Read
Write Write 1 Write 2 aurora_write aurora_read aurora Step 1 Step 2 Step 3 Step 4 nil Aurora Aurora Aurora Aurora
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
移行ステップと対応するフラグ名一覧 aurora_write aurora_read aurora Step 1 Step 2 Step 3
Step 4 nil
Step1: Abstractor を経由していることを保証する Write Read • フラグは nil • これまで通り
R/W が MongoDB に対して行わ れている状態 • これまでと違うのは Abstractor を経由して R/W が行われていること • Moped::Query#initialize にモンキーパッチをあ てクエリを組み立てる前に Abstractor を経由 して呼びだされていなければ警告を出すよ うにした • Step1 をデプロイし 2,3 日様子をみて上記の 警告が出ていなければ全ての DB へのアクセ スが Abstractor 経由で行われていることをお およそ保証できる nil Aurora
Step 2: MongoDB に Write した後 Aurora にも Write する
• フラグは aurora_write • Step1 と同じく R/W は MongoDB に対して行う • Step1 と違うのは Mongo への Write に成功し た後 Aurora にも Write すること • aurora_write フラグをつけるのはある時点の MongoDB のデータを Aurora に移行する Rake タスク Aurora inserter を実行した後 • aurora_write フラグがついたら Aurora inserter タスク実行中に変更されたデータを aurora に移行する aurora_upserter タスクを実行する Read Write 2 Write 1 aurora_write Aurora
Aurora inserter Rake タスクとは • Aurora Inserter とは実行開始時点 (A) 以前に更
新された MongoDB のデータを Aurora のスキ ーマに合わせて Insert する Rake タスク • ユーザごとに実行しエラーなしで Insert が 完了した後、そのユーザに対して aurora_write フラグを付与する • 地点 A での MongoDB のデータを Aurora に移 行できる • タスク実行中の地点 A~B に変更のあったデ ータも関しては Aurora にはない状態 ( ユー ザごとなので A~B の期間は短い ) • 地点 B 以降は aurora_write フラグにより MongoDB と同じのデータが Aurora にも書き 込まれる A B A: タスク実行開始 B: タスク実行終了
Step 2: MongoDB に Write した後 Aurora にも Write する
• フラグは aurora_write • Step1 と同じく R/W は MongoDB に対して行う • Step1 と違うのは Mongo への Write に成功し た後 Aurora にも Write すること • aurora_write フラグをつけるのはある時点の MongoDB のデータを Aurora に移行する Rake タスク Aurora inserter を実行した後 • aurora_write フラグがついたら aurora inserter タスク実行中に変更されたデータを aurora に移行する aurora_upserter タスクを実行する Read Write 2 Write 1 aurora_write Aurora
Aurora upserter Rake タスク • Aurora upserter タスクとは指定された期間に 変更のあった MongoDB
のデータを Aurora の スキーマに合わせて Insert or Update or Delete する Rake タスク • Aurora upserter タスクの期間として Aurora Inserter タスク実行中の地点 AB を指定する ことでユーザごとにすべてのデータを Aurora に移行できる • Aurora upserter タスク実行後、ユーザごとに すべてのデータが両方の DB に存在するかを チェックする Rake タスク consistency checker を実行する • Aurora checker タスクに失敗する場合は Abstractor や Aurora へのデータコンバートな どにバグがある可能性があるので修正し て、再度 Aurora upserter タスクをかける A B A: タスク実行開始 B: タスク実行終了
Step 3: Aurora から Read する • フラグは aurora_read •
R/W を Aurora に対して行う • Aurora への Write に成功した後 MongoDB にも Write する (step2 とは逆 ) • aurora_read フラグをつけるのは consistency checker タスクを実行し両方の DB に同じデ ータがあることを確認した後 • MongoDB にも Write しているので、 Aurora へ の R/W でなにか問題が発生した場合、デプ ロイなしですぐに aurora_write フラグに戻し MongoDB の R/W に戻すことができる aurora_read Read Write 1 Write 2 Aurora
Step 4: Aurora のみを使う • フラグは aurora • Aurora のみを使う
• すべてのユーザに対して aurora フラグがつ いたらデータ移行完了 • その後、 Abstractor を削除してフラグを参照 せず常に Aurora を使うようリファクタした 後、全ユーザーの Aurora フラグを削除する aurora Read Write
移行ステップと対応するフラグ名一覧 Write Read Read Write 2 Read Write 1 Read
Write Write 1 Write 2 aurora_write aurora_read aurora Step 1 Step 2 Step 3 Step 4 nil Aurora Aurora Aurora Aurora
フラグ実装のポイント • フラグを切り替えた瞬間に現在処理中のリクエストが MongoDB から Aurora に切り替わるとタイミングによってはエ ラー発生 or 不整合データができる
• リクエストごとに利用するフラグは同じものを利用する必 要がある
フラグ実装のポイント • Controller のインスタンス変数として保持する方法 • • • • フラグは様々な箇所で参照されるため、この設計の場合 Controller
から current_flag を引数で引き回すことになりいま いち
フラグ実装のポイント • クラス変数にフラグを保持してグローバルで参照 • • • • こんなことはやってはいけない • スレッドセーフではない
フラグ実装のポイント • `Thread.local` に flag を保持しグローバルに参照 ver1 • • •
• • • • • スレッドセーフだが同じスレッドが別のリクエストを処理 した場合、前回のリクエストでセットした値が格納された ままなので、これもよくない
フラグ実装のポイント • `Thread.local` に flag を保持しグローバルに参照 ver2 • • •
• • • • • リクエストごとに必ず値をクリアすれば OK • もしくはフラグセット時に `||=` せず毎回上書きしてもよい • もしくは steveklabnik/request_store gem 使うのがよい
フラグ実装のポイント • Rails5.2 にマージされた ActiveSupport::CurrentAttributes もやりたいこ とは同じでリクエストごとにグローバルな値を保持できる
フラグベースのデータ移行のデメリット • すべてのデータを一回で移行し新しいコードベースで動かす方 法と比べると、アプリケーションコードの修正コストがかかる • ユーザごとのデータ移行なので一回で移行するより時間がかか る • Step2,3 は両方の
DB に書き込むので、増えた分多少レスポンスタ イムが増えサーバー負荷があがる
フラグベースのデータ移行のメリット • ダウンタイムがない • すべてのデータを一回で移行し移行先の DB を利用した新しいコ ードベースで動かす方法と比べると、ユーザごとにデータ移行 と新しいコードベースを徐々に使うことで、バグの影響範囲を 小さくしかつバグ早期発見が可能
• なにか問題が発生したとき • 小さいスコープで ( ユーザごとに ) • できるだけはやく ( コード修正 && デプロイなしでフラグを 更新するだけで ) • 正常に動く状態に戻すことができる ( 元の DB を利用しサー ビスを提供し続けることができる )
移行ツールの内部実装
移行ツールの内部実装 • Aurora Inserter タスクとは • 実行開始時点以前に更新された MongoDB のデータを Aurora
のスキーマに合わせて Insert する Rake タスク • Aurora Inserter タスクが内部でやっていること 1. node_values のデータ移行 2. page_node_values のデータ移行 3. page_node_values にある node_values の外部キーを更新 • MongoDB では主キーに BSON::ObjectId を使用していたが、 Aurora 移行に伴い auto increment される id を使用したかっ たため。 ( なお MongoDB の主キーは mongo プリフィック スをつけて mongo_id として varchar で Aurora にも保持 )
移行ツールの内部実装 • Aurora Inserter タスクのパフォーマンス • 1500 レコード ( ドキュメント
) / 秒 • (text 型のデータもあるのでレコード単位での計測結果は 正確とは言えないが、移行するレコード数からおおよそ の実行時間を見積もることができる位の精度 ) • 7 億レコード保持するユーザも存在し、このユーザだけで 約 5.4 日かかる計算 ( そんなに待ちたくない ) • Aurora Inserter タスク以外にも Aurora upserter や consistency checker タスクのことも考えるとさらに時間がかかる • I / O の割合が多いんだから thread 使って書きなおすことに した
移行ツールの内部実装 • Producer Consumer パターン Producer 1 Producer 3 Producer
X Consumer 1 Consumer 2 Consumer X Queue • Producer は仕事に必要なデータを生産して Queue に詰める • Consumer は Queue からデータを取り出して仕事を消費する • ある Producer が Queue を参照し書き込み終わるまで、他の Producer に割り込まれてはいけない • ある Consumer が Queue を参照し取り出すまで、他の Consumer に割り込まれてはいけない • Queue が上限まできたら書き込みを待ち、空なら取り出すの をまたなければいけない
移行ツールの内部実装 • Producer Consumer パターン Producer 2 Consumer 2 Queue
Producer 1 Producer 3 Consumer 1 Consumer X • Producer は MongoDB から移行データを取得して Queue につめる • Consumer は移行データを Queue から取りだし Aurora 用に加工し Aurora につめる • Thread 数の調整や Queue の中継によって Producer と Consumer 間 の処理スピードの差異を吸収しパフォーマンスの向上が期待 できる • 簡略化したサンプルコードは次のスライドに掲載 Aurora
None
作戦
マルチスレッド
移行ツールの内部実装 • Aurora Inserter タスク ver マルチスレッドのパフォーマンス • スレッド数 (producer
2, consumer 6) • コア数 4 • 2300 レコード ( ドキュメント ) / 秒 1.5x faster • 7 億レコード保持するユーザが約 3.5 日かかる計算 ( そんな に待ちたくない ) • コア数とスレッド数を上げればパフォーマンスは上がる が、 I / O 以外の処理も並列にできればさらなるパフォー マンスの向上が期待できた • そこで
None
マルチサーバー
JRuby 採用理由 • Real threading でマルチコアを活用したい • jruby-9.1.8.0
JRuby 採用理由 • Real threading でマルチコアを活用したい • jruby-9.1.8.0 • 余談
: ProducerConsumer パターンを実装する際、ひそかに JRuby でも動かせるよう書いていた
ディレクトリ構成 • Rails アプリのルートディレク トリで `mkdir juby` して `rbenv local
jruby-9.1.8.0` • Gemfile には `activerecord- jdbcmysql-adapter` と `mongoid` を 指定。 DB の接続情報を jruby/config 配下に記載 • jruby/models 配下に Rails の app/models 配下のモデルファイ ルなど今回使用するファイル をコピーする • Rails アプリで使用していた Gem に依存したコードを削除 • 細かいところはまだあるが、 おおよそこれで動く
移行ツールの内部実装 • Aurora Inserter タスク (JRuby マルチスレッドのパフォーマンス ) • スレッド数
(producer 2, consumer 6) • コア数 4 • 4300 レコード ( ドキュメント ) / 秒 2.8x faster • 7 億レコード保持するユーザが約 1.9 日かかる計算 ( そんなに 待ちたくない ) • 10x 以上はやくしたい • そこで
マルチサーバー X マルチスレッド • コア数 8 のサーバーを 20 台用意 •
移行対象のドキュメントの主キーを 20 分割し、各サーバにファ イルとして配布 • 事前に DB 負荷状況や同時接続数の上限などを確認 • 実装の都合上、 Aurora Inserter の処理 (step1~3) の step3 は全サーバ ーが step1,step2 を終わってから実行する必要があった。ようは マルチサーバーとはいえ同期的に各 Step を行う必要があった
None
capistrano/sshkit gem • Capistrano gem の dependency に指定されている gem で
Capistrano の ssh 関連のコードはこの sshkit gem のラッパー • ssh 経由でパラレルに実行する部分と同期的に実行する部分 を DSL で簡単に指定できる • sshkit は JRuby で CI が回っていなかったため Rails アプリ側の gemfile に追加し Rails の rake タスクから jruby のコードを実行
所管
結果
2.4 時間 54x faster
None
FIN