Ruby on Rails 8 ์™„๋ฒฝ ๊ฐ€์ด๋“œ 2026: ์‹ ๊ทœ ๊ธฐ๋Šฅ๊ณผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐฉ๋ฒ•

Rails 8์˜ Solid Trifecta, ๋‚ด์žฅ ์ธ์ฆ, Kamal 2, Propshaft๋ฅผ ์ฝ”๋“œ ์˜ˆ์ œ์™€ ํ•จ๊ป˜ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. Rails 7์—์„œ 8๋กœ์˜ ๋‹จ๊ณ„๋ณ„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Ruby on Rails 8 ์‹ ๊ทœ ๊ธฐ๋Šฅ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ - Solid Trifecta์™€ Kamal 2 ๋ฐฐํฌ ๋‹ค์ด์–ด๊ทธ๋žจ

Ruby on Rails 8์€ Rails 7 ์ดํ›„ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ฆด๋ฆฌ์Šค๋กœ, Redis๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ ์ธํ”„๋ผ, ๋‚ด์žฅ ์ธ์ฆ ์‹œ์Šคํ…œ, ๊ฐ„์†Œํ™”๋œ ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ์„ ๋„์ž…ํ•˜์˜€์Šต๋‹ˆ๋‹ค. 2024๋…„ 11์›”์— ์ถœ์‹œ๋˜์—ˆ์œผ๋ฉฐ 2025๋…„ 10์›” Rails 8.1 ํ›„์† ๋ฆด๋ฆฌ์Šค๋ฅผ ํ†ตํ•ด ์„ฑ์ˆ™ ๋‹จ๊ณ„์— ์ ‘์–ด๋“  Rails 8์€ ๊ธฐ์กด์— ๋ณ„๋„์˜ ์ธํ”„๋ผ๋ฅผ ์š”๊ตฌํ•˜๋˜ ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. ํ•œ๊ตญ์˜ ๊ฐœ๋ฐœํŒ€์—๊ฒŒ ์ด ๋ณ€ํ™”๋Š” ์šด์˜ ๋ณต์žก๋„ ๊ฐ์†Œ, ์ธํ”„๋ผ ๋น„์šฉ ์ ˆ๊ฐ, ์ค‘์†Œ ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์˜ ์ง„์ž… ์žฅ๋ฒฝ ์™„ํ™”๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

Rails 8 ํ•ต์‹ฌ ์š”์•ฝ

Rails 8์€ Redis๋ฅผ Solid Trifecta(Solid Queue, Solid Cache, Solid Cable)๋กœ ๋Œ€์ฒดํ•˜๊ณ , ๋‚ด์žฅ ์ธ์ฆ ์ƒ์„ฑ๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉฐ, ๊ธฐ๋ณธ ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ์„ Propshaft๋กœ ์ „ํ™˜ํ•˜๊ณ , ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ Kamal 2๋ฅผ ํƒ‘์žฌํ•ฉ๋‹ˆ๋‹ค. Ruby 3.2 ์ด์ƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Solid Trifecta: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ ์ธํ”„๋ผ

Rails 8 ์ด์ „์—๋Š” ๋Œ€๋ถ€๋ถ„์˜ ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก, ์บ์‹ฑ, WebSocket pub/sub์˜ ์„ธ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์— Redis๋ฅผ ์˜์กดํ•˜์˜€์Šต๋‹ˆ๋‹ค. Solid Trifecta๋Š” ์ด ์„ธ ๊ฐ€์ง€๋ฅผ PostgreSQL, MySQL, SQLite์™€ ํ˜ธํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ ์–ด๋Œ‘ํ„ฐ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

