はじめに
Rails7.2がbetaフェーズとなっています。
リリースに先立ち、新機能や変更点などをまとめつつ、実際に検証してみました。
後編も併せてご覧ください。
devcontainer
Rails7.2ではVSCode上で使えるdevcontainerを設定する機能が追加されました。
rails new
を実行する際に--devcontainer
オプションを付与できるようになっています。
またdevcontainer
コマンドを実行すれば、既存のRailsアプリケーションでも開発用コンテナの設定が可能です。
以下のIssueで導入の議論がされていました。
実際にやってみた
Rails7.1にはdevcontainer
コマンドが存在しないのでエラーとなります。
rails7.1-app % rails devcontainer
Unrecognized command "devcontainer" (Rails::Command::UnrecognizedCommandError)
ではRails7.2で同じコマンドを実行してみます。
rails7.2-app % rails devcontainer
Generating Dev Container with the following options:
app_name: rails72_app
database: sqlite3
active_storage: true
redis: true
system_test: true
node: false
create .devcontainer
create .devcontainer/devcontainer.json
create .devcontainer/Dockerfile
create .devcontainer/compose.yaml
gsub test/application_system_test_case.rb
.devcontainer
ディレクトリとその配下に以下の3ファイルが生成されました。
devcontainer.json
Dockerfile
compose.yaml
各ファイルの内容はそれぞれ次のとおりです。
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
{
"name": "rails72_app",
"dockerComposeFile": "compose.yaml",
"service": "rails-app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/rails/devcontainer/features/activestorage": {},
"ghcr.io/rails/devcontainer/features/sqlite3": {}
},
"containerEnv": {
"CAPYBARA_SERVER_PORT": "45678",
"SELENIUM_HOST": "selenium",
"REDIS_URL": "redis://redis:6379/1"
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000, 6379],
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root",
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bin/setup"
}
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.2.2
FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
name: "rails72_app"
services:
rails-app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
volumes:
- ../..:/workspaces:cached
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
# Uncomment the next line to use a non-root user for all processes.
# user: vscode
# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
depends_on:
- selenium
- redis
selenium:
image: seleniarm/standalone-chromium
restart: unless-stopped
redis:
image: redis:7.2
restart: unless-stopped
volumes:
- redis-data:/data
volumes:
redis-data:
これを使ってdevcontainerを実際に立ち上げてみます。
VSCodeの画面右下の通知から「コンテナーで再度開く」を押します。
しばらく待って以下のようにvscode → /workspaces/rails7.2-app
のようになればOKです。
rails s
でサーバーが立ち上がることを確認してみてください。
allow_browserメソッドによるブラウザのアクセス制限
コントローラーの各アクションにアクセスできるブラウザとそのバージョンを制限できる機能が追加されました。
allow_browser
メソッドをコントローラー内に記述することで実現できます。
rails new
した時点でapp/controllers/application_controller.rb
に記述されていました。
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
end
ブラウザ毎にバージョンを指定したり、特定のアクションだけへのアクセスだけを許可することもできるみたいです。
class ApplicationController < ActionController::Base
# ブラウザ毎にバージョンを指定(ieはアクセスさせない)
allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
end
class MessagesController < ApplicationController
# onlyオプションで特定のアクションだけを許可する
allow_browser versions: { opera: 104, chrome: 119 }, only: :show
end
以下のIssueで導入が提案されていました。
実際にやってみた
まずは検証用のPostコントローラーを作成
rails7.2-app % rails g controller posts index
create app/controllers/posts_controller.rb
route get "posts/index"
invoke erb
create app/views/posts
create app/views/posts/index.html.erb
invoke test_unit
create test/controllers/posts_controller_test.rb
invoke helper
create app/helpers/posts_helper.rb
invoke test_unit
config/routes.rb
の設定
Rails.application.routes.draw do
get "posts", to: "posts#index"
end
この状態でChromeブラウザからhttp://127.0.0.1:3000/posts
にアクセスできることを確認
app/controllers/application_controller.rb
を修正しChromeブラウザからのアクセスを制限する
class ApplicationController < ActionController::Base
# Chromeからはアクセスできないよう設定
allow_browser versions: { chrome: false }
end
Chromeブラウザからhttp://127.0.0.1:3000/posts
にアクセスしてみます。
たしかにアクセスが制限されました!
ログは以下のようになっています。
Started GET "/posts" for 127.0.0.1 at 2024-07-18 10:47:10 +0900
Processing by PostsController#index as HTML
Rendering public/406-unsupported-browser.html
Rendered public/406-unsupported-browser.html (Duration: 0.4ms | GC: 0.0ms)
Filter chain halted as #<Proc:0x000000010828cb58 /opt/homebrew/lib/ruby/gems/3.2.0/gems/actionpack-7.2.0.beta3/lib/action_controller/metal/allow_browser.rb:48 (lambda)> rendered or redirected
Completed 406 Not Acceptable in 3ms (Views: 1.1ms | ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)
念のためSafariからもhttp://127.0.0.1:3000/posts
にアクセスしてみます。
Safariではアクセスできました!
ちなみに余談ですが、EdgeはChromiumベースのブラウザなのでchrome: false
と書いた場合、Edgeからのアクセスも制限されてしまいます。
これはEdgeがChromiumベースのブラウザだからです。
Chromiumベースのブラウザは、User-Agent文字列に "Chrome" という文字列を含んでいます。
そしてallow_browser
メソッドはUser-Agent文字列に含まれる値でどのブラウザからのアクセスなのか?を判定しています。
よってchrome: false
と書くとMicrosoft Edgeからのアクセスも制限されるのです。
Ruby3.1以上をサポート
Rails7.1.3.4ではRuby2.7以上をサポートしていましたが、Rails7.2がサポートするのはRuby3.1以上です。
またRubyのサポートに関するポリシーも変更されたようです。
以前までのポリシー
Railsのメジャーバージョンアップ(例:6.0から7.0へ)の時にサポートするバージョンの削除が行われていた
これからのポリシー
Railsのマイナーバージョンアップ(例:7.0から7.1へ)の際にも、サポートが終了したRubyバージョンの削除が行われるかもしれない
サポートするRubyのバージョンとこれらのポリシーについては以下のPRで議論されていました。
デフォルトでPWAを搭載
デフォルトでPWA用のファイルが作成されるようになりました。
rails new
した時点でapp/views/pwa
配下に以下の2ファイルが生成されます。
- manifest.json.erb
- service-worker.js
それぞれの中身は次のとおりです。
{
"name": "Rails72App",
"icons": [
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"scope": "/",
"description": "Rails72App.",
"theme_color": "red",
"background_color": "red"
}
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
// const { title, options } = await event.data.json()
// event.waitUntil(self.registration.showNotification(title, options))
// })
//
// self.addEventListener("notificationclick", function(event) {
// event.notification.close()
// event.waitUntil(
// clients.matchAll({ type: "window" }).then((clientList) => {
// for (let i = 0; i < clientList.length; i++) {
// let client = clientList[i]
// let clientPath = (new URL(client.url)).pathname
//
// if (clientPath == event.notification.data.path && "focus" in client) {
// return client.focus()
// }
// }
//
// if (clients.openWindow) {
// return clients.openWindow(event.notification.data.path)
// }
// })
// )
// })
またPWA用のルーティングも自動で追加されています。
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Render dynamic PWA files from app/views/pwa/*
get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# Defines the root path route ("/")
# root "posts#index"
get "posts", to: "posts#index"
end
以下のIssueで導入の提案が行われていました。
Rubocopがデフォルトで搭載
Rails7.2にはRubocopが搭載されるようになりました。
rubocop-rails-omakase
というgemのルールがデフォルトで適用されます。
Rubocop自体はRubyの静的解析ツールですが、rubocop-rails-omakase
は特にRails開発のためのルールを提供するものです。
スタイル強制が軽いため、Rubyならではの高い表現力を損なわなずに最低限のリンターとして機能させられる、というのがrubocop-rails-omakase
の採用理由だそうです。
Rubocop導入の是非を議論しているのは以下のPRで確認できます。
実際にやってみた
rails new
した時点でrubocop-rails-omakase
がGemfile
に存在しています。
source "https://rubygems.org"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.2.0.beta3"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use sqlite3 as the database for Active Record
gem "sqlite3", ">= 1.4"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
gem "brakeman", require: false
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false
end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
end
rubocop
コマンドを実行してみました。
rails7.2-app % rubocop
Inspecting 31 files
....C......................C...
Offenses:
app/controllers/application_controller.rb:4:4: C: [Correctable] Layout/TrailingEmptyLines: Final newline missing.
end
test/application_system_test_case.rb:14:1: C: [Correctable] Layout/EmptyLinesAroundClassBody: Extra empty line detected at class body end.
31 files inspected, 2 offenses detected, 2 offenses autocorrectable
たしかに使えました!これは便利そうですね。
一方でインストールをスキップしたい場合はrails new myapp --skip_rubocop
とオプションを指定できるようにもなっています。
GitHub ActionsのCI設定ファイル
GitHub ActionsのCI設定ファイルがデフォルトで作成されるようになります。
前述したRubocop
や後述するbrakeman
がデフォルトで含まれるようになったため、これらのチェックを自動化するための機能を含める流れとなりました。
また自動テストの習慣をRails開発者に教える意図も含まれているようです
議論の過程は以下のIssueで確認できます。
実際に見てみる
.github/
─ workflows/
─ ci.yml
─ dependabot.yml
ci.yml
とdependabot.yml
の中身はそれぞれ以下のとおりでした。
name: CI
on:
pull_request:
push:
branches: [ main ]
jobs:
scan_ruby:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Scan for security vulnerabilities in Ruby dependencies
run: bin/brakeman --no-pager
scan_js:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Scan for security vulnerabilities in JavaScript dependencies
run: bin/importmap audit
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Lint code for consistent style
run: bin/rubocop -f github
test:
runs-on: ubuntu-latest
# services:
# redis:
# image: redis
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Install packages
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libsqlite3-0 libvips
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Run tests
env:
RAILS_ENV: test
# REDIS_URL: redis://localhost:6379/0
run: bin/rails db:test:prepare test test:system
- name: Keep screenshots from failed system tests
uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshots
path: ${{ github.workspace }}/tmp/screenshots
if-no-files-found: ignore
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
ci.yml
では以下の内容をチェックしています。
- Rubyの依存関係にまつわる脆弱性
- JavaScriptの依存関係にまつわる脆弱性
- リンターによるコードスタイル
- テスト実行
- 失敗したテストのスクリーンショット保存
dependabot.yml
ではGemfileとGitHub Actionsの設定をチェックし、更新が必要な依存関係やアクションがないかをチェックしています。
更新が必要な場合、最大10件までプルリクエストを作成します。
またこれらのファイル生成が不要な場合はrails new . --skip_ci
とオプションを指定できます。
Brakemanがデフォルトで搭載
Railsアプリケーションの脆弱性を検出してくれるbrakeman
というgemもデフォルトで含まれるようになりました。
これはRuby on Railsを初めて使う新参者がセキュリティ面のミスをしないように、という配慮のようです。
以下のIssueで導入が提案されていました。
先ほどのGitHub ActionsのCIフローにもbrakeman
によってセキュリティ脆弱性を検知するジョブが組み込まれていましたね。
実際にやってみた
rails new
した時点でbrakeman
がGemfile
に存在しています。
source "https://rubygems.org"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.2.0.beta3"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use sqlite3 as the database for Active Record
gem "sqlite3", ">= 1.4"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
gem "brakeman", require: false
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
gem "rubocop-rails-omakase", require: false
end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
end
brakeman
コマンドを実行してみました(Application Pathは改変しています)
rails7.2-app % brakeman
Loading scanner...
Processing application in /Users/hogehoge/rails7.2-app
Processing gems...
[Notice] Detected Rails 7 application
Processing configuration...
[Notice] Escaping HTML by default
Parsing files...
Detecting file types...
Processing initializers...
Processing libs...
Processing routes...
Processing templates...
Processing data flow in templates...
Processing models...
Processing controllers...
Processing data flow in controllers...
Indexing call sites...
Running checks in parallel...
- CheckBasicAuth
- CheckBasicAuthTimingAttack
- CheckCrossSiteScripting
- CheckContentTag
- CheckCookieSerialization
- CheckCreateWith
- CheckCSRFTokenForgeryCVE
- CheckDefaultRoutes
- CheckDeserialize
- CheckDetailedExceptions
- CheckDigestDoS
- CheckDynamicFinders
- CheckEOLRails
- CheckEOLRuby
- CheckEscapeFunction
- CheckEvaluation
- CheckExecute
- CheckFileAccess
- CheckFileDisclosure
- CheckFilterSkipping
- CheckForgerySetting
- CheckHeaderDoS
- CheckI18nXSS
- CheckJRubyXML
- CheckJSONEncoding
- CheckJSONEntityEscape
- CheckJSONParsing
- CheckLinkTo
- CheckLinkToHref
- CheckMailTo
- CheckMassAssignment
- CheckMimeTypeDoS
- CheckModelAttrAccessible
- CheckModelAttributes
- CheckModelSerialize
- CheckNestedAttributes
- CheckNestedAttributesBypass
- CheckNumberToCurrency
- CheckPageCachingCVE
- CheckPathname
- CheckPermitAttributes
- CheckQuoteTableName
- CheckRansack
- CheckRedirect
- CheckRegexDoS
- CheckRender
- CheckRenderDoS
- CheckRenderInline
- CheckResponseSplitting
- CheckRouteDoS
- CheckSafeBufferManipulation
- CheckSanitizeConfigCve
- CheckSanitizeMethods
- CheckSelectTag
- CheckSelectVulnerability
- CheckSend
- CheckSendFile
- CheckSessionManipulation
- CheckSessionSettings
- CheckSimpleFormat
- CheckSingleQuotes
- CheckSkipBeforeFilter
- CheckSprocketsPathTraversal
- CheckSQL
- CheckSQLCVEs
- CheckSSLVerify
- CheckStripTags
- CheckSymbolDoSCVE
- CheckTemplateInjection
- CheckTranslateBug
- CheckUnsafeReflection
- CheckUnsafeReflectionMethods
- CheckValidationRegex
- CheckVerbConfusion
- CheckWeakRSAKey
- CheckWithoutProtection
- CheckXMLDoS
- CheckYAMLParsing
Checks finished, collecting results...
Generating report...
== Brakeman Report ==
Application Path: /Users/hogehoge/rails7.2-app
Rails Version: 7.2.0.beta3
Brakeman Version: 6.1.2
Scan Date: 2024-07-18 22:59:23 +0900
Duration: 0.350629 seconds
Checks Run: BasicAuth, BasicAuthTimingAttack, CSRFTokenForgeryCVE, ContentTag, CookieSerialization, CreateWith, CrossSiteScripting, DefaultRoutes, Deserialize, DetailedExceptions, DigestDoS, DynamicFinders, EOLRails, EOLRuby, EscapeFunction, Evaluation, Execute, FileAccess, FileDisclosure, FilterSkipping, ForgerySetting, HeaderDoS, I18nXSS, JRubyXML, JSONEncoding, JSONEntityEscape, JSONParsing, LinkTo, LinkToHref, MailTo, MassAssignment, MimeTypeDoS, ModelAttrAccessible, ModelAttributes, ModelSerialize, NestedAttributes, NestedAttributesBypass, NumberToCurrency, PageCachingCVE, Pathname, PermitAttributes, QuoteTableName, Ransack, Redirect, RegexDoS, Render, RenderDoS, RenderInline, ResponseSplitting, RouteDoS, SQL, SQLCVEs, SSLVerify, SafeBufferManipulation, SanitizeConfigCve, SanitizeMethods, SelectTag, SelectVulnerability, Send, SendFile, SessionManipulation, SessionSettings, SimpleFormat, SingleQuotes, SkipBeforeFilter, SprocketsPathTraversal, StripTags, SymbolDoSCVE, TemplateInjection, TranslateB:
ちゃんと使えました。
これもrubocop-rails-omakase
と同様に、インストールをスキップしたい場合はrails new . --skip_brakeman
とオプションを指定できます。
Pumaのデフォルトのスレッド数が5→3へ変更
Rails7.2からPumaのデフォルトのスレッド数が5スレッドから3スレッドに変更されます。
これまでPumaはデフォルトで5つのスレッドを使用していましたが、この数が多すぎるのではないかと疑問が出されました。
スレッドの数はアプリケーションの性能に大きく影響しており、数を減らせば減らすほど、個々の処理の速度(レイテンシ)は向上が見込めますが、同時に処理できる量(スループット)は下がってしまいます。
そのため適切なスレッド数を選び、応答速度と処理能力の程よいバランスを取ることが求められます。
議論の過程では1スレッドや2スレッドにする案も登場していますが、最終的な落とし所として3スレッドへの変更で同意が取られました。
議論の過程は以下のIssueで確認できます。
実際に見てみる
Rails7.1の場合
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
# Specifies that the worker count should equal the number of processors in production.
if ENV["RAILS_ENV"] == "production"
require "concurrent-ruby"
worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
workers worker_count if worker_count > 1
end
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
の部分でスレッド数を5に設定しています。
続いてRails7.2の場合
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# to prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
# Specifies the `environment` that Puma will run in.
rails_env = ENV.fetch("RAILS_ENV", "development")
environment rails_env
case rails_env
when "production"
# If you are running more than 1 thread per process, the workers count
# should be equal to the number of processors (CPU cores) in production.
#
# Automatically detect the number of available processors in production.
require "concurrent-ruby"
workers_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.available_processor_count })
workers workers_count if workers_count > 1
preload_app!
when "development"
# Specifies a very generous `worker_timeout` so that the worker
# isn't killed by Puma when suspended by a debugger.
worker_timeout 3600
end
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
# Only use a pidfile when requested
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
の部分でデフォルトのスレッド数が3に設定されています。
コメントアウト部分にも、スループットとレイテンシのバランスを考慮して3スレッドに設定している旨が書かれていますね。
おわりに
この記事では、Rails 7.2の新機能や変更点について、一部の内容を実際に試しながら解説しました。
betaフェーズにあるバージョンの新機能を追うのは初めてですが、とても勉強になりました。
単に追加される機能を知るだけでなく、IssueやPRなどを通じて背景や議論の過程も含めて把握することが大切だと感じます。
しかしRails7.2の新機能はまだこれだけではありません。
現在、続編を執筆中です。
後編も併せてご覧ください。
またこの記事に関して間違っている点がありましたら、教えていただけますと幸いです。
参考資料