Rails 8.0: No PaaS Required

작성: dhh

최신 웹 앱을 빠르고 안전하며 쉽게 업데이트할 수 있게 만드는 것은 너무나 복잡해져서, 많은 개발자들이 PaaS(Platform as a Service) 없이는 감히 시도조차 하지 않으려 합니다. 하지만 말도 안 되죠. 단지 배포를 간단하고 사용하기 쉽게 만들기 위해, 기본적인 컴퓨팅 자원 비용을 몇 배로 지불해야 한다는 건 납득하기 어렵습니다. 이 문제는 오픈 소스가 해결해야 마땅하며, Rails 8이 바로 그 해답을 제시합니다. 성공적인 베타 릴리스와 여러 차례의 후보 릴리스를 거쳐, 이제 드디어 Rails 8.0의 최종 버전을 선보이게 되어 정말 기쁩니다!


상자 속의 주요 변경 사항:


Kamal 2 + Thruster의 등장

Rails 8은 애플리케이션을 어디에나 배포할 수 있도록 Kamal 2를 기본 설정과 함께 제공합니다. 클라우드 VM이든 직접 소유한 하드웨어든 관계없습니다. Kamal은 깨끗한 리눅스 서버에 단 한 번의 kamal setup 명령으로 애플리케이션용 또는 부속 서버용 환경을 마련합니다. SSH 키가 등록된 서버들의 IP 주소만 있으면, 2분도 안 되어 프로덕션 환경을 구성할 수 있습니다.

이렇게 Kamal이 수월하게 동작할 수 있는 이유는 Rails가 이미 프로덕션용 컨테이너 이미지를 자동으로 만들어낼 수 있는, 고도로 효율적이고 최적화된 Dockerfile을 기본으로 제공하기 때문입니다. 사용자는 Docker Hub나 GitHub 같은 컨테이너 레지스트리 계정만 준비하면 됩니다.

Rails 8에서는 이 Dockerfile을 업그레이드해 새 프록시 Thruster를 도입했습니다. Thruster는 Puma 웹 서버 앞에 위치해 X-Sendfile 가속, 에셋 캐싱, 에셋 압축을 처리합니다. 즉, 이제 별도의 Nginx나 다른 웹 서버를 앞단에 둘 필요가 없으며, 기본 Rails 컨테이너는 즉시 인터넷 트래픽을 받을 준비가 되어 있습니다.

Kamal 2에는 새 프록시인 Kamal Proxy도 포함되어 있습니다. 이는 초기에 쓰였던 범용 Traefik 옵션을 대체하며, 초고속 무중단 배포(zero-downtime deploy), Let’s Encrypt를 통한 자동 SSL 인증서 발급, 그리고 복잡한 설정 없이 여러 애플리케이션을 단일 서버에서 구동할 수 있는 기능들을 제공합니다.

여기에, 시크릿(secret) 관리 전략을 새로 개편해 1password, Bitwarden, LastPass 등의 도구와 기본 연동이 가능해졌고, kamal console처럼 원격 Rails 콘솔 세션을 여는 명령어를 쓸 수 있게 해주는 새 aliases 기능도 추가되었습니다. 이로써 애플리케이션의 배포뿐만 아니라 운영 전반까지 간편하게 다룰 수 있는 완벽한 패키지가 완성되었습니다.

Kamal 2는 37signals의 Donal McBreen이 리드했고, Kamal Proxy와 Thruster는 37signals의 Kevin McConnell이 개발했습니다.


종속성 대폭 감소

Rails를 배포하기 쉽게 만들려면 필요한 부속 서비스의 수를 줄이는 것이 중요합니다. 예전 Rails에서는 MySQL이나 PostgreSQL 같은 DB와 Redis가 있어야 잡, 캐싱, WebSocket 등 모든 기능을 온전히 쓸 수 있었습니다. 그러나 이제는 새롭게 추가된 세 가지 DB 기반 어댑터 Solid Cable, Solid Cache, Solid Queue 덕분에, SQLite만으로도 이러한 기능들을 모두 처리할 수 있게 되었습니다.

이 어댑터들은 모두 같은 전제를 공유합니다. “디스크 성능이 충분히 빨라져서 굳이 많은 작업에 RAM을 쓸 필요가 없다.” 이렇게 SSD나 NVMe 드라이브가 예전 회전식 디스크(HDD) 대비 훨씬 빨라진 덕에, 디스크 사용으로부터 간소화의 이점을 누릴 수 있게 된 것입니다.


