メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

ClinPeer Railsプロジェクトの設定値管理(2025年版)

こんにちは。サーバーサイドエンジニアの三村(@t_mimura39)です。

こちらでご案内した通り、弊社で新しくリリースした「ClinPeer」の裏側をご紹介します。 tech.medpeer.co.jp

今回は小ネタとして「Railsプロジェクトの設定値管理」についてご紹介します。

目次

設定値管理とは

ここでは「実行環境毎に異なる値」を「Rubyなどのプログラムコードとは別のYAMLなどのファイル」で管理する方法について言及します。
例えば、「SaaSのAPIを利用するための接続情報」だったり、もっと単純に「異常発生時の通知先Slackチャンネル」のようなものがあります。
この仕組みを利用することでプログラムの複雑さを軽減することができ、可読性の向上に繋がります。

before

# 環境差分を設定値として切り出さずにプログラム内でif分岐
def host
  if Rails.env.production?
    "prd.example.com"
  else
    "dev.example.com"
  end
end

after

# 環境差分を設定ファイルに切り出すことでプログラムが単純化する
def host
  Setting.host
end
production:
  host: prd.example.com
development:
  host: dev.example.com

Railsプロジェクトでの設定値管理の選択肢

Settingslogic

github.com

馴染み深い方も多いのではないでしょうか。
シンプルな機能で使い勝手も良かったため私も多用していました。

しかし、ここ最近 メンテナンスが停滞しており 今後の継続利用が難しい状況です。
脱Settingslogicを検討した・されているプロジェクトが多くあるかと思います。

SettingsCabinet

github.com

Settingslogicの置き換えを狙って作成されたGemです。
採用事例は少ないですが、薄い実装のGemのためSettingslogicの使い勝手が特に気に入っている方はこちらを検討してみると良いかと思います。 zenn.dev

その他諸々Gem

他にも様々な設定値管理用のGemがありますがここでは割愛します。
以下のようなGemがありますが、いずれも機能過多に感じます。

github.com github.com github.com github.com

Rails標準 config_for

というわけで、今回採用した方法はこちらになります。

api.rubyonrails.org

脱Settingslogicからconfig_forに乗り換えたプロジェクトも多いのではないでしょうか。
しかし、config_forには「設定値をネストできない」という弱点があります。正確にはネストはできますがHashでの管理となるため参照する際に気持ちが良くありません。

# config/dummy.yml
development:
  a:
    b: c
Rails.application.config_for(:dummy)
#=> {a: {b: "c"}}

Rails.application.config_for(:dummy).a
#=> {b: "c"}

Rails.application.config_for(:dummy).a[:b]
#=> "c"

Rails.application.config_for(:dummy).a.b
undefined method 'b' for an instance of Hash (NoMethodError)

ClinPeerでは少し工夫してconfig_forを利用することで本弱点を回避しています。

config_forの活用方法(定義・参照)

一つの config/application.yml などに全ての設定値を集約定義するのではなく、設定値の種類毎にファイルを分離して管理しています。
以下のような使い方です。

$ tree config/settings/
config/settings/
├── application.yml
├── firebase.yml
├── host.yml
├── rollbar.yml
├── sendgrid.yml
├── slack.yml
└── zendesk.yml
# config/settings/sendgrid.yml
development:
  api_key: dummy_development_api_key
test:
  api_key: dummy_test_api_key
Rails.env
#=> "development"
Setting::SendGrid.api_key
#=> "dummy_development_api_key"

このように管理する理由は二つあります。
まず一つ目は「単純に管理がしやすい」点です。全ての設定値を一つの設定ファイルで管理すると数百、数千行にもなることがあり管理が煩雑になります。

二つ目は「config_forの弱点(設定値のネストがしにくい)を軽減できる」点です。設定値のネストがしたくなる粒度でファイルを分割することで本問題を回避することができます。ファイルが量産されることによる管理の煩雑さが懸念としてありますが、現状問題になったことはありません。

config_for活用方法(裏側)

このような形で Setting名前空間配下に設定値種類毎の専用モジュールを定義する形で実装しています。

# config/initializers/002_setting.rb
module Setting
  {
    "Application" => "config/settings/application.yml",
    "Firebase" => "config/settings/firebase.yml",
    "Host" => "config/settings/host.yml",
    "Rollbar" => "config/settings/rollbar.yml",
    "SendGrid" => "config/settings/sendgrid.yml",
    "Slack" => "config/settings/slack.yml",
    "Zendesk" => "config/settings/zendesk.yml",
  }.each do |k, v|
    const_set k, Rails.application.config_for(Rails.root.join(v))
  end

  def self.method_missing(name)
    Application[name]
  end

  def self.respond_to_missing?(name, _include_private)
    Application.key?(name)
  end
end

ちなみに、 application.yml にはより汎用的な設定値を定義しており Setting.revision のようなシンプルなI/Fで参照できるようにしています。

なぜ config.x. を使わないのか

Rails Guide では config.x.payment_processing.schedule = :dailyconfig.payment = config_for(:payment) のような使い方が紹介されています。
もちろんこのような形でも利用可能ですが、 config.x.payment_processing.schedule のような参照方法は長く気持ちが良くないため採用しませんでした。
Setting::PaymentProcessing.schedule のようなI/Fの方が美しいですよね。

また、I/Fが美しいということはテスト時にもモックしやすいと同義です(要出典)。

allow(Setting::PaymentProcessing).to receive(:schedule).and_return(:daily)

なぜ動的に定義しないのか

Rails.root.glob("config/settings/*.yml") のような形で config/settings 配下に配置されているファイルに対応するモジュールを動的に定義することも可能です。が、ClinPeerはあえてそのような形にはせずに先のような「モジュール名」と「ファイルパス」のマッピングを定義しています。
大した理由はないのですが、余談程度にご説明します。
設定値の多くはSaaS毎に定義されます。SaaSの名称は専用のinflection定義が必要になる場面があり(SendGridなど)その管理コストを払うくらいであれば明示的にマッピング定義をした方が分かりやすくて良いだろうと判断した結果になります。
あとはシンプルにコード検索もしやすいですね。

こういった仕組みは一度作られると中々改善されないので、些細なところまでこだわりました。

秘匿値管理方法

少し話を変えて、ClinPeer内での秘匿値の管理方法についてご紹介します。
秘匿値の特性に応じて主に2種類の管理方法を使い分けています。

管理方法 参照・更新可能者 特性
環境変数 DBパスワード、JWT秘密鍵 一部のリードレベルのエンジニアのみ 漏洩すると個人情報の流出につながるリスクのある秘匿性の高い値
Rails標準のCredential管理 SaaSのAPIキー ClinPeer開発エンジニア全員 万が一漏洩してもイタズラ程度でしか悪用されることがない値

環境変数

実際の秘匿値はRailsリポジトリとは別のTerraform管理用リポジトリで暗号化された状態で管理されています。
Rails ECSコンテナ起動時に環境変数として注入されるため、一部のエンジニア以外は設定値に触れることなくセキュアに管理することができます。

一方で、秘匿値を追加する際に二つのリポジトリを跨いで修正し、デプロイに関しても順序を気をつける必要があるため若干管理が煩雑になるデメリットがあります。

Rails標準のCredential管理

