파이썬과 러스트

안녕하세요, 추천팀 제이입니다. 저는 팀 내 프로젝트에 새롭게 러스트(Rust)를 도입하는 과정에서 동일한 애플리케이션을 파이썬(Python)과 러스트로 각각 개발하여, 비교 및 분석하는 과정을 경험했습니다. 이 경험을 토대로, 실무적 관점에서 파이썬과 러스트의 차이점을 말씀드리고, 러스트 도입 시 개발자들이 고려하면 좋을 부분들을 공유하겠습니다.

개발 배경

저희 추천팀에서는 기존의 머신러닝 플랫폼을 대체할 새로운 플랫폼 개발을 진행하고 있었습니다. 저희가 고려하고 있던 이 플랫폼의 기능 중에는 사용자 지표 정보를 처리하는 애플리케이션이 있었는데요, 클릭 수, 클릭률, 노출한 상품의 다양성과 같은 사용자 지표를 종합하고, 지표들의 분석을 도와주는 기능을 구현하고자 했습니다.

세부적으로는 이 애플리케이션을 사용하기 위해서 관리자가 어떤 데이터로 어떤 사용자 지표를 만들지 명시하면, 해당 내용을 바탕으로 개별 사용자 피드백이 필터, 변형, 분류, 집합 과정을 거쳐 최종 사용자 지표로 계산되는 것을 구상했었습니다. 즉, 개발 중이던 애플리케이션의 주된 임무는 명시된 내용을 해석하고 그에 맞는 데이터 조작 알고리즘을 수행하는 것이라고 할 수 있습니다.

해당 애플리케이션은 현재 외부 자원으로 카프카와 몽고 DB를 사용하고, 외부에서 조회를 응답하기 위한 웹 API가 구현되어 있습니다.

애플리케이션 개발을 시작하던 당시 저희 팀은 러스트 도입을 고민하고 있었습니다. 팀 내 주력 언어인 파이썬으로 애플리케이션의 성능과 안전성을 모두 챙기기에는 조금 힘든 부분이 있어, 상대적으로 이 부분에 강점이 있다는 러스트를 주목했습니다. 또한 아마존, 페이스북, 구글 등의 빅테크 기업에서 러스트를 적극적으로 활용하고, 러스트 도입 성공 사례가 늘어나면서 러스트 언어의 신뢰도가 점차 높아지는 상황이었습니다.

이러한 상황에서 최종적으로 러스트 도입하기 위해서는, 내부적으로 서비스 영향이 작은 애플리케이션을 성공적으로 러스트로 전환해 보며 그 가능성을 판단해 보는 시도가 남았었습니다.  그래서 사용자 지표 애플리케이션에 러스트를 도입하여, 러스트로 애플리케이션을 구현하는 것이 적절한지 검증하는 절차를 진행하게 되었습니다.

더 많은 기업의 러스트 도입 사례는 rust-companies를 참고해 주세요.

지표 애플리케이션에 러스트를 도입하기 위해, 처음 개발은 핵심 기능을 포함하는 프로토타입을 파이썬으로 구현하였고, 이 애플리케이션을 다시 러스트로 작성해 두 가지 버전의 애플리케이션을 완성했습니다. 이제 본격적으로 러스트 전환 과정에서 느낀 점과 두 결과물의 비교를 몇 가지 키워드로 갈무리해 보겠습니다.

