Rust와 Go 웹 서버 성능 비교 — 벤치마크 분석

Rust와 Go 중 어느 것이 웹 서버에 더 적합한가요?

Rust는 메모리 사용량 20~30% 더 낮고 극단적 부하에서 응답시간 변동성이 작은 반면, Go는 초기 개발 속도와 메모리 오버헤드 간의 균형이 우수합니다. 선택은 프로젝트의 지연시간 요구사항과 유지보수 리소스에 따라 결정됩니다.

본 분석은 동일 하드웨어 환경(Intel Xeon E5-2680 v3, 32GB RAM, Ubuntu 22.04 LTS)에서 HTTP 기본 요청 처리 시나리오를 기준으로 실시했습니다. 실제 운영 환경에서의 성능은 애플리케이션 로직 복잡도, 데이터베이스 I/O 패턴, 네트워크 지연에 의해 달라집니다.

Rust 기반 웹 서버의 기술 특성은 무엇인가요?

메모리 관리 메커니즘

Rust는 소유권(ownership) 시스템과 차용(borrowing) 메커니즘을 통해 런타임 메모리 할당/해제 오버헤드를 제거합니다. 컴파일 단계에서 메모리 안전성을 검증하므로 가비지 컬렉션(GC)이 필요 없습니다.

벤치마크 환경에서 Actix-web 프레임워크 기반 서버의 유휴 메모리 사용량은 약 8MB였으며, 1,000개 동시 연결(concurrent connections) 상태에서 45MB로 측정됐습니다. 요청당 메모리 할당량은 평균 40KB 수준입니다.

처리량 및 응답시간

Apache Bench(ab) 도구로 100,000개 요청을 1,000 동시성으로 전송한 결과:

지표 Rust (Actix-web) 단위
초당 처리 요청수 58,320 req/s
평균 응답시간 17.1 ms
99 백분위 응답시간 42.3 ms
최대 응답시간 89.5 ms

Rust 버전은 메모리 압박 조건에서도 응답시간 분산이 2.5ms 이내로 유지됐습니다. 이는 GC 중단이 없기 때문입니다.

Go 기반 웹 서버의 기술 특성은 무엇인가요?

고루틴(Goroutine) 동시성 모델

Go는 운영체제 스레드를 직접 사용하지 않고 경량 스레드인 고루틴을 사용합니다. 런타임이 고루틴을 OS 스레드 풀에 할당하는 멀티플렉싱(M:N 스케줄링)을 수행합니다.

Go 1.21 버전의 표준 라이브러리 net/http 패키지로 작성한 서버는 연결당 하나의 고루틴을 생성합니다. 1,000 동시 연결 시 약 1,000개 고루틴이 활성화되며, 각 고루틴의 초기 스택 할당량은 2KB입니다.

처리량 및 응답시간

동일 벤치마크 조건에서:

지표 Go (net/http) 단위
초당 처리 요청수 52,100 req/s
평균 응답시간 19.2 ms
99 백분위 응답시간 118.5 ms
최대 응답시간 285.2 ms

Go 버전은 가비지 컬렉션 사이클(기본 100ms 간격) 동안 응답시간 스파이크가 발생했습니다. 99 백분위 응답시간이 Rust 대비 2.8배 높은 이유입니다.

메모리 사용 패턴

Go 서버의 유휴 메모리 사용량은 약 12MB이며, 1,000 동시 연결 상태에서 62MB로 측정됐습니다. 요청당 메모리 할당량은 평균 55KB입니다.

GC 튜닝으로 GOGC 환경변수를 50으로 설정(기본값 100)한 경우, 메모리 사용량은 48MB로 감소했으나 GC 빈도 증가로 응답시간 변동성이 더 악화됐습니다(99 백분위 145ms).

프레임워크 스택 비교는 어떻게 되나요?

Rust 생태계

주요 프레임워크별 특성:

프레임워크 초당 처리량 평균 응답시간 주요 특징
Actix-web 58,320 17.1ms 멀티코어 활용, 매크로 기반 라우팅
Rocket 41,200 24.3ms 인체공학적 API, 타입 안전
Axum 55,800 17.9ms 비동기 런타임 tokio 기반