์ด ์•„ํ‚คํ…์ฒ˜ ์ „ํ™˜์€ ์šด์˜ ๋ณต์žก๋„๋ฅผ ํฌ๊ฒŒ ์ค„์—ฌ ์ค๋‹ˆ๋‹ค. Redis ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณ„๋„๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ํ™•์žฅํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง‘๋‹ˆ๋‹ค. ์„ฑ๋Šฅ ๋ฉด์—์„œ์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋Š” ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์šฉ๋Ÿ‰ ์ฒ˜๋ฆฌ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ๋Š” Redis๊ฐ€ ์—ฌ์ „ํžˆ ๋” ๋น ๋ฅด์ง€๋งŒ, ๋Œ€๋‹ค์ˆ˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ ์–ด๋Œ‘ํ„ฐ๋กœ ์ถฉ๋ถ„ํ•œ ์„ฑ๋Šฅ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Basecamp๋Š” 10TB ๊ทœ๋ชจ์˜ Solid Cache๋ฅผ 60์ผ ๋ณด์กด ๊ธฐ๊ฐ„์œผ๋กœ ํ”„๋กœ๋•์…˜์—์„œ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Solid Queue: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์žก ์ฒ˜๋ฆฌ

Solid Queue๋Š” Sidekiq, Resque, Delayed Job์„ ๋Œ€์ฒดํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ธฐ๋ฐ˜ Active Job ๋ฐฑ์—”๋“œ์ž…๋‹ˆ๋‹ค. PostgreSQL 9.5+์™€ MySQL 8.0+์—์„œ FOR UPDATE SKIP LOCKED๋ฅผ ํ™œ์šฉํ•˜๋ฉฐ, SQLite์—์„œ๋Š” ์•ˆ์ •์ ์œผ๋กœ ํด๋ฐฑ(fallback) ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

ruby
# config/environments/production.rb
config.active_job.queue_adapter = :solid_queue

# config/solid_queue.yml
production:
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 5
      processes: 2
      polling_interval: 0.1

Puma ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์›น ์„œ๋ฒ„์™€ ํ•จ๊ป˜ Solid Queue๋ฅผ ์‹œ์ž‘ํ•˜๋ฏ€๋กœ, ๋ณ„๋„์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹คํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

ruby
# config/puma.rb
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] || Rails.env.development?

Solid Queue๋Š” ๋ฐ˜๋ณต ์žก(recurring jobs), ๋™์‹œ์„ฑ ์ œ์–ด, ํ๋ณ„ ์ผ์‹œ ์ค‘์ง€ ๋ฐ ์žฌ๊ฐœ์™€ ๊ฐ™์€ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๋ถ„๋‹น 10,000๊ฑด ๋ฏธ๋งŒ์˜ ์žก์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ณ„๋‹ค๋ฅธ ๋ถ€ํ•˜ ์—†์ด ์•ˆ์ •์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

Solid Cache์™€ Solid Cable

Solid Cache๋Š” HTML ํ”„๋ž˜๊ทธ๋จผํŠธ ์บ์‹œ๋ฅผ Memcached๋‚˜ Redis ๋Œ€์‹  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ๋””์Šคํฌ ์Šคํ† ๋ฆฌ์ง€(SSD/NVMe)๋Š” RAM์— ๋น„ํ•ด ๋น„์šฉ์ด ํฌ๊ฒŒ ๋‚ฎ์œผ๋ฏ€๋กœ, ๋” ํฐ ์บ์‹œ ํ’€๊ณผ ๋” ๊ธด ๋ณด์กด ๊ธฐ๊ฐ„์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ruby
# config/environments/production.rb
config.cache_store = :solid_cache_store

Solid Cable์€ Action Cable์˜ Redis pub/sub ์–ด๋Œ‘ํ„ฐ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. WebSocket ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์— ๊ธฐ๋ก๋˜๊ณ  ๊ธฐ๋ณธ์ ์œผ๋กœ 100ms ๊ฐ„๊ฒฉ์œผ๋กœ ํด๋ง๋ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ ์‚ฌ๋ก€์—์„œ ๊ฑฐ์˜ ์‹ค์‹œ๊ฐ„์— ๊ฐ€๊นŒ์šด ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฉ”์‹œ์ง€๋Š” 24์‹œ๊ฐ„ ํ›„ ์ž๋™์œผ๋กœ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

yaml
# config/cable.yml
production:
  adapter: solid_cable
  polling_interval: 0.1
  keep_messages_around_for: 1.day

๋‚ด์žฅ ์ธ์ฆ ์ƒ์„ฑ๊ธฐ