Solid Cable

Solid Cable은 Redis가 담당해왔던 WebSocket 메시지 전달(서로 다른 프로세스에 연결된 클라이언트에게 메시지를 중계)을 대체합니다. 빠른 폴링 방식을 쓰지만, SQLite를 함께 썼을 때도 Redis 못지않게 빠른 속도를 보여줍니다(동일 서버에서 실행할 경우). 대부분의 앱에는 충분한 성능입니다. 또 메시지를 데이터베이스에 기본적으로 하루간 보관하므로, 실시간 업데이트 관련 디버깅도 훨씬 편해집니다.

Solid Cable은 Working Not Working의 Nick Pezza가 만들었습니다.


Solid Cache

Solid Cache는 Redis나 Memcached 대신 HTML 프래그먼트 캐싱을 담당합니다. 부속 서비스가 줄어들 뿐만 아니라, 디스크 기반 저장소를 이용하기 때문에 더 크고 저렴한 캐시를 운용할 수 있습니다. RAM에 의존하지 않아도 되므로, 더 긴 기간(예: 30일, 60일) 캐시를 보관하며 95% 혹은 99% 구간대(퍼센트 타일)에서 훨씬 많은 요청을 커버할 수 있습니다. 게다가 이 캐시는 암호화가 가능하며, 명시적인 보존 기한(예: 30일, 60일 등)을 설정해 운영할 수 있어 현대적인 보안 및 개인정보 보호 정책을 지키기도 수월합니다.

Basecamp에서는 Solid Cache를 1년 넘게 프로덕션 환경에서 사용해왔으며, 10TB에 달하는 데이터를 저장하고 60일 보존 기간을 유지하는 동시에, 95% 구간 렌더링 시간을 절반으로 줄이는 데 성공했습니다.

Solid Cache는 37signals의 Donal McBreen이 개발했습니다.


Solid Queue

Solid Queue는 Redis와 별도의 잡 실행 프레임워크(Resque, Delayed Job, Sidekiq 등)까지도 대부분 대체할 수 있습니다. 고성능 환경에서는 PostgreSQL 9.5에서 처음 도입되었던 FOR UPDATE SKIP LOCKED 매커니즘(이제 MySQL 8.0 이상에서도 사용 가능)을 활용합니다. 간단한 요구 사항이라면 SQLite와 함께 쓸 수도 있어, “추가 종속성 없는” 상태에서 HELLO WORLD가 뜨는 순간까지 바로 갈 수 있는 길을 열어줍니다.

Solid Queue는 단일 서버 환경에서는 기본적으로 Puma 플러그인 형태로 실행되며, 별도 디스패처를 구동하려면 bin/jobs 명령어를 사용하면 됩니다. 여러 디스패처가 별도의 큐를 전담해 성능을 조정할 수도 있고, 간단한 기본 설정에서 시작해 필요해지면 세세한 튜닝이 가능하도록 유연한 구성 방식을 채택했습니다.

이 시스템은 현대적인 잡 큐에 필요한 거의 모든 기능을 갖추고 있습니다. 동시성 제어, 실패 시 재시도 및 알림, 주기적 잡 스케줄링 등 다양한 기능이 제공됩니다. 37signals의 HEY에서는 무려 6개나 되는 Resque 관련 젬을 한 번에 대체하며, 하나로 통합된 솔루션이 되었습니다.

Solid Queue는 지난 18개월간 실 프로덕션 환경에서 꼼꼼히 다듬어졌고, 현재 37signals의 HEY에서는 하루 2천만 건 이상의 잡을 처리하고 있습니다.

Solid Queue는 37signals의 Rosa Gutiérrez가 개발했습니다.


프로덕션용 SQLite 준비

이렇게 DB 기반 Solid 어댑터 3종(Solid Cable, Solid Cache, Solid Queue)이 생기면서, 이제 SQLite만으로도 Action Cable, Rails.cache, Active Job을 전부 돌릴 수 있게 되었습니다. 그리고 Rails 8에서는 SQLite 어댑터와 Ruby 드라이버를 프로덕션 환경에서도 충분히 활용할 수 있도록 상당한 개선 작업이 이뤄졌습니다.

