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
Kyuden Masahiro
March 24, 2018
Technology
23
10k
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
1.6k
Why Rails 5.1
kyuden
1
660
Rails Authorization
kyuden
21
14k
One Night Vue.js
kyuden
14
3.6k
Other Decks in Technology
See All in Technology
IBC 2024 動画技術関連レポート / IBC 2024 Report
cyberagentdevelopers
PRO
0
110
[CV勉強会@関東 ECCV2024 読み会] オンラインマッピング x トラッキング MapTracker: Tracking with Strided Memory Fusion for Consistent Vector HD Mapping (Chen+, ECCV24)
abemii
0
220
OCI 運用監視サービス 概要
oracle4engineer
PRO
0
4.8k
Lambdaと地方とコミュニティ
miu_crescent
2
370
なぜ今 AI Agent なのか _近藤憲児
kenjikondobai
4
1.4k
誰も全体を知らない ~ ロールの垣根を超えて引き上げる開発生産性 / Boosting Development Productivity Across Roles
kakehashi
1
220
フルカイテン株式会社 採用資料
fullkaiten
0
40k
Making your applications cross-environment - OSCG 2024 NA
salaboy
0
180
Why does continuous profiling matter to developers? #appdevelopercon
salaboy
0
180
AWS Lambda のトラブルシュートをしていて思うこと
kazzpapa3
2
170
Amazon CloudWatch Network Monitor のススメ
yuki_ink
1
200
Application Development WG Intro at AppDeveloperCon
salaboy
0
180
Featured
See All Featured
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Side Projects
sachag
452
42k
Building an army of robots
kneath
302
43k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Optimizing for Happiness
mojombo
376
70k
[RailsConf 2023] Rails as a piece of cake
palkan
52
4.9k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
28
2k
Docker and Python
trallard
40
3.1k
The Pragmatic Product Professional
lauravandoore
31
6.3k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
0
89
Adopting Sorbet at Scale
ufuk
73
9.1k
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