Rails 8์€ ์„ธ์…˜ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ • ์Šค์บํด๋”ฉ์„ ์ƒ์„ฑํ•˜๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ์ธ์ฆ ์ƒ์„ฑ๊ธฐ๋ฅผ ํƒ‘์žฌํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ์ธ์ฆ์— ์™ธ๋ถ€ ์ ฌ(gem)์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

bash
# Generate the authentication scaffolding
bin/rails generate authentication

์ด ๋ช…๋ น์€ User ๋ชจ๋ธ, Session ๋ชจ๋ธ, SessionsController, PasswordsController, Authentication concern์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” bcrypt์™€ ํ•จ๊ป˜ has_secure_password๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, IP๋‹น 3๋ถ„์— 10ํšŒ์˜ ๋กœ๊ทธ์ธ ์‹œ๋„ ์ œํ•œ(rate limiting)์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ruby
# app/controllers/concerns/authentication.rb
module Authentication
  extend ActiveSupport::Concern

  included do
    before_action :require_authentication
    helper_method :authenticated?
  end

  private

  def require_authentication
    resume_session || request_authentication
  end

  def resume_session
    Current.session = find_session_by_cookie
  end

  def find_session_by_cookie
    Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
  end

  def request_authentication
    session[:return_to_after_authenticating] = request.url
    redirect_to new_session_path
  end

  def authenticated?
    Current.session.present?
  end
end

๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ํ† ํฐ์ด ์•„๋‹Œ, has_secure_password๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ์„œ๋ช…๋˜๊ณ  ์œ ํšจ ๊ธฐ๊ฐ„์ด ์ œํ•œ๋œ ํ† ํฐ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ์ƒ์„ฑ๊ธฐ๋Š” ์˜๋„์ ์œผ๋กœ ํšŒ์›๊ฐ€์ž…, ์ด๋ฉ”์ผ ์ธ์ฆ, ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๊ธฐ๋ฐ˜์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด์ง€, Devise๋ฅผ ์™„์ „ํžˆ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค.

์ธ์ฆ ์ƒ์„ฑ๊ธฐ ๋ฒ”์œ„

๋‚ด์žฅ ์ƒ์„ฑ๊ธฐ๋Š” ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ, ๋น„๋ฐ€๋ฒˆํ˜ธ ์žฌ์„ค์ •์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํšŒ์›๊ฐ€์ž…, ์ด๋ฉ”์ผ ์ธ์ฆ, OAuth, ๋‹ค๋‹จ๊ณ„ ์ธ์ฆ(MFA)์€ ์ง์ ‘ ๊ตฌํ˜„ํ•˜๊ฑฐ๋‚˜ Authentication Zero ๊ฐ™์€ ์ ฌ์„ ํ†ตํ•ด ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

params.expect๋ฅผ ํ†ตํ•œ ์•ˆ์ „ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ

Rails 8์€ ๊ธฐ์กด์˜ params.require.permit ํŒจํ„ด์„ ๋Œ€์ฒดํ•˜๋Š” ๋” ์•ˆ์ „ํ•œ ๋Œ€์•ˆ์ธ params.expect๋ฅผ ๋„์ž…ํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฉ”์„œ๋“œ๋Š” ํ‚ค๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์„ ๋•Œ nil์„ ์กฐ์šฉํžˆ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

ruby
# Before (Rails 7)
def user_params
  params.require(:user).permit(:name, :email, :role)
end

# After (Rails 8)
def user_params
  params.expect(user: [:name, :email, :role])
end

expect ๋ฉ”์„œ๋“œ๋Š” :user ํ‚ค๊ฐ€ ์กด์žฌํ•˜๊ณ  ํ—ˆ์šฉ๋œ ์†์„ฑ๋งŒ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š”์ง€ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ํ‚ค๊ฐ€ ๋ˆ„๋ฝ๋œ ๊ฒฝ์šฐ ์ฆ‰์‹œ ActionController::ParameterMissing์ด ๋ฐœ์ƒํ•˜์—ฌ, ์ดํ›„ ์ฒ˜๋ฆฌ ํ๋ฆ„์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌด์Œ(silent) ๋ฒ„๊ทธ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Propshaft: ์ƒˆ๋กœ์šด ๊ธฐ๋ณธ ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ

Propshaft๋Š” ๊ธฐ๋ณธ ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ Sprockets๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. Sprockets๊ฐ€ ์ปดํŒŒ์ผ, ์••์ถ•, ํ•‘๊ฑฐํ”„๋ฆฐํŒ…์„ ํ•˜๋‚˜์˜ ๋ชจ๋†€๋ฆฌ์‹ ์‹œ์Šคํ…œ์—์„œ ์ฒ˜๋ฆฌํ–ˆ๋‹ค๋ฉด, Propshaft๋Š” ์ •์  ์—์…‹์˜ ์ œ๊ณต๊ณผ ํ•‘๊ฑฐํ”„๋ฆฐํŒ…์—๋งŒ ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค.

JavaScript ๋ฒˆ๋“ค๋ง์€ esbuild, Vite, Bun ๊ฐ™์€ ์ตœ์‹  ๋„๊ตฌ์— ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค. CSS ์ฒ˜๋ฆฌ๋Š” Tailwind CLI ๋˜๋Š” dart-sass๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ๊ทธ ๊ฒฐ๊ณผ, ๋” ๋น ๋ฅด๊ณ  ์˜ˆ์ธก ๊ฐ€๋Šฅํ•˜๋ฉฐ ํ˜„์žฌ์˜ ํ”„๋ก ํŠธ์—”๋“œ ๋„๊ตฌ ์ƒํƒœ๊ณ„์™€ ์ž˜ ๋งž๋Š” ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ruby
# Gemfile (Rails 8 default)
gem "propshaft"

# No configuration needed for basic usage
# Assets in app/assets are served and fingerprinted automatically

