Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Tokyu.rb(TokyuRuby会議)

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

RailsとRubyの Updateの話

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

事例その1 migration_comments

Slide 35

Slide 35 text

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でコメントをつけることができる

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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 # 以下略

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

動作確認 改修前と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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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できない状態

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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 みなさん的には何番でしょう?

Slide 45

Slide 45 text

事例その2 switch_point

Slide 46

Slide 46 text

複数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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

動作確認 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”

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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できない状態

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

事例その3 sidekiq6(extension)

Slide 57

Slide 57 text

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を作る 呼び出す

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

代替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)

Slide 62

Slide 62 text

任意の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

Slide 63

Slide 63 text

共通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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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できない状態

Slide 67

Slide 67 text

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だが将来的に使えなくなる可能性

Slide 68

Slide 68 text

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だが将来的に使えなくなる可能性 みなさん的には何番でしょう?

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

ふりかえり

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

One more thing…

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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