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

技術的負債の借り換え on Ruby and Rails update

ginkouno
November 05, 2023

技術的負債の借り換え on Ruby and Rails update

https://kaigionrails.org/2023/talks/ginkouno/
Kaigi on Rails 2023登壇時の資料です。

ginkouno

November 05, 2023
Tweet

More Decks by ginkouno

Other Decks in Programming

Transcript

  1. 技術的負債の
    借り換え
    on Rails and Ruby Update

    View Slide

  2. @ginkouno
    河野 誠
    ソフトウェアエンジニア
    1998年〜2008年 組み込み8割、Windows2割 C、C++、ASM
    2008年〜2023年 Web系 Rails8割、GoとかKotlinとか2割
    アイコンはAI画伯に描いてもらいました

    View Slide

  3. 銀座Rails
    第1回(2018/09) 〜 第36回(2021/08) 主催
    フリーランス
    最近は主にレガシーな箇所の対応が多い
    コード書いたり技術顧問したり

    View Slide

  4. Tokyu.rb(TokyuRuby会議)

    View Slide

  5. 事例の紹介にご同意頂いたのは
    ありがとうございます

    View Slide

  6. RailsとRubyの
    Updateの話

    View Slide

  7. RailsとRubyの
    Updateの話
    なんだけど、主に
    gemのUpdateの話

    View Slide

  8. ・暗号通貨取引所での話
    ・Rails6.0を6.1に、Ruby2.7を3.0にUpdateする状況
    ・コード例は実際のコードを大幅に省略
    ・気合が3倍要るレベルの規模のアプリ(model数1497)
     ・Kaigi on Rails 2022 既存Railsアプリ攻略法
      藤村さん「500を超えると気合が入ってくるな」
    前提

    View Slide

  9. https://inside.pixiv.blog/sue445/4751
    永久保存版Railsアップデートガイド
    @sue445さん
    https://qiita.com/jnchito/items/0ee47108972a0e302caf
    永久保存版!?伊藤さん式・Railsアプリの
    アップグレード手順
    @jnchito(Junichi Ito)さん
    Rails Updateの定番記事

    View Slide

  10. 永久保存版Railsアップデートガイド
    永久保存版!?伊藤さん式・Railsアプリのアップグレード手順
    ”Rails 以 外 のgemを 全 部 最 新 にする
    Railsのアップデートに引きずられて他のgemも上げることが多いので、先にRails以外のgemを最新にして
    おくとRailsアップデート後に不具合が起きた時に切り分けがしやすいです。 ”
    ”コラム:gemは普段からこまめにバージョンアップしよう
    gemのバージョンアップ作業は実際にやってみると、かなり骨が折れると思います。
    古いバージョンのgemが多ければ多いほど、この作業のしんどさが増えるので、日頃からこまめにgemを最
    新版にアップデートしていくことをお勧めします。”

    View Slide

  11. Updateが完璧には
    できない
    ときもある

    View Slide

  12. Rails、RubyのEOL
    着手
    gem
    update
    その1
    想定される事態

    View Slide

  13. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    想定される事態

    View Slide

  14. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    gem
    update
    その3
    想定される事態

    View Slide

  15. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    gem
    update
    その3
    gem
    update
    その4
    想定される事態

    View Slide

  16. 心の海◯雄山
    「この料理を作ったのは誰だ!
     この店は賞味期限切れの材料で
     作った料理を出すのか!」

    View Slide

  17. 心の海◯雄山
    「この料理を作ったのは誰だ!
     この店は賞味期限切れの材料で
     作った料理を出すのか!」
    (皆さんの心の巨匠とその言葉に置き換えて下さい)

    View Slide

  18. なんでこうなった(個人の感想)
    ・Join直後、技術を楽しむメンバーが多いのに不思議だった
    ・ビジネスドメイン領域のセキュリティの難易度が高い
    ・通貨関連サービスの拡大(変化も激しい)
    ・法令遵守のために発生するタスク(最優先)
    ・暗号通貨ならではの発生タスク(ハードフォークなど)
    ・毎日億が飛び交うシステム保守の緊張感
    ・deploy自体も慎重になりがち → 結果として後手に

    View Slide

  19. これも「技術的負債」の蓄積

    View Slide

  20. これも「技術的負債」の蓄積
    負債は返済がつらい

    View Slide

  21. これも「技術的負債」の蓄積
    負債は返済がつらい
    特に利子がつらい

    View Slide

  22. 「そうだ、借り換えよう」

    View Slide

  23. 負債の借り換えの大事なポイント

    View Slide

  24. 負債の借り換えの大事なポイント
    以前よりも利子が少なくなること

    View Slide

  25. 負債の借り換えの大事なポイント
    以前よりも利子が少なくなること
      → 負債には高金利から低金利までいろいろある

    View Slide

  26. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    世間の金利の例

    View Slide

  27. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    世間の金利の例
    EOL超過はこの辺

    View Slide

  28. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    世間の金利の例
    EOL超過はこの辺 こっちに借り換え

    View Slide

  29. 負債の借り換えの大事なポイント
    以前よりも利子が少なくなること
      → 負債には高金利から低金利までいろいろある
    借り換え手数料が少ないこと

    View Slide

  30. 負債の借り換えの大事なポイント
    以前よりも利子が少なくなること
      → 負債には高金利から低金利までいろいろある
    借り換え手数料が少ないこと
      → 手数料 = 開発リソース、工数など

    View Slide

  31. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    gem
    update
    その3
    gem
    update
    その4

    View Slide

  32. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    gem
    update
    その3
    gem
    update
    その4

    View Slide

  33. Rails、RubyのEOL
    着手
    gem
    update
    その1
    gem
    update
    その2
    gem
    update
    その3
    gem
    update
    その4
    完済まで至らずとも、借り換え手数料 = 工数 をあまりかけずに
    利率の低い負債に借り換えたい

    View Slide

  34. 事例その1
    migration_comments

    View Slide

  35. def self.up
    set_table_comment :table_name, "A table comment"
    set_column_comment :table_name, :column_name, "A column comment"
    end
    def self.create_table :table_name, :comment => "A table comment" do |t|
    t.string :column_name, :comment => "A column comment"
    end
    ● Rails5.0から同様の機能が搭載されはじめ、5.1で十分な機能に
    ● migration_comments gemの最後のcommitは7年前
    ● Rails6.1では動かない
    migrationでコメントをつけることができる

    View Slide

  36. 対策案
    ・Rails6.1で使えるようにforkしてcommitを積む
     ← もうほかの利用者も居なそう、メンテコスト増
    ・migration fileをRails標準機能利用に書き換える
     ← migration fileが1540、書き換え箇所が580で少々怖い
    ・この際RidgePoleに移行して圧縮する
     ← 個人的には好みだけど依存するgemの増加を懸念する意見
    ・wrapperとなるmethodを作り、migration_commentsを利用停止
     ← code少量、標準機能使うしメンテコスト小【採用】

    View Slide

  37. wrapperとなるmethodを作る(1)
    migration_commentsのmethodを呼ぶと、
    標準のRailsの機能が呼ばれるようにする(set、remove)
    module ActiveRecord
    class Migration
    def set_column_comment(table_name, column_name, comment)
    change_column_comment(table_name, column_name, comment)
    end
    def remove_column_comment(table_name, column_name)
    change_column_comment(table_name, column_name, nil)
    end
    # 以下略

    View Slide

  38. wrapperとなるmethodを作る(2)
    migration_commentsのmethodを呼ぶと、
    標準のRailsの機能が呼ばれるようにする(t.comment)
    module ConnectionAdapters
    module MySQL
    class Table
    def comment(comment)
    ActiveRecord::Migration.change_table_comment(name, comment)
    end
    end
    end
    end

    View Slide

  39. 動作確認
    改修前とtable構造に変化がないか、
    table schema同士のdumpを比較して確認する
    % mysql -u root --no-data co_development > schema_without_migration_comments
    % diff schema_with_migration_comments schema_without_migration_comments
    c12177c12177
    < -- Dump completed on 2023-02-14 16:08:03
    ---
    > -- Dump completed on 2023-02-14 17:05:28

    View Slide

  40. CI監視
    migration_commentsのmethodが新しく呼ばれないよう、
    CIで監視する
    #!/bin/bash
    if find/db/migrate | sort | awk -F/ ‘int($3) > 2023213000000’ | xargs grep -Hn
    ‘set_column_comment\|set_table_comment\|remove_column_comment\|remove_table_comm
    ent\|t\.column’; then
    echo “migration_comments gemのmethodを使わず、Railsが提供するmethodを利用して
    下さい”
    exit 1
    fi

    View Slide

  41. 作業開始から2日程度で
    migration_comments gemを
    取り外すことに成功

    View Slide

  42. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態

    View Slide

  43. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態
    Rails6.1にUpdate可能
    Rails標準機能利用 + 少量のcode

    View Slide

  44. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態
    Rails6.1にUpdate可能
    Rails標準機能利用 + 少量のcode
    みなさん的には何番でしょう?

    View Slide

  45. 事例その2
    switch_point

    View Slide

  46. 複数DBに対応できるgem
    Book.with_readonly { Book.first } # replica dbから読む
    Book.with_readonly do
    author = Book.first.authors.first # replica dbから読む
    author.family_name = 'new_family_name'
    Author.with_writable do
    author.save! # primary dbに書く
    end
    end

    View Slide

  47. Rails6.1には対応していない
    ● 6.0から同様の機能がある
    ● switch_pointは6.0まで対応、6.1から非対応
    ● 6.0であるうちに、Rails標準機能に切り替える必要

    View Slide

  48. 対策案
    ・Rails6.1で使えるようにforkしてcommitを積む
     ← もうほかの利用者も居ないのではないか。メンテコスト増
    ・該当箇所の書き換えを実施する
     ← 230箇所くらいの書き換え箇所がある、ちょっと怖い
    ・wrapperとなるmethodを作る
     ← code少量、標準機能使うしメンテコスト小【採用】

    View Slide

  49. Ruby on Rails Guidesの指示通りにdatabase.ymlを書き換える
    database.ymlの修正
    production:
    primary:
    database: my_database
    username: root
    password: <%= ENV['ROOT_PASSWORD'] %>
    adapter: mysql2
    primary_replica:
    database: my_database
    username: root_readonly
    password: <%= ENV['ROOT_READONLY_PASSWORD'] %>
    adapter: mysql2
      replica: true

    View Slide

  50. wrapperとなるmethodを作る
    switch_pointと同名のwrapper methodを作り、Rails標準のmethodを呼ぶ
    class ApplicationRecord < ActiveRecord::Base
    self.abstract_class = true
    connects_to database: { writing: :primary, reading: :primary_replica }
    def self.with_readonly(&block)
    ActiveRecord::Base.connected_to(role: :reading, &block)
    end
    end

    View Slide

  51. 動作確認
    docker containerを複数立ち上げてそれぞれのquery logを監視
    read / writeが各dbに対しSQLが意図通り振り分けられ発行されるか確認
    services:
    primary_mysql:
    build: ./mysql/primary
    container_name: ‘primary_mysql’
    ports:
       -“3306:3306”
    replica_mysql:
    build: ./mysql/replica
    container_name: ‘replica_mysql’
    ports:
    - “3307:3306”

    View Slide

  52. wrapperとなるmethodを作る作戦
    ● 調査開始から8営業日目でリリース、置き換え完了
    ● トラブルは2件のみ
    ○ switch_pointではreplicaに切り替わったが
    Rails標準機能ではだめなパターンがあった
    ○ 開発環境を複数dbにしたままだった

    View Slide

  53. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態

    View Slide

  54. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態
    Rails6.1にUpdate可能
    Rails標準機能利用 + 少量のcode

    View Slide

  55. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Rails6.1に
    Updateできない状態 みなさん的には何番でしょう?
    Rails6.1にUpdate可能
    Rails標準機能利用 + 少量のcode

    View Slide

  56. 事例その3
    sidekiq6(extension)

    View Slide

  57. sidekiqのふつうの使い方
    class MessageJob
    include Sidekiq::Job
    def perform(article_id, message)
    Message.create!(article_id: article_id, message: message)
    end
    end
    MessageJob.new.perform_async(article_id, “hello”)
    Jobを作る
    呼び出す

    View Slide

  58. sidekiq extensionはより気軽に呼べる
    Message.sidekiq_delay.create!(article_id: article_id, message: message)

    View Slide

  59. 問題点
    ● Ruby3.0では動かない
    ● Sidekiq7では動かない
    ○ 3rd party gemもSidekiq6まで対応

    View Slide

  60. 対策案
    ・該当箇所を本来の使い方に修正する
     ← JobClass約200を作り、利用箇所を1000箇所以上要修正
    ・extensionを改造して、Ruby3.0に対応させる
     ← できなくもないが。。。その後のメンテも大変
    ・自前でextension相当のものを実装する
     ← 現在利用しているケースのみ対応し最小限に【採用】

    View Slide

  61. 代替methodを定義
    ・代替methodを定義、WrapperClassのinstanceを返す
    ・Moduleでincludeし、どのClassからも呼べるようにしておく
    module SidekiqDelayPatch
    module Klass
    def sidekiq_worker_delay(**sidekiq_option)
    return SidekiqDelayPatch::DelayWrapper.new(self, (**sidekiq_option))
    end
    end
    end
    Module.__send__(:include, SidekiqDelayPatch::Klass)

    View Slide

  62. 任意のmethodを受け取る仕組みを作る
    ・WrapperClassのinstanceは、初期化時にClass名を取得
    ・method_missingでmethod名、引数を取得、YAML Dumpして共通Jobに渡す
    class DelayWrapper < DelayWrapperBase
    def initialize(klass, **sidekiq_option)
    @class_name = klass.to_s
    @sidekiq_option = sidekiq_option
    end
    def method_missing(method_name, *args, **kwargs, &block)
    CommonJob
    .set(@sidekiq_option)
    .perform_async(args_yaml(method_name, args,kwargs))
    end
    end

    View Slide

  63. 共通Jobが任意のmethodを実行
    ・渡されたClass名、method名、引数を復元し実行
    class CommonJob
    include Sidekiq::Job
    def perform(args_yaml)
    klass, method_name, args = ::YAML.load(args_yaml)
    kwargs = args.list.is_a?(Hash) ? args.pop : {}
    if klass.ancestors.include?(ActionMailer::Base)
    klass.public_send(method_name, *args, **kwargs).deliver_now
    else
    klass.public.send(method_name, *args, **kwargs)
      end
    end
    end

    View Slide

  64. 代替methodへの置き換えと動作確認
    ・sidekiq_delayを使っている箇所をスプレッドシートに列挙
     ー> 1189箇所
    ・マネージャに号令を掛けてもらい、全チームに改修と動作確認を依頼
    ・調査から置き換えが完了するまで、2ヶ月弱かかった
     ・他の業務の合間を縫っての並行改修
     ・JobClassへの作り替えであったならば、もっとかかる

    View Slide

  65. 代替methodへの置き換えと動作確認
    ・sidekiq_delayを使っている箇所をスプレッドシートに列挙
     ー> 1189箇所
    ・マネージャに号令を掛けてもらい、全チームに改修と動作確認を依頼
    ・調査から置き換えが完了するまで、2ヶ月弱かかった
     ・他の業務の合間を縫っての並行改修
     ・JobClassへの作り替えであったならば、もっとかかる
    後に人々は「Sidekiqまつり」と呼ぶことになる

    View Slide

  66. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Ruby3.0に
    Updateできない状態

    View Slide

  67. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Ruby3.0に
    Updateできない状態
    Ruby3.0にUpdate可能な状態
    自前codeだが将来的に使えなくなる可能性

    View Slide

  68. 6 5 4 3 2 1
    ウ◯ジマ
    くん
    ミ◯ミの
    銀次
    ほのぼの
    約束
    銀行
    カーローン
    住宅
    ローン
    完済
    10日で5割 10日で1割 年4.0%〜    
    18.0%
    年0.85%〜    
    3.5%
    0.319%〜    
    1.58%
    0%
    Ruby3.0に
    Updateできない状態
    Ruby3.0にUpdate可能な状態
    自前codeだが将来的に使えなくなる可能性
    みなさん的には何番でしょう?

    View Slide

  69. ともあれ
    こうして無事
    Railsは6.1に
    Rubyは3.0に
    Updateできました

    View Slide

  70. ふりかえり

    View Slide

  71. Keep
    Problem
    Try
    とりあえずUpdateができた
    ドメイン知識深い開発者や
    チーム横断での協力得られた
    テストが書かれていた
    Updateが遅れて工数増大
    負債を残したところも
    定常的なリソースの確保
     → 実現
    「アプリケーション基盤G」
    技術負債解消や基盤開発を行う部門

    View Slide

  72. リソースがあるからできていること
    ・Sidekiqの負債の解消
     ・Jobの書き方のコード規約の制定
     ・増えないようにsidekiq_worker_delayを
      CIで規制
     ・Jobへの置き換えも徐々に進行
    ・Ruby3.1へのUpdateももうすぐ

    View Slide

  73. 大事だと感じたこと
    ・細かく分割してやっつけていく
     ・一旦借り換えることで金利が低くなる
     ・進捗を感じることができる
     ・Ruby3.1 Updateも同様にやっています
      ・Psych、BigDecimalのUpdateは影響大
      ・まずRuby3.1にしてから、個別にやっつける

    View Slide

  74. まとめ
    ・Updateが遅れると大変です
    ・後からのUpdateには専用リソースが必要です
    ・部署を横断した協力も必要です
    ・分割してやっていくと金利が削れていい感じ

    View Slide

  75. One more thing…

    View Slide

  76. Update業やってみると楽しいです
    ・コツコツやって成果が出る
    ・ソースコードをじっくり読む機会
    ・新たなgemとの出会い

    View Slide

  77. まとめ
    ・Updateが遅れると大変です
    ・後からのUpdateには専用リソースが必要です
    ・部署を横断した協力も必要です
    ・分割してやっていくと金利が削れていい感じ
    ・Update業楽しいです

    View Slide