皆様お馴染みの bin/rails credentials:editそれです。

ClinPeerではRails標準の「development, test, production」の他に「staging」というRAILS_ENVも定義しています。
Multi Environment Credentialsの機能を活用して環境毎に分離して秘匿値を管理しています。

「Multi Environment Credentials」って便利なのにRails公式のドキュメントに記載ないのですよね。以下のブログが詳しそうだったため詳細はこちらをご参照ください。 blog.saeloun.com

ClinPeerプロジェクト発足時にはこれらのどちらかを選択するか悩みましたが、どちらを利用しても良いのだということに気がつき適宜使い分ける運用としています。

参照方法

秘匿値の管理方法は上記の通り2種類ありますが、アプリケーションコード上で参照する際にはその点を意識しなくて済むような形としたいです。
設定YAMLファイルの中でERB評価する形で値を埋め込むことで、Setting::SendGrid.api_key Setting::Rollbar.server_access_token のような統一的なI/Fで参照できるようになっています。

# config/settings/sendgrid.yml
development:
  api_key: dummy_api_key
test:
  api_key: dummy_api_key
staging:
  api_key: <%= ENV["SENDGRID_API_KEY"] %>
production:
  api_key: <%= ENV["SENDGRID_API_KEY"] %>
# config/settings/rollbar.yml
development:
  server_access_token:
test:
  server_access_token:
staging:
  server_access_token: <%= Rails.application.credentials.dig(:rollbar, :server_access_token) %>
production:
  server_access_token: <%= Rails.application.credentials.dig(:rollbar, :server_access_token) %>

Rubyな値を一度YAMLに変換した後にconfig_forでRubyな値に変換するという若干無駄なコストを払っていますが、この使い方が今のところ一番扱いやすく感じています。

おわり

前回のブログ記事(ClinPeer Railsプロジェクトの技術選定(2025年版) - メドピア開発者ブログ)とは違い、少し実装に踏み込んだ話をしてみました。
今回のような小ネタが続く予定ですが、どうぞお楽しみください。

(オマケ)Slack通知先チャンネルの管理方法について

ClinPeerでは特定のイベント発生をトリガーとして、Slackチャンネルに通知するような実装がいくつかあります。
実行環境毎に通知先を分ける必要があり、その通知先管理についても本config_forの仕組みを活用しています。

その際のTipsとして、「通知先チャンネル名」をそのまま設定値として管理するのではなく「通知先チャンネルのID」を管理することをオススメします。 (SlackのAPIは通知先を指定する際にチャンネル名でなくチャンネルIDを受け付けているため、大抵のコードは置き換えるだけで動作するはずです)

# config/settings/slack_channel.yml
development:
  ops: "C07AAAAAAAA" # playground
test:
  ops: "C07AAAAAAAA" # playground
staging:
  ops: "C07AAAAAAAA" # playground
production:
  ops: "C07BBBBBBBB" # ops

このようにしておくと、万が一Slackのチャンネル名が変更された際にも通知処理でエラーにならずに継続動作します。 設定値管理とは本質的には無関係の超小ネタでした。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

ClinPeer Railsプロジェクトの技術選定(2025年版)

こんにちは。サーバーサイドエンジニアの三村(@t_mimura39)です。

こちらでご案内した通り、弊社で新しくリリースした「ClinPeer」の裏側をご紹介します。 tech.medpeer.co.jp

今回はClinPeerのバックエンドについての簡単なシステム概要と選定技術の紹介編です。
2024-2025年にrails newをした新鮮なRailsプロジェクトの様子をお楽しみください。

目次

システム概要

細かな選定技術の紹介の前に簡単なシステム概要をご紹介します。
弊社は主にRuby on Railsを採用しており、今回も特別な技術的な要件がなかったためRuby on Railsを採用しました。
また、社内標準的にクラウドはAWS環境が採用されています。

「Amazon CloudFront -> ALB -> AWS Fargate(puma -> Rails)」で構成されるごく一般的なWebシステムです。

rails stats

次にRailsシステムの規模感のご紹介です。

定量的な指標としてrails statsを記載します。

bin/rails stats
+----------------------+--------+--------+---------+---------+-----+-------+
| Name                 |  Lines |    LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers          |   2982 |   2207 |     106 |     296 |   2 |     5 |
| Helpers              |      4 |      2 |       0 |       0 |   0 |     0 |
| Jobs                 |    239 |    162 |      16 |      20 |   1 |     6 |
| Models               |  10463 |   6557 |     230 |     714 |   3 |     7 |
| Mailers              |    685 |    611 |       9 |      17 |   1 |    33 |
| Views                |   3904 |   3662 |       0 |       0 |   0 |     0 |
| Libraries            |    634 |    451 |       5 |      33 |   6 |    11 |
| Config specs         |     49 |     39 |       0 |       0 |   0 |     0 |
| Controller specs     |     17 |     13 |       0 |       0 |   0 |     0 |
| Form_builder specs   |   1059 |    855 |       0 |      24 |   0 |    33 |
| Job specs            |    577 |    439 |       0 |       0 |   0 |     0 |
| Lib specs            |    404 |    350 |       0 |       1 |   0 |   348 |
| Mailer specs         |    608 |    500 |       0 |       1 |   0 |   498 |
| Model specs          |  15063 |  12527 |       2 |      14 |   7 |   892 |
| Request specs        |   5319 |   4560 |       0 |      45 |   0 |    99 |
| System specs         |   3339 |   2588 |       0 |       3 |   0 |   860 |
| Task specs           |    165 |    132 |       0 |       0 |   0 |     0 |
| Validator specs      |    278 |    214 |       0 |       0 |   0 |     0 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total                |  45789 |  35869 |     368 |    1168 |   3 |    28 |
+----------------------+--------+--------+---------+---------+-----+-------+
  Code LOC: 13652     Test LOC: 22217     Code to Test Ratio: 1:1.6

statsの結果から分かる通りまだまだ規模が小さいシステムですね。
Modelsが230とありますが、この内ActiveRecordなクラス(= テーブル数)は87個とこれまた両手に収まる程度の規模感です。

RSpecを採用している点以外はRails標準のディレクトリ構成となっています。

Gemfile

「技術選定」と称していますが、特殊な技術を採用しているわけではないため実質利用しているGemのご紹介となります。

熟練のRubyistはGemfileを見るだけでそのRailsシステムの全容が分かると聞いたことがある(要出典)のでそのまま公開します。

gem "rails"

# Rack
gem "puma"

gem "actionpack-cloudfront_viewer_address"

# DB
gem "trilogy"

# Authn / Authz
gem "bcrypt"
gem "jwt"

# Job
gem "mission_control-jobs"
gem "solid_queue"

# Cache
gem "solid_cache"

# ActiveRecord / ActiveModel Extension
gem "kaminari"

# Frontend
gem "propshaft"
gem "vite_rails"

# Network
gem "async-http-faraday"
gem "faraday"
gem "ueki"

# View Templating Engine / Serializer
gem "jb"

# SDK
gem "aws-sdk-cloudfront"
gem "aws-sdk-s3"
gem "datadog"
gem "googleauth"
gem "rollbar"
gem "sendgrid-ruby"
gem "zendesk_api"

