Scala 3가 우리를 느리게 만들었나?
(kmaliszewski9.github.io)- Scala 2.13에서 Scala 3로의 코드베이스 마이그레이션 과정에서 예상치 못한 성능 저하가 발생
- 초기 테스트와 배포 환경에서는 모든 지표가 정상이었으나, 몇 시간 후 Kafka 지연(lag) 이 증가
- 부하 테스트 결과, 세분화된 메시지 처리 시 처리율 급감 현상이 확인
- async-profiler 분석을 통해 Quicklens 라이브러리의 체인 평가 비효율 버그가 원인으로 드러남
- 라이브러리 업데이트 후 성능이 복구되었으며, Scala 버전 간 라이브러리 동작 차이에 대한 주의 필요성 강조
서비스 마이그레이션 과정
- 기존 서비스는 Scala 2.13에서 Scala 3.7.3으로 이전
- 매크로를 사용하지 않는 데이터 수집 중심 서비스였으며, 성능이 중요한 구성
- 의존성, 컴파일러 옵션, 타입 및 문법 변경을 적용 후 컴파일 성공
- 테스트 환경과 단계적 배포에서도 로그와 메트릭 모두 정상으로 나타남
- 인프라, JVM, 애플리케이션 수준의 지표 모두 건강한 상태로 확인
원인 불명의 성능 저하
- 배포 후 약 5~6시간 뒤 Kafka lag 증가 현상 발생
- 데이터 스파이크가 아닌 상황에서도 인스턴스당 처리율 감소
- 롤백 후 처리율이 즉시 복구되어 코드 변경이 원인임을 확인
성능 분석 및 원인 추적
- 부하 테스트에서는 초기에는 성능 회귀가 재현되지 않음
- 메시지의 세분화 및 이질적 페이로드에서만 처리율 급감이 발생
- 의존성 라이브러리(직렬화, DB SDK, Docker 이미지, 설정 라이브러리 등)를 하나씩 되돌려 테스트했으나 변화 없음
-
async-profiler로 CPU 프로파일링 수행 결과,
- Scala 3에서는 JIT 컴파일러와 디코딩 단계의 CPU 점유율 급증
- Flamegraph 상단에서 Quicklens 호출이 전체 CPU 시간의 절반 차지
- Scala 2.13에서는 동일 호출이 0.5% 수준에 불과
문제의 근본 원인
- Quicklens 라이브러리의 체인 평가 비효율 버그가 Scala 3에서 발생
- 관련 수정은 GitHub PR #115에 반영
- 라이브러리 업데이트 후 Scala 3과 2.13 간 성능 차이 해소
교훈 및 권장 사항
- 라이브러리의 메타프로그래밍 의존성이 Scala 버전 간 성능 차이를 유발할 수 있음
- 마이그레이션이 정상적으로 완료되어도, 핫스팟과 병목 구간을 벤치마크해야 함
- 성능이 중요한 서비스에서는 “잘 동작한다”는 가정 대신, 실측 기반 검증이 필수
- 코드가 아닌 벤치마크가 병목을 드러내는 상황을 방지하기 위한 사전 점검 필요
Hacker News 의견
- 나는 Scala 팬은 아니지만, 문제를 깊이 분석하고 디버깅한 과정이 인상적이었음
이런 식으로 기술 블로그가 쓰여야 함. AI가 이런 수준의 사고 과정을 대체하긴 어려움- 우리 서비스 중 하나를 리프레시하면서 Scala 2.13에서 Scala 3로 마이그레이션했음
첫 질문은 단순했음 — “왜 굳이 업그레이드해야 하지?”였음
- 우리 서비스 중 하나를 리프레시하면서 Scala 2.13에서 Scala 3로 마이그레이션했음
- Scala 3에서는
inline키워드가 매크로 시스템의 일부로 동작함
파라미터에inline을 쓰면 호출 지점에서 표현식을 인라인하도록 컴파일러에 지시함
하지만 이게 크면 JIT 컴파일러에 큰 부담을 줌
Scala 2에서는@inline이 단순한 제안이었지만, 3에서는 무조건 적용됨
따라서 단순히@inline을inline으로 바꾸는 건 큰 실수임- 이 차이는 예전 C/C++의
register키워드가 겪었던 일과 비슷함
초기엔 강제였지만, 최적화가 발전하면서 단순한 권장사항이 되었고 결국 무시됨
C++의inline도 비슷한 과정을 거쳤음 - Kotlin은 거의 모든 곳에서
inline을 적극적으로 사용함
map같은 함수에서 람다 오버헤드 제거를 위해서임
성능 문제는 거의 없었는데, Scala의 매크로 시스템과 결합되면 복잡한 표현식이 생겨 문제가 될 수도 있을 것 같음
- 이 차이는 예전 C/C++의
- 라이브러리를 업그레이드하자 Scala 3의 성능이 Scala 2.13과 거의 동일해졌음
Ruby 2→3 업그레이드 때도 비슷한 경험이 있었음
단순히 언어만 올리는 게 아니라 의존성 전체를 최신화해야 시스템이 안정됨 - Scala 3의 문제는 아무도 원하지 않았다는 것임
Scala 2의 타입 추론 문제는 여전히 해결되지 않았고, 대신 언어만 바뀜
시장 요구를 무시하고 아무도 원치 않는 제품을 만든 셈임
PS: 컴파일러에 진짜 유닛 테스트 스위트를 만들어야 함- 슬프지만 동의함. Scala는 2010~15년쯤엔 유망했음
하지만 Scala 3 리라이트는 컴파일 속도와 툴링 문제를 해결하지 못했고, 프로젝트의 모멘텀을 완전히 잃게 했음
지금 2025년에 새 프로젝트를 Scala로 시작할 사람이 있을까 싶음 - 여러 번 Scala를 배우려 했지만 결국 Clojure로 돌아갔음
Scala는 학자들이 만든 언어 같고, 산업계에서 잠시 유행한 게 오히려 이상했음 - 핵심을 잘 짚었음
이제 모든 툴이 Scala 3에 맞춰야 하고, 심지어 IntelliJ도 아직 완벽히 지원하지 못함
Scala 2를 점진적으로 개선했으면 좋았을 텐데, 학문적 성공에만 집중한 듯함
예를 들어 tpolecat의 글처럼early return을 두고도 논쟁이 많지만, Kotlin은 아무 문제 없이 지원함 - “테스트가 없다”는 말은 거짓 정보임
Scala 컴파일러에는 수천, 수만 개의 테스트가 있고,
공식 테스트 디렉토리와
커뮤니티 빌드 시스템으로 수백만 LOC를 검증함 - 글을 제대로 읽지 않은 듯함. 논점이 완전히 엇나감
- 슬프지만 동의함. Scala는 2010~15년쯤엔 유망했음
- 이 글에서 가장 흥미로웠던 건, 자동화된 성능 테스트와 flamegraph 분석을 기본으로 갖춰야 한다는 점임
특히 언어 버전 업그레이드 같은 큰 변화에서는 필수임- 벤치마크 테스트는 일반 테스트와 달리 세밀한 설정이 필요함
우리는 C++로 작성된 툴을 지속적으로 벤치마크하지만, 환경 노이즈 때문에 결과 일관성이 어려움
같은 머신에서 여러 번 실행해 비교하는 방식을 고민 중임 - JVM에서 요즘 어떤 성능 테스트 도구를 쓰는지 궁금함
- 벤치마크 테스트는 일반 테스트와 달리 세밀한 설정이 필요함
- Scala 3의 유일한 문제는 Python 부러움임
두 번째 문법을 만들고 그걸 미래라고 밀어붙인 게 문제임
이로 인해 툴링 생태계도 느려졌음- 지금은 대부분의 툴이 새 문법을 지원함
컴파일러나 scalafmt로 스타일을 자동 변환할 수도 있음 - Scala답게 같은 일을 여러 방식으로 할 수 있게 됨
이제는 중괄호 문법과 들여쓰기 문법 두 배로 늘어남 - 차라리 Haskell과 비교했으면 더 매력적이었을 것 같음
- 예전 Scala 팬으로서, 새 문법 예시를 보고 당황스러웠음
match구문이 너무 장황하고 Python 흉내 같음
- 지금은 대부분의 툴이 새 문법을 지원함
- 나는 예전에 Scala 2.x 마이그레이션을 했는데, 의존성 대기가 가장 힘들었음
그때 Spark 덕분에 Scala가 주목받았지만, 상업적 언어로 발전할 기회를 놓쳤음
지금은 Spark는 Python으로, JVM의 현대 언어 자리는 Kotlin이 차지했음
결국 Scala는 다시 학문적 언어로 돌아간 느낌임- 하지만 Scala는 여전히 대기업에서 널리 쓰이고 있음
Scala Adoption Tracker를 보면 알 수 있음
Scala 3의 새로운 기능들은 언어 생태계를 다시 혁신할 잠재력이 있음
예: Capture Checking 설명 - Kotlin이 정말 JVM을 장악했는지는 의문임
Java가 함수형 기능을 추가하면서 Scala의 매력을 일부 흡수했음 - 서버 사이드 JVM에서는 Kotlin의 존재감이 거의 없음
내 경험상 시장 수요도 미미함 - Kotlin은 사실상 Android 전용 언어임
Google이 Java 지원을 제한하면서 그렇게 된 것뿐임
JVM 전체 시장에서는 10% 정도 점유율에 불과함
- 하지만 Scala는 여전히 대기업에서 널리 쓰이고 있음
- 라이브러리 버전 업그레이드 없이 Scala 3로 옮겼다는 게 의아했음
보안 감사(PIC-DSS 등)를 받는다면 최신 라이브러리 유지는 필수임- “최신 유지가 좋은 습관”이라는 말은 논의를 막는 표현임
나는 오히려 의존성을 오래된 상태로 유지하는 편임
새 버전은 새 버그를 가져오고, 유지보수자 변경이나 보안 리스크도 있음 - 나도 혼란스러웠음. 글에서 “의존성을 업데이트했다”고 했는데, 나중엔 “업데이트 후 성능이 같아졌다”고 함
처음엔 일부만 올린 듯함. 작은 단계로 나누는 게 일반적이지만 운이 나빴던 듯함 - 버그 원인을 알고 나니 덜 인상적이었음
문제는 Scala 3 자체가 아니라 여러 요인의 상호작용이었음 - 언어 버전 업그레이드처럼 큰 변경일수록 한 번에 한 가지씩 바꾸는 게 좋음
- Maven/Gradle/SBT에서 버전 제약을 걸면 언어 버전과는 별개로 유지됨
다만 Scala 전용 라이브러리는 버전에 Scala 버전이 포함되어 있어 주의가 필요함
- “최신 유지가 좋은 습관”이라는 말은 논의를 막는 표현임
- SoftwareMill과 Scala GitHub의 버그 리포트는 작지만 정확한 수정이었음
Scala의 표현력과 타입 안정성이 돋보였음
Li Haoyi의 글처럼 Python 대체 언어로도 충분히 매력적임 - 주요 교훈은, 언어나 프레임워크의 메이저 업그레이드 시 라이브러리도 함께 최신화해야 한다는 것임
특히 매직 기능을 남용하는 라이브러리가 많을수록 중요함