# Action Cable과 WebSocket 완벽 가이드: 2026년 Ruby on Rails 기술 면접 대비 > Ruby on Rails Action Cable과 WebSocket 심층 분석. 커넥션, 채널, 브로드캐스팅, Rails 8 Solid Cable, Redis 스케일링, 테스트 패턴까지 기술 면접에서 자주 출제되는 핵심 내용을 코드 예제와 함께 정리합니다. - Published: 2026-05-07 - Updated: 2026-05-07 - Author: Anthony Fillion-Maillet - Tags: ruby on rails, action cable, websockets, real-time, solid cable, rails 8 - Reading time: 12 min --- 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은 세 가지 핵심 추상화로 구성된 계층 아키텍처를 따릅니다. 커넥션은 인증을 처리하고, 채널은 비즈니스 로직을 캡슐화하며, 서브스크립션은 컨슈머와 특정 채널을 연결합니다. ```ruby # 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를 등록하여, 해당 연결의 모든 채널 구독에서 사용할 수 있게 합니다. ```ruby # 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 서버에 연결하고 구독을 관리합니다. 각 구독은 서버 측 채널에 매핑됩니다. ```javascript // app/javascript/channels/chat_channel.js 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", `
` ) }, sendMessage(body) { // Calls ChatChannel#receive on the server this.perform("receive", { body: body }) } } ) ``` `received` 콜백은 서버가 구독 중인 스트림에 브로드캐스트할 때마다 실행됩니다. `perform` 메서드는 클라이언트에서 서버로 데이터를 전송하여 해당 채널 메서드를 호출합니다. ## Rails 8의 Solid Cable: 데이터베이스 기반 Pub/Sub Rails 8에서는 Solid Queue, Solid Cache와 함께 "Solid 삼총사"의 일원으로 Solid Cable이 도입되었습니다. Solid Cable은 메시지를 데이터베이스 테이블에 저장하여 pub/sub 백엔드로서의 Redis를 대체합니다. ```yaml # 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.day ``` Solid Cable은 각 브로드캐스트 메시지를 데이터베이스 테이블에 기록하고, 설정 가능한 간격으로 새 메시지를 폴링하는 방식으로 동작합니다. 기본 폴링 간격인 100ms는 대부분의 애플리케이션에서 거의 실시간에 가까운 전달을 보장합니다. 트레이드오프는 명확합니다. Solid Cable은 인프라 의존성(Redis)을 제거하는 대신, 약간 더 높은 지연 시간과 데이터베이스 부하가 발생합니다. 이미 PostgreSQL이나 MySQL을 운영 중인 애플리케이션에서는 이 트레이드오프가 합리적인 선택인 경우가 많습니다. 초당 수천 건의 메시지가 발생하는 고빈도 브로드캐스팅에서는 여전히 Redis가 더 적합합니다. ```ruby # config/database.yml (Rails 8 multi-database) production: primary: <<: *default database: myapp_production cable: <<: *default database: myapp_cable_production migrations_paths: db/cable_migrate ``` Solid Cable은 전용 데이터베이스를 사용하여 프라이머리 데이터베이스와의 경합을 방지합니다. 이 분리를 통해 메시지 폴링이 애플리케이션 쿼리에 간섭하는 것을 막습니다. ## 브로드캐스팅 패턴과 서버 측 트리거 모델, 잡, 컨트롤러에서의 브로드캐스팅은 프로덕션 Rails 애플리케이션에서 가장 흔히 사용되는 세 가지 패턴입니다. ```ruby # 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 업데이트를 구현합니다. ```ruby # 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 %>