# XML
gem "nokogiri"

# bi
gem "blazer"

# Other
gem "active_hash"
gem "bootsnap", require: false
gem "flipper"
gem "flipper-active_record"
gem "flipper-ui"
gem "fugit"
gem "lograge"
gem "maintenance_tasks"
gem "recite_csv"
gem "repl_type_completor", require: false
gem "rqrcode"
gem "useragent"
gem "validate_url"
gem "warning"

group :development, :test do
  gem "annotaterb"
  gem "debug", require: "debug/prelude"
  gem "factory_bot_rails"

  gem "rbs_rails", require: false, github: "pocke/rbs_rails", branch: "master"

  gem "prosopite"
  gem "steep", require: false

  gem "traceroute"
end

group :ruby_lint do
  gem "brakeman", require: false
  gem "erb_lint", require: false
  gem "rubocop", require: false
  gem "rubocop-capybara", require: false
  gem "rubocop-factory_bot", require: false
  gem "rubocop-performance", require: false
  gem "rubocop-rails", require: false
  gem "rubocop-rspec", require: false
  gem "rubocop-rspec_rails", require: false
  gem "rubocop-thread_safety", require: false
end

group :test do
  gem "capybara"
  gem "capybara-playwright-driver"
  gem "ci_logger"
  gem "committee-rails"
  gem "database_rewinder"
  gem "rspec-rails"
  gem "rspec-snapshot"
  gem "simplecov", require: false
  gem "super_diff"
  gem "test-prof"
  gem "webmock", require: false
end

group :development do
  gem "letter_opener_web"
  gem "web-console"

  gem "ruby-lsp", require: false
  gem "ruby-lsp-rails", require: false
  gem "ruby-lsp-rspec", require: false
end

少しだけ社内用Gemを取り除いていますが、それ以外は実際のGemfileの内容と同等です。
特別な技術は利用しておらず、素直なrailsシステムであることが分かるかと思います。

それではこれらから抜粋して、もう少し深掘って紹介させてください。

技術選定

Ruby

もちろん最新です。

$ cat .ruby-version
3.4.2

ちなみに、最近話題のdevin.aiにv3.4.2にアップデートしてもらいました。

devin.aiにRubyバージョンをアップデートしてもらっている様子

最近のRubyは破壊的なアップデートがほとんどないため、積極的に安心してバージョンアップすることができて素晴らしいですね。

Ruby on Rails

こちらも最新の「v8.0.1」です。

2024年7月に以下のコマンドでrails newを実行しました。この時点でrails v8のリリースが予想できていたため最初からbeta版を採用していました。

rails _7.2.0.beta3_ new ./ --name=clinpeer --database=trilogy --skip-action-mailbox --skip-action-text --skip-active-storage --skip-action-cable --skip-asset-pipeline --skip-javascript --skip-hotwire --skip-jbuilder --skip-test --skip-system-test --devcontainer

ClinPeerでは最新のrailsに追従できるように Rails Edge(mainブランチ)をCIで回しています。 そのおかげでrailsの最新バージョンがリリースされたら数日後にはすぐに更新対応できるような体制が築けています。

Rails EdgeをCIで回す方法はタイミーさんのブログが参考になるかと思います。 tech.timee.co.jp

Puma

github.com

https://github.com/Shopify/pitchfork も気になるところですが、今回は素直にRails標準のPumaを採用しました。
特別な使い方は特にしていないため次にいきましょう。

ActionPack::CloudfrontViewerAddress

github.com

自作Gemです。
元々ClinPeerの開発で本対応が必要になったため先行してGemとして公開しました。完全新規のプロジェクト立ち上げはこういった機会にもなるので良いですね。

Gemの詳細はこちらのブログ記事をご参照ください。 tech.medpeer.co.jp

Trilogy

github.com

ClinPeerはMySQLを採用しました。
「PostgreSQL vs MySQL(vs SQLite)」の話もありそうですが、これだけで重厚なブログネタになりそうなため今回は割愛します。

これまでRailsでMySQLといえば https://github.com/brianmario/mysql2 でしたが、昨今は次世代のMySQLアダプタとして「Trilogy 」が目立っています。
ネイティブライブラリへの依存が少なく、今後のメンテナンス性の観点からもtrilogyが優れていると判断し迷いなくこちらを採用しました。
今のところは不具合・デメリットなど特になく安定して稼働しています。

SolidQueue

github.com

こちらは今回の技術選定の目玉ポイントの一つ目です。 ActiveJobのアダプタとしてSolidQueueを採用しました。

弊社では https://github.com/sidekiq/sidekiq の採用実績が多く知見も溜まっている一方で、SolidQueueは国内外問わずまだまだ採用実績が少ないためチーム内から若干の不安の声も挙がっていました。
しかし、Rails v8から標準アダプタになることは分かっており今後のデファクタスタンダードになるだろうという期待も込めて先陣切って採用することにしました。

実際にコードを読んでみると、ActiveRecordを活用したアプリケーションとして実装されているため今となってはSidekiqよりも親近感があるほどです。
SolidQueueを採用するにあたり執筆した解説ブログも公開しているのでこちらも良ければ目を通してください。 tech.medpeer.co.jp

SolidQueueを採用する上で問題視される点の一つが「パフォーマンス」ですが、幸い(?)現在のClinPeerでは大量で尚且つ即時性が求められるようなJobがないためこの点は現在問題となっていません。今後運用する中で課題が見えてきたら随時ご報告します。

SolidQueueについて語りたいことはまだまだありますが、長くなってしまうため次の機会でということで。

SolidCache

github.com

導入はしているものの実は使っていません(正直)
「Redisを使わない宣言」という意味合いで先行してSolidCacheのセットアップをしましたが、Rails内でのキャッシュを活用する場面に遭遇していない現状です。
パフォーマンスやDB負荷などに課題があり、CloudFrontでのキャッシュで対応しきれないようなケースが発生したら利用する想定です。

導入されていて負債になっているわけでもないためそれまで眠らせてあります。
ちなみに、SolidQueue同様にActiveRecordな実装なため内部処理を読み解くのは容易であり学びもありました。

Kaminari

github.com

王道のページネーションGemですね。
https://github.com/ddnexus/pagy と少し悩みましたが、使い勝手の面でKaminariの方が好みでした。 Pagyの方が軽量で高速と評されていますが、正直ページネーション処理のパフォーマンスはリクエスト全体と比較すると軽微なためこの点は重視しませんでした。

Async::HTTP

github.com

Firebase Cloud Messaging(以後FCM)のAPIを並列で500件ほど呼び出すために利用しています。
FCMはHTTP/2に対応しているため、クライアントもHTTP/2に対応しているものを探しました。
https://honeyryderchuck.gitlab.io/httpx/wiki/Faraday-Adapterhttps://github.com/dleavitt/faraday-typhoeus なんかも候補でしたが、今後のメンテナンス性も考慮した結果 https://github.com/socketry/async-http を採用することとしました。

Ueki

github.com

Ruby, Rails, SolidQueue と並ぶようなGemではありませんが紹介です。 HTTP API Clientを自作するためのGemです。

詳細はこちらをご参照ください。 tech.medpeer.co.jp

はい、ただの宣伝でした。次いきましょう。

Jb

