원문: Open-sourcing Metals v2: Databricks’ Java and Scala Language Server 저자: Ólafur Páll Geirsson, Iulian Dragos, Alessandro Patti 게시일: 2026년 8월 11일
요약
- Databricks는 널리 쓰이는 Scala 언어 서버 Metals를 확장해 일급(first-class) Java 지원과 모노레포 규모의 확장성을 갖춘 Metals v2를 오픈소스로 공개했습니다.
- 이를 통해 그동안 IntelliJ만이 감당할 수 있던 규모에서, Cursor·VS Code·Neovim 같은 경량 에디터가 코드 인텔리전스를 제공할 수 있게 되었습니다.
- Metals v2는 빌드 없는 리포 인덱스, Scala·Java 컴파일러 파이프라인, 메타데이터 우선 빌드 통합이라는 세 계층으로 2,600만 줄 모노레포에서 저지연 탐색을 실현합니다.
이제 Databricks의 코드는 대부분 에이전트가 작성합니다. 엔지니어가 여전히 직접 손을 대야 하는 순간에는, 빠르게 뜨고 별다른 설정 없이 코드를 탐색하게 해 주는 경량 에디터를 찾습니다. 하지만 Scala와 Java에서는 수년간 IntelliJ가 기준이었습니다. 우리 모노레포 규모에서는 사실상 유일하게 그 속도를 따라갈 수 있는 에디터였습니다. 이 글은 우리가 어떻게 대안을 만들었는지 — 널리 쓰이는 Scala 언어 서버 Metals를 확장해 일급 Java 지원을 갖추고 우리 모노레포 크기에 맞게 확장했는지 — 를 공유합니다. 업스트림 Metals 팀과 협력하여, 이제 우리는 Metals v2를 오픈소스로 공개했습니다. 대규모 Java·Scala 코드베이스를 가진 누구나 자신의 코딩 에이전트를 경량 에디터와 짝지을 수 있도록 하기 위해서입니다. Metals v2는 오늘부터 Cursor, VS Code, Neovim에서 사용할 수 있으며, 설치 방법은 Metals 웹사이트에서 확인할 수 있습니다.
IDE 플라이휠 구축하기
2025년 5월, 우리는 Databricks의 일상 에디터 워크플로를 Cursor 중심으로 표준화하기 시작했습니다. Cursor와 VS Code는 이미 프런트엔드와 여타 비(非) JVM 작업에서 Databricks에서 널리 쓰이고 있었고, 둘 다 클라우드 기반 개발 환경을 위한 강력한 SSH 원격 지원을 갖추고 있었습니다. 그러나 우리 서비스 대부분은 Scala와 Java로 작성되어 있고, 모노레포 규모에서 이를 탐색하는 것이 마지막 걸림돌이었습니다. 바로 우리가 풀고자 한 문제였습니다. 하나의 에디터로 표준화하는 것은 개인 취향을 넘어서는 의미가 있었습니다. 통합된 IDE 플랫폼은 플라이휠을 만듭니다. 팀들은 코드 탐색과 개발에 하나의 기준선을 공유하고, 플랫폼 팀은 한곳에 투자를 집중할 수 있습니다. Cursor는 우리 모노레포의 JVM 작업을 위한 주력 에디터가 되었고, 우리는 충분히 통합을 이룬 결과 올해 IntelliJ 라이선스 대부분을 갱신하지 않았습니다.
그림 1: 폭넓은 도입은 플랫폼 투자의 레버리지를 키우고, 이는 공유 IDE를 개선해 다시 도입을 강화합니다.
Cursor로의 전환이 얼마나 진행되었는지는 세 가지 신호가 보여 줍니다. 전체 IDE 사용량, Metals가 임계 경로(critical path)에 놓이는 Scala·Java 파일 열기 이벤트, 그리고 Databricks 밖에서의 도입입니다.
IDE 사용량
가장 넓은 신호는 전체 IDE 사용량입니다. Cursor 도입은 Databricks에서 자생적으로 늘었지만 2025년 9월 무렵 정체되어 몇 달간 그대로였습니다. 성장은 Metals v2 롤아웃과 함께 재개되어, 2026년 7월에는 주간 활성 IDE 사용자의 92%가 Cursor를 여는 반면 IntelliJ는 12%였습니다. 단일 IDE만 쓰는 엔지니어 중에서는 이제 2.4천 명이 Cursor를 쓰는 데 비해 IntelliJ는 120명입니다.
그림 2: 2025년 4월 ~ 2026년 7월 IDE 사용량. 검은 선은 첫 내부 Metals v2 릴리스를 표시하며, 이는 Scala·Java 탐색을 Metals가 제공하는 소스패스(sourcepath) 위로 옮겼습니다(아래에서 자세히 다룹니다).
Scala·Java 파일 열기 이벤트
전체 IDE 사용량보다 더 가까운 신호는, Metals v2가 임계 경로에 놓이는 언어인 Scala와 Java에서 각 IDE로 열리는 파일의 비율입니다. 첫 주요 Metals v2 개선 사항이 2025년 10월 Cursor에 반영된 이후, Scala·Java 파일 열기 이벤트에서 Cursor의 점유율은 40%에서 78%로 올랐습니다. IntelliJ가 가장 깊이 자리 잡고 있던 언어가 Scala와 Java이기에 그 오름세는 전체 IDE 도입보다 느리지만, Cursor의 점유율은 매월 계속 상승하고 있습니다.
그림 3: 2025년 4월 ~ 2026년 7월 IDE별 Scala·Java 파일 열기 이벤트.
초기 외부 도입
Metals v2는 Databricks 포크로 시작했지만, 목표는 언제나 그 작업을 오픈소스 커뮤니티로 되돌리는 것이었습니다. Cursor 팀, 그리고 VirtusLab의 핵심 Metals 메인테이너들과 함께, 우리는 현재의 안정 v1 릴리스를 대체할 안정적인 Metals v2 릴리스를 준비하고 있습니다.“우리는 Cursor가 수백만 줄 규모의 Java 코드베이스에서 잘 동작하도록 Metals v2에 기여하고 있으며, Bazel 지원·디버깅·테스트 개선에 초점을 두고 있습니다.” — Kevin Niparko, Cursor
“AI는 개발자가 IDE를 쓰는 방식을 바꾸고 있고, Metals v2는 올바른 방향으로 나아갑니다. 빠른 시작, 신뢰할 수 있는 코드베이스 방향 잡기, 그리고 대규모 코드베이스를 위해 설계된 아키텍처가 그것입니다. Databricks는 이 접근법을 이례적인 규모에서 검증했고, VirtusLab은 이 작업을 더 넓은 Scala·JVM 커뮤니티로 가져오는 데 힘을 보태게 되어 기쁩니다.” — Krzysztof Romanowski, Head of Development Productivity, VirtusLab다른 대규모 JVM 코드베이스들과의 대화는 우리가 Databricks에서 본 것과 같은 패턴을 재확인해 주었습니다. 강력한 SSH 원격 지원을 갖춘 Cursor·VS Code·Neovim에 대한 수요, 통합 도구로 향하는 플랫폼 차원의 압력, 그리고 모노레포 규모에서 기존 JVM 언어 서버로는 신뢰할 만한 길이 없다는 점입니다.
“우리는 한 달도 채 되지 않아 Stripe에서 Metals v2를 롤아웃하기 시작했는데, 그것이 우리 Java 코드베이스에서 얼마나 잘 동작하는지, 엔지니어들의 열띤 반응이 어떤지, 그리고 메인테이너들과 함께 일하고 배우는 것이 얼마나 즐거운지에 끊임없이 감탄하고 있습니다.” — Mahib Hosain, Developer Platform, Stripe이것이 우리가 Metals v2에 바라는 모델입니다. 대규모 JVM 코드베이스를 위한 공유 인프라를, 공개적으로 유지보수하고, 그것이 대규모로 동작해야 하는 회사들이 함께 다듬어 가는 것입니다. 지속적인 개발은 VirtusLab이 주도하며, 질문·피드백·기여는 GitHub 이슈 트래커 또는 metals@virtuslab.com으로 연락하면 됩니다.
심층 분석: Metals v2는 어떻게 확장하는가
위 내용은 모두 Metals v2를 도입해야 할 이유였습니다. 이제부터는 2,600만 줄 모노레포 전반에서 저지연 코드 인텔리전스를 구현한 엔지니어링을 더 깊이 이해하고 싶은 독자를 위한 것입니다. 에이전트가 코드 대부분을 작성하기 때문에, 포괄적인 Language Server Protocol(LSP) 자동완성·리팩터링 커버리지보다 빠른 코드베이스 방향 잡기가 더 중요했습니다. 그로써 문제의 범위는 좁혀졌지만, 해법이 자명해진 것은 아니었습니다. 우리는 여전히 이 정도로 큰 Scala·Java Bazel 모노레포에 대해 설정이 쉽고 저지연인 탐색을 제공해야 했고, 기성 LSP들은 그 규모를 지원하도록 설계되지 않았습니다. 우리는 리포지토리 규모의 코드 인텔리전스를 원리부터(from first principles) 구축해야 했고, 시작 시점의 유용성을 측정하고 개선할 수 있는 핵심 지표로 삼아야 했습니다.초기 인텔리전스 도달 시간(TTII) 정의하기
**초기 인텔리전스 도달 시간(Time-to-initial-intelligence, TTII)**은 리포지토리를 연 뒤 에디터가 얼마나 빨리 유용해지는지를 측정합니다. 언어 서버가 활성화될 때 시계를 시작하고, 사용자 개입 없이 가장 중요한 기능들이 사용 가능해질 때 멈춥니다. 이 핵심 기능에는 워크스페이스 심볼 퍼지(fuzzy) 검색, 정의로 이동(jump-to-definition), 리포지토리 전체에 걸친 심볼 사용처 찾기가 포함됩니다.Metals v2의 세 계층
Metals v2는 Scala와 Java를 위한 언어 서버입니다. 우리는 공식 Scala 언어 서버인 Metals v1에서 출발했지만, TTII 목표에 도달하려면 가장자리를 튜닝하는 것 이상이 필요했습니다. 우리는 이를 포크한 뒤 세 개의 핵심 계층을 재설계했습니다. 리포 인덱스, Scala·Java 컴파일러 파이프라인, 그리고 빌드 통합 경계입니다.- 빌드 없는 리포 인덱스: Metals v2는 아래 절에서 설명하는 자체 mbt 인덱스로 워크스페이스 소스를 직접 인덱싱함으로써, 시작 임계 경로에서 Build Server Protocol(BSP)을 제거합니다. Metals v1과의 핵심적 차이는, 이제 빌드 서버가 초기 프로젝트 모델을 공급해 주기를 기다리는 대신 Metals가 그것을 직접 소유한다는 점입니다.
- Scala·Java를 위한 컴파일러 기반 인터랙티브 파이프라인: Metals v2는 심볼 로딩을 빌드가 제공하는 클래스패스(classpath)에서 Metals가 제공하는 소스패스(sourcepath)로 옮깁니다. 이로써 진단(diagnostics)과 탐색이, 마지막 성공한 컴파일의 낡은 스냅샷이 아니라 디스크의 실제 코드를 더 정확히 반영합니다. 이를 위해서는 v1 코드베이스의 길게 이어진 핵심 가정들을 다시 생각해야 했습니다.
- 메타데이터 우선 빌드 통합: Metals v2는 여전히 BSP를 쓰지만, 더 좁은 계약(contract)으로 씁니다. Metals v1은 진단을 위해 에디터 핫패스에서 빌드 서버를 사용했지만, Metals v2는 일상적 진단을 빌드 서버 밖으로 옮기고 BSP는 주로 빌드 메타데이터 — 의존성, 생성된 소스, 테스트 탐색, 디버그 런처 — 를 질의하는 데 씁니다. 이 전환은 Metals v2용 BSP 서버를 구현하는 장벽을 낮추며, 우리 내부 Bazel BSP 서버는 이 모델이 대규모 Bazel 모노레포로 확장됨을 검증합니다.
mbt 인덱스: 빌드 동기화 이전의 리포 전역 인텔리전스
mbt는 Metals Build Tool의 약자이며, mbt 인덱스는 TTII, 즉 “즉시 유용하다”는 계약을 가능하게 하는 주된 요소입니다. 이는 워크스페이스 소스의 콘텐츠 주소화(content-addressed) 인덱스입니다. Metals는git ls-files --stage 명령으로 파일을 발견하고, Git 블롭(blob) OID로 어떤 인덱스 항목을 재사용할 수 있는지 판단합니다. 빌드 동기화 이전에 리포 전역 정보가 준비되므로, Metals는 “첫 1마일” 질문에 답할 수 있습니다.
- 교차 파일 참조에 대한 진단
- 퍼지 심볼 검색
- 외부 의존성이나 생성된 코드는 제외한, 리포 전체에 걸친 정의로 이동
- 문서별 블룸 필터(bloom filter)의 창의적 활용을 통한 폭넓은 참조 찾기·구현 찾기
git ls-files --stage 상태에 맞춰 업데이트하고, Scala·Java 프리젠테이션 컴파일러를 재시작하기까지의 시간입니다: p50 8.7초, p90 36.7초. 290만 개 워크스페이스 심볼에 대한 퍼지 심볼 검색은 p50 10ms, p90 95ms입니다. TTII를 더 줄일 여지는 있지만, 이 수치에서는 우리가 다음으로 다뤄야 할 병목이 아닙니다.
Scala 파이프라인: 단일 컴파일러 인스턴스로 2,400만 줄 밀어 넣기
Scala 파이프라인은 프리젠테이션 컴파일러(presentation compiler) — 컴파일 실행 간에 심볼 테이블 정보를 캐시하고 재사용하는 Scala 타입 체커의 한 모드 — 를 중심으로 구축됩니다. 이 재사용은 컴파일러의 지연(lazy) 심볼 해석과 결합되어, 단일 인스턴스가 2,400만 줄 전체 Scala 코드베이스를 스코프에 유지하면서도 진단을 p50 0.9초, p90 8.9초에 게시하게 해 줍니다. 그 단일 Scala 컴파일러 인스턴스는, 편집 중인 파일에 대해 Metals가 빌드 서버로부터 얼마나 많은 정보를 갖고 있느냐에 따라 두 가지 모드 중 하나로 실행됩니다. 빌드 동기화 이전에는 *폴백 컴파일러(fallback compiler)*가 관대한 관점을 취해, 리포지토리의 모든 소스 파일을 적격 의존성 후보로 취급합니다. 그래서 아직 Bazel에서 컴파일되지 않는 코드에 걸쳐서도 탐색이 즉시 유용해집니다. 빌드 동기화 이후에는 *정밀 컴파일러(precise compiler)*가 빌드 서버가 보고한 클래스패스·소스패스 경계로 스스로를 제한하며, 이것이 그 진단과 의존성 정보를 빌드에 정확하게 만듭니다. 정밀 모드와 폴백 모드는 이만큼 큰 소스패스를 다룰 만하게 유지하기 위해 동일한 두 가지 기법에 기댑니다.- 열지 않은 소스에 대한 아웃라인 모드(outline mode). 에디터에서 열려 있지 않은 소스는 타입 체크 전에 메서드 본문(method body)이 제거됩니다. 이는 컴파일러가 필요로 하는 타입 시그니처는 보존하면서, 타입 체크 시간의 대부분이 쓰이는 메서드 본문에서의 작업은 피합니다.
- 인메모리 소스 레이아웃 인덱스. Scala 소스 경로는 패키지 구조를 신뢰할 만하게 인코딩하지 않으므로, Metals는 파서를 돌려 클래스를 소스 위치에 매핑하는 경량 인덱스를 만든 뒤, 파일 시스템을 스캔하는 대신 그 인덱스를 통해 심볼을 로드합니다. 폴백 컴파일러는 소스에서 다시 파싱하는 대신 Metals가 이미 가진 리포 전역 데이터를 재사용해, 이 인덱스를 mbt 인덱스로부터 50ms 미만에 직접 구축합니다.
Java 파이프라인: Turbine으로 초당 백만 줄의 Java 코드 처리하기
Metals v2는 Java LSP 표면(surface)을javac API 위에 직접 구현합니다. 우리는 기존 Java 언어 서버(JDT 및 NetBeans 기반 구현)를 재사용하는 방안을 검토했지만, 둘 다 Metals v1과 같은 방식으로 빌드 중심이었습니다. 바로 v2가 제거하려던 그 결합입니다. 대신 javac 위에 구축함으로써 Java가 Scala와 Metals v2의 소스패스·빌드 동기화 모델을 공유할 수 있게 되었고 — Scala는 여전히 우리 모노레포의 대부분을 차지합니다 — 우리가 필요로 한 Java 표면은 우리가 직접 유지보수할 만큼 작았습니다.
javac API는 진단, 탐색, 시맨틱 하이라이팅, 그 밖의 핵심 LSP 메서드를 구현하기에 충분한 컴파일러 접근을 제공했고, 부분적으로 깨진 코드에도 잘 버텨 주었습니다. 이는 인터랙티브 에디터 워크플로에서 중요합니다. Scala와 마찬가지로, 리팩터링이나 자동완성 같은 능동 편집 기능은 의도적으로 범위를 좁게 유지했습니다.
이 아키텍처의 주요 확장성 문제는, 심볼 아웃라인 계층에서 수백만 줄의 코드를 전이적으로 임포트해 javac “enter” 단계에서 병적인(pathological) 성능을 유발하는 파일에서 나타났습니다. Metals v2는 기본으로 활성화되는 javaSymbolLoader: "turbine-classpath" 모드로 이를 해결합니다. 이 모드는 Turbine 헤더 컴파일러의 수정판을 사용해, 이름 해석 오류의 우아한 처리(graceful handling)를 포함하여 IDE 환경에서 동작하는 리포지토리 클래스패스를 만들어 냅니다. Turbine은 단일 스레드에서 초당 백만 줄에 가까운 Java 코드를 처리하며, 이는 Metals가 일정한 간격으로 전체 Java 코드베이스를 재컴파일할 수 있음을 뜻합니다. 이로써 거의 모든 교차 파일 심볼이 소스패스가 아니라 클래스패스에 놓이고, 따라서 javac “analyze” 단계는 인터랙티브 사용에 실질적으로 가능한 한계 — 우리 벤치마크 기준 초당 거의 10만 줄 — 에 가깝게 실행될 수 있습니다.