사이드 이펙트 없는 레거시 퍼포먼스 개선 (with. Rust FFI)
안녕하세요. 아임웹 Foundation Squad 엔지니어 장태웅입니다.
최근 레거시 퍼포먼스 개선 PoC 를 진행하며 얻은 인사이트를 공유하기 위해 이 글을 작성합니다.
들어가기에 앞서
우리가 작성하는 대부분의 코드는 언젠가 레거시가 됩니다. 심지어 어제 작성한 코드조차 오늘 보면 이게 맞나? 라는 생각이 드는 상황도 빈번하죠. 특히 프로젝트의 기간이 오래되고 규모가 커질 수록 작은 수정에도 다양한 사이드 이펙트가 발생할 수 있습니다.
물론 사이드 이펙트를 방지하기 위해 높은 커버리지의 테스트 코드 작성, 문서화 등 다양한 예방책이 있지만, 그마저도 없다면 테스트 코드를 작성하기 위한 리팩토링을 위한 리팩토링을 위한… 같이 필요한 리소스는 계속해서 증가하게 됩니다.
Problem
현재 아임웹에서는 특정 데이터들에 대한 엑셀 다운로드 기능을 지원하고 있지만, 서버에서 백그라운드로 생성하는 방식과 더불어 여러 루프가 중첩된 비즈니스 로직은 데이터의 양이 일정 이상 늘어나게 되면 파일 생성에 매우 많은 시간이 소요되게 됩니다. 이러한 문제는 당연히 유저 경험에 매우 좋지 않으며 결론적으로 서비스 품질 저하의 원인이 됩니다.
Goal
물론 엑셀 다운로드 기능을 Stream 으로 처리하도록 변경하는 등, 레거시한 비즈니스 로직을 개선하는 방법도 있지만
최소한의 수정으로 빠르게 해결해야 한다 가 가장 중요한 조건이었습니다.
하지만 최소한의 수정으로 해결하기에는 이미 기존 레거시 엑셀 다운로드 기능을 사용하는 영역이 너무 광범위했기 때문에 개선보다는 해당 기능을 다시 개발하는게 더 빠른 방법으로 보였습니다. Rust 를 알기 전까지는 말이죠.
FFI, 그리고 Rust
A 언어에서 B 언어의 함수를 호출하는 기능을 FFI (Foreign Function Interface) 라고 합니다. 보통 C 또는 C++ 등으로 개발된 네이티브 확장(.so, .dll 등) 을 다른 언어에서 사용하는 경우가 많은데요. 물론 C 로도 개발이 가능하지만 개발 환경 구성의 어려움, 어려운 디버깅 환경 등 C 의 개발자 경험은 악명 높기로 유명합니다.
이때 Rust 가 떠오릅니다. Rust 또한 FFI 를 지원하며 러닝 커브는 다소 있는 편이지만, 강력한 도구인 cargo, 뛰어난 호환성 등 C 에 비하면 훨씬 편한 환경에서 개발할 수 있으면서도 퍼포먼스가 거의 비슷하죠.
위 내용에서 좋은 아이디어가 떠오릅니다. 기존 엑셀 생성과 관련된 비즈니스 로직은 거의 수정하지 않으면서, Rust 로 개발한 확장 모듈에서 엑셀 생성만 담당하면 퍼포먼스를 개선할 수 있을 거 같다는 기대감으로 PoC 를 진행하게 됩니다.
Example
아래의 코드들은 전부 의사코드입니다. 코드의 변경사항에만 집중해서 봐주세요.
기존에 의존하던 ExcelJS 를 Rust 로 개발한 라이브러리로 변경하는데 필요한 코드 수정은 보는 것과 같이 단 두 줄이었습니다. 이제 실제 벤치마킹 결과를 확인해 보죠.
Benchmarking
기존 레거시에서 단 두 줄의 코드 변경으로 성능이 무려 약 129% 상승했으며 CPU 사용률도 상대적으로 약 90% 감소했습니다. 또한 엑셀 파일 생성을 담당하는 의존성만 변경했기 때문에 기존 로직의 사이드 이펙트도 거의 없을 것으로 판단됩니다.
Conclusion
개선할 엄두가 안 나던 레거시 코드 내 특정 의존성만을 재개발하여 기존 로직을 그대로 재사용할 수 있는 점과 확연한 성능 향상이 있던 점에서 Rust FFI 는 아주 매력적이었습니다. 중간 과정에서 Cross-platforrm 빌드 등의 이슈가 약간은 있었지만 이마저도 cross 등의 좋은 라이브러리가 이미 많이 있으니 주저하지 말고 시도해 보시길 추천드립니다.
TMI
- 라이브러리 이름은
Excelerator
입니다. (Excel + Accelerator, 발음은 그대로 엑셀러레이터) Accelerator
는 자동차에서가속 페달
을 의미하는 단어입니다. (동의어로Gas pedal
도 있음)- 콩글리시로는 보통
악셀
이라고 부르죠. - 저는 네이밍에 매우 진심인 편입니다.