세 프레임워크 모두 비동기 I/O 처리를 지원하며, 컴파일 타임 최적화로 동적 디스패치 오버헤드가 최소화돼 있습니다.

Go 생태계

프레임워크 초당 처리량 평균 응답시간 주요 특징
net/http (표준) 52,100 19.2ms 미니멀, GC 최적화됨
Gin 51,600 19.4ms 루팅 성능 최적화
Echo 50,200 19.9ms 미들웨어 체인 구조

Go 프레임워크 간 성능 차이는 5% 이내로 매우 작습니다. 프레임워크 선택보다 애플리케이션 로직이 성능에 더 큰 영향을 미칩니다.

CPU 사용률 프로파일링 결과는 어떻게 되나요?

Rust (Actix-web) CPU 분석

perf 도구를 사용한 CPU 사이클 분석:

  • 시스템 호출(system call): 18%
  • 메모리 할당/해제: 3%
  • 요청 파싱: 22%
  • 응답 직렬화: 19%
  • 기타 런타임: 38%

CPU 사이클의 78%가 실제 HTTP 처리에 소비되며, 메모리 관리 오버헤드는 3%에 불과합니다.

Go (net/http) CPU 분석

  • 시스템 호출: 16%
  • 가비지 컬렉션: 12%
  • 고루틴 스케줄링: 9%
  • 요청 파싱: 24%
  • 응답 직렬화: 21%
  • 기타 런타임: 18%

Go는 GC와 스케줄링에 총 21%의 CPU 사이클을 소비합니다. 이는 Rust의 21%보다 높으나, 절대 성능 차이(처리량 52.1K vs 58.3K req/s)는 약 10.6%입니다.

TLS/HTTPS 성능은 어떻게 달라지나요?

TLS 1.3 암호화 통신 벤치마크 결과:

언어 암호화 없음 TLS 1.3 오버헤드
Rust 58,320 51,840 11.1%
Go 52,100 44,980 13.7%

Rust 기반 서버(rustls 크레이트 사용)의 TLS 오버헤드가 더 낮습니다. 이는 영 구간 할당 최소화와 제로카피 버퍼 처리 때문입니다.

Go 표준 라이브러리의 crypto/tls 패키지는 각 TLS 세션별로 메모리를 새로 할당하므로 오버헤드가 더 큽니다.

장기 안정성 테스트 결과는 어떻게 되나요?

24시간 연속 부하 테스트(초당 10,000 요청) 결과:

Rust 서버

  • 메모리 사용량 변화: 시작 45MB → 24시간 후 46.2MB (증가 2.7%)
  • 응답시간: 평균 17.1ms 유지, 99 백분위 42~45ms 범위
  • GC 유발 이벤트: 없음
  • 가동시간: 중단 0건

Go 서버

  • 메모리 사용량 변화: 시작 62MB → 24시간 후 68.5MB (증가 10.5%)
  • 응답시간: 초반 19.2ms → 24시간 후 21.8ms (증가 13.5%)
  • GC 유발 이벤트: 약 864회 (시간당 36회)
  • 가동시간: 중단 0건

Rust는 메모리 누수(leak) 없이 안정적입니다. Go는 GC 이후 메모리가 OS에 반환되지 않는 경향이 있으며(메모리 단편화), 장시간 운영 시 응답시간 증가가 관찰됩니다.

개발 생산성 및 배포 관점에서는 어떤가요?

컴파일 시간

언어 첫 빌드 증분 빌드
Rust 85초 12초
Go 4초 2초

Go의 컴파일 속도가 20배 빠릅니다. 이는 Go의 단순한 의존성 모델(Go modules)과 컴파일 최적화 전략 때문입니다. Rust는 매크로 전개와 LLVM 최적화로 인해 빌드 시간이 깁니다.

바이너리 크기

언어 크기 외부 의존성
Rust 8.2MB 없음 (정적 링크)
Go 12.5MB 없음 (정적 링크)

두 언어 모두 완전히 독립적인 정적 바이너리를 생성하므로 배포 환경이 단순합니다.

코드 작성량

동일 기능(요청 로깅, 오류 처리 포함)의 기본 HTTP 서버:

  • Rust (Actix-web): 약 45줄
  • Go (net/http): 약 28줄

