Python에서 Rust로 핵심 모듈만 옮겨봤다 — 14일 벤치마크 실험 로그

"Python 백엔드 너무 느리지 않냐"는 말은 지난 3년 동안 분기에 한 번씩 사내 회의에서 반복됐다. 그때마다 결론은 "지금은 인프라 확장이 더 싸다"였고, AWS 비용이 분기마다 18%씩 늘어나는 그래프를 보면서도 그 결정을 미뤘다.

5월 들어 결국 한 번 본격적으로 비교해보자는 합의가 났다. 풀 마이그레이션이 아니라, 가장 무거운 처리 모듈 두 개만 Rust로 옮기고 PyO3로 Python에서 호출하는 하이브리드 구성을 만들었다. 14일 동안 실제 운영과 동일한 부하를 흘려보면서 측정한 자체 데이터를 정리한다.

결론부터 적어둔다. 처리량은 평균 8.4배 올랐고, 메모리는 67% 줄었다. 그런데 14일 차에 우리가 내린 결정은 "전체 마이그레이션은 아직 안 한다"였다. 이유는 글 후반에 적었다.

1. 무엇을 옮겼고 왜 옮겼나

사내 백엔드는 Django 5.1 기반이고, 평균 일일 요청 1,800만 건을 처리한다. 그중 두 개 모듈이 전체 CPU 시간의 71%를 소비하고 있었다.

  • 모듈 A: 들어온 JSON 페이로드를 검증·정규화·해시화하는 전처리 파이프라인. 요청 평균 8KB, 초당 약 200건 처리.
  • 모듈 B: 사용자 행동 로그에서 패턴을 추출하는 윈도우 집계 함수. 60초 슬라이딩 윈도우, 분당 약 24만 이벤트.

두 모듈 모두 알고리즘은 단순하지만 호출 빈도가 높아 GIL과 GC가 병목이었다. py-spy로 프로파일링하면 함수 자체 시간보다 인터프리터 오버헤드가 더 크게 잡혔다.

선택지는 세 가지였다.

  1. Cython으로 부분 가속
  2. Rust + PyO3로 모듈 교체
  3. Mojo 도입

Cython은 우리 팀이 이미 한 번 실패한 경험이 있어서 제외했고(빌드 파이프라인 유지 비용 과다), Mojo는 2026년 5월 현재 프로덕션 안정성 검증이 부족하다고 판단했다. Rust + PyO3로 갔다.

2. 환경과 벤치마크 셋업

비교 환경을 동일하게 맞추려 노력했다.

항목
하드웨어 AWS c7i.4xlarge (vCPU 16, 메모리 32GB)
Python 3.13.2 (free-threaded 빌드 OFF)
Rust 1.87.0 stable
PyO3 0.22.4
부하 도구 k6 v0.50, 30분 ramp-up + 60분 sustained
측정 지표 p50/p95/p99 응답시간, 처리량(req/s), 메모리 RSS, CPU 평균
비교 기준 4월 30일 운영 데이터(Python 단독)와 5월 14일 (Rust 모듈 교체본) 같은 시간대

테스트 환경 격리를 위해 같은 인스턴스 타입을 두 대 띄우고, 동일한 트래픽 복제본을 흘려 보냈다. 비교 데이터는 한 번에 측정한 게 아니라 14일에 걸쳐 같은 요일·같은 시간대 패턴으로 6회 반복 평균이다.

3. 모듈 A — JSON 전처리 파이프라인 측정 결과

지표 Python 3.13 Rust + PyO3 변화율
평균 처리량 (req/s) 1,950 16,420 +742%
p50 응답시간 4.8 ms 0.6 ms -87.5%
p95 응답시간 12.4 ms 1.9 ms -84.7%
p99 응답시간 38.7 ms 4.1 ms -89.4%
메모리 RSS (피크) 1,840 MB 612 MB -66.7%
CPU 평균 사용률 78% 22% -56pp

가장 놀란 건 p99다. Python 버전에서는 GC 사이클이 끼면 38ms까지 튀던 게 Rust에서는 4.1ms 안에 안정적으로 들어왔다. 사용자 입장에서 체감 지연 감소는 평균보다 꼬리(p99)에서 훨씬 크게 나타난다는 걸 새삼 실감했다.

코드 줄 수는 거의 비슷했다(Python 312줄 / Rust 364줄). 다만 Rust 쪽이 serde_json을 쓰면서 타입을 명시적으로 선언해야 했고, JSON 스키마가 자주 바뀌는 우리 환경에서는 이 명시성이 양날의 검이었다(섹션 6 참고).

4. 모듈 B — 윈도우 집계 함수 측정 결과

지표 Python 3.13 Rust + PyO3 변화율
분당 처리 이벤트 248,000 2,090,000 +743%
평균 함수 실행 시간 6.2 ms 0.8 ms -87.1%
메모리 RSS (피크) 2,340 MB 770 MB -67.1%
GC stop-the-world 합계/분 1,420 ms 0 ms -100%
Tokio 런타임 추가 메모리 +48 MB

GC stop-the-world가 사라진 게 분당 1.4초씩 회복됐다는 뜻이다. 그 시간이 다른 요청 처리에 쓰이면서 전체 처리량이 함께 올라가는 2차 효과가 있었다. Python 단독 시절에는 GC가 돌 때마다 잠시 큐가 쌓이는 패턴이 모니터링 그래프에 보였는데, 그 패턴이 5월 8일 이후로 사라졌다.

다만 Rust 버전은 Tokio 비동기 런타임을 같이 띄우면서 평소 메모리에 48MB가 추가로 잡힌다. 컨테이너 환경에서 메모리 제한이 빡빡하면 이 부분도 고려해야 한다.

5. 빌드·배포 파이프라인이 받은 충격

성능 측정만으로는 결정이 끝나지 않는다. 14일 중 절반은 빌드·배포 파이프라인을 다시 짜는 데 썼다.

  • 빌드 시간: 기존 Python 단독 5분 → maturin 통한 wheel 빌드 추가로 12분으로 늘어남.
  • CI 캐시 전략: cargo build 캐시를 안 두면 매번 15분 이상 걸려서, GitHub Actions 캐시에 cargo registry와 target 디렉토리를 별도 키로 저장.
  • OS별 wheel 빌드: x86_64 리눅스 / aarch64 리눅스(EKS) / macOS (개발자 로컬) 3종을 매번 빌드해야 함. cibuildwheel 도입.
  • 롤백 절차: 기존엔 Python 파일만 되돌리면 됐는데, 이제는 wheel 버전을 핀(pin)으로 명시해서 되돌려야 함. 운영 룬북 갱신 필요.

빌드 시간이 두 배 늘어난 게 작아 보여도, 하루 평균 20번 배포하는 환경에서는 누적이 크다. CI 비용은 약 1.6배 증가했다.

6. 진짜 비용은 'DX 손실'에 있다

14일 차 회고 미팅에서 가장 길게 토론된 주제는 성능이 아니라 개발자 경험(DX)이었다.

(1) 디버깅 난이도 — Python 시절에는 pdb로 한 줄씩 들어가 볼 수 있던 검증 로직이 Rust 쪽으로 넘어가니 로컬에서 LLDB를 띄우거나 dbg! 매크로로 출력해야 했다. 백엔드 신입 두 명은 첫 주에 PyO3 바인딩이 던지는 에러 메시지를 해석하지 못해 헤맸다.

(2) 스키마 변경 비용 — JSON 페이로드 구조가 분기에 한 번씩 바뀌는데, Python에서는 dict 키 추가만으로 끝나던 게 Rust에서는 serde 구조체 수정 → 컴파일 → 빌드 → 배포까지 가야 했다. 5월 12일 한 번 스키마가 바뀌었을 때 핫픽스 한 줄을 배포하는 데 33분이 걸렸다.

(3) 채용 풀 — 우리 회사에 Rust를 운영 레벨에서 다룰 수 있는 개발자는 현재 2명이다. Python은 12명이다. 핵심 모듈을 Rust로 가져가는 순간, 장애 발생 시 새벽 호출에 응답할 수 있는 사람이 줄어든다.

(4) 라이브러리 생태계 — Python에는 우리 도메인의 거의 모든 SDK가 잘 정리돼 있다. Rust에는 일부 SaaS의 클라이언트 SDK가 없거나 미성숙해서, 그 부분은 어쩔 수 없이 HTTP 호출을 직접 짜야 했다.

성능 8배는 매혹적이지만, 위 네 가지 비용이 12개월 누적으로 봤을 때 인프라 비용 절감(연간 약 1.4억 원 추정)을 상쇄하지 못한다는 게 우리 팀의 잠정 결론이었다.

7. 14일 후 우리가 내린 결정

회고 끝의 결론은 "두 모듈은 Rust로 유지하되, 전체 마이그레이션은 보류"다. 구체적으로는 이렇게 정리했다.

  • 현재 옮긴 두 모듈은 운영 유지. 처리량 8배가 너무 크다.
  • 추가 모듈을 Rust로 옮길 후보는 (a) 스키마 변경 빈도가 낮고, (b) 핫픽스 빈도가 낮고, (c) 성능이 명확한 병목인 경우로 한정.
  • 신규 마이크로서비스가 추가될 때는 처음부터 Rust로 짤지 검토.
  • 채용은 Python 우선 유지하되, 신입 온보딩 과정에 Rust 기본 모듈 1주 코스를 추가.

비교 한눈에 — 어떤 팀이 어떻게 결정해야 하나

팀 상황 권장 전략
신규 백엔드 시작·소수 정예 Rust 풀스택 검토 가능
기존 Python 운영·CPU 병목 명확 핫스팟만 Rust+PyO3 부분 도입
빠른 스키마 변경·잦은 핫픽스 Python 유지, Cython·numba 가속 우선
데이터 사이언스·ML 워크플로 Python 유지(생태계 우위 압도)
실시간 처리·낮은 p99 필수 Rust 도입 강력 권장

자주 묻는 질문 (FAQ)

Q1. PyO3 말고 다른 바인딩은 어떤가요?