Figure 1. 개발 배경 (로고 출처 https://www.python.org/community/logos/, https://rustacean.net/)

성능

먼저, 두 결과물의 성능을 비교해 보겠습니다. 저희 팀이 러스트를 도입하는 가장 큰 이유 중 하나가 성능이기 때문입니다.

먼저 언급드리고 싶은 점은, 어떠한 성능 파라미터에 관심을 두는지에 따라, 두 개발 언어의 성능 비교에 큰 차이가 있다는 것입니다. 이 글에서 언급된 성능 수치는 참고만 하시는 것을 추천드리며, 실제 값은 애플리케이션을 직접 작성해 비교해 주시기 바랍니다.

애플리케이션은 크게 카프카(Kafka) 메시지로 유입되는 사용자 피드백에서 데이터를 추출해 수집하는 수집 모듈과 웹 API로 요청된 사용자 쿼리를 해석해 사용자 지표를 실시간으로 계산해 응답하는 쿼리 모듈로 나뉩니다. 성능 비교는 더 중요하다고 생각하는 수집 모듈을 기준으로 진행했습니다.

파이썬으로 작성된 결과물은 이 작업에서 파이썬 3.10 버전, confluent-kafka, orjson, motor를 사용합니다.

러스트로 작성된 결과물은 이 작업에서 러스트 1.67 버전, serde-json, rdkafka, mongodb를 사용합니다.

다음은 카프카 메시지 50건을 소비, 데이터 변형, 몽고 DB에 저장하는 작업의 시간(Elapsed time)과 메모리 사용량입니다.

Figure 2. 성능 비교

위의 표를 참조하면, 러스트가 파이썬보다 작업을 1.9배 정도 빠르게 처리한 것을 알 수 있습니다. 또한, 메모리 사용량은 파이썬이 러스트 보다 4.5배 정도 더 사용했습니다. 마지막으로, 표에는 없지만 파이썬의 CPU 사용량은 러스트보다 최대 3배, 평균 2배 정도 높았습니다.

결과적으로 같은 조건에서 러스트 결과물이 파이썬 결과물보다 메시지 처리량과 처리 속도가 높다고 할 수 있습니다. 구현한 애플리케이션은 클라우드에 배포되어 카프카(Kafka) 메시지 유입량에 따라 수평 확장(Scale out)을 하므로, 이와 같은 성능 차이는 전체 운영 비용이 절감되는 효과를 가집니다.

당연하지만, 다양한 방식의 최적화를 통해 두 결과물의 차이를 줄이거나 더 벌릴 수 있을 것입니다. 실제로 파이썬의 asyncio와 러스트의 tokio가 각 언어의 애플리케이션에 적용되어, 비동기 처리가 동일하게 구현된 단일 스레드 환경에서도, 러스트의 메시지 처리 속도가 파이썬 보다 10배 정도 빨랐습니다. 아마도 파이썬의 경우에, 이미 적용한 confluent-kafka 라이브러리 대신, 비동기를 지원하면서 성능이 더 좋은 파이썬 카프카 라이브러리를 사용하면 다시 둘 간의 성능 차이는 줄어들 것입니다. 하지만 레퍼런스를 참조하기 쉽고, 라이선스 문제가 없는 라이브러리를 찾지 못해, 아쉽게도 라이브러리를 교체하지 못했습니다.

파이썬과 러스트의 구체적인 성능 비교가 궁금하신 분들은 Web Frame Benchmarks – Python, Rust programming-language-benchmarks: python-vs-rust를 참고해 주세요.

CPU 사용량을 봤을 때 두 언어에서 성능 차이의 대부분은 입출력(Input Output. 이하, IO) 작업인 카프카 메시지 소비 작업과 몽고 DB 도큐먼트 저장 작업에서 비롯된 것이었습니다. 그리고 두 작업은 전적으로 애플리케이션 구현 시 사용하는 라이브러리가 결정합니다. 이러한 라이브러리에 종속적인 전체 작업 시간의 관점에서 볼 때, 러스트 언어의 고성능 라이브러리를 사용하면, 적은 노력으로도 파이썬 대비 높은 성능을 꾀할 수 있을 것으로 생각했습니다.

학습 비용

러스트의 큰 단점 중 하나는 학습 비용이 많이 든다는 것입니다. 소유권, 수명 같은 생소한 개념과 열거형, 매크로트레이트 같은 기능을 모두 학습해야 구현이 가능하기 때문입니다. 그에 반해 파이썬은 문법이 쉽고 간결하며 프로그래밍 언어를 모르는 사람도 금방 배우기 좋은 언어입니다. 보통 하나의 언어에 능통하다면 다른 언어를 쉽게 익히는 편이지만, 파이썬과 러스트는 컴파일, 메모리 관리 그리고 동적/정적 타입까지 서로 다른 확연한 차이점 때문에, 파이썬에 능숙하더라도 러스트에 익숙해지는 데 상당한 시간이 필요합니다.

이와 같은 높은 학습 비용은 개발 비용을 수직 상승하게 하는 주범일 수 있습니다. 러스트를 잘 다루는 개발자를 구하기 힘들 수 있고, 구현 이후에도 팀의 소수 인원이 애플리케이션을 유지보수할 가능성이 높으며, 상황이 여의치 않을 때는 학습 시간을 작업 시간에 온전히 포함해야 하는 경우도 있기 때문입니다.

Figure 3. 글쓴이가 느낀 러스트 학습 난이도 곡선

생산성

파이썬은 애플리케이션을 빠르게 구현해서 프로토타입을 통해 구현 가능성을 증명하는 데 장점이 있습니다. 언어의 문법이 쉬우며, 다양한 레퍼런스가 있고 관련 패키지만 설치되어 있으면 어느 플랫폼에서도 테스트할 수 있기 때문입니다. 개인적으로는 다양한 상황에서 프로그램을 구현할 때, 파이썬을 사용하면 개발 생산성이 굉장히 높다고 생각합니다.

하지만 애플리케이션이 보장해야 하는 요구 조건들이 많아지면 생산성이 높던 상황이 달라질 수 있습니다. 저희 팀은 비즈니스에 활용되는 애플리케이션을 파이썬으로 개발하는 경우, 파이썬의 타입 어노테이션을 활용하고 있습니다. 구체적으로는 mypy strict 옵션을 활성화해 모든 변수와 함수에 유의미한 타입 어노테이션을 기술하도록 강제하고 있습니다.

사용자 지표 애플리케이션을 개발할 때도 마찬가지로 타입 어노테이션을 강제했습니다. 그 결과, 파이썬으로 개발을 하고 있는데도, 러스트에서처럼 여러 타입을 오가며 씨름하는 시간이 필요하게 되었습니다. 이 때문인지 사용자 지표 애플리케이션을 두 가지 버전으로 구현하면서, 파이썬과 러스트 간의 개발 시간 차이를 크게 체감하지는 못했습니다.

반면, 생산성 측면에서 체감되는 가장 큰 차이점은 컴파일 시간이었습니다. 러스트는 긴 컴파일 시간을 가지고 있는 언어이고, 파이썬은 컴파일이 필요 없는 언어기 때문입니다. 참고로 러스트로 작성된 사용자 지표 애플리케이션의 릴리즈 빌드 시간은 4 코어(Skylake, IBRS) 환경에서 대략 280초입니다. 패키지 다운로드와 빌드 시간을 포함한 도커 이미지 빌드 시간을 비교하면, 러스트는 대략 340초 수준이고, 파이썬의 경우 대략 100초 수준입니다.

이와 같은 러스트의 긴 컴파일 시간은 개발 주기를 민첩하게 가져가지 못하게 하며, 개발자의 생산성을 조금씩 방해합니다. 비록 러스트의 증분 빌드 기능이 뛰어나기 때문에, 개발 중에 모든 소스를 컴파일할 일은 잘 없지만, 코드 구현이 빌드 과정마다 조금씩 보류되면서 작업자의 집중력을 떨어뜨리기에 충분합니다. 또한 테스트를 수행하고 배포를 하는 과정이 길기 때문에, 전체 개발 주기를 조금씩 느리게 만듭니다. 환경에 따라서 컴파일 시간이 달라지겠지만, 컴파일이 필요 없는 파이썬과 비교하면 긴 컴파일 시간은 러스트의 큰 단점이라고 할 수 있습니다.

Figure 4. 컴파일 시간에 관한 밈 (출처. https://xkcd.com/)

개발 경험

이 주제에서는 개인적으로 느낀 개발 경험을 말씀드리고, 그 과정에서 사용했던 도구의 차이를 간략하게 서술하겠습니다.

먼저 타입 시스템입니다. 이전에도 언급했지만, 저희 팀은 파이썬에서 타입 어노테이션을 사용하고 있습니다. 결국 파이썬과 러스트 모두 어떤 방식으로든 타입 시스템을 사용하고 있는데요, 두 언어가 타입을 사용하는 방식에는 확실한 차이가 있었습니다.

PEP 484에 따라 파이썬의 타입 검사는 외부 정적 검사 도구를 사용해 수행됩니다. 저희 팀이 사용하는 mypy의 경우, 기본적인 타입 추론을 제공할 뿐만 아니라 제네릭과 프로토콜 관련 타입 검사에 대해서도 잘 동작하였습니다.

하지만 파이썬 외부 라이브러리를 사용하는 경우에, 타입 검사를 패키지에 제대로 구현하지 않은 경우가 많아 구현 과정에서 종종 불편한 경험을 했습니다. 타입 정보가 없는 라이브러리에서 만들어진 데이터는 타입 검사가 무시되는 Any 타입을 가집니다. 이 Any 타입 데이터에서 파생되는 데이터 또한 불투명한 타입을 가지게 되고, 전체 프로젝트에서 타입 검사 정확도를 낮추는 원인이 됩니다.

반면, 러스트는 강타입의 현대 언어답게 타입 시스템을 보조하는 기능이 많습니다. 컴파일러의 강력한 타입 추론 기능은 프로그램 구현 과정에서 타입에 관련된 개발 시간을 상당히 줄여주었습니다. 예를 들어, 다음과 같이 제너릭 타입인 bar 변수는 선언 당시 비결정적이지만, 뒤에 사용되는 코드에 따라 그 타입이 결정됩니다. 참고로, 해당 예제의 경우 주석을 해제하면 타입 불일치 오류가 발생합니다.

Figure 5. 러스트의 강력한 타입 추론 예시

그리고 러스트는 이종(Heterogeneous) 유니언(Union) 타입인 열거형(Enum) 타입, 불변성과 참조에 따라 나뉘는 다양한 타입 및 그것을 지원하는 타입 변환(Coercion) 등이 전체 타입 시스템을 보조하고 있어, 타입을 사용하기 매우 편리했습니다.

또한, 두 언어의 오류 처리 방식도 많은 차이가 있습니다. 파이썬의 경우는 예외를 발생시키는 방식으로 오류를 처리합니다. 발생한 예외는 사용자가 직접 처리할 수도 있고, 처리를 구현하지 않는 경우에는 파이썬이 예외를 출력하고 프로그램을 종료시킵니다. 반면, 러스트에는 예외가 없습니다. Result라는 enum 타입으로 오류를 표현하는데, 발생할 수 있는 모든 오류에 대해 어떻게 처리할지 명시하지 않으면 컴파일이 실패합니다. 따라서 발생할 수 있는 오류에 대해 대응할 전략과 코드가 항상 구현되어 있어야 합니다. 간단하게 만들어서 사용하는 스크립트라면 이와 같은 특성은 전체 생산성을 떨어뜨릴 수 있지만, 안전성을 추구하는 애플리케이션이라면 장점으로 작용할 것 같습니다.

마지막으로 개발 도구도 언급하면 좋을 것 같습니다. 두 언어 모두 개발 과정에서 패키지 관리자(Package manager), 린터(Linter), 포매터(Formatter), 유닛 테스트(Unit test) 등등 유지보수를 용이하게 하기 위해 많은 도구를 사용하고 있습니다. 사용자 지표 애플리케이션에서 각각의 경우에 사용했던 도구를 표를 통해 요약하면 다음과 같습니다.

Figure 6. 개발 도구 비교

러스트의 경우 cargo가 기본적으로 제공하는 기능이 많았습니다. 파이썬 또한 최근에는 개발 도구가 많이 고도화되었기 때문에, 설치만 한다면 비교적 쉽게 사용할 수 있었습니다.

마치며

사용자 지표 애플리케이션을 간단하게 구현해 보며 러스트 데모를 성공적으로 마쳤고, 현재는 기능 고도화 단계에 있습니다. 도입을 하면서 느꼈던 장점들이 있었기에, 팀 내부의 다른 프로젝트에도 러스트 도입을 시도하고 있습니다.

그럼에도 파이썬과 러스트는 각 언어의 특징과 장단점이 두드러지기 때문에, 러스트를 도입하는 게 항상 옳은 결정은 아닙니다. 위에서 언급드린 내용 외에도 관련 커뮤니티, 레거시 연동 여부 및 개발 환경 등, 도입을 위해 고려해야 할 부분이 많습니다. 러스트를 도입한다면 무엇을 기대하는지 먼저 정의하고, 환경에 따라 고려해야 할 다양한 조건들을 비교해 도입을 진행하시는 것을 추천합니다.

이로써 러스트와 파이썬으로 동일한 애플리케이션을 작성하면서 느낀 점들을 정리해 보았습니다. 러스트 도입을 고민하시는 분들에게 작게나마 도움이 되었으면 좋겠습니다.

감사합니다.

카카오톡 공유 보내기 버튼

Latest Posts

제5회 Kakao Tech Meet에 초대합니다!

Kakao Tech Meet #5 트렌드와 경험 및 노하우를 자주, 지속적으로 공유하며 개발자 여러분과 함께 성장을 도모하고 긴밀한 네트워크를 형성하고자 합니다.  다섯 번째

테크밋 다시 달릴 준비!

(TMI: 이 글의 썸네일 이미지는 ChatGPT와 DALL・E로 제작했습니다. 🙂) 안녕하세요, Kakao Tech Meet(이하 테크밋)을 함께 만들어가는 슈크림입니다. 작년 5월에 테크밋을 처음 시작하고,