Skip to content

필사 모드: 모던 Ruby & Rails 2026 — Ruby 3.4 / Rails 8 / Hotwire / Sorbet / Kamal 2 / Solid Queue 심층 가이드

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

1장 · 2026년 Ruby/Rails — "죽었다"고 한 사람들에게

2026년의 풍경 하나. 한 친구가 Y Combinator 데모데이에서 자기 스타트업을 자랑한다. "MVP를 3주 만에 만들어서 출시했고, 첫 50만 매출 찍었어." 무슨 스택 썼냐고 묻자 약간 부끄러운 듯 답한다. "Rails." 옆 테이블의 Next.js 풀스택 엔지니어가 눈을 동그랗게 뜬다. "아직도?"

그렇다, 아직도. 그리고 2026년에는 그 어느 때보다도 조용히, 많은 곳에서 돌아간다.

2020년대 초반의 인터넷 담론은 "Rails는 죽었다"였다. 2026년의 현실은 그 반대다 — **Rails는 죽지 않았고, 오히려 가장 조용하게, 가장 많이 일하고 있는 스택 중 하나**가 되었다. GitHub, Shopify, Stripe, Airbnb, Basecamp, HEY, GitLab, Square, Coinbase, Cookpad, Mercari, 카카오페이의 일부, 토스의 일부. 이름을 다 적으면 끝이 없다.

왜 죽지 않았는가? 세 가지가 동시에 일어났다.

1. **Ruby 3.4(2024년 12월)** — YJIT(Yet Another JIT)이 진짜로 빨라졌고, error_highlight가 디폴트가 되면서 디버깅이 훨씬 친절해졌다.

2. **Rails 8(2024년 11월)** — 37signals가 Redis 의존을 떼어내는 "Solid 3총사"(Queue, Cache, Cable)를 발표했다. Postgres 또는 SQLite 하나로 잡 큐, 캐시, WebSocket을 다 돌릴 수 있다.

3. **Kamal 2** — `kamal deploy` 한 줄로 자체 서버에 Docker로 배포한다. 헤로쿠/Vercel/AWS Fargate에 매달 수백·수천 달러를 내지 않아도 되는 시대가 왔다.

그리고 그 위에 **Hotwire**(Turbo + Stimulus)가 있다 — React 빌드 파이프라인 없이도 인터랙티브한 UI를 만들 수 있다. **Sorbet**(Stripe가 만든 점진적 타입 체커)과 **Tapioca**(RBI 자동 생성기)가 타입을 입혀준다. **Mission Control**이 Sidekiq 없이도 잡을 모니터링한다. **Propshaft**가 sprockets를 대체한 새 에셋 파이프라인이고, **Action Notifier**가 Rails 8의 새 알림 프레임워크다.

이 글은 2026년의 Ruby/Rails 풀스택을 한 자리에서 본다. 1장에서 11장까지는 각 부품의 해부, 12장에서 14장까지는 대안과 생태계와 "누가 골라야 하는가"를 본다. 끝까지 읽으면, 다음 사이드 프로젝트에서 Next.js+Vercel 대신 Rails 8+Kamal을 골랐을 때 후회하지 않을지에 대한 답이 나올 것이다.

2장 · Ruby 3.4 (2024.12) — YJIT 개선과 error_highlight 디폴트

Ruby 3.4는 2024년 12월 25일(매년 크리스마스가 Ruby 메이저 릴리스 날이다)에 나왔다. 큰 헤드라인은 두 가지다.

YJIT — Ruby의 JIT은 진짜다

YJIT(Yet Another JIT)은 Shopify가 만든 메소드 기반 JIT 컴파일러로, Ruby 3.1에 실험적으로 들어왔다가 3.2에서 프로덕션 레디가 되었고, 3.3에서 ARM64 지원과 메모리 사용량을 줄였고, **3.4에서 워밍업 시간을 또 줄였다**. Shopify의 자체 벤치마크에 따르면 Rails 워크로드에서 평균 1.4x ~ 2x 빨라진다. 단순 산술 루프 같은 마이크로벤치는 더 극단적인 숫자가 나오지만, 진짜 의미는 Rails 컨트롤러 액션 같은 실제 워크로드에서 측정된다.

활성화는 한 줄이다.

config/boot.rb 또는 환경변수

config/boot.rb 맨 위

require 'bootsnap/setup' if ENV['DISABLE_BOOTSNAP'].nil?

환경변수로

RUBY_YJIT_ENABLE=1 bundle exec rails server

또는 코드에서 명시적으로.

config/application.rb

require_relative "boot"

require "rails/all"

if defined?(RubyVM::YJIT.enable)

RubyVM::YJIT.enable

end

...

