$30 off During Our Annual Pro Sale. View Details »

Update Billion Records

ta1kt0me
October 27, 2023

Update Billion Records

Kaigi on Rails 2023

ta1kt0me

October 27, 2023
Tweet

More Decks by ta1kt0me

Other Decks in Programming

Transcript

  1. Update Billion Records
    Kaigi on Rails 2023

    View Slide

  2. Kaigi on Rails 2023 開催 🎉

    View Slide

  3. • ⾃⼰紹介
    • 課題と前提
    • レコード更新チャレンジ
    • 学び
    写真・画像
    Agenda

    View Slide

  4. 写真・画像
    Hiroki Tokutomi
    株式会社TimeTree
    • CTO室所属、Backendチーム/SREチーム
    https://twitter.com/talkto_me
    https://github.com/ta1kt0me
    ⾃⼰紹介

    View Slide

  5. 写真・画像
    • 簡単に予定を共有できる
    • カレンダーの中で気軽に相談できる
    スマホの中で⾒れる壁掛けカレンダー
    カレンダーシェアアプリ

    View Slide

  6. View Slide

  7. begin

    View Slide

  8. 写真・画像 課題と前提

    View Slide

  9. きっかけ
    IUUQTUJNFUSFFBQQDPNJOUMKBOFXTSPPNTIBSFEFWFOUDMPTF

    View Slide

  10. 👦 < ちょっとモデルの関連キー⾒直したいんだよね!

    View Slide

  11. 👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど!
    👦 < ちょっとモデルの関連キー⾒直したいんだよね!

    View Slide

  12. 👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど!
    👦 < ちょっとモデルの関連キー⾒直したいんだよね!
    👦 < 少し前にも全件更新したし、いけるいける!

    View Slide

  13. 👦 < 新キーのカラムは追加してるよ!数⼗億件あるけど!
    👦 < ちょっとモデルの関連キー⾒直したいんだよね!
    👦 < 少し前にも全件更新したし、いけるいける!

    View Slide

  14. 整理してみる
    • 解消する価値のある技術的負債
    • 対象は1テーブル、レコード数は50~60億
    • 変更内容はデータを埋めるだけ
    • 作業の1年前に⼤体数⼗⽇かけて同じテーブルの全データ更新を実施
    • 更新作業に時間がかかることはチームで認識している
    • 更新期間も安定してサービスを提供したい
    要求

    View Slide

  15. • Monolith Ruby on Rails
    • Sidekiq
    • AWS
    • CloudFront
    • S3
    • ECS
    • Aurora MySQL
    • DynamoDB
    • Elasticache for Redis
    • etc

    Backend構成要素

    View Slide

  16. 以前はどうやってたの?
    複数プロセス起動して並列に更新する
    • rails runner
    • grosser/parallel
    • zdennis/activerecord-import
    ⼤量データを⼀括で更新するバッチ処理でよく⾒かけるパターン
    巨⼤テーブルでなければこのアプローチを使うことが多い
    • 更新対象の from/to
    • parallelで起動するプロセスの並列数

    View Slide

  17. 写真・画像
    レコード
    更新チャレンジ

    View Slide

  18. Take 1. 前例踏襲

    View Slide

  19. 過去のアプローチに従ってみる
    • 複数プロセス更新での実⾏時間を計測したい
    • アプローチの課題を理解したい

    View Slide

  20. 結果
    • 実⾏時間は約70分/1,000万件
    • サービスへの影響はなかった
    • この時点で⼤きめのパフォーマンスチューニングはしていない
    対象は⼤体500~600倍、実⾏し続ければ1ヶ⽉弱…?

    View Slide

  21. ⾒えてきた課題
    更新対象の指定で処理時間のブレが⼤きい
    サービス成⻑に起因(後述)
    更新処理をコントロールしづらい
    実⾏状況を外部からモニタリングしづらい
    ⻑時間実⾏し続けることが難しい、けど⻑時間実⾏し続けたい
    • 環境の都合上リリースの度に中断
    • スパイクが予測される状況で中断

    View Slide

  22. 課題を深掘り
    更新対象の指定
    • 処理対象の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

    View Slide

  23. 更新期間の指定のムラ

    View Slide

  24. 改善アイデア
    更新対象の指定で処理時間のブレが⼤きい
    from/toを⼀定の期間で分割、分割期間ごとに並列で処理する
    更新処理をコントロールしづらい
    更新処理をSidekiq Workerで処理する⾮同期ジョブにする
    • rails runner の実⾏環境の制限に依存しない
    • 実⾏状況のモニタリングも容易

    View Slide

  25. 期間の分割⾒直し
    Ұఆͷظؒ5ͰJEΛ෼ׂ
    ىಈ#BUDIͷύϥϝʔλͱͯ͠ࢦఆՄೳ
    ظؒ5ΛฒྻͰॲཧ
    Take 1
    改善案
    ฒྻ਺ͰJEͷൣғΛ෼ׂ

    View Slide

  26. Take 2.

    View Slide

  27. Take 2 アイデア
    • 更新対象の from/to
    • 分割期間

    View Slide

  28. Take 2 アイデアちょっと待って
    • 更新対象の from/to
    • 分割期間

    View Slide

  29. ⾮同期で更新
    前提
    • 通常のワークロードより優先度低
    • データベースの負荷を抑えたい
    • 全件更新に時間がかかることは問題ない
    制御したいこと
    • workerあたりの実⾏時間
    • Sidekiqクラスター全体での更新Job数
    • 更新処理を全て停⽌するトリガー

    View Slide

  30. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ②from/toの分割リストをRedisにpush
    ④更新対象のfrom/toを
    pop、なければ終了
    ⑤更新
    ③⾮同期Jobを登録
    ⑥⾮同期Jobを登録

    View Slide

  31. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数

    View Slide

  32. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ②from/toの分割リストをRedisにpush

    View Slide

  33. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ②from/toの分割リストをRedisにpush
    ③⾮同期Jobを登録

    View Slide

  34. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ④更新対象のfrom/toをpop(   )
    ③⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  35. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ④更新対象のfrom/toをpop
    ⑤更新
    ③⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  36. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑤更新
    ⑥⾮同期Jobを登録
    ③⾮同期Jobを登録
    ④更新対象のfrom/toをpop
    ②from/toの分割リストをRedisにpush

    View Slide

  37. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑤更新
    ⑥⾮同期Jobを登録
    ③⾮同期Jobを登録
    ④更新対象のfrom/toをpop
    ②from/toの分割リストをRedisにpush

    View Slide

  38. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑦更新対象のfrom/toをpop(  )
    ⑤更新
    ③⾮同期Jobを登録
    ⑥⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  39. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑦更新対象のfrom/toをpopできない
    ⑤更新
    ③⾮同期Jobを登録
    ⑥⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  40. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑧分割リストのデータを削除
    ⑤更新
    ③⾮同期Jobを登録
    ⑥⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  41. 全体像
    ①rails runnerで起動処理実⾏
    • 更新対象の from/to
    • 分割期間
    • Jobの同時起動数
    ⑧分割リストのデータを削除
    ⑤更新
    ③⾮同期Jobを登録
    ⑥⾮同期Jobを登録
    ②from/toの分割リストをRedisにpush

    View Slide

  42. コンセプト: 起動処理

    View Slide

  43. コンセプト: 更新ジョブ

    View Slide

  44. 結果
    実⾏時間
    実⾏時間は約40分/1,000万件(変更前の175%⾼速化)
    実⾏し続ければ約2~3週間強
    Take.1 の課題
    実⾏中もリリース可能、問題があれば全処理停⽌可能
    カスタマイズせず、Sidekiqの管理画⾯やAPMのメトリクスで実⾏状況を確認できる
    Workerごとの実⾏時間もパラメータによりある程度調整可能

    View Slide

  45. 早くなった、楽できた、はっぴー🎃

    View Slide

  46. やりきった 🎉

    View Slide

  47. 写真・画像 学び

    View Slide

  48. 学び
    ⼤量データの変更には時間がかかる
    • ⻑期戦になるので関係者の理解が必要
    • ⼩さなデータで簡単なことでも、後に回せば回すほどもっと⾟くなる
    ⼩さく試して進める
    • PoCのフィードバックサイクルを素早く回す
    • 可能であれば⼩さいデータセットから始め、少しずつ⼤きくして問題を捉えていく
    選択肢を広げるためのinputの⼤切さ
    • 改善のヒントは様々な場所に潜んでいるので広く、深く探す
    • 前例や背景、変更対象や関連のドメインの把握、制限やトレードオフの理解、少数の異常データの存在

    View Slide

  49. 他の全件更新に適⽤できるか?
    🙅 or 🙆
    • 🙆 適⽤できたケースもある
    • 😰 変更対象が今回と同じでもさらにデータが増え続けたら…
    • 🙅 READよりもWRITE(更新)が多いケースはLockが多発して使えなかった
    • 🧐 変更内容や対象の特性次第で前例に捉われずに考え続ける

    View Slide

  50. end

    View Slide

  51. ⼀緒に最⾼のカレンダーサービス作りませんか?

    View Slide