github.com

A simpler and faster Jbuilder alternative.

このコンセプトが大好きなので愛用しています。
が、ClinPeerは諸般の事情によりそこまで活用していません。

jbにデメリットがあるため活用していないわけではなく、APIのレスポンスに特殊な形式を採用しているが故の結果です。
この点については本連載記事の中で後日ご紹介します。

Nokogiri

github.com

王道中の王道ですね。
XMLファイルをパースする処理を実装する必要がありそこで利用しています。

弊社社員がメンテナでもある https://github.com/ruby/rexml を利用しても良かったのですが、Railsの依存で結局Nokogiriはインストールされるためパフォーマンス面で有意なNokogiriを利用することとしました。

naitoh.hatenablog.com

Blazer

github.com

正式にサービスがリリースされるまでの簡易的なBIツールとして利用していました。
弊社ではBIツールとして Redash がよく使われていましたが、それと比べてRailsエンジンとしてお手軽に導入できる点が魅力的でした。

ActiveHash

github.com

区分値管理Gemとして古くから使われてきたGemですね。
これに代替するようなGemを知らないため採用しましたが、もし他におすすめのGemがあれば教えてください。

Flipper

github.com

王道なフィーチャーフラグGemです。
Flipperを採用している方は多くいると思いますが、ClinPeerでは少し使い方を工夫しています。
こちらに関しても別途本連載記事にてご紹介予定ですのでお楽しみに。

Lograge

github.com

こちらも古くから使われているGemですね。Railsのリクエストログを構造化するものです。
最近だと https://github.com/reidmorrison/semantic_logger なんかも注目を集めていますが、Logrageが手に馴染んでいるためこちらを採用しました。

Rails本体で構造化ログに関して動きがありそう(最近は停滞していそう)なので楽しみにしています。
ログの構造化に関しても一工夫入れているためまとめ記事を執筆予定ですしました。

tech.medpeer.co.jp

MaintenanceTasks

github.com

今回の技術選定での注目ポイント二つ目です。
開発初期は特にデータマイグレーション作業など必要になる場面が多く、ついrails consoleでデータ更新をしたくなるものです。
MaintenanceTasksはrails consoleの甘い誘惑からの脱却を手助けしてくれるGemです。

じわじわと知名度が上がってきていて、最近ではohbaryeさんのプレゼンで知った方も少なくないのではないでしょうか。
MaintenanceTasksが何を解決してくれるのか、などを歴史的経緯も含め丁寧に解説されているためぜひご覧ください。 kaigionrails.org

さりげなく私も「MaintenanceTask利用者」として紹介していただきました。めっちゃMaintenanceTask推している人みたいに見える。

(追記)弊チームの伏見さんが紹介記事を書いてくださったのでこちらも合わせてご参照ください。 tech.medpeer.co.jp

ReciteCSV

github.com

私のお気に入りGemの一つなのでご紹介します。
CSVを読み取る処理を実装することはありますか?ありますよね。
標準のCSVライブラリは row[0]row["col"] のように使う必要があり可読性の点で問題が生じることが多々あります(個人の感想です)。
そういった時に便利なGemでして、CSVの行を1行1行 POROなオブジェクトとして row.col のように取り扱えるようにしてくれるGemです。

私の恩師が作者なこともあり手によく馴染んでいるという理由もありますが、これ以上にCSV読み取り処理を綺麗に実装するのを助けれくれるGemはないのではないかと私は思っています。 blog.yujigraffiti.com

UserAgent

github.com

UserAgentをパースするGemってたくさんあって迷いますよね。
正直なところそこまでリッチな機能は求めていなかったためどのGemでも良いかなと考えていました。
が、そういえば最近ActionPackの依存にuseragent Gemが追加されていることを思い出したため素直にこれを利用することとしました。

github.com

AnnotateRb

github.com

長年 https://github.com/ctran/annotate_models を愛用していましたが、ここ最近メンテナンスが停滞しつつあるためアクティブにメンテされているforkされた annotaterb を採用しました。
基本的な機能は annotate_models と同等ですが、table commentなど若干の追加機能が実装されていて嬉しいですね。

RBS系各種

まだ私は頑張っていません tech.medpeer.co.jp

Prosopite

github.com

ActiveRecordのN+1検知Gemです。
N+1検知といえば https://github.com/flyerhzm/bullet が有名ですが、Bulletは誤検知(偽陽性・偽陰性)や最新バージョンRailsへの追従の遅さが課題となることが多々ありました。
BulletはActiveRecordにモンキーパッチを当ててN+1を検知するアプローチなのに対して、ProsopiteはActiveRecordが発行したSQLを監視するというアプローチを採用しています。
ActiveSupport::Notifications.subscribe 'sql.active_record' を活用したシンプルな作りなので、Railsのバージョンアップの影響を受けにくい利点があります。

zero false positives / false negatives. をアピールしていますが、判定がシンプルなためどうしても実装次第では誤検知は生じてしまっています。
誤検知といっても発行されるSQLを確認すると「まぁ仕方ないよね」と素直に受け入れることができるため、Bulletの誤検知の理不尽さ?はないように感じています。(Bulletは賢い判定な分誤検知発生時に原因の特定が難しいのですよね)

また、他にもRails標準のStrictLoadingに関しても採用を検討しましたがガチガチに利用しようとすると開発体験の悪化につながりそうと判断し今回は採用を見送りました。

Traceroute

github.com

「ルーティング定義されているがアクションメソッドが定義されていない」「アクションメソッドが定義されているがルーティングが定義されていない」の二つを検知するGemです。古くからあるGemなので知っている方も多いのではないでしょうか。

Rails本家に rails routes --unused という機能が実装されていますが、Tracerouteを完全に置き換えられるものではないため継続利用しています。

RuboCop

昨今の大抵のRailsシステムにはRuboCopが導入されているかと思うので紹介は割愛します。
最近のRailsでは https://github.com/rails/rubocop-rails-omakase が標準となっていますが、肌に合わなかったため採用しませんでした。

Copは最初からガチガチに固めているわけではなく、Metrics系のCopを一律無効化したりとチームメンバーの特性に応じて使い分けています。 深く可読性について考えないままに「Metrics/AbcSizeに指摘されたのでとりあえずメソッド分割しました」のような対応をするストレスフルな開発体験から脱却できていて幸せです。

CiLogger

github.com

弊社技術顧問willnetさん作のGemです。
CI環境化でのRSpec実行において、失敗したテストに関するログのみを出力するように制限してくれます。Flaky Test発生時の原因特定などが捗ったりログ出力量が抑えられるため若干のコスト削減に繋がったりします。
導入デメリット特にないのでとにかく入れておいて良いのではないでしょうか。

blog.willnet.in

Committee / Committee::Rails

github.com

APIのI/F定義をOpenAPIなYAMLファイルで管理しています。
I/F定義通りにAPIが実装されていることを担保するために、RequestSpecでCommitteeを活用し「テスト時のリクエスト内容」と「レスポンス」の妥当性を検証しています。

RSpec::Snapshot

github.com

RSpecのテスト期待値をスナップショットファイルという形で管理してくれるGemです。
JavaScript のテストでJestなんかを利用する方はイメージしやすいのではないでしょうか。