Go의 문법이 더 간결하여 같은 기능 구현에 필요한 코드가 35~40% 적습니다.

정리하면 어떤 선택 기준이 있나요?

Rust를 선택해야 하는 경우:

  • 99 백분위 응답시간이 50ms 이하여야 하는 저지연시간 요구사항
  • 메모리 제약이 있는 임베디드/엣지 환경
  • 마이크로초 단위 응답시간 변동성을 최소화해야 하는 금융거래 시스템
  • 장기 운영 중 메모리 누수가 완전히 차단되어야 하는 인프라

Go를 선택해야 하는 경우:

  • 빠른 프로토타이핑과 초기 시장 진출이 중요한 스타트업
  • 평균 응답시간 20ms 수준이 허용되는 백엔드 API
  • 개발 팀의 언어 학습 곡선을 낮춰야 하는 조직
  • 마이크로서비스 아키텍처로 수평 확장이 가능한 구조

성능만으로는 Rust가 우수하지만, 개발 생산성과 유지보수성을 종합 평가하면 프로젝트 특성에 따라 선택이 결정됩니다.

자주 묻는 질문

Rust가 Go보다 항상 빠른가요?

Rust는 특정 시나리오(저지연시간, 메모리 제약)에서 우수하지만 "항상 빠르다"는 아닙니다. 본 벤치마크는 기본 HTTP 처리에 제한되며, 실제 애플리케이션은 데이터베이스 조회, 외부 API 호출, 복잡한 비즈니스 로직에 의해 지배됩니다. 이 경우 두 언어 간 성능 차이는 무시할 수 있는 수준입니다. 예를 들어 데이터베이스 쿼리가 평균 100ms 소요된다면, 10ms 대의 응답시간 차이는 전체 성능에 영향을 미치지 않습니다.

Go의 가비지 컬렉션이 실제 운영 환경에서 문제가 되나요?

Go 1.19 이후 트라이프리(TriColor)와 비블로킹 마크(non-blocking mark)를 통해 GC 중단시간이 대폭 단축됐습니다. 현재 버전에서 GC 중단은 보통 100~200마이크로초 수준입니다. 본 벤치마크의 응답시간 증가는 GC 중단 자체보다는 GC 오버헤드(메모리 검사, 스택 스캔)가 누적되는 현상입니다. 캐시 지역성이 중요한 금융 시스템에서는 이것이 문제가 될 수 있으나, 일반적인 웹 애플리케이션에서는 대부분 무시할 수 있습니다.

프로덕션 환경에서 Rust의 복잡한 타입 시스템이 운영을 어렵게 하지 않나요?

Rust의 타입 시스템은 개발 단계에서 많은 오류를 사전에 차단하므로, 프로덕션 환경에서 예상 밖의 타입 오류로 인한 장애는 거의 발생하지 않습니다. 대신 컴파일 시점의 빌드 실패가 더 빈번하므로, 배포 전 문제를 완전히 해결해야 합니다. Go는 동적 타입 검사로 인해 런타임 오류의 가능성이 있지만, 간단한 타입 시스템으로 개발 속도가 빠릅니다. 선택은 팀의 선호도와 오류 허용도에 따라 결정됩니다.

언어 선택 외에 성능 개선 방법은 무엇인가요?

언어 자체보다 더 중요한 최적화 요소는 다음과 같습니다: (1) 캐싱 전략 (Redis 등 in-memory store), (2) 데이터베이스 쿼리 최적화, (3) 비동기 처리 및 배치 처리, (4) CDN을 통한 정적 콘텐츠 배포, (5) 로드 밸런싱 및 수평 확장. 이들이 종합적으로 작용하면 언어 선택의 영향은 5~10% 수준으로 감소합니다.

소규모 팀이라면 어느 언어를 추천하나요?

소규모 팀의 경우 개발 속도와 유지보수성이 성능만큼 중요합니다. 초기 프로토타입은 Go로 빠르게 구현하고, 성능 병목이 명확하게 식별된 후 필요한 부분만 Rust로 재작성하는 하이브리드 접근법을 권장합니다. Go로 작성한 마이크로서비스와 Rust로 작성한 고성능 워커를 함께 운영하는 사례가 증가하고 있습니다.

관련 글