๊ธฐ์กด์— Sprockets๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ๋Š” Sprockets ์ „์šฉ ๋””๋ ‰ํ‹ฐ๋ธŒ(//= require)๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  import maps ๋˜๋Š” JavaScript ๋ฒˆ๋“ค๋Ÿฌ๋ฅผ ์ฑ„ํƒํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

Ruby on Rails ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

Kamal 2์™€ Thruster: ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ

Rails 8์€ Kamal 2๊ฐ€ ์‚ฌ์ „ ๊ตฌ์„ฑ๋˜์–ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. Kamal 2๋Š” ์ƒˆ Linux ์„œ๋ฒ„๋ฅผ ๋‹จ์ผ ๋ช…๋ น์œผ๋กœ ํ”„๋กœ๋•์…˜ ์„œ๋ฒ„๋กœ ํ”„๋กœ๋น„์ €๋‹ํ•˜๋Š” ๋ฐฐํฌ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. Kamal 2๋Š” Traefik์„ ๋ชฉ์ ์— ๋งž๊ฒŒ ์„ค๊ณ„๋œ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ์ธ Kamal Proxy๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

bash
# Deploy to a fresh server
kamal setup

# Subsequent deployments
kamal deploy

Thruster๋Š” Kamal Proxy์™€ Puma ์‚ฌ์ด์— ์œ„์น˜ํ•˜์—ฌ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•œ X-Sendfile ๊ฐ€์†, ์ž๋™ ์—์…‹ ์••์ถ•(gzip/brotli), HTTP/2 ์ง€์›์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Rails 8์ด ์ƒ์„ฑํ•˜๋Š” Dockerfile์—๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Thruster๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

dockerfile
# Excerpt from Rails 8 generated Dockerfile
RUN gem install thruster
CMD ["thrust", "./bin/rails", "server"]

์ด ์Šคํƒ(Kamal 2 + Kamal Proxy + Thruster + Puma)์€ SSL ์ข…๋ฃŒ, ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ, ๋กค๋ง ์žฌ์‹œ์ž‘์„ Nginx๋‚˜ HAProxy ๊ฐ™์€ ์™ธ๋ถ€ ์„œ๋น„์Šค ์—†์ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜์„ ์œ„ํ•œ SQLite ๊ฐ•ํ™”

Rails 8์€ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ์˜ SQLite ์ง€์›์„ ํฌ๊ฒŒ ๊ฐœ์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ปค๋„ฅ์…˜ ํ’€๋ง, WAL ๋ชจ๋“œ, ํ–ฅ์ƒ๋œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ๋กœ ์ค‘์†Œ ๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ SQLite๋ฅผ ํ”„๋กœ๋•์…˜์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Solid Trifecta์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ๋‹จ์ผ ์„œ๋ฒ„์—์„œ ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„ ์—†์ด Rails 8 ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด๋ฅผ SQLite๋กœ ์šด์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

yaml
# config/database.yml (SQLite production setup)
production:
  primary:
    adapter: sqlite3
    database: storage/production.sqlite3
    pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  cache:
    adapter: sqlite3
    database: storage/production_cache.sqlite3
  queue:
    adapter: sqlite3
    database: storage/production_queue.sqlite3
  cable:
    adapter: sqlite3
    database: storage/production_cable.sqlite3

์ด ๊ตฌ์„ฑ์€ ๋„ค ๊ฐœ์˜ SQLite ํŒŒ์ผ๋กœ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์Šคํƒ์„ ์šด์˜ํ•ฉ๋‹ˆ๋‹ค. Kamal์€ ์Šคํ† ๋ฆฌ์ง€ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์˜๊ตฌ ๋ณผ๋ฅจ์œผ๋กœ ๋งˆ์šดํŠธํ•˜์—ฌ ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ ์‹œ์—๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์กด๋ฉ๋‹ˆ๋‹ค.

Rails 7์—์„œ Rails 8๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ธฐ

Rails 7.2์—์„œ Rails 8.0์œผ๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ด๋ฏธ ๋””ํ”„๋ฆฌ์ผ€์ด์…˜ ๊ฒฝ๊ณ ๋ฅผ ํ•ด๊ฒฐํ•œ ์ƒํƒœ๋ผ๋ฉด ์›ํ™œํ•˜๊ฒŒ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. Rails 7.0 ๋˜๋Š” 7.1์„ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋‹จ๊ณ„์ ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 7.0์—์„œ 7.1๋กœ, 7.1์—์„œ 7.2๋กœ, ๊ทธ๋ฆฌ๊ณ  7.2์—์„œ 8.0์œผ๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

์—…๊ทธ๋ ˆ์ด๋“œ ์ „ ํ™•์ธ ์‚ฌํ•ญ

Rails 8์€ Ruby 3.2 ์ด์ƒ์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค. Ruby ๋ฒ„์ „์„ ํ™•์ธํ•˜๊ณ , Rails 7.2์—์„œ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๋ฉฐ, ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— RailsBump๋ฅผ ํ†ตํ•ด ์ ฌ ํ˜ธํ™˜์„ฑ์„ ์ ๊ฒ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1๋‹จ๊ณ„: ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ

ruby
# Gemfile
gem "rails", "~> 8.0"
bash
bundle update rails

2๋‹จ๊ณ„: ์—…๋ฐ์ดํŠธ ํƒœ์Šคํฌ ์‹คํ–‰

bash
bin/rails app:update

์ด ๋ช…๋ น์€ ๊ตฌ์„ฑ ํŒŒ์ผ์„ Rails 8 ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. git diff๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ฃผ์˜ ๊นŠ๊ฒŒ ๊ฒ€ํ† ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์—๋Š” ์ƒˆ๋กœ์šด Regexp.timeout ๊ธฐ๋ณธ๊ฐ’(1์ดˆ), ๋ณ€๊ฒฝ๋œ db:migrate ๋™์ž‘(์ƒˆ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์Šคํ‚ค๋งˆ ์ž๋™ ๋กœ๋“œ), Propshaft ๊ตฌ์„ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

3๋‹จ๊ณ„: ์Šคํ‚ค๋งˆ ์žฌ์ •๋ ฌ ์ฒ˜๋ฆฌ

Rails 8์€ schema.rb์˜ ์ปฌ๋Ÿผ ์ˆœ์„œ๋ฅผ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปฌ๋Ÿผ ์ˆœ์„œ์™€ ์ผ์น˜ํ•˜๋„๋ก ์žฌ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค. ์Šคํ‚ค๋งˆ ๋คํ”„๋ฅผ ์ฆ‰์‹œ ์‹คํ–‰ํ•˜์—ฌ ์ด ์™ธํ˜•์  ๋ณ€๊ฒฝ์„ ์‹ค์ œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ณ€๊ฒฝ๊ณผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

bash
bin/rails db:schema:dump
git add db/schema.rb
git commit -m "Reorder schema columns for Rails 8"

4๋‹จ๊ณ„: ์ƒˆ ๊ธฐ๋Šฅ ์ ์ง„์  ๋„์ž…

Solid Trifecta, Propshaft, ์ธ์ฆ ์ƒ์„ฑ๊ธฐ๋Š” ๊ธฐ์กด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ ํƒ์ ์œผ๋กœ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”์–ด ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์•ˆ์ •ํ™”๋œ ํ›„ ๊ฐœ๋ณ„์ ์œผ๋กœ ์ฑ„ํƒํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

  1. ์žก ์–ด๋Œ‘ํ„ฐ๋ฅผ Solid Queue๋กœ ๊ต์ฒด
  2. ์บ์‹œ ์Šคํ† ์–ด๋ฅผ Solid Cache๋กœ ์ „ํ™˜
  3. Action Cable์„ Solid Cable๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜(ํ•ด๋‹น๋˜๋Š” ๊ฒฝ์šฐ)
  4. ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ์˜ Propshaft ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ‰๊ฐ€

5๋‹จ๊ณ„: ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ์—…๋ฐ์ดํŠธ

์ปจํŠธ๋กค๋Ÿฌ ์ „์ฒด์—์„œ params.require.permit ํ˜ธ์ถœ์„ params.expect๋กœ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ์€ ์„ ํƒ ์‚ฌํ•ญ์ด์ง€๋งŒ, ๋” ๊ฐ•๋ ฅํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ์„ ์œ„ํ•ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

Rails 8.1: Continuable Jobs์™€ ๋‚ด์žฅ CI

2025๋…„ 10์›”์— ์ถœ์‹œ๋œ Rails 8.1์€ Rails 8์˜ ๊ธฐ๋ฐ˜ ์œ„์— ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Continuable Jobs(ActiveJob::Continuable)๋Š” ์žฅ์‹œ๊ฐ„ ์‹คํ–‰๋˜๋Š” ์žก์„ ์žฌ๊ฐœ ๊ฐ€๋Šฅํ•œ ๋‹จ๊ณ„๋กœ ๋ถ„ํ• ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ์ž„ํฌํŠธ ๋„์ค‘ ์žฌ์‹œ์ž‘๋˜๋”๋ผ๋„ ์žก์€ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ  ์ค‘๋‹จ๋œ ์ง€์ ์—์„œ ์ •ํ™•ํžˆ ์žฌ๊ฐœ๋ฉ๋‹ˆ๋‹ค.

ruby
# app/jobs/import_records_job.rb
class ImportRecordsJob < ApplicationJob
  include ActiveJob::Continuable

  def perform(cursor:)
    records = Record.where("id > ?", cursor.value || 0).limit(1000)

    records.each do |record|
      process(record)
      cursor.advance(record.id)
    end
  end
end

Rails 8.1์€ ๋‚ด์žฅ CI ๊ตฌ์„ฑ๊ณผ ๋„ค์ดํ‹ฐ๋ธŒ Markdown ๋ Œ๋”๋ง๋„ ๋„์ž…ํ•˜์—ฌ ์™ธ๋ถ€ ์˜์กด์„ฑ์„ ๋”์šฑ ์ค„์ž…๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

Rails 8์€ ํ”„๋ ˆ์ž„์›Œํฌ ์ฒ ํ•™์˜ ์ค‘์š”ํ•œ ์ „ํ™˜์ ์œผ๋กœ, ๊ธฐ๋Šฅ์„ ํฌ์ƒํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์šด์˜ ๋‹จ์ˆœ์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•ต์‹ฌ ์‚ฌํ•ญ์„ ์ •๋ฆฌํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • Solid Trifecta(Queue, Cache, Cable)๋Š” ์žก, ์บ์‹ฑ, WebSocket์—์„œ Redis๋ฅผ ํ•„์ˆ˜ ์˜์กด์„ฑ์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค
  • ๋‚ด์žฅ ์ธ์ฆ ์ƒ์„ฑ๊ธฐ๋Š” ์„œ๋“œํŒŒํ‹ฐ ์ ฌ ์—†์ด ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ์˜ ๊ธฐ๋ฐ˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค
  • params.expect๋Š” params.require.permit์„ ๋” ์—„๊ฒฉํ•˜๊ณ  ์•ˆ์ „ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ฒ˜๋ฆฌ๋กœ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค
  • Propshaft๋Š” Sprockets๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ๋ณธ ์—์…‹ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ, ๋ฒˆ๋“ค๋ง์€ ์ตœ์‹  ๋„๊ตฌ์— ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค
  • Kamal 2์™€ Thruster๋Š” Nginx๋‚˜ Capistrano ์—†์ด ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค
  • Rails 7.2์—์„œ์˜ ์—…๊ทธ๋ ˆ์ด๋“œ๋Š” ์ ์ง„์ ์œผ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ, app:update ์‹คํ–‰, ์ƒˆ ๊ธฐ๋Šฅ์˜ ๋‹จ๊ณ„์  ๋„์ž… ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค
  • Rails 8.1์€ Continuable Jobs์™€ ๋‚ด์žฅ CI๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์™ธ๋ถ€ ๋„๊ตฌ ์˜์กด์„ฑ์„ ๋”์šฑ ์ค„์ž…๋‹ˆ๋‹ค

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#ruby-on-rails
#rails-8
#tutorial
#migration
#solid-queue
#solid-cache
#authentication
#kamal
#propshaft

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Solid Queue and Solid Cache in Rails 8 guide

Rails 8์˜ Solid Queue์™€ Solid Cache: 2026๋…„ ๊ธฐ์ˆ  ๋ฉด์ ‘ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

Rails 8์—์„œ ๊ธฐ๋ณธ ํƒ‘์žฌ๋œ Solid Queue์™€ Solid Cache๊ฐ€ Redis๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๋ฐฉ์‹์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์•„ํ‚คํ…์ฒ˜, ์„ค์ •, ๋™์‹œ์„ฑ ์ œ์–ด, 2026๋…„ ๋ฉด์ ‘ ํ•ต์‹ฌ ์งˆ๋ฌธ๊นŒ์ง€ ์ด์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Rails API ๋ชจ๋“œ๋ฅผ ํ™œ์šฉํ•œ RESTful API ์„ค๊ณ„์™€ ์ง๋ ฌํ™” ๋ชจ๋ฒ” ์‚ฌ๋ก€

2026๋…„ Rails API ๋ชจ๋“œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ: RESTful API ์„ค๊ณ„, ์ง๋ ฌํ™” ์ „๋žต๊ณผ ๊ธฐ์ˆ  ๋ฉด์ ‘ ํ•ต์‹ฌ ์งˆ๋ฌธ

Rails 8.1 API ๋ชจ๋“œ๋กœ ํ”„๋กœ๋•์…˜๊ธ‰ RESTful API๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. Alba์™€ jsonapi-serializer ๋น„๊ต, JWT ์ธ์ฆ, ์—๋Ÿฌ ์ฒ˜๋ฆฌ, ํ…Œ์ŠคํŠธ ํŒจํ„ด๊ณผ ๋ฉด์ ‘ ์งˆ๋ฌธ๊นŒ์ง€ ์ด์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Rails Turbo์™€ Hotwire 2026 ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์‹ค์‹œ๊ฐ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ๊ณผ ๋ฉด์ ‘ ๋Œ€๋น„

Rails Turbo์™€ Hotwire 2026 ์™„๋ฒฝ ๊ฐ€์ด๋“œ: ์‹ค์‹œ๊ฐ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ๊ณผ ๋ฉด์ ‘ ๋Œ€๋น„

Rails 8์˜ Turbo 8๊ณผ Hotwire๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์‹ค์ „ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค. Turbo Streams, ๋ชจํ•‘, Stimulus ๊ตฌํ˜„ ํŒจํ„ด๊ณผ ๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ ์ž์ฃผ ๋‚˜์˜ค๋Š” ์งˆ๋ฌธ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.