ClinPeerでは主にメールの本文の期待値管理として利用しています。
テキストメールもHTMLメールも真面目に期待値をテストしようとすると管理が煩雑になるためJestっぽいスナップショット管理の仕組みが欲しくなり、少し探してみたら本Gemを見つけました。

メール本文以外にも少し期待値が巨大なテキストファイルになるようなケースでも活用しています。

SuperDiff

github.com

RSpecでのdiffをリッチな表示にし差分を分かりやすくしてくれるGemです。
一時期作者のメンテ停止の流れがありましたが、別の方がメンテを引き継ぐことになりました。 github.com

その他

認証系Gem

https://github.com/heartcombo/devisehttps://github.com/Sorcery/sorcery など著名なGemも検討しましたが、重厚なGemは将来の負債になると考え Rails公式提供のAuthentication Generator をベースに必要最低限の機能を自前実装する方針としました。

実は開発初期の段階では「とりあえずdeviseを入れておこう」くらいの軽い気持ちで技術選定していました。しかし、deviseを使っている内に「deviseの嬉しみ」より「deviseの辛み(というより実装不透明さ)」を感じるようになり、早い段階で脱deviseな対応を入れました。

Ridgepole

github.com

開発初期はDBスキーマの更新が活発なためRidgepoleでの気軽なスキーマ更新が魅力的だったため活用していました。

Ridgepoleは発行されるALTERが若干暗黙的になるためサービス実運用が開始する際には一工夫(--dry-runで発行されるALTERを事前確認するなど)が必要となります。サービス実運用が開始するとスキーマ更新の頻度は落ち着いてきたため現在はRails標準のマイグレーションを利用しています。

設定値管理(Rails.application.config_for)

設定値管理系のGemもたくさんありますよね。

ClinPeerではいずれも利用せずに、Rails標準のconfig_for を利用しています。
少し工夫した使い方をしているためこちらに関しても別途ブログ執筆予定ですしました。

tech.medpeer.co.jp

ファイル管理(ActiveStorage)

https://github.com/shrinerb/shrine も選択肢にありましたが、Shrineを採用する理由も特になかったため「Rails標準」という理由(使い勝手なども考慮しつつ)からActiveStorageを採用しました。
ActiveStorageに関してもトレーサビリティを向上させるような工夫を入れたりしているため、機会があればご紹介したいですしました。

tech.medpeer.co.jp

HTMLテンプレートエンジン(ERB)

SlimHaml なんかも候補にありましたが、シンプルさを重視して素直なERBを採用しました。
個人的には https://github.com/amatsuda/string_template なんかも気にはなっていましたが、ERBに慣れているメンバーも多かったためこれに関しては討議するまでもなくあっさりとERBに決まりました。

ヘルスチェック(Rails標準Health)

弊社内では https://github.com/jphenow/okcomputer を利用しているプロジェクトが多かったのですが、 (Rails標準のHealth)https://github.com/rails/rails/pull/46936 で必要十分そうだったためGemの採用は見送りました。

おわり

ClinPeerのRailsプロジェクトの概要と選定技術について、Gemを中心に紹介してみました。 新しいものも積極的に採用しながら、古き良きものも大事にするという精神で技術選定をしました。一つ二つ発見があれば幸いです。
次の連載記事ではもう少し深いお話を紹介する予定ですので、どうぞお楽しみに!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。
ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

ClinPeer Androidアプリ開発の裏側とFirebase Dynamic Links(FDL)の代替について

はじめに

こんにちは!メドピアにてモバイルアプリエンジニアをしている佐藤です。
1月27日、 がん専門医のための論文キュレーションアプリ 「 ClinPeer 」 を正式にリリースしました。私はAndroidアプリエンジニアとして要件定義から参画しました。

clinpeer.medpeer.jp

今回はClinPeerアプリ開発の裏側の第一回目の記事ということで、ClinPeerでのAndroidアプリ開発の構成や採用技術、Firebase Dynamic Links(FDL)の代替としてClinPeerではAdjustを採用した為、Adjustの活用事例について書いていきたいと思います。

モバイルアプリチームの開発構成

ClinPeerでモバイルアプリエンジニアとして参画したメンバーは以下の通りとなります。

  • Androidアプリエンジニア:1人月
  • iOSアプリエンジニア:1人月
  • モバイルアプリテックリーダー:0.5人月

サーバーサイドチームと比較すると1/3となる人数だった為、広範にわたる仕様への理解と強い責任感が求められましたが、その分裁量を持って開発に取り組むことが出来ました。

採用技術

Androidアプリ開発においてはFullCompose + Clean Architecture + マルチモジュールで構成で開発を進めました。
弊社で他に展開しているアプリだと、レイアウトだけや一部の機能だけJetpack Composeを採用しているアプリがほとんどでFull Composeのアプリはなかった為、今回の開発を通じてFull Composeならではの設計や実装の知見が蓄積され、今後のアプリ開発にも活かせる貴重なナレッジを得ることが出来ました。

また、ClinPeerはFDLが求められるサービスでしたが、FDLは2025年8月25日に廃止される為、代替サービスとしてAdjustを採用しました。
そこでClinPeerにおけるAdjustの活用事例および実装例について、一部にはなりますが以下でご紹介します。

Adjustの活用事例と実装例

以下でご紹介する実装例はAdjustSdkを導入されていることが前提です。AdjustSdkの導入についてはこちらをご参照ください。

Adjustディープリンク(FDLの代替)

FDL同様にアプリ未インストール時にはストアページ、インストールされている場合はアプリを開くだけであればとても簡単です。

Adjustダッシュボード上のカスタムリンクから新規リンクの作成を進め、ユーザーの遷移先の設定でアプリをインストールしたユーザーの遷移先をアプリ内画面、サポートされているデバイスを使用するユーザーの遷移先をアプリストアに設定してリンクを作成します。
PC等アプリがインストール出来ない場合の遷移先はデフォルトではアプリストアとなっているので、PCから開いた時に特定のURLに遷移させたい場合は、「 フォールバックを追加 」からリダイレクトURLを設定するのがおすすめです。

ユーザーの遷移先の設定

カスタムリンクについて詳しくはこちらからご確認いただけます。

イベント送信

ClinPeerではイベント情報をFirebaseに送信していますが、Adjustにもイベントを送信しています。
Adjust上でもイベント送信することで流入元に応じたイベント計測や、設定したイベントを指標にした到達率の計測などが出来るようになります。

イベント設定もとても簡単で、Adjustダッシュボード上のイベントからイベント追加と進み、イベント名を設定してイベントを追加します。イベント追加時、ユニークイベントとして設定のチェックをONにすることで、デバイスで一度だけしかイベントが計測されないようにできます。

イベント追加

イベント追加後はイベントトークンが発行されますので、イベントを発火したい箇所で以下の通りにするだけでイベント送信が可能です。

val adjustEvent = AdjustEvent("abc123")
Adjust.trackEvent(adjustEvent)

イベント送信について詳しくはこちらからご確認いただけます。

Androidアプリリンク

ClinPeerのアプリではブラウザ上での新規会員登録後のアプリ復帰でAndroidアプリリンクを用いており、Androidアプリリンクでの復帰時に会員登録情報をサーバ側と同期するようにしています。
この要件だけであればAdjustを用いずとも可能ですが、アプリ未インストール時やPCでの遷移先を制御したかった為、AndroidアプリリンクでもAdjustを活用しました。