37signals에서는 ONCE라는 프로젝트를 통해 SQLite를 실제 프로덕션에 적용한 앱들을 꾸준히 늘려나가고 있습니다. 이미 Campfire나 Writebook 같은 수많은 설치형 앱이 SQLite를 이용해 구동되고 있어, Rails(와 Ruby) 모두 이 파일 기반 DB를 최적의 방식으로 사용할 수 있도록 현장에서 압박을 받고 있습니다. WAL(Write-Ahead Logging), IMMEDIATE 모드 같은 적절한 기본값 설정도 그 결과물입니다. 특별히 Stephen Margheim은 이러한 개선에 큰 기여를 했고, Mike Dalessio는 Ruby 드라이버에서 발생한 마지막 순간의 SQLite 파일 손상 이슈를 해결해주었습니다.


Sprockets를 대체한 Propshaft

물론 Rails 8이 배포나 DB 어댑터 개선만을 담은 것은 아닙니다. 이제 새 기본 에셋 파이프라인으로 Propshaft가 도입되었는데, 이는 Rails 7에서 시도했던 #NOBUILD 철학(복잡한 JavaScript 빌드 설정은 bun/esbuild/vite 등에 맡기고, Rails는 최대한 심플하게)을 더욱 발전시킨 결과물입니다.

Propshaft는 2009년까지 거슬러 올라가는 오래된 Sprockets 시스템을 대체합니다. 당시는 지금 같은 JavaScript 트랜스파일러나 빌드 파이프라인이 없었고, HTTP/2 같은 최신 기술을 떠올리기 전의 시대였죠. 따라서 오늘날 상황에 맞춰 “처음부터 새로 설계해본다면?”이라는 관점으로 접근해보았습니다.

그 결과, #NOBUILD를 표방하는 현재 Rails에는 에셋 파이프라인에서 딱 두 가지만 하면 충분하다는 결론에 이르렀습니다.

Sprockets는 이 외에 너무나 많은 일을 처리해왔고, 현대 흐름에 맞지 않거나 유지가 제대로 안 되는 기능도 많았습니다. 때문에 15년간의 긴 서비스를 마치고, Rails의 새로운 에셋 파이프라인은 Propshaft가 맡게 됩니다. Rails 8 신규 앱에서는 기본이 Propshaft가 되지만, 기존 앱에서의 Sprockets 지원은 계속 이어집니다.

Propshaft는 37signals의 David Heinemeier Hansson과 FestaLab의 Breno Gazzola가 함께 개발했습니다.


인증(로그인) 기초를 자동 생성

마지막으로, 배포를 쉽게 한다면 보안도 쉽게 다룰 수 있어야 하죠. Rails는 오랫동안 고수준 추상화를 통해 뛰어난 인증 시스템을 간단히 구현할 수 있도록 노력해 왔습니다. Rails 5에서 도입된 has_secure_password를 비롯해 Rails 7.1에서 추가된 generates_token_for :password_resetauthenticate_by 같은 기능들이 그 예입니다. Rails 8에서는 이 모든 요소를 하나의 완전한 인증 시스템 제너레이터로 묶어 제공합니다.

명령어 bin/rails generate authentication만 실행하면 SessionUser 모델은 물론, PasswordsMailer, SessionsController, 그리고 Authentication 컨선(concern)까지 생성됩니다. 각 애플리케이션마다 회원가입 절차(가입 폼 등)가 조금씩 다르므로, 그 부분만 직접 구현하면 됩니다. 이렇게 기본 인증 기능을 제공받으면 직접 인증 로직을 만드는 것을 두려워할 이유도, (혹은 심지어 해당 기능을 위해 외부 벤더에게 비용을 지불해야 할까 하는 걱정도) 할 필요가 없습니다!


그 외 모든 것들

Rails 8은 Rails 7.2가 나온 지 몇 달 만에 릴리스되었지만, 위에서 소개한 놀라운 기능들 외에도 정말 많은 개선과 버그 수정이 포함되어 있습니다. Rails는 지금 그 어느 때보다 열정적으로 진화하고 있습니다. Rails 프레임워크에 참여하기에 이보다 더 좋은 시점은 없었다고 말해도 과언이 아닙니다.

#NOBUILD나 #NOPAAS 모토에 끌리든, 아니면 단순히 복잡성을 최대한 줄이면서도 아름다운 코드와 높은 생산성을 동시에 추구하는 커뮤니티에 끌리든, Rails는 환영합니다. 뛰어난 개발자 커뮤니티가 함께하고 있으니 꼭 참여해보세요!

번역글 게시: 이원섭