필사 모드: 모던 Ruby & Rails 2026 — Ruby 3.4 / Rails 8 / Hotwire / Sorbet / Kamal 2 / Solid Queue 심층 가이드
한국어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만 매출 찍었어." 무슨 스택 썼...