活用するにはまず、Adjustダッシュボード上の対象アプリのAppView→Androidのプラットフォームから進み、Androidアプリリンクでディープリンクを設定のトグルをONに設定し、GooglePlayConsoleから取得したSHA-256 証明書フィンガープリントと任意のアプリスキームを設定して保存します。

ブランドドメインの設定

それが完了したらAndroidManifest.xmlに以下を追加します。

<activity
    android:name=".MainActivity">

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="https" />
        <data android:host="設定したブランドドメイン.go.link" />
    </intent-filter>
</activity>

Adjustを用いない場合はこの後assetlinks.jsonファイルのホスティングが必要ですが、Adjustを用いた場合は自動でホストしてくれる為、とても手軽に進めることができます。
AndroidStudioのApp Links Assistantでデジタルアセットリンクの検証が問題ないことが確認出来たら、その後は設定したURLからアプリを起動することで、onCreateまたはonNewIntentからディープリンク情報を取得できるので、取得した情報からハンドリングが可能です。

val data = intent?.data
// ディープリンクに応じてハンドリング

Androidアプリリンクの設定について詳しくはこちらからご確認いただけます。

最後に

ClinPeerアプリ開発の裏側の第一回目の記事として、今回ClinPeer Androidアプリ開発の裏側とFirebase Dynamic Links(FDL)の代替であるAdjustの活用事例についてご紹介させていただきました。
今後もClinPeerアプリ開発に携わった多くのエンジニアがリレー形式でClinPeerアプリ開発の裏側を紹介していく予定ですので、お楽しみに!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

Android/iOS向けアプリ「ClinPeer」をリリースしました! -ClinPeerアプリ開発の裏側連載開始のお知らせ-

こんにちは!メドピアにてモバイルアプリエンジニアをしている佐藤です。

弊社ではこの度Android/iOS向けにがん専門医の為の論文キュレーションアプリ「ClinPeer」をリリースしました!
clinpeer.medpeer.jp

そこで今回、ClinPeerアプリ開発の裏側について、この開発者ブログで連載することにしました!
開発中に工夫したポイントやぶつかった課題、用いた技術や開発体制などの内容をお届けします。

ブログは毎週または隔週のペースで更新予定です。
各エンジニアがそれぞれの視点で記事を執筆するので、多彩な内容をお楽しみいただけると思います。

それでは次回のブログ更新をお楽しみに!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

数億データを処理する仕組みを提供する gem 『MedPipe』 を OSS として公開しました

こんにちは。サーバーエンジニアの佐藤太一(@teach_kaiju)です。
本記事では社内で開発した、数億のデータを処理する仕組みを提供する gem MedPipe を紹介します。

MedPipe とは

「Log のデータを全て取得し、フォーマットして tsv として S3 にアップロードする」という要件があったとします。
この要件を実現するために、例えば以下のような実装を考えることができます。

upload_file_name = "hoge_logs.csv"
# 1. S3にアップロードするための file を用意
Tempfile.create do |file|
  # 2. Log のデータを DB から取得
  HogeLog.find_each do |log|
    # 3. フォーマット処理
    formatted_data = format(log)
    # 4. ファイルに書き込み
    line = CSV.generate_line(formatted_data, col_sep: "\t")
    file.puts(line)
  end

  # 5. S3にアップロード
  upload_s3(file, upload_file_name)
end

def format(log)
  # 処理
end

def upload_s3(file, upload_file_name)
  # 処理
end

それに対して、MedPipe を使うと以下のように記述できます。

upload_file_name = "hoge_logs.csv"
pipeline = MedPipe::Pipeline.new
pipeline.apply(PipelineTask::HogeLogReader.new) # 1. Log のデータを DB から取得
        .apply(PipelineTask::HogeLogFormatter.new) # 2. フォーマット処理
        .apply(MedPipe::PipelineTask::TsvGenerater.new) # 3. ファイルに書き込み
        .apply(PipelineTask::S3Uploader.new(upload_file_name)) # 4. S3にアップロード
pipeline.run

このように、MedPipe を使うことで処理の流れが明確になり、可読性を向上させることができます。

それに加えて以下のような機能を容易に実装することができます。

  • 並列処理
  • クエリ最適化のための、in_batches を用いない独自データ取得処理
  • 件数のカウント
  • アップロードするファイルサイズの保存

Ruby エンジニアにとっては Dataflow 等の大規模データ処理ツールと比べて学習コストが低いため、導入を比較的容易に行うことができます。

コンセプト

MedPipe Concept

MedPipe では Pipeline に PipelineTask を登録し、それを順番に実行します。
PipelineTask はやりたいことそのものであるため、独自で実装する必要があります。
PipelineTask が実装する必要のあるメソッドは call のみで非常にシンプルです。

def call(context, prev_result)
  yield "次のTaskの第二引数に渡す値"
end

ただし、大量のデータを扱う際には全部のデータをメモリにのせて次の Task に渡すわけにはいきません。
そこで、基本的には Enumerable::Lazy を後続 Task に渡します。
(lazy で Enumerable を Enumerable::Lazy に変換できます)

def call(_context, _)
  yield HogeLog.find_each.lazy
end

後続 Task は Enumerable::Lazy を受け取り、map で処理を挟むことで Enumerable::Lazy を維持できます。

  def call(_context, records)
    yield records.map { |record| format_line(record) }
  end

PipelineTask の他にも PipelineTask を Pipeline に登録する処理など使う準備は必要です。

Usageとサンプルを参考にしてください。

DB からのデータ取得方法

実務で find_each を使う場合には 2 つの問題がありました。

  1. ActiveRecord のメモリ使用量が多い
  2. クエリが最適化されない

1 に関しては in_batches + pluck を使うことで解決できますが、2 に関しては解決できません。
参考: Railsでin_batches使うととても遅い

これを解決するために、MedPipe では BatchReader というクラスを開発しました。

使用例:

  def call(_context, _)
    yield MedPipe::BatchReader.new(
      HogeLog,
      scope: HogeLog.where(created_at: @target_date.all_day),
      pluck_columns:,
      batch_size: BATCH_SIZE
    ).each.lazy
  end

これによって find_each のように1件ずつ、pluck_columns で pluck されたデータを後続 Task に渡すことができます。

プロファイリングの仕方

実務では memory_profiler を用いて、以下のようなコードでプロファイリングを行いました。 ※ 執筆にあたり一部修正しています。

module Profiler
  class << self
...
    def report(&block)
      start_time = Time.current
      result = MemoryProfiler.report(&block)
      elapsed_time = Time.current - start_time

      puts "\n\n===== Profiler Report ====="
      puts "Total allocated: #{bytes_to_mb(result.total_allocated_memsize)} MB (#{result.total_allocated} objects)"
      puts "Total retained: #{bytes_to_mb(result.total_retained_memsize)} MB (#{result.total_retained} objects)"
      puts "Elapsed time: #{elapsed_time.round(2)} sec"
    end
...
    private

...
    # bytes to MB 小数点第二位まで
    def bytes_to_mb(bytes)
      (bytes / 1024.0 / 1024.0).round(2)
    end
  end
