Rails, AnyCable, 그리고 YJS를 활용한 실시간 협업

JP Camara, Real-time collaboration with Rails, AnyCable and Yjs. San Francisco Ruby Conference 2025.

작성자
Evil Martians
발행일
2025년 12월 27일

핵심 요약

  • 1 Rails 애플리케이션에서 AnyCable을 사용하여 고성능 WebSocket 연결을 구축하고, YJS(CRDT)로 충돌 없는 실시간 동시 편집 기능을 구현할 수 있습니다.
  • 2 AnyCable은 Action Cable과 호환되며 Go로 작성되어 낮은 지연 시간, 적은 메모리 사용, 높은 CPU 효율성으로 WebSocket 연결을 효과적으로 확장합니다.
  • 3 YJS는 CRDT(충돌 없는 복제 데이터 유형)를 기반으로 하여 여러 사용자의 동시 편집을 자동으로 병합하고 일관된 문서 상태를 유지하며, 연결 끊김 및 재연결 시에도 데이터 손실 없이 히스토리를 재생할 수 있습니다.

도입

본 발표는 Rails 애플리케이션에서 실시간 협업 기능을 구현하는 방법에 대해 다룹니다. 발표자 JP Camarra는 Wealthbox의 Principal Engineer로서, Rails, AnyCable, YJS 기술 스택을 활용하여 실시간 협업 기능을 구축한 경험을 공유합니다. 실시간 협업은 여러 사용자가 동시에 단일 문서나 정보 소스에서 작업하며 즉각적인 피드백을 받는 것을 의미하며, 서버 잠금 없이 일관성을 유지하는 것이 핵심입니다. 이 발표는 연결 관리, 낮은 지연 시간, 히스토리 재생, 동시 편집, 커서 인식 및 영구 저장 등 실시간 협업의 주요 난제들을 해결하는 방안을 제시합니다.

실시간 협업의 난제들

실시간 협업은 다양한 기술적 난제를 수반합니다. 여기에는 WebSocket 연결 관리, 최대한 낮은 지연 시간 유지, 연결 끊김 후에도 데이터 손실 없이 히스토리 재생, 동시 편집 및 순서 이탈 배달 처리, 커서 및 타이핑 인식, 그리고 효율적인 데이터 영구 저장이 포함됩니다.

AnyCable: 고성능 WebSocket 서버

Action Cable은 Rails의 WebSocket 기능이지만, 연결 수가 증가할수록 브로드캐스트 지연 시간과 리소스 사용량이 급증하는 경향이 있습니다. AnyCable은 Action Cable 호환 WebSocket 서버로 Go 언어로 작성되어 이러한 문제를 해결합니다.

  • 성능 이점: 벤치마크 결과, AnyCable은 Action Cable 대비 연결 수가 많아질수록 브로드캐스트 지연 시간이 현저히 낮고(10,000 연결 시 1초 미만), 메모리 및 CPU 사용량도 훨씬 효율적입니다.

  • 아키텍처: AnyCable은 Rails 웹 서버와 WebSocket 서버를 분리하여 확장성을 높입니다. Rails는 웹 요청을, AnyCable은 WebSocket 연결을 전담하며, HTTP 또는 gRPC를 통해 Rails와 통신합니다. 이로써 Ruby 비즈니스 로직을 그대로 유지하면서 고성능 WebSocket을 활용할 수 있습니다.

  • Reliable Streams: AnyCable은 연결이 끊겼다가 다시 연결될 때 메시지 히스토리를 자동으로 재생하는 ‘Reliable Streams’ 기능을 제공하여 데이터 손실 없이 사용자 경험을 개선합니다.

YJS와 CRDT: 충돌 없는 동시 편집

CRDT(Conflict-free Replicated Data Type)는 분산 시스템에서 동시 편집 시 충돌 없이 데이터가 항상 일관된 최종 상태로 수렴하도록 설계된 특수 데이터 구조입니다. YJS는 가장 인기 있는 CRDT 구현체 중 하나입니다.

  • 작동 원리: YJS는 모든 변경 사항을 ‘아이템’이라는 형태로 표현하며, 내부적으로 연결 리스트 CRDT를 사용합니다. 각 아이템은 클라이언트 ID와 램포트 타임스탬프(Lamport timestamp)로 구성된 고유 ID를 가집니다. 이는 독립적인 시스템 간의 순서를 보장하며, 절대적인 위치나 시간 대신 아이템 간의 관계를 저장하여 동시 편집을 처리합니다.

  • 데이터 유형: YJS는 텍스트, 맵, XML 요소 등 다양한 데이터 유형을 지원하여 개발자가 CRDT의 복잡한 내부 구조를 직접 관리할 필요 없이 쉽게 사용할 수 있도록 합니다.

  • 오프라인 지원: 클라이언트가 오프라인 상태에서 편집한 내용도 다시 연결될 때 충돌 없이 병합됩니다.

커서 및 타이핑 인식과 AnyCable Whisper

실시간 협업에서 다른 사용자의 커서 위치나 타이핑 상태를 시각적으로 보여주는 것은 매우 중요합니다. YJS는 ‘awareness’ 클래스를 통해 이를 위한 프로토콜을 제공하며, 이 또한 내부적으로 CRDT를 사용하여 일관성을 유지합니다.

  • AnyCable Whisper: 커서 위치와 같은 실시간성 높은 임시 데이터는 영구 저장할 필요가 없습니다. AnyCable의 ‘Whisper’ 기능은 Rails 애플리케이션 서버를 거치지 않고 AnyCable 서버가 직접 클라이언트 간에 메시지를 전달하게 하여, Rails의 부하를 줄이고 초저지연 통신을 가능하게 합니다.

영구 저장 및 Compaction

CRDT는 모든 변경 사항을 개별 업데이트 패키지로 저장하므로, 시간이 지남에 따라 저장해야 할 데이터의 양이 기하급수적으로 증가합니다. 이를 해결하기 위해 ‘Compaction’ 과정이 필요합니다.

  • Compaction: 여러 업데이트를 하나의 단일 업데이트로 압축하여 저장 공간을 최적화하고, 초기 동기화 시 전송해야 할 데이터 양을 줄입니다.

  • YRB: Rust로 구현된 YJS 래퍼인 YRB Gem을 사용하여 Ruby 서버 측에서 YJS 문서에 대한 Compaction 및 기타 작업을 수행할 수 있습니다. 다만, 현재는 스레드 안전성 문제가 있어 뮤텍스(mutex)를 통한 조정이 필요합니다.

결론

Rails 애플리케이션에서 실시간 협업 기능을 구현하는 것은 복잡한 작업이지만, AnyCable과 YJS를 조합하여 Node.js 서버 없이도 강력하고 확장 가능한 솔루션을 구축할 수 있습니다. AnyCable은 고성능 WebSocket 연결을 제공하여 Rails 웹 서버의 부담을 줄이고, YJS는 CRDT를 통해 동시 편집의 핵심 난제인 충돌 없는 데이터 병합을 가능하게 합니다. 커서 인식과 같은 임시 데이터는 AnyCable의 Whisper 기능을 통해 효율적으로 처리되며, 데이터 영구 저장은 Compaction 전략으로 최적화될 수 있습니다. 비록 구현 난이도가 높지만, 사용자에게 탁월한 협업 경험을 제공할 수 있는 강력한 선택지이며, '저장' 버튼만으로는 제공할 수 없는 가치를 창출합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!