!원문 캡처 · wecodefire.com

Mystic은 37signals의 Rust 기반 채팅 앱 Campfire를 Kotlin으로 옮겨 AMD EPYC Milan 서버에서 측정한 결과, JVM 버전이 비교한 여러 조건에서 Rust 버전과 비슷하거나 더 많은 요청을 처리했다고 밝혔다. 다만 이 비교는 방 페이지 한 개를 대상으로 한 실험실 측정이며, 언어의 성능만을 비교한 결과는 아니다.

비교 조건과 결과

DHH의 Campfire Rust 저장소 README에 실린 방 페이지 처리량 표는 Ryzen AI MAX+ 395에서 앱당 4개 코어, 16개 클라이언트를 사용한 결과다. 표의 값은 Rails 4,101, Django 1,507, Laravel 3,872, Express 42,636, Elixir 5,350, Go 53,060, Rust 106,494, C 137,524 요청/초다. Mystic은 Rails의 처리량이 하루 사이 언어·프레임워크·하드웨어 변경 없이 230에서 4,101 요청/초로 증가한 점을 들어, 개선된 아키텍처가 언어만큼이나 결과에 영향을 준다고 지적했다. DHH는 각 구현에 아키텍처 개선을 반영하도록 AI 에이전트에 맡겼다고 설명했다.

Mystic은 Rust 버전을 기준으로 Kotlin 포트를 만들고, 응답이 헤더 순서까지 포함해 바이트 단위로 일치하는지 확인했다. 비교 대상은 GET /rooms/:id 방 페이지뿐이며, Rails 미들웨어와 라우팅, 세션 처리, 캐시 등 요청 경로를 포함했다. Kotlin 구현은 JVM(Java 25)과 GraalVM 네이티브 이미지로 실행했다.

응답 캐시가 없는 1코어 측정에서 JVM은 Rust보다 연결 수별로 2~22% 높은 처리량을 보였다. 2코어에서는 25~48% 높았다. 네이티브 이미지는 1코어에서 Rust의 84~114%, 2코어에서 108~125% 수준이었다. 캐시가 적용된 비교에서도 JVM은 대부분 Rust와 비슷하거나 앞섰다. 다만 2코어·1,024 연결 측정은 가상 네트워크에서 패킷 손실이 발생해 앱 성능 비교로 보기 어려워 제외했다.

조건과 한계

측정은 Hetzner의 AMD EPYC Milan 전용 vCPU 서버에서 앱을 한 번에 하나씩 실행하는 방식으로 진행했다. 연결 수를 1~4,096개로 바꾸며 워밍업 후 반복 측정했고, 표에는 중앙값을 사용했다. Rust와 Kotlin은 같은 데이터베이스를 사용했으며, 각 응답의 바이트 단위 일치도 검사했다. 주요 수치에서는 요청 로깅을 껐다. Mystic은 Docker 로그 드라이버의 처리 비용이 이 장비에서 처리량을 약 30% 낮췄다고 밝혔다.

Rust는 메모리 사용량과 시작 시간, 워밍업 후 지연 시간에서 유리했다. Rust는 유휴 상태에서 15~17MB를 사용하고 docker run부터 첫 응답까지 약 175ms가 걸렸다. JVM은 유휴 상태에서 117~170MB를 사용했고 첫 응답까지 약 1.5초가 걸렸다. 고정 부하에서 JVM의 p99 지연 시간은 일반적인 워밍업 조건에서 315~389ms였으며, 같은 요청으로 워밍업해도 31~80ms였다. Rust는 각각 5ms, 2~3ms였다.

GraalVM 네이티브 이미지는 첫 응답까지 약 165ms가 걸렸고 유휴 메모리는 28~29MB였다. 측정 조건에 따라 처리량은 캐시가 없을 때 Rust의 84~125%, 캐시가 있을 때 87~120%였다. 다만 2코어·1,024 연결의 캐시 미적용 조건에서는 힙이 555MB까지 늘었다. Mystic은 운영 환경에서 힙 상한 설정이 필요하다고 설명했다.

이번 결과는 한 페이지와 소형 서버 두 종류에 한정된다. 메시지 작성, 웹소켓, 검색, 첨부 파일은 측정하지 않았고 TLS도 사용하지 않았다. DHH의 표와는 하드웨어가 달라 절대 처리량을 직접 비교할 수 없다. Mystic은 애플 실리콘에서는 JVM이 Rust와 비슷한 수준이었고 네이티브 이미지는 뒤처졌다고 덧붙였다.

재현 자료