end
class PipelineTask::Profiler
  def call(_context, input)
    Profiler.report do
      # Lazy の場合、測定するために発火する
      input.force if input.is_a?(Enumerator::Lazy)

      yield(input)
    end
  end
end
pipeline.apply(PipelineTask::Profiler.new)

既存のスクリプトを修正することなく、プロファイリングを行うことができます。

おわりに

本記事では、MedPipe の紹介を行いました。本 gem は弊社初のオープンソースの gem です。
普段様々な OSS のお世話になっているため、提供する側として業界に貢献できることを嬉しく思います。
OSS として世に出すことを許可していただいた会社や一緒に開発した同僚の近藤さん(@tetetratra)に感謝です!
実装が参考になったり、使ってみてよかった場合は、ぜひ MedPipe の GitHub リポジトリにスターをいただけると励みになります。


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

ActiveRecord クエリキャッシュのメモリ使用量と無効化

こんにちは。サーバーエンジニアの佐藤太一(@teach_kaiju)です。
本記事では、クエリキャッシュのメモリ使用量と有効/無効の切り替え方法について紹介します。

クエリキャッシュとは

Active Recordのクエリキャッシュは、1つのリクエストまたはジョブの実行中に同じSQLクエリが複数回実行された場合、2回目以降のクエリの実行を省略し、最初の結果をメモリ上にキャッシュして再利用する機能です。

# 1回目のクエリ実行時
Book.first
# Book Load (2.9ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

# 2回目のクエリ実行時
Book.first
# CACHE Book Load (0.1ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

※ 本記事で用いるモデル名(Book)は執筆にあたって差し替えたものであり、実際に使用したモデル名とは異なります。

キャッシュが使用される場合には発行されたクエリのログの先頭にCACHEと付いています。

2024/11 時点で、Sidekiq および SolidQueue ではクエリキャッシュがデフォルトで有効になっていること、 Rails コンソールでは無効になっていることを確認しました。

クエリキャッシュのメモリ消費量

クエリキャッシュはその性質上、大量のレコードを扱うジョブではメモリ使用量が膨大になりえます。

では、クエリキャッシュで実際どの程度メモリが圧迫されるのでしょうか? memory_profiler を用いて計測しました。 ※ モデル名は実際のものから差し替えています。

計測用コード

# 渡されたブロック内のメモリ消費および時間を出力
def report(&block)
  start_time = Time.current
  result = MemoryProfiler.report(&block)
  elapsed_time = Time.current - start_time

  puts "\n\n===== Profiler Report ====="
  puts "Total allocated: #{bytes_to_mb(result.total_allocated_memsize)} MB (#{result.total_allocated} objects)"
  puts "Total retained: #{bytes_to_mb(result.total_retained_memsize)} MB (#{result.total_retained} objects)"
  puts "Elapsed time: #{elapsed_time.round(2)} sec"
  puts "Query cache: #{Book.connection.query_cache.size} queries"
end

# bytes to MB 小数点第二位まで
def bytes_to_mb(bytes)
  (bytes / 1024.0 / 1024.0).round(2)
end

結果

データ数: 50 万
取得カラム: 数値と日付、合計 6 つ
実装: batch_size 1 万で上記のデータを取得する
※ find_each 等クエリキャッシュをスキップするメソッドは使用しません(後述)

クエリキャッシュ無効 クエリキャッシュ有効
Total allocated 281.03 MB (3510676 objects) 272.02 MB (3512618 objects)
Total retained 3.83 MB (71 objects) 99.24 MB (1500823 objects)
Query cache: 56 queries
Elapsed time 17.46 sec 20.83 sec

考察

allocated の差は誤差です。クエリキャッシュの有効/無効でアロケーション数はそんなに変わらないでしょう。
retained (使用中のメモリ) はキャッシュ分大幅に増加しています。

状況によって大きく差が出るため参考程度ですが、 50 万のデータでおよそ 100MB 程度のメモリを確保することがわかりました。
時間は誤差かもしれませんが、クエリキャッシュが無効なほうが少し高速なようです。

もし batch_size が 1 万ではなく 1000 であれば実行するクエリの数は 500 を超えます。Rails 7.1 以上であればクエリキャッシュの数の制限 (default 100) を超えるため、その分 retained は大幅に減少するでしょう。

find_each 等ではクエリキャッシュが無効になる

find_eachfind_in_batchesそしてin_batchesではクエリキャッシュが無効になります。

def batch_on_unloaded_relation(relation:, start:, finish:, load:, cursor:, order:, use_ranges:, remaining:, batch_limit:)
...
  relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching

batches.rb

したがって、バッチ処理で上記メソッドを使用する分にはクエリキャッシュを気にする必要はあまりありません。 最適化のために上記メソッドを使わずにバッチ処理を行うときに気をつける必要があります

クエリキャッシュを無効化する方法

ActiveRecordのモデル.uncachedを使うのがおすすめです。ドキュメント

Model.uncached do
  # この中ではクエリキャッシュが無効になる
end

ActiveRecordのモデル.uncachedを使うと、リードレプリカ等の別DBを参照した場合でもクエリキャッシュを無効化することができます。

切り替え検証

※ Rails コンソールではキャッシュがデフォルト無効なため、無効 -> 有効の切り替えを行なっています

# 通常 (Rails コンソールのためキャッシュがデフォルト無効)
Book.first
Book.first
#  Book Load (2.2ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
#  Book Load (0.4ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

# キャッシュを有効化
ActiveRecord::Base.cache do
  Book.first
  Book.first
end
#  Book Load (2.9ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
#  CACHE Book Load (0.1ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

# 別DBに接続 (キャッシュが有効にならない)
ApplicationRecord.connected_to(role: :primary_replica) do
  ActiveRecord::Base.cache do
    Book.first
    Book.first
  end
end
#  Book Load (3.9ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
#  Book Load (1.9ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

# 別DBでキャッシュを有効化する
ApplicationRecord.connected_to(role: :primary_replica) do
  Book.cache do
    Book.first
    Book.first
  end
end
#  Book Load (2.8ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
#  CACHE Book Load (0.2ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1

本検証ではログにCACHEと付くかどうかを見ていますが、以下でも確認可能です

Book.connection.query_cache_enabled

おわりに

本記事では ActiveRecord のクエリキャッシュについて紹介しました。メモリ使用量が気になる方は、ぜひクエリキャッシュの無効化を検討してみてください。その際に本記事の内容が参考になれば幸いです。

参考文献

Sidekiq: Problems and Troubleshooting

ShakaCode: Rails 7.1 makes ActiveRecord query cache an LRU


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp

Vue Fes Japan 2024 After Meetupを開催しました!

こんにちは!メドピアの福田(@Yusa136)です。

2024年11月01日(金)に弊社オフィスにて、MNTSQ株式会社、STORES株式会社と3社でVue Fes Japan 2024 After Meetupを合同開催しました!

Vue Fes Japan 2024 の感想や思い出を語り合いました。この記事では当日の様子やセッションの内容をお届けします。

LT

「VitePressで見つけたアウトプット習慣」

トップバッターは弊社メドピアの岡澤さんです!

