Action Cable과 WebSocket 완벽 가이드: 2026년 Ruby on Rails 기술 면접 대비
Ruby on Rails Action Cable과 WebSocket 심층 분석. 커넥션, 채널, 브로드캐스팅, Rails 8 Solid Cable, Redis 스케일링, 테스트 패턴까지 기술 면접에서 자주 출제되는 핵심 내용을 코드 예제와 함께 정리합니다.

Action Cable은 WebSocket 지원을 Rails 프레임워크에 직접 통합하는 기능으로, 채팅, 알림, 실시간 대시보드와 같은 실시간 기능을 외부 의존성 없이 구현할 수 있게 합니다. 2026년 기술 면접에서 Action Cable의 내부 동작 원리――커넥션 라이프사이클부터 프로덕션 스케일링까지――에 대한 깊은 이해는 뛰어난 지원자와 그렇지 않은 지원자를 구분하는 핵심 기준이 됩니다.
Action Cable은 컨트롤러와 모델에서 사용하는 것과 동일한 Rails 규약으로 WebSocket을 통합합니다. Rails 8에서 도입된 Solid Cable은 데이터베이스 기반 어댑터로 pub/sub 메시징에서 Redis 의존성을 제거합니다.
WebSocket 프로토콜 기초와 Rails에서의 역할
WebSocket 프로토콜(RFC 6455)은 단일 TCP 연결을 통해 지속적인 전이중 통신 채널을 수립합니다. HTTP의 요청-응답 사이클과 달리, WebSocket은 연결을 유지하며 클라이언트와 서버 양쪽에서 언제든지 메시지를 전송할 수 있습니다.
Action Cable은 이 프로토콜을 Rails 친화적인 추상화로 감싸줍니다. 서버는 /cable 경로에서 WebSocket 업그레이드를 처리하고, 커넥션 인증을 관리하며, 채널을 통해 메시지를 라우팅합니다. 클라이언트 측 JavaScript 라이브러리가 구독의 생성과 관리를 자동으로 수행합니다.
일반적인 HTTP 요청은 밀리초 단위로 완료되고 종료되지만, WebSocket 연결은 수 분, 수 시간, 또는 세션 전체 기간 동안 유지됩니다. 이 근본적인 차이가 Action Cable의 모든 아키텍처 결정을 좌우합니다.
Action Cable 아키텍처: 커넥션, 채널, 서브스크립션
Action Cable은 세 가지 핵심 추상화로 구성된 계층 아키텍처를 따릅니다. 커넥션은 인증을 처리하고, 채널은 비즈니스 로직을 캡슐화하며, 서브스크립션은 컨슈머와 특정 채널을 연결합니다.
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
# Cookies are available in WebSocket handshake
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end커넥션 클래스는 WebSocket 핸드셰이크마다 한 번 실행됩니다. 인증은 여기서 수행되며, 개별 채널에서는 수행하지 않습니다. identified_by 선언은 사용자 ID를 등록하여, 해당 연결의 모든 채널 구독에서 사용할 수 있게 합니다.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
room = ChatRoom.find(params[:room_id])
# Authorization: verify user belongs to this room
if room.members.include?(current_user)
stream_for room
else
reject
end
end
def receive(data)
message = ChatMessage.create!(
room_id: params[:room_id],
user: current_user,
body: data["body"]
)
ChatChannel.broadcast_to(
message.room,
{ id: message.id, body: message.body, user: current_user.name }
)
end
def unsubscribed
# Cleanup: mark user as offline
AppearanceTracker.mark_offline(current_user)
end
end채널은 세 가지 라이프사이클 콜백을 정의합니다: subscribed, receive, unsubscribed. stream_for 메서드는 구독을 특정 모델 인스턴스에 바인딩하여 네임스페이스가 지정된 스트림을 생성합니다. 해당 스트림으로의 브로드캐스팅은 연결된 모든 구독자에게 메시지를 전달합니다.
클라이언트 측 JavaScript 구독
클라이언트 측 컨슈머는 Action Cable 서버에 연결하고 구독을 관리합니다. 각 구독은 서버 측 채널에 매핑됩니다.
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: roomId },
{
connected() {
// Called when the subscription is ready
console.log("Connected to chat room", roomId)
},
disconnected() {
// Called when the subscription is closed
console.log("Disconnected from chat room")
},
received(data) {
// Called when data is broadcast to the channel
const messageList = document.getElementById("messages")
messageList.insertAdjacentHTML("beforeend",
`<div class="message"><strong>${data.user}</strong>: ${data.body}</div>`
)
},
sendMessage(body) {
// Calls ChatChannel#receive on the server
this.perform("receive", { body: body })
}
}
)received 콜백은 서버가 구독 중인 스트림에 브로드캐스트할 때마다 실행됩니다. perform 메서드는 클라이언트에서 서버로 데이터를 전송하여 해당 채널 메서드를 호출합니다.
Ruby on Rails 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Rails 8의 Solid Cable: 데이터베이스 기반 Pub/Sub
Rails 8에서는 Solid Queue, Solid Cache와 함께 "Solid 삼총사"의 일원으로 Solid Cable이 도입되었습니다. Solid Cable은 메시지를 데이터베이스 테이블에 저장하여 pub/sub 백엔드로서의 Redis를 대체합니다.
# config/cable.yml (Rails 8 default)
development:
adapter: solid_cable
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.daySolid Cable은 각 브로드캐스트 메시지를 데이터베이스 테이블에 기록하고, 설정 가능한 간격으로 새 메시지를 폴링하는 방식으로 동작합니다. 기본 폴링 간격인 100ms는 대부분의 애플리케이션에서 거의 실시간에 가까운 전달을 보장합니다.
트레이드오프는 명확합니다. Solid Cable은 인프라 의존성(Redis)을 제거하는 대신, 약간 더 높은 지연 시간과 데이터베이스 부하가 발생합니다. 이미 PostgreSQL이나 MySQL을 운영 중인 애플리케이션에서는 이 트레이드오프가 합리적인 선택인 경우가 많습니다. 초당 수천 건의 메시지가 발생하는 고빈도 브로드캐스팅에서는 여전히 Redis가 더 적합합니다.
# config/database.yml (Rails 8 multi-database)
production:
primary:
<<: *default
database: myapp_production
cable:
<<: *default
database: myapp_cable_production
migrations_paths: db/cable_migrateSolid Cable은 전용 데이터베이스를 사용하여 프라이머리 데이터베이스와의 경합을 방지합니다. 이 분리를 통해 메시지 폴링이 애플리케이션 쿼리에 간섭하는 것을 막습니다.
브로드캐스팅 패턴과 서버 측 트리거
모델, 잡, 컨트롤러에서의 브로드캐스팅은 프로덕션 Rails 애플리케이션에서 가장 흔히 사용되는 세 가지 패턴입니다.
# Broadcasting from a model callback
class Notification < ApplicationRecord
belongs_to :user
after_create_commit :broadcast_to_user
private
def broadcast_to_user
ActionCable.server.broadcast(
"notifications_#{user_id}",
{ id: id, title: title, read: false }
)
end
end
# Broadcasting from a background job
class DashboardUpdateJob < ApplicationJob
queue_as :default
def perform(dashboard_id)
dashboard = Dashboard.find(dashboard_id)
stats = dashboard.compute_stats
ActionCable.server.broadcast(
"dashboard_#{dashboard_id}",
{ stats: stats, updated_at: Time.current.iso8601 }
)
end
end모델 콜백은 레코드 변경에 의해 트리거되는 간단한 알림에 적합합니다. 백그라운드 잡은 대시보드 통계 계산이나 데이터 집계와 같은 무거운 처리를 요청 사이클을 차단하지 않고 수행합니다. 브로드캐스트 자체는 항상 가볍습니다. 페이로드를 직렬화하고 어댑터에 발행하는 것이 전부입니다.
Turbo Streams와 Action Cable 통합
Rails 8의 Hotwire는 Action Cable을 Turbo Streams의 전송 레이어로 사용하여, 커스텀 JavaScript를 작성하지 않고도 실시간 DOM 업데이트를 구현합니다.
# app/models/message.rb
class Message < ApplicationRecord
belongs_to :chat_room
# Automatically broadcasts append to subscribers
broadcasts_to :chat_room
end
# app/views/chat_rooms/show.html.erb
<%= turbo_stream_from @chat_room %>
<div id="messages">
<%= render @chat_room.messages %>
</div>broadcasts_to 매크로는 after_create, after_update, after_destroy 콜백을 생성하여 Turbo Stream 프래그먼트를 Action Cable을 통해 브로드캐스트합니다. 클라이언트 측의 turbo_stream_from 헬퍼는 해당 스트림을 구독합니다. 새 메시지를 생성하면, 렌더링된 파셜이 연결된 모든 클라이언트의 DOM에 자동으로 추가됩니다.
이 패턴은 실시간 기능을 모델 선언과 뷰 헬퍼만으로 축소합니다. 커스텀 채널도, JavaScript 핸들러도, 수동 DOM 조작도 필요 없습니다.
프로덕션 환경에서의 Action Cable 스케일링
프로덕션 배포에서는 어댑터 선택, 연결 수 제한, 수평 스케일링에 대한 신중한 고려가 필요합니다.
# config/cable.yml — Redis adapter for high-scale production
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: myapp_productionRedis는 pub/sub의 백본으로 동작하여, 한 Rails 프로세스에서 발행된 메시지가 다른 프로세스에 연결된 구독자에게 확실히 전달되도록 합니다. Redis(또는 Solid Cable) 없이 async 어댑터를 사용하면 브로드캐스트가 단일 프로세스로 제한되어, 멀티 프로세스나 멀티 서버 배포에서는 사용할 수 없습니다.
연결 한도는 서버에 따라 달라집니다. Puma와 Action Cable은 HTTP 요청과 동일한 프로세스에서 WebSocket 연결을 처리합니다. 각 WebSocket은 지속적인 연결을 유지하며 스레드(또는 Ruby 3.x의 파이버 기반 스케줄러에서는 파이버)를 소비합니다. 5개 스레드와 4개 워커의 일반적인 Puma 구성에서는 포화 상태 전까지 약 200개의 동시 WebSocket 연결을 처리할 수 있습니다.
수천 개의 동시 연결이 필요한 애플리케이션에서는 AnyCable이 Ruby WebSocket 서버를 Go 기반 서버로 대체하여, 프로토콜 수준에서 연결을 처리하면서 gRPC를 통해 채널 로직을 Rails로 라우팅합니다. 이 아키텍처는 노드당 10,000개 이상의 동시 연결을 지원합니다.
면접관은 Action Cable의 스케일링 한계에 대해 자주 질문합니다. 핵심 답변 포인트: Action Cable 자체가 병목이 아니라, WebSocket 연결을 처리하는 Ruby 프로세스가 병목입니다. AnyCable은 연결 관리를 Go로 오프로딩하면서 채널 로직은 Ruby에 유지하여 이 문제를 해결합니다.
Action Cable 채널 테스트
Rails는 채널 단위 테스트를 위한 ActionCable::Channel::TestCase와 커넥션 인증 테스트를 위한 ActionCable::Connection::TestCase를 제공합니다.
# test/channels/chat_channel_test.rb
require "test_helper"
class ChatChannelTest < ActionCable::Channel::TestCase
test "subscribes to a valid room" do
room = chat_rooms(:general)
user = users(:alice)
stub_connection current_user: user
subscribe room_id: room.id
assert subscription.confirmed?
assert_has_stream_for room
end
test "rejects subscription for unauthorized user" do
room = chat_rooms(:private)
user = users(:outsider)
stub_connection current_user: user
subscribe room_id: room.id
assert subscription.rejected?
end
test "broadcasts messages to room subscribers" do
room = chat_rooms(:general)
stub_connection current_user: users(:alice)
subscribe room_id: room.id
assert_broadcast_on(room, hash_including(body: "Hello")) do
perform :receive, body: "Hello"
end
end
endstub_connection 메서드는 실제 WebSocket 없이 커넥션 컨텍스트를 설정합니다. assert_has_stream_for는 스트림 바인딩을 검증합니다. assert_broadcast_on은 블록 내의 브로드캐스트를 캡처하여, 올바른 페이로드가 올바른 스트림에 도달하는지 확인합니다.
Action Cable 기술 면접 빈출 질문
Action Cable 관련 기술 면접에서는 프로토콜에 대한 이해, 아키텍처 결정, 프로덕션 운영 과제가 주로 출제됩니다.
Action Cable은 어떻게 커넥션을 인증하는가? 인증은 WebSocket 핸드셰이크 중 ApplicationCable::Connection#connect에서 수행됩니다. HTTP 세션의 쿠키는 이 시점에서 사용 가능합니다. 토큰 기반 인증에서는 WebSocket URL의 쿼리 파라미터로 토큰을 전달하고 connect에서 검증합니다.
WebSocket 연결이 끊어지면 어떻게 되는가? 클라이언트 라이브러리는 지수 백오프를 적용한 자동 재연결을 구현합니다. 서버 측에서는 해당 연결이 구독하고 있던 각 채널에 대해 unsubscribed가 실행됩니다. 상태 변경 정리(사용자 오프라인 표시, 잠금 해제)는 unsubscribed에서 처리합니다.
Solid Cable과 Redis 중 언제 어떤 것을 사용해야 하는가? Solid Cable은 중간 수준의 실시간 요구 사항(초당 100개 이하의 메시지)을 가지며 Redis 인프라를 피하고 싶은 애플리케이션에 적합합니다. 고처리량 시나리오나 10ms 미만의 전달 지연이 필요한 경우에는 Redis가 여전히 필요합니다.
개별 채널이 아닌 커넥션 클래스에 인가 로직을 배치하는 실수가 발생합니다. 커넥션은 인증(이 사용자는 누구인가?)을 담당하고, 채널은 인가(이 사용자가 이 채팅방에 접근할 수 있는가?)를 담당합니다. 이 책임을 혼동하면 보안 취약점이 발생합니다.
결론
- Action Cable은 커넥션, 채널, 서브스크립션을 핵심 추상화로 하여 WebSocket 프로토콜을 Rails 규약에 통합한다
- 인증은
ApplicationCable::Connection에서, 인가는 개별 채널의subscribed콜백에서 수행한다 - Rails 8의 Solid Cable은 데이터베이스를 pub/sub에 활용하여 Redis 의존성을 제거한다. 중간 수준의 처리량을 가진 애플리케이션에 적합하다
- Turbo Streams는 Action Cable을 활용하여 최소한의 코드로 자동 실시간 DOM 업데이트를 구현한다
- 프로덕션 스케일링에는 멀티 프로세스 배포 시 Redis 또는 Solid Cable이 필요하다. AnyCable은 WebSocket 관리를 Go로 오프로딩하여 10,000개 이상의 연결을 처리한다
- 채널 테스트에는
stub_connection,assert_has_stream_for,assert_broadcast_on을 사용하며, 실제 WebSocket 연결은 불필요하다 - 이러한 개념을 확실히 다지기 위해 Action Cable & WebSocket 면접 문제로 연습하는 것이 효과적이다
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

ActiveRecord: Ruby on Rails에서 N+1 쿼리 문제 해결하기
ActiveRecord로 Rails의 N+1 쿼리를 탐지하고 해결하는 완전한 가이드입니다. includes, preload, eager_load와 자동 탐지 도구를 익혀보십시오.

Ruby on Rails 면접 질문: 2026년 Top 25
가장 자주 묻는 Ruby on Rails 면접 질문 25선. MVC 아키텍처, Active Record, 마이그레이션, RSpec 테스트, REST API에 대한 자세한 답변과 코드 예제.

Ruby on Rails 7: 리액티브 애플리케이션을 위한 Hotwire와 Turbo
Rails 7에서 Hotwire와 Turbo 완벽 가이드. Turbo Drive, Frames, Streams로 JavaScript 없이 리액티브 애플리케이션을 구축하는 방법.