Rails 8은 production 환경에서 YJIT을 디폴트로 켠다(`config/environments/production.rb`에서 명시적으로 끄지 않는 한). YJIT 통계를 보려면.

Rails console에서

RubyVM::YJIT.runtime_stats

=> {compile_time_ns: ..., compiled_iseq_count: ..., ...}

error_highlight — 디폴트로 친절해진 에러 메시지

Ruby 3.1에 들어왔던 `error_highlight` gem이 3.4부터는 더 정교해졌다. `NoMethodError`가 났을 때 단순히 "undefined method `foo'"이 아니라, **소스 코드의 어느 부분에서 났는지 캐럿(`^`)으로 표시**해준다.

전통적인 Ruby 1.x ~ 2.x의 에러는 이랬다.

NoMethodError: undefined method `name' for nil:NilClass

app/models/user.rb:42:in `display'

3.4에서는 이렇다.

app/models/user.rb:42:in 'User#display':

puts "Hello, " + user.profile.name

^^^^^

NoMethodError: undefined method 'name' for nil

`user.profile`이 nil이라는 게 한눈에 보인다. Sentry/Honeybadger/Bugsnag 같은 에러 추적 서비스도 이 캐럿 정보를 그대로 보존한다.

그 외 변경

- `it` 블록 매개변수가 정식으로 들어왔다 — `[1,2,3].map { it * 2 }` 이런 식으로 단일 매개변수 블록을 짧게 쓸 수 있다.

- `Range#step`이 `Range`를 리턴하던 게 `Enumerator`를 리턴하도록 바뀌었다.

- `Prism` 파서가 디폴트가 되었다 — Ruby 표준 파서를 대체하는 새 파서로, IDE/LSP 도구가 훨씬 빨라진다.

- 가비지 컬렉터의 변형 가능한 옵션이 늘어났다(MMTk 호환 모드).

3장 · Rails 8 (2024.11) — Solid 트리오로 Redis 의존을 떼어내다

Rails 8은 2024년 11월에 나왔고, DHH의 키노트 한 마디가 모든 걸 요약한다.

> "We're going Redis-free by default."

수년간 Rails 앱의 표준 인프라는 이랬다: **Postgres + Redis + Sidekiq**. Redis는 Sidekiq 잡 큐, Rails 캐시, Action Cable 펍섭에 동시에 쓰였다. Redis가 죽으면 잡도 죽고, 캐시도 죽고, 웹소켓도 죽는다. 작은 팀이 Redis를 운영하는 것은 늘 비용이었다.

Rails 8의 답: **Solid Queue, Solid Cache, Solid Cable**. 셋 다 데이터베이스(Postgres/MySQL/SQLite)에 데이터를 쓴다. Redis가 사라지면 의존성 트리가 크게 단순해진다.

[전통적 Rails 7] [Rails 8 기본]

───────────────── ─────────────

Rails app Rails app

│ │

├── Postgres (데이터) └── Postgres

├── Redis (Sidekiq 큐) ├── solid_queue_* 테이블

├── Redis (캐시) ├── solid_cache_entries 테이블

└── Redis (Action Cable) └── solid_cable_messages 테이블

Rails 8 새 앱은 디폴트로 SQLite에서 Solid 3개를 다 돌릴 수도 있다 — 정말로 단일 바이너리에 가까운 단순함이다. 작은 사이드 프로젝트라면 EC2 t4g.small 하나에 SQLite + Solid Queue + Solid Cache + Solid Cable + Kamal로 충분하다.

Rails 8 새 앱 생성.

gem install rails -v "~> 8.0"

rails new myapp

디폴트로 SQLite + Solid Queue + Solid Cache + Solid Cable + Propshaft + Importmap + Hotwire

Postgres를 쓰려면.

rails new myapp --database=postgresql

기존 Rails 7 앱을 8로 올리는 방법은 별도 가이드가 필요할 정도라 여기서는 다루지 않는다 — Rails Edge Guides의 Upgrade Guide를 참고하자.

4장 · Solid Queue — Redis 의존 제거의 의미

Sidekiq은 10년 넘게 Rails 잡 큐의 사실상 표준이었다. Redis를 백엔드로 쓰고, BRPOP으로 잡을 가져오고, fork+threads 모델로 워커를 돌린다. 빠르고 견고하다. 그런데 단점은? **Redis가 필요하다.**

Solid Queue는 37signals가 HEY와 Basecamp에서 직접 쓰던 잡 큐를 오픈소스로 푼 것이다. Postgres/MySQL/SQLite에 잡 테이블을 만들고, FOR UPDATE SKIP LOCKED(또는 SQLite의 트랜잭션)로 잡을 잡아간다.

설치는 간단하다.

bundle add solid_queue

bin/rails solid_queue:install

bin/rails db:migrate

`config/queue.yml`로 워커를 설정한다.

default: &default

dispatchers:

- polling_interval: 1

batch_size: 500

workers:

- queues: "*"

threads: 5

processes: 1

polling_interval: 0.1

development:

<<: *default

production:

<<: *default

workers:

- queues: [ critical, default ]

threads: 10

processes: 2

polling_interval: 0.1

- queues: [ low_priority ]

threads: 3

processes: 1

polling_interval: 1

잡 클래스는 ActiveJob의 표준 인터페이스를 그대로 따른다.

class WelcomeEmailJob < ApplicationJob

queue_as :default

def perform(user_id)

user = User.find(user_id)

UserMailer.welcome(user).deliver_now

end

end

어디서든

WelcomeEmailJob.perform_later(user.id)

WelcomeEmailJob.set(wait: 1.hour).perform_later(user.id)

Sidekiq에서 Solid Queue로 옮길 때 코드 변경은 거의 없다(ActiveJob 인터페이스를 썼다면). 단, **성능 특성이 다르다** — Redis 인메모리 큐보다 Postgres 디스크 큐가 100배 느리지만, 작은 앱에는 충분하다. 분당 수십만 건 처리하는 메가 워크로드라면 여전히 Sidekiq + Redis가 낫다. 그러나 **분당 수천 건 미만**이라면 Solid Queue가 운영 단순성에서 이긴다.

5장 · Solid Cache + Solid Cable

Solid Cache

37signals의 HEY는 매일 테라바이트급의 캐시를 쓴다. Redis 인메모리로는 RAM 비용이 폭발했다. 답은? **디스크 기반 캐시**다. SSD가 빨라진 2020년대에는 디스크 캐시가 충분히 빠르다.

config/cache.yml

production:

database: cache

store_options:

max_age: <%= 2.weeks.to_i %>

max_size: 256.megabytes

namespace: <%= Rails.env %>

쓰는 방법은 일반 `Rails.cache`와 동일하다.

Rails.cache.fetch("expensive_query", expires_in: 1.hour) do

ExpensiveQuery.compute

end

HEY 기준으로는 캐시 히트 레이턴시가 평균 1-3ms 수준이다. Redis보다는 느리지만, 가격 대비 용량이 훨씬 크다.

Solid Cable

Action Cable이 Redis 펍섭에 의존하던 부분을 데이터베이스로 옮긴 것이다. 메시지를 `solid_cable_messages` 테이블에 쓰고, `LISTEN/NOTIFY`(Postgres) 또는 폴링(SQLite/MySQL)으로 구독자에게 전달한다.

config/cable.yml

production:

adapter: solid_cable

connects_to:

database:

writing: cable

polling_interval: 0.1.seconds

message_retention: 1.day

채팅 메시지를 보내는 컨트롤러.

class ChatMessagesController < ApplicationController

def create

message = @room.messages.create!(body: params[:body], user: current_user)

Solid Cable로 다른 사용자들에게 브로드캐스트

ChatChannel.broadcast_to(@room, render_to_string(partial: "messages/message", locals: { message: message }))

head :no_content

end

end

수백 동시 접속자가 있는 채팅이라면 충분하다. **수만 명 이상**이라면 Redis 어댑터(AnyCable 같은) 또는 전용 솔루션이 필요하다.

6장 · Hotwire (Turbo + Stimulus) — React 없는 인터랙티브

Hotwire는 "HTML Over the Wire"의 줄임말이다. 철학은 단순하다: **JSON으로 데이터를 보내고 클라이언트가 React로 렌더링하는 대신, 서버가 HTML 조각을 보내고 클라이언트가 DOM에 끼워넣는다.**

세 부품으로 되어 있다.

1. **Turbo Drive** — 모든 링크 클릭과 폼 제출을 fetch로 가로채서, 응답 HTML의 `<body>`만 교체한다. SPA 같은 페이지 전환을 무료로 제공한다.

2. **Turbo Frames** — `<turbo-frame id="cart">` 같은 컨테이너로 페이지의 일부만 갱신한다.

3. **Turbo Streams** — 서버가 "이 요소를 추가/교체/제거하라"는 명령을 HTML 조각과 함께 보낸다. WebSocket이나 SSE로 실시간 업데이트가 가능하다.

Turbo Drive 예제

<!-- app/views/posts/index.html.erb -->

<%= link_to "새 글 작성", new_post_path %>

이 링크를 클릭하면 Turbo가 fetch로 잡아채서, 응답 `<body>`만 갈아끼운다. JavaScript 한 줄 안 쓰고도 SPA 느낌이다.

Turbo Frames 예제

<!-- app/views/posts/show.html.erb -->

<%= render @post.comments %>

<%= link_to "댓글 추가", new_post_comment_path(@post) %>

<!-- app/views/comments/new.html.erb -->

<%= form_with model: [@post, @comment] do |f| %>

<%= f.text_area :body %>

<%= f.submit "올리기" %>

<% end %>

"댓글 추가" 링크를 클릭하면 `id="comments"` 프레임 안에서만 폼이 로드된다. 페이지 전체는 변하지 않는다.

Turbo Streams로 실시간 업데이트

app/models/comment.rb

class Comment < ApplicationRecord

belongs_to :post

belongs_to :user

broadcasts_to ->(comment) { [comment.post, "comments"] }

end

<!-- app/views/posts/show.html.erb -->

<%= turbo_stream_from @post, "comments" %>

<%= render @post.comments %>

이제 어떤 사용자가 댓글을 추가하면, 같은 페이지를 보고 있는 모든 사용자에게 WebSocket(Solid Cable)으로 새 댓글이 즉시 추가된다. React/Vue 한 줄 없이.

Stimulus — 가벼운 JS 컨트롤러

복잡한 클라이언트 상호작용이 필요할 때만 Stimulus를 쓴다.

// app/javascript/controllers/counter_controller.js

export default class extends Controller {

static targets = ["display"]

initialize() { this.count = 0 }

increment() {

this.count += 1

this.displayTarget.textContent = this.count

}

}

HTML이 항상 진실의 원천이다. React의 "가상 DOM이 진실이고 실제 DOM은 거기서 파생"과 정반대 철학이다.

7장 · Sorbet (Stripe) + Tapioca — 점진적 타입

Ruby는 동적 언어다. 변수에 타입이 없다. 이게 장점이자 단점이다. Stripe는 코드베이스가 수백만 줄로 커지면서 동적 타이핑의 한계를 절감했고, **Sorbet**을 만들었다(2019년 오픈소스화).

Sorbet은 Ruby에 점진적 타입을 입힌다. 모든 파일에 한 줄을 추가한다.

typed: true

require "sorbet-runtime"

class User

extend T::Sig

sig { params(name: String, age: Integer).returns(User) }

def self.create(name:, age:)

new(name: name, age: age)

end

sig { params(name: String, age: Integer).void }

def initialize(name:, age:)

@name = name

@age = age

end

sig { returns(String) }

def display

"#{@name} (#{@age})"

end

end

`# typed: true`는 strict 정도를 의미한다(`ignore` / `false` / `true` / `strict` / `strong`). `sig { ... }`로 메소드 시그니처를 선언한다. 런타임에는 sorbet-runtime이 인자 타입을 검증해서 에러를 던지고, 정적 분석에서는 `srb tc`로 타입 체크를 돌린다.

bundle add sorbet

bundle add sorbet-runtime

bundle exec srb init

bundle exec srb tc

Tapioca — Sorbet의 자동 RBI 생성기

Sorbet은 `.rbi` 파일(RBI = Ruby Interface)에서 타입 시그니처를 읽는다. 외부 gem에 대한 시그니처를 직접 쓰는 것은 지옥이다 — **Tapioca**가 자동 생성한다.

bundle add tapioca --group=development

bundle exec tapioca init

bundle exec tapioca gems # 모든 gem의 RBI 생성

bundle exec tapioca dsl # Rails DSL (스코프, 어소시에이션, 어트리뷰트)의 RBI 생성

Tapioca DSL이 만든 RBI 예제.

sorbet/rbi/dsl/user.rbi (자동 생성)

class User

sig { returns(T.nilable(String)) }

def email; end

sig { params(value: T.nilable(String)).returns(T.nilable(String)) }

def email=(value); end

ActiveRecord 스코프

sig { returns(ActiveRecord::Relation) }

def self.active; end

end

이제 IDE에서 `user.emil` 같은 오타도 잡힌다. Stripe 사내에서는 코드의 80% 이상이 `# typed: true` 이상이라고 한다.

8장 · Kamal 2 (37signals) — `kamal deploy`로 직접 배포

37signals의 DHH는 2023년에 "We have left the cloud"라는 글로 유명해졌다. Basecamp/HEY를 AWS에서 자체 데이터센터로 이전하면서 연 700만 달러를 절약했다는 글이다. 그 이전 과정에서 만든 도구가 Kamal(원래 이름은 MRSK)이다.

Kamal 2(2024년 출시)는 **Docker + SSH + 한 줄짜리 명령**으로 어떤 리눅스 서버에든 앱을 배포한다. 헤로쿠/Vercel/AWS Fargate 없이.

gem install kamal

kamal init

`config/deploy.yml`로 배포를 정의한다.

service: myapp

image: myteam/myapp

servers:

web:

hosts:

- 1.2.3.4

- 1.2.3.5

job:

hosts:

- 1.2.3.6

cmd: bin/jobs

registry:

server: ghcr.io

username: myteam

password:

- KAMAL_REGISTRY_PASSWORD

env:

clear:

RAILS_ENV: production

RAILS_LOG_TO_STDOUT: 1

secret:

- RAILS_MASTER_KEY

- DATABASE_URL

accessories:

postgres:

image: postgres:16

hosts:

- 1.2.3.4

env:

secret:

- POSTGRES_PASSWORD

volumes:

- /var/lib/postgresql/data:/var/lib/postgresql/data

proxy:

ssl: true

host: myapp.com

배포는 한 줄.

kamal deploy

이게 무엇을 하는가.

1. 로컬에서 Docker 이미지 빌드(또는 GitHub Actions에서 빌드한 것을 pull).

2. 레지스트리에 push.

3. 모든 서버에 SSH로 들어가서 새 이미지 pull.

4. 새 컨테이너 띄우고, Kamal Proxy(Traefik 후속)가 트래픽을 갈아끼우는 무중단 배포.

5. 오래된 컨테이너 정리.

처음 배포할 때는 `kamal setup`을 한 번 더 호출하면 Docker 설치부터 다 해준다.

kamal setup # Docker 설치 + 초기 배포

kamal deploy # 이후의 매 배포

kamal rollback # 롤백

kamal logs # 로그 조회

kamal app exec --interactive --reuse "bin/rails console" # console 접속

EC2 t4g.medium 한 대(월 30달러)에 Postgres + Solid Queue + Rails 앱을 다 띄우면, **헤로쿠 hobby tier(월 25달러)에 가까운 가격에 훨씬 강력한 인스턴스**가 나온다. 트래픽이 커지면 서버를 늘리고 `kamal deploy --hosts ...`로 수평 확장.

9장 · Mission Control — Rails 네이티브 잡 모니터링

Sidekiq에는 항상 좋은 웹 UI(`/sidekiq`)가 있었다. Solid Queue로 옮기면 이걸 잃어버리는가? 아니다 — Rails 8은 **Mission Control - Jobs**라는 공식 웹 UI를 제공한다.

bundle add mission_control-jobs

config/routes.rb

Rails.application.routes.draw do

mount MissionControl::Jobs::Engine, at: "/jobs"

...

end

config/application.rb

config.mission_control.jobs.base_controller_class = "AdminController" # 인증

`/jobs`에 접속하면 큐 상태, 진행 중인 잡, 실패한 잡, 재시도, 잡 일시정지/재개를 다 할 수 있다. Sidekiq 웹 UI의 거의 모든 기능을 제공하면서, Solid Queue의 모든 어댑터(Postgres/MySQL/SQLite)에서 동작한다.

추가로 잡 모니터링에 좋은 도구들.

- **Skylight** / **Scout APM** — 잡의 메모리/CPU 프로파일링.

- **Sentry** / **Honeybadger** — 잡 실패 시 에러 추적과 알람.

- **Datadog** — Solid Queue 테이블 메트릭을 직접 수집.

10장 · Propshaft / Action Notifier — Rails 8의 새 기본

Propshaft — sprockets의 후속

Sprockets는 Rails 2부터 있던 에셋 파이프라인이다. CoffeeScript, Sass, ERB로 자바스크립트와 CSS를 컴파일하고, 다이제스트 핑거프린팅을 붙여 캐싱한다. 그런데 2020년대에는 esbuild, Vite, Bun, importmap-rails 같은 도구들이 트랜스파일을 더 잘한다. **Propshaft는 트랜스파일을 안 한다** — 파일을 디지스트 핑거프린팅만 해서 서빙한다.

Gemfile

gem "propshaft"

디지스트가 붙은 파일을 만들기

bin/rails assets:precompile

새 에셋 파이프라인 철학.

- **트랜스파일은 esbuild/Vite/jsbundling-rails가 한다.**

- **번들링도 그 도구들이 한다.**

- **Propshaft는 단순히 디지스트 핑거프린팅과 서빙만 한다.**

Rails 8 새 앱은 디폴트로 **Propshaft + Importmap-rails + Hotwire**를 쓴다. 자바스크립트는 importmap으로 브라우저가 네이티브로 가져오고, CSS는 propshaft가 핑거프린팅한다.

Action Notifier (Rails 8 새 추가)

Rails 8에는 알림(notification) 시스템이 정식으로 들어왔다. 이메일, SMS, 푸시, Slack, 데이터베이스 등 여러 채널로 동시에 알림을 보내는 표준 추상화다.