VitePressを活用したアウトプット習慣の作り方についての話が印象的でした。「飽き性だけど続けられる」という岡澤さんの言葉の示す通り、VitePressはフロントエンドだけでブログやドキュメントを簡単に作成でき、Vue.jsを使って気軽に情報発信ができるツールです。

なぜVitePressなのか?

岡澤さんがあげた理由は以下の通りです。

  • Markdownで簡単に書ける点
  • フロントエンドで完結
  • Vueチーム推奨のSSG
  • Vueで遊びたい!

VitePressはファイルベースルーティングを採用しています、必要なパッケージも少なく、設定がシンプルな点も良い点として挙げられていました。また、簡単にダークテーマも切り替えられるなど、カスタマイズの自由度も魅力的ですね!

私としてもアウトプットできる場を求めていたので、VitePressを通してVue.jsを学び直すのも良いと思いました!みなさんもぜひこの機会にVitePress触ってみてはいかがでしょうか?

「ReactからVueへの転向:思考の変化とアプローチの違い」

speakerdeck.com

続いては、MNTSQ株式会社の森山凪さんのLTでした!

ReactとVue.jsのライフサイクル表現の変化についてのお話が印象的でした!両者のアプローチの違いに注目してお話しされています。

1. ライフサイクルの違い

Reactのライフサイクル

useEffectを使うと、更新(update)、マウント(mount)、アンマウント(unmount)の処理をまとめて管理できます。例えば初回のマウント時や、ある状態が更新されたときにAPIコールを行うといったことが可能です。この凝集性の高さから、関連する処理を一つのuseEffect内に完結させやすいのが特徴みたいです。 useEffectは再利用しやすいことが大きなメリットですね!

メリットが大きいのは理解できましたが、やはりuseEffectは難しいですね。。。 森山さんも「lifecycleを理解している人でないと(useEffect)難しい」とおっしゃってしました。

2. Vue.jsのライフサイクルフック

一方でライフサイクルフックはどうでしょうか?

Vue.jsではmountedやbeforeUnmountといったフックで、それぞれのタイミングに応じた処理を明確に分けています。機能が時間ごとに分かれるため、コードの流れが直感的に理解しやすいのが利点みたいです!

ReactとVue.jsは機能的な凝集性と時間的な凝集性で異なるみたいです。

Vue.jsでも機能的な凝集性が欲しい場面が出てくると思います。そこでVue.jsのComposableでライフサイクルフックを使うと機能的な凝集性が高いライフサイクルフックが実装できる「いいとこどり!」だと紹介されていました。

そのほかにも、ReactのJSXとVue.jsのSFCについてどちらも「関心の分離」を重視していることをお話しされていました!

「1つのtsxコンポーネントをVueとReact向けにビルドする」

vue-fes-after-meetup-2024-ushironokos-projects.vercel.app

最後のLTはSTORES株式会社のushironokoさんです!

Vue.jsとReactの両方に対応できるtsxコンポーネントについて話されました!TypeScriptで書いたコンポーネントをどちらのフレームワークでも活用できる点が魅力的ですね

tsxとは?

TypeScriptを使ってテンプレートを定義できるファイル形式で、tsxだけではコンポーネントに状態を保つことはできないみたいです。

tsx では Vue.js も書くことができ、Babelのプラグインを用いることでtsxに変換できることを紹介されていました。 レンダー関数と JSX | Vue.js

Reactでは独自にtsxを解釈、ReactのランタイムでJSを生成するとお話しされていました。最終的にはcreateElementになりますが、オプションを組み合わせることで、ビルド後のjsx構文を誰に・どのように解釈させるかを指示できるらしいです。

tsxからVue.jsとReact向けにビルドする

状態管理が不要な部分はtsxのみで対応し、工夫が必要な部分については「unbuild」などのビルドツールで補完すると良いみたいです。

stateを持たせるとReactかVue.jsのランタイムに依存してしまうようです。なので、stateを持たなければ、ReactとVue.jsで使えるコンポーネントになるみたいです。

Vue.jsには固有の問題と解決方法について紹介されていました。

  • esbuildのオプションでtsconfigを拡張し、tsx:"preserve",jsxFactory: "h"を追加する
  • hが参照エラーにならないようにビルド結果にimport{ h }from "vue";を追加する
  • 元コードがClassNameを用いている場合は、classに変換する。

VueとReactのどちらのエコシステムでも活用できるtsxコンポーネントの可能性が広がるお話でした。

パネルディスカッション

「Vue Fes LGTM」

登壇者のご紹介

  • メドピア株式会社:小林さん
  • STORES株式会社:ushironokoさん
  • MNTSQ株式会社:安積洋さん

最後のパネルディスカッションでは、小林さんが司会役としてVueのマイグレーションや最新トレンドについてざっくばらんに話し 合いました。今年は新しいことにチャレンジする前向きな話が多く、昨年に比べてバランス良く知見が共有された印象でした。

議論のハイライト

マイグレーションと技術負債の解消

まず話題に上ったのは、Vueのマイグレーションについてです。去年のVue Fesでは、Vue 2からVue 3への移行に関するセッションが多く開催され、技術負債の解消がテーマとして取り上げられました。一方で、今年は次世代ツールチェインや新技術の導入など、未来志向の明るい話も増えており、会場には期待感が漂っていました。

Rustと次世代ツールチェインへの関心

特に注目を集めたのが、JavaScriptと並行して注目を浴びているRustについてです。「われわれはRustを書くしかないのか?」という話も飛び出す中、パフォーマンス向上や開発体験を改善するため、RustのエコシステムをVueと組み合わせて活用する動きが増えているとのことでした。例えば、Rustで書かれた「oxc」などのツールチェインが紹介され、これによりビルド時間やローカル開発の効率化が期待されています。

一方で、Rustを採用するかどうかについては、「事業にどれほどのインパクトがあるか?」という実務面での意見もあり、導入には慎重な検討が必要という話も。VueエコシステムにRustがどう寄与していくのか、今後の動向が注目されると感じました。

ブースのお話

よくわからないトラック名が呼ばれていたと話題にあがりました。Vue Fes Japan 2024でのトラック名、「MNTSQが全ての合意をフェアにするぞ」トラックは私もスタッフとして参加した時に印象に残っています。 STORESさんのブースではコードに改善点として付箋を貼る展示をされていました。コードが見えなくなるほど付箋が貼られていたみたいですね。私が訪れた時にはLGTM付箋も多かった印象です! 弊社メドピアでは握力測定を行いました。脅威の75kgを記録された方がいるみたいです! 当日の参加レポートはこちら Vue Fes Japan 2024に参加しました!#vuefes - メドピア開発者ブログ

懇親会の様子

懇親会ではたくさんのご飯をMNTSQさん、ドリンクはSTORESさんが用意してくださり、皆さんで交流を行いました。

多くの参加者と交流することができ、良い交流ができました。 私としてもすごく刺激になりました。

最後に

登壇者の皆さん、合同開催のMNTSQ株式会社、STORES株式会社の皆さんありがとうございました!

LTやパネルディスカッションでもあった通り明るい話が多かった印象でした。

これからもVue、Nuxtのコミュニティの発展を祈るとともに、貢献していきたいと思います!

来年のVue Fes Japanも楽しみです!


是非読者になってください!


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp