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
Update Billion Records
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
ta1kt0me
October 27, 2023
Programming
5.4k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Update Billion Records
Kaigi on Rails 2023
ta1kt0me
October 27, 2023
More Decks by ta1kt0me
See All by ta1kt0me
過去の改善から考える オブザーバビリティの必要性
ta1kt0me
0
59
Running with version up
ta1kt0me
0
80
omotesandorb_8.pdf
ta1kt0me
1
430
Rubyでのプロファイリング
ta1kt0me
0
130
開発環境でdockerを使ってみた
ta1kt0me
0
470
Other Decks in Programming
See All in Programming
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
210
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.7k
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
570
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
220
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
400
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
270
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
800
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
130
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
7
1.4k
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
5.8k
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
560
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
300
Featured
See All Featured
The Cost Of JavaScript in 2023
addyosmani
55
10k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
200
Scaling GitHub
holman
464
140k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
Discover your Explorer Soul
emna__ayadi
2
1.1k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.4k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.1k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.4k
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
Transcript
Update Billion Records Kaigi on Rails 2023
Kaigi on Rails 2023 開催 🎉
• ⾃⼰紹介 • 課題と前提 • レコード更新チャレンジ • 学び 写真・画像 Agenda
写真・画像 Hiroki Tokutomi 株式会社TimeTree • CTO室所属、Backendチーム/SREチーム https://twitter.com/talkto_me https://github.com/ta1kt0me ⾃⼰紹介
写真・画像 • 簡単に予定を共有できる • カレンダーの中で気軽に相談できる スマホの中で⾒れる壁掛けカレンダー カレンダーシェアアプリ
None
begin
写真・画像 課題と前提
きっかけ IUUQTUJNFUSFFBQQDPNJOUMKBOFXTSPPNTIBSFEFWFOUDMPTF
👦 < ちょっとモデルの関連キー⾒直したいんだよね!
👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど! 👦 < ちょっとモデルの関連キー⾒直したいんだよね!
👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど! 👦 < ちょっとモデルの関連キー⾒直したいんだよね! 👦 < 少し前にも全件更新したし、いけるいける!
👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど! 👦 < ちょっとモデルの関連キー⾒直したいんだよね! 👦 < 少し前にも全件更新したし、いけるいける!
整理してみる • 解消する価値のある技術的負債 • 対象は1テーブル、レコード数は50~60億 • 変更内容はデータを埋めるだけ • 作業の1年前に⼤体数⼗⽇かけて同じテーブルの全データ更新を実施 •
更新作業に時間がかかることはチームで認識している • 更新期間も安定してサービスを提供したい 要求
• Monolith Ruby on Rails • Sidekiq • AWS •
CloudFront • S3 • ECS • Aurora MySQL • DynamoDB • Elasticache for Redis • etc … Backend構成要素
以前はどうやってたの? 複数プロセス起動して並列に更新する • rails runner • grosser/parallel • zdennis/activerecord-import ⼤量データを⼀括で更新するバッチ処理でよく⾒かけるパターン
巨⼤テーブルでなければこのアプローチを使うことが多い • 更新対象の from/to • parallelで起動するプロセスの並列数
写真・画像 レコード 更新チャレンジ
Take 1. 前例踏襲
過去のアプローチに従ってみる • 複数プロセス更新での実⾏時間を計測したい • アプローチの課題を理解したい
結果 • 実⾏時間は約70分/1,000万件 • サービスへの影響はなかった • この時点で⼤きめのパフォーマンスチューニングはしていない 対象は⼤体500~600倍、実⾏し続ければ1ヶ⽉弱…?
⾒えてきた課題 更新対象の指定で処理時間のブレが⼤きい サービス成⻑に起因(後述) 更新処理をコントロールしづらい 実⾏状況を外部からモニタリングしづらい ⻑時間実⾏し続けることが難しい、けど⻑時間実⾏し続けたい • 環境の都合上リリースの度に中断 • スパイクが予測される状況で中断
課題を深掘り 更新対象の指定 • 処理対象のfrom/to にテーブルのidカラム(PK)の値を指定 • Idに Snow fl ake
ID を利⽤ • 64ビットの整数値、timestampに基づいて⽣成される時系列ソート済みのID • Twitter の TweetのIDで利⽤されていた • TimeTreeの場合、timestampにcreated_atを使っている • 1億件分更新したい場合、⼤体1億件分の期間の from, to をこのフォーマットに変換して指定 + ————— —————— ——— ——— — —— — —— — —— ——— — —— ——— — —— — —+ | timestamp (41ビット) | ゾーンID (10ビット) | シーケンス番号 (13ビット) | + ————— —————— ——— ——— — —— — —— — —— ——— — —— ——— — —— — —+ 例)timestampに “2020-01-01 00:00:00.000 UTC”、ゾーンIDに”10”、シーケンス番号に”1”を指定 10110111101011110011001101110100000000000 0000001010 0000000000001 => 13235854403174481921
更新期間の指定のムラ
改善アイデア 更新対象の指定で処理時間のブレが⼤きい from/toを⼀定の期間で分割、分割期間ごとに並列で処理する 更新処理をコントロールしづらい 更新処理をSidekiq Workerで処理する⾮同期ジョブにする • rails runner の実⾏環境の制限に依存しない
• 実⾏状況のモニタリングも容易
期間の分割⾒直し Ұఆͷظؒ5ͰJEΛׂ ىಈ#BUDIͷύϥϝʔλͱͯ͠ࢦఆՄೳ ظؒ5ΛฒྻͰॲཧ Take 1 改善案 ฒྻͰJEͷൣғΛׂ
Take 2.
Take 2 アイデア • 更新対象の from/to • 分割期間
Take 2 アイデアちょっと待って • 更新対象の from/to • 分割期間
⾮同期で更新 前提 • 通常のワークロードより優先度低 • データベースの負荷を抑えたい • 全件更新に時間がかかることは問題ない 制御したいこと •
workerあたりの実⾏時間 • Sidekiqクラスター全体での更新Job数 • 更新処理を全て停⽌するトリガー
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
②from/toの分割リストをRedisにpush ④更新対象のfrom/toを pop、なければ終了 ⑤更新 ③⾮同期Jobを登録 ⑥⾮同期Jobを登録
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
②from/toの分割リストをRedisにpush ③⾮同期Jobを登録
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
④更新対象のfrom/toをpop( ) ③⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
④更新対象のfrom/toをpop ⑤更新 ③⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑤更新 ⑥⾮同期Jobを登録 ③⾮同期Jobを登録 ④更新対象のfrom/toをpop ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑤更新 ⑥⾮同期Jobを登録 ③⾮同期Jobを登録 ④更新対象のfrom/toをpop ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑦更新対象のfrom/toをpop( ) ⑤更新 ③⾮同期Jobを登録 ⑥⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑦更新対象のfrom/toをpopできない ⑤更新 ③⾮同期Jobを登録 ⑥⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑧分割リストのデータを削除 ⑤更新 ③⾮同期Jobを登録 ⑥⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
全体像 ①rails runnerで起動処理実⾏ • 更新対象の from/to • 分割期間 • Jobの同時起動数
⑧分割リストのデータを削除 ⑤更新 ③⾮同期Jobを登録 ⑥⾮同期Jobを登録 ②from/toの分割リストをRedisにpush
コンセプト: 起動処理
コンセプト: 更新ジョブ
結果 実⾏時間 実⾏時間は約40分/1,000万件(変更前の175%⾼速化) 実⾏し続ければ約2~3週間強 Take.1 の課題 実⾏中もリリース可能、問題があれば全処理停⽌可能 カスタマイズせず、Sidekiqの管理画⾯やAPMのメトリクスで実⾏状況を確認できる Workerごとの実⾏時間もパラメータによりある程度調整可能
早くなった、楽できた、はっぴー🎃
やりきった 🎉
写真・画像 学び
学び ⼤量データの変更には時間がかかる • ⻑期戦になるので関係者の理解が必要 • ⼩さなデータで簡単なことでも、後に回せば回すほどもっと⾟くなる ⼩さく試して進める • PoCのフィードバックサイクルを素早く回す •
可能であれば⼩さいデータセットから始め、少しずつ⼤きくして問題を捉えていく 選択肢を広げるためのinputの⼤切さ • 改善のヒントは様々な場所に潜んでいるので広く、深く探す • 前例や背景、変更対象や関連のドメインの把握、制限やトレードオフの理解、少数の異常データの存在
他の全件更新に適⽤できるか? 🙅 or 🙆 • 🙆 適⽤できたケースもある • 😰 変更対象が今回と同じでもさらにデータが増え続けたら…
• 🙅 READよりもWRITE(更新)が多いケースはLockが多発して使えなかった • 🧐 変更内容や対象の特性次第で前例に捉われずに考え続ける
end
⼀緒に最⾼のカレンダーサービス作りませんか?