class NewCommentNotifier < ApplicationNotifier

deliver_by :email, mailer: "CommentMailer", method: "new_comment"

deliver_by :slack, channel: "#engineering"

deliver_by :database

param :comment, :user

def message

"#{params[:user].name}이(가) 새 댓글을 달았습니다."

end

end

호출

NewCommentNotifier.with(comment: @comment, user: current_user).deliver(@post.author)

여러 채널로 같은 메시지를 보내는 흔한 패턴을 한 곳에 둔다. Noticed gem이 영감의 출처고, Rails 8에서 공식화된 것이다.

11장 · RuboCop / Standard / Brakeman — 정적 분석

RuboCop — 가장 많이 쓰이는 린터

RuboCop은 Ruby의 ESLint 같은 존재다. 스타일 위반부터 잠재적 버그까지 잡는다.

bundle add rubocop --group=development

bundle exec rubocop

bundle exec rubocop -a # 자동 수정

`rubocop-rails`, `rubocop-rspec`, `rubocop-performance` 같은 확장 gem들로 영역을 늘린다.

.rubocop.yml

require:

- rubocop-rails

- rubocop-rspec

- rubocop-performance

AllCops:

TargetRubyVersion: 3.4

TargetRailsVersion: 8.0

NewCops: enable

Style/StringLiterals:

EnforcedStyle: double_quotes

Layout/LineLength:

Max: 120

Standard — 의견 없는 RuboCop 래퍼

매번 `.rubocop.yml`을 정하는 게 피곤하다면 **Standard**를 쓰자. RuboCop을 래핑하면서 "이미 다 결정해뒀으니 가만히 있어"라는 철학이다.

bundle add standard --group=development

bundle exec standardrb

bundle exec standardrb --fix

Tenderlove(Aaron Patterson)와 많은 OSS 메인테이너가 쓴다. Prettier가 JS에서 한 일을 Ruby에서 한다.

Brakeman — 정적 보안 분석

Brakeman은 Rails 전용 보안 정적 분석기다. SQL injection, mass assignment, XSS, command injection 같은 일반적인 취약점을 잡는다.

bundle add brakeman --group=development

bundle exec brakeman

CI 파이프라인에 넣고 PR마다 돌리면 좋다. GitHub Actions 예제.

name: Brakeman

on: [pull_request]

jobs:

brakeman:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1

with:

ruby-version: 3.4

bundler-cache: true

- run: bundle exec brakeman --no-pager

Rails 8 새 앱은 디폴트로 RuboCop, Brakeman, 그리고 GitHub Actions 워크플로우를 같이 만들어준다.

12장 · 대안 — Hanami 2 / Roda / Sinatra / dry-rb

Rails가 모든 답은 아니다. Ruby 생태계에는 다른 좋은 선택지가 있다.

Hanami 2

Hanami는 "Rails보다 명시적이고 모듈화된 프레임워크"를 목표로 한다. 2022년에 Hanami 2가 나오면서 본격적으로 프로덕션에 쓸 만해졌다. 특징.

- **의존성 주입(DI)** 컨테이너가 일급 시민. dry-system 위에 만들어졌다.

- 라우터 / 컨트롤러(액션) / 뷰 / 뷰 모델이 명시적으로 분리.

- "관습보다 명시적" 철학. 마법이 적다.

slices/main/actions/books/index.rb

module Main

module Actions

module Books

class Index < Main::Action

include Deps[

"repositories.book_repo"

]

def handle(_request, response)

response[:books] = book_repo.all

end

end

end

end

end

소규모 ~ 중규모 API/SaaS에 적합하다. Rails보다 학습 곡선이 가파르지만, 큰 코드베이스에서 명시적인 구조가 가치 있을 때 빛난다.

Roda

Jeremy Evans(Sequel 작가)가 만든 마이크로 라우팅 트리 기반 프레임워크. 매우 빠르고, 트리 라우팅으로 라우트가 자연스럽게 중첩된다.

require "roda"

class App < Roda

plugin :json

plugin :halt

route do |r|

r.root { { hello: "world" } }

r.on "users" do

r.get Integer do |id|

{ user_id: id }

end

r.post do

새 유저

end

end

end

end

Sinatra보다 빠르고, Hanami보다 가볍다. Shrine, Sequel 같은 라이브러리와 잘 어울린다.

Sinatra

가장 오래된 마이크로 프레임워크. 단순한 API나 작은 도구에 여전히 좋다.

require "sinatra"

get "/hello/:name" do

"Hello, #{params[:name]}"

end

dry-rb / Rom-rb

dry-rb는 Hanami가 기반으로 한 라이브러리 컬렉션이다. `dry-validation`(스키마 검증), `dry-monads`(Result/Maybe 모나드), `dry-types`(타입), `dry-system`(DI 컨테이너), `dry-effects`(이펙트). Rails와도 같이 쓸 수 있다.

Rom-rb는 데이터 매핑 패턴 ORM이다. ActiveRecord와 달리 도메인 객체와 영속성을 분리한다. DDD를 진지하게 하는 팀에 매력적이다.

13장 · 한국 / 일본 Ruby 생태계 — Cookpad, Mercari, 카카오페이

일본 — Ruby의 본가

Ruby는 Matz(마츠모토 유키히로)가 1995년에 만들었다. 일본은 Ruby의 본가이고, 일본 기업들의 Ruby/Rails 사용이 가장 깊고 오래되었다.

- **Cookpad** — 일본 최대 레시피 사이트. 거의 모든 백엔드가 Rails. RubyKaigi 메인 스폰서. Ruby/Rails 컨퍼런스 발표에서 사내 모놀리스를 어떻게 운영하는지 자주 공유한다.

- **Mercari** — 일본 최대 중고 거래 앱. 백엔드의 큰 부분이 Ruby/Rails이고, 다른 부분은 Go/Python으로 이전 중이다. Go로 옮기는 부분도 Ruby에서 시작한 비즈니스 로직을 천천히 마이그레이션 한다.

- **GMO 페퍼보이** — 라이브 커머스/EC.

- **Money Forward** — 가계부/회계 SaaS. Rails로 시작해서 성장한 케이스.

- **Sansan** — 명함 관리. Rails 기반.

- **freee** — 회계 SaaS. Rails로 IPO까지 갔다.

일본은 매년 RubyKaigi(2-3일짜리 컨퍼런스)를 열고 Matz가 직접 키노트를 한다. Rails보다 Ruby 자체에 대한 발표가 많고, MRI/YJIT/언어 디자인 깊은 토론이 일상이다.

한국 — Rails 사용이 늘고 있다

한국의 Rails 사용은 일본만큼 크지는 않지만, 꾸준히 늘고 있다.

- **카카오페이** — 일부 백엔드가 Rails. 결제/멤버십 관련 일부 도메인.

- **토스** — 일부 내부 도구와 사내 시스템이 Rails.

- **Bridge Plus / 디캠프** — 스타트업 액셀러레이터들이 자주 Rails로 시작.

- **잘 알려진 한국의 Rails 사례** — 클래스101, 미리내, 직방 일부 백오피스 등.

한국은 2010년대 중반에 RubyKR 컨퍼런스가 활발했고, 최근에는 RubyKR 슬랙/디스코드 커뮤니티가 작지만 활발하다. 한국에서 Rails 개발자 채용은 일본만큼은 아니어도 꾸준히 있다(특히 시니어/스타트업 기술 리드 포지션).

글로벌 — 잘 알려진 사례

- **Shopify** — 세계 최대 Rails 앱. YJIT의 주요 후원자. 다양한 Rails 코어 컨트리뷰션.

- **GitHub** — Rails로 시작해서 지금도 큰 부분이 Rails. 모놀리스 운영의 모범 사례.

- **Stripe** — Sorbet의 산실. 결제 처리 백엔드의 큰 부분이 Ruby.

- **Airbnb** — 초기부터 Rails. 일부는 Java/Kotlin으로 이전, 일부는 여전히 Ruby.

- **Basecamp / HEY** — 37signals의 자체 제품. DHH의 비전이 그대로 구현된 곳.

- **GitLab** — Rails. 자체 호스팅과 SaaS 모두.

- **Twitch** — 초기 Rails, 일부는 Go/Erlang으로 이전.

- **Coinbase** — Rails로 시작, 일부는 Go로 이전.

14장 · 누가 Ruby/Rails를 골라야 하나 — 1-2인 / 스타트업 / B2B SaaS

1-2인 / 사이드 프로젝트

**압도적으로 Rails를 추천한다.** 이유.

- **속도** — `rails new` 한 줄에 인증, 잡 큐, 캐시, 웹소켓, 마이그레이션, 테스트, 어드민까지 셋업된다.

- **인프라 단순함** — Rails 8 + Solid 트리오 + SQLite로 EC2 t4g.small 한 대에 다 띄울 수 있다.

- **Kamal로 배포 비용 0에 가까움** — 헤로쿠/Vercel 없이 한 달 5달러로 시작.

- **Hotwire로 프론트엔드 빌드 파이프라인 없음** — Node 의존성 제로.

Next.js + Vercel + Postgres + Upstash Redis + Resend + Stripe로 짜는 것보다 Rails + Kamal + Postgres 하나로 짜는 것이 빠르고 싸다.

초기 스타트업 (1-15명)

