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
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Kyuden Masahiro
March 24, 2018
Technology
11k
23
Share
365日24時間稼働必須サービスの 完全無停止DB移行
Rails Developers Meetup 2018: Day 1 発表資料
https://techplay.jp/event/639872
Kyuden Masahiro
March 24, 2018
More Decks by Kyuden Masahiro
See All by Kyuden Masahiro
Red-Black Tree for Ruby
kyuden
1
2.1k
Why Rails 5.1
kyuden
1
780
Rails Authorization
kyuden
21
15k
One Night Vue.js
kyuden
14
3.8k
Other Decks in Technology
See All in Technology
React、まだ楽しくて草
uhyo
3
500
美味しいスイスチーズを作ろう🧀🐭
taigamikami
1
190
サプライチェーンセキュリティの空白地帯 - 信頼できる”依存性”の未来を考える
rung
PRO
2
510
AI時代から振り返るTerraform drift運用の歴史 / AI Age Reflections on the History of Terraform Drift Operations
aeonpeople
0
610
Java正規表現エンジン(NFA)の仕組みと パフォーマンスを維持するための最適化手法
takeuchi_132917
0
160
Javaで学ぶSOLID原則
negima
1
240
個人の発見を、組織の知恵に 〜生成AI活用を"探索"から"組織の仕組み"へ〜
kintotechdev
2
190
20260528_生成AIを専属DSに_Howの次にすべきことを考える
doradora09
PRO
0
270
Spring AI × MCP 入門〜AIエージェントへのツール公開、境界設計から始める最小構成 〜
yuyamiyamoto
0
190
Kiro CLI v2.0.0がやってきた!
kentapapa
0
230
インフラが苦手でも大丈夫! 紙芝居 Kubernetes -WWGT 10周年編-
aoi1
1
310
Databricks 月刊サービスアップデート 2026年05月号
tyosi1212
0
120
Featured
See All Featured
Optimizing for Happiness
mojombo
378
71k
Balancing Empowerment & Direction
lara
6
1.1k
Designing for Timeless Needs
cassininazir
1
240
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Chasing Engaging Ingredients in Design
codingconduct
0
200
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
61
44k
Mobile First: as difficult as doing things right
swwweet
225
10k
The Invisible Side of Design
smashingmag
302
52k
BBQ
matthewcrist
89
10k
Thoughts on Productivity
jonyablonski
76
5.2k
Agile that works and the tools we love
rasmusluckow
331
21k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
340
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