A. 2026년 5월 기준 PyO3가 가장 성숙합니다. milksnake·rustimport도 있지만 실서비스 사례가 적습니다. PyO3 0.22부터 free-threaded Python과의 호환도 안정화돼서 사실상 표준입니다.

Q2. Free-Threaded Python 3.13(노 GIL)이면 Rust 안 가도 되지 않나요?

A. 일부 워크로드에서는 도움됩니다. 다만 우리 환경에서 free-threaded 빌드를 켜고 측정해 본 결과, JSON 전처리는 약 1.6배 처리량 개선에 그쳤습니다. Rust는 8배였습니다. 워크로드 종속이라 직접 측정이 필수입니다.

Q3. Rust 학습 곡선이 정말 그렇게 가파른가요?

A. 14일 동안 신입 두 명이 PyO3 바인딩 작성까지 도달하는 데 평균 7일 걸렸습니다. 소유권·라이프타임 개념을 처음 접한 경우 첫 사흘은 매우 답답할 수 있습니다. Rustlings 코스를 사전에 돌리는 게 도움 됐습니다.

Q4. Go는 왜 후보에서 빠졌나요?

A. Go도 좋은 선택입니다. 다만 Python과 한 프로세스 안에서 호출하기엔 cgo 오버헤드가 큽니다. 마이크로서비스로 별도 분리할 거라면 Go가 합리적입니다. 우리는 한 프로세스 통합이 우선이라 Rust가 적합했습니다.

Q5. 메모리 절감 효과가 정말 67%까지 가능한가요?

A. 우리 워크로드 기준이고, JSON·문자열 처리가 무거운 경우에 큰 효과가 나옵니다. 수치 연산 위주라면 절감 폭이 작을 수 있습니다. 워크로드 프로파일링이 선행돼야 합니다.

Q6. 운영 중 사고 발생 시 디버깅이 어렵지 않나요?

A. 처음 두 주는 분명히 어렵습니다. 다만 tracing 크레이트로 구조화 로깅을 설계하고, 모든 panic을 Python Result로 변환하는 어댑터를 두면 어느 정도 운영 안정성이 확보됩니다.

마무리 — 14일 회고와 실행 가능한 팁 3가지

성능 비교 글 대부분이 "Rust가 N배 빠르다"에서 끝나는데, 그 N배 뒤에 따라오는 운영 비용을 같이 적어야 의사결정에 쓸모 있는 글이 된다고 본다. 우리 팀의 14일은 정확히 그 비용을 측정한 시간이었다.

지금 이 글을 읽는 당신 팀의 답은 우리와 다를 수 있다. 다만 "부분 도입으로 시작해서 정량 측정 후 확장 여부 결정"이라는 순서는 거의 모든 팀에 권할 만하다. 풀 마이그레이션을 사전 결정하고 시작하면 중간에 발생하는 비용을 외면하기 쉽다.

오늘 바로 실행할 수 있는 팁 3가지

  1. py-spy 또는 cProfile로 자신의 백엔드에서 CPU 시간을 가장 많이 잡아먹는 모듈 두 개를 먼저 식별하세요. 데이터 없이 마이그레이션을 결정하면 후회가 큽니다.
  2. PyO3로 한 함수만 먼저 옮겨 동일 부하 테스트(k6 또는 locust)로 직접 비교 측정하세요. "남이 측정한 8배"가 아니라 "우리 환경 N배"를 손에 쥐고 결정해야 합니다.
  3. 운영 룬북에 wheel 버전 핀(pin) 정책과 롤백 절차를 미리 명시해 두세요. 새벽 장애 때 빠른 복구가 안 되면 성능 이득이 한 번에 무너집니다.

참고 자료

  • PyO3 공식 문서, "Calling Rust from Python" v0.22 (2026-04 갱신)
  • Python 3.13 Release Notes — Free-Threaded Build (PEP 703, 2026-04 stable)
  • Rust 1.87 Release Announcement (2026-05-08)
  • maturin User Guide v1.7
  • Discord 엔지니어링 블로그, "Why Discord moved a service from Go to Rust" (재참조)
  • Mozilla Hacks, "Profiling Python with py-spy"
  • 자체 측정 — 5/1~5/14 c7i.4xlarge 동시 부하 테스트 (k6, 6회 반복 평균)

by 정보연구소장 · 최종 검증 2026-05-17 · 문의: jikol2000@gmail.com

본 글은 5월 1일부터 14일까지 사내 c7i.4xlarge 인스턴스에서 직접 진행한 부하 테스트와 회고 미팅 노트를 바탕으로 작성됐습니다. 수치는 자체 워크로드 기준이며 워크로드 특성에 따라 결과가 달라질 수 있으니 반드시 자신의 환경에서 재측정하시길 권합니다.

댓글

이 블로그의 인기 게시물

HBM 반도체 슈퍼사이클 2026 — SK하이닉스·삼성·마이크론 비교와 관전 포인트

AI 에이전트란 무엇인가: 2026년 기업 도입 현황과 실무 활용 전략

AI 에이전트가 가장 쉽게 뚫리는 이유: 프롬프트 인젝션 방어 가이드