**Rails는 여전히 최고의 선택이다.** 37signals, GitHub, Stripe, Shopify가 다 Rails로 시작해서 IPO/유니콘까지 갔다. PMF를 찾는 단계에서는 풀스택 프레임워크의 응집력이 절대적이다. Next.js의 모노레포 + RPC + 인증 라이브러리 + 잡 큐 의존성 트리는 PMF 단계에서 짐이다.

좋은 조합.

- Rails 8 + Postgres + Solid 트리오 + Hotwire + Kamal 2.

- Sentry로 에러 추적, Skylight나 Scout APM으로 APM.

- RSpec 또는 Minitest, FactoryBot, Capybara/Cuprite로 테스트.

- 점진적으로 Sorbet 도입(처음부터 다 strict로 하지 말고, 핵심 도메인부터).

B2B SaaS (15-100명)

여전히 Rails가 좋다. 단, **타입과 도메인 분리**에 더 신경 쓰자.

- Sorbet으로 핵심 도메인의 타입을 잡는다.

- Service Object / Command 패턴으로 fat controller를 피한다.

- Trailblazer, Dry-rb, Hanami의 패턴을 부분 도입한다.

- 모놀리스를 유지하되, Engine으로 도메인을 분리한다(Shopify의 "Modular Monolith" 패턴).

100명 이상 / 마이크로서비스 단계

Rails도 잘 돌지만, 이때부터는 언어 선택보다 **조직 설계**가 더 중요하다. Rails 모놀리스가 자연스럽게 분해되어야 한다면, 일부 서비스를 Go/Java/Kotlin으로 옮기는 것을 고려할 만하다(Shopify가 핵심은 Rails, 일부 인프라성 서비스는 Go/Rust로 이전한 것처럼).

Rails를 피해야 하는 경우

- **초저지연(`<10ms`)이 비즈니스의 핵심**인 경우 — 게임 서버, 고빈도 트레이딩, 임베디드. Go, Rust, Elixir, C++가 낫다.

- **머신러닝 인퍼런스/학습 파이프라인이 본업**인 경우 — Python이 답이다.

- **타입이 절대적으로 필수**인 도메인 — Sorbet으로 어느 정도 해결되지만, 처음부터 TypeScript/Kotlin/Rust가 더 직선적인 경로다.

- **이미 Node/Python 단일 언어 정책**인 회사 — 굳이 Ruby를 추가할 이유가 없다.

15장 · 참고 / References

- Ruby 3.4 Release Notes: https://www.ruby-lang.org/en/news/2024/12/25/ruby-3-4-0-released/

- Rails 8 Release Notes: https://rubyonrails.org/2024/11/8/rails-8-no-paas-required

- Rails Edge Guides: https://edgeguides.rubyonrails.org/

- YJIT Documentation: https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md

- Solid Queue: https://github.com/rails/solid_queue

- Solid Cache: https://github.com/rails/solid_cache

- Solid Cable: https://github.com/rails/solid_cable

- Hotwire: https://hotwired.dev/

- Turbo Handbook: https://turbo.hotwired.dev/handbook/introduction

- Stimulus Handbook: https://stimulus.hotwired.dev/handbook/introduction

- Sorbet: https://sorbet.org/

- Tapioca: https://github.com/Shopify/tapioca

- Kamal: https://kamal-deploy.org/

- Mission Control Jobs: https://github.com/rails/mission_control-jobs

- Propshaft: https://github.com/rails/propshaft

- RuboCop: https://docs.rubocop.org/rubocop/

- Standard Ruby: https://github.com/standardrb/standard

- Brakeman: https://brakemanscanner.org/

- Hanami: https://hanamirb.org/

- Roda: https://roda.jeremyevans.net/

- Sinatra: https://sinatrarb.com/

- dry-rb: https://dry-rb.org/

- Rom-rb: https://rom-rb.org/

- 37signals "Leaving the Cloud": https://world.hey.com/dhh/we-have-left-the-cloud-251760fb

- Cookpad Tech Blog: https://techlife.cookpad.com/

- Mercari Engineering Blog: https://engineering.mercari.com/en/

- RubyKaigi: https://rubykaigi.org/

- Shopify Engineering on YJIT: https://shopify.engineering/yjit-just-in-time-compiler-for-ruby

- GitHub on Modular Monoliths: https://github.blog/2022-08-03-monoliths-and-microservices/

현재 단락 (1/442)

2026년의 풍경 하나. 한 친구가 Y Combinator 데모데이에서 자기 스타트업을 자랑한다. "MVP를 3주 만에 만들어서 출시했고, 첫 50만 매출 찍었어." 무슨 스택 썼...

작성 글자: 0원문 글자: 18,026작성 단락: 0/442