Skip to main content
원문: 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를 개선해 다시 도입을 강화한다 그림 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 사용량 그림 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 파일 열기 이벤트 그림 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)의 창의적 활용을 통한 폭넓은 참조 찾기·구현 찾기
mbt 인덱스는 사실상 소스 파일에서 파일 지역 요약(file-local summary)으로 가는 해시 맵입니다. 각 항목은 파일의 패키지 선언, 소스 위치가 딸린 정의(definition), 그리고 파일에서 참조된 식별자에 대한 컴팩트한 블룸 필터를 기록합니다. 정의는 워크스페이스 심볼 검색과 정의로 이동을 뒷받침하고, 블룸 필터는 더 정밀한 검사를 하기 전에 참조를 담을 수 없는 파일을 Metals가 빠르게 배제하게 해 줍니다. 각 항목이 오직 하나의 파일에서 도출되므로 증분 업데이트는 단순하게 유지됩니다. 파일이 바뀌면 Metals는 그 파일의 항목만 다시 계산해 교체합니다. 우리 모노레포에서 지속(persist)된 mbt 인덱스는 비압축 기준 936MB이며, 14만 2천 개가 넘는 Scala·Java·Protobuf 파일에 걸친 290만 개 심볼의 정보를 담습니다. 깨끗한 벤치마크 빌드는 32코어 전체를 완전 활용해 22초가 걸리고, 미리 빌드된 인덱스를 디스크에서 파싱하는 데는 5초가 걸립니다. 프로덕션에서 우리는 TTII를 다음의 시간으로 측정합니다 — 서버를 시작하고, 낡은 mbt 인덱스를 로드하고, 이를 최신 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 미만에 직접 구축합니다.
정밀 모드는 세 번째 기법을 더합니다. 전이적(transitive) 의존성 소스를 소스패스에 유지하여, 서로 다른 Bazel 타깃의 파일에 걸친 편집이 Bazel이 새 클래스패스를 만들어 낼 때까지 기다릴 필요 없이 에디터에 즉시 반영되게 합니다. 이는 Metals v1을 발목 잡던 제약이었습니다. 2,400만 줄 코드베이스를 단일 프리젠테이션 컴파일러 인스턴스에 담는 것은 Scala 컴파일러가 원래 만들어진 목적을 한참 벗어난 일입니다. Metals v2는 소스패스 확장을 위한 공유 기법 위에 세워진 두 개의 컴파일러 모드를 통해 이를 달성합니다. 에디터에서 가장 많이 쓰이는 기능인 정의로 이동은 리포지토리 전체에 걸쳐 p50 7ms, p90 575ms로 실행됩니다. Scala는 우리 엔지니어 대부분이 작업하는 언어이므로, 이 파이프라인은 Cursor가 우리 모노레포에서 IntelliJ의 진정한 대안이 되기 위해 반드시 동작해야 하는 부분이었습니다.

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만 줄 — 에 가깝게 실행될 수 있습니다.

빌드 통합: 28만 5천 개 Bazel 타깃을 에디터 지연 시간에 질의하기

“빌드 동기화 이전에 유용하다”는 말이 “빌드를 무시한다”는 뜻은 아닙니다. Metals는 여전히 긴 꼬리의 기능들 — 서드파티 의존성이나 생성된 소스로의 탐색, 커스텀 셰이딩(shading) 규칙 준수, 테스트 스위트 탐색, 디버그 런처 자동 구성 — 을 위해 빌드 그래프 충실도가 필요합니다. Databricks에서는 28만 5천 개 Bazel JVM 타깃의 메타데이터를 에디터 지연 시간에 질의 가능하게 만드는 것을 뜻합니다. 이 계층의 우리 프로덕션 구현은 Go로 작성되고 우리 Bazel 규칙에 맞춰진 내부 BSP 서버입니다. 이 BSP 서버는 이번 오픈소스 릴리스에 포함되지 않지만, 그 설계 선택은 다른 Bazel BSP 구현에 옮겨 갈 가치가 있습니다. 첫째, Metals v2는 사용자가 명시적으로 요청하지 않는 한 BSP 서버를 통해 Bazel을 결코 호출하지 않습니다. 대규모 모노레포에서 백그라운드 IDE 동기화는 Bazel 락(lock)을 잡아 개발자가 시작한 빌드와 경쟁할 수 있으므로, 빌드 동기화는 시작 시 동작이나 백그라운드 유지보수 작업이 아니라 명시적인 사용자 액션입니다. 그 결과 메타데이터는 반복되는 레이블·경로·리포지토리 접두어에 대한 상수 풀링(constant pooling)으로 확장되는 JSON 스냅샷에 저장됩니다. 사용자가 동기화하면 BSP 서버는 이 스냅샷에 더 많은 타깃을 증분적으로 추가하고, 갱신된 메타데이터를 BSP로 서빙합니다. 둘째, 공유되는 동기화 설정 포맷이 없습니다. 사용자는 편집하면서 탐색이나 진단 충실도를 높이기 위해 개별 파일이나 디렉터리를 필요할 때(on-demand) 동기화합니다. 우리 경험상 사전 정의된 동기화 세트는 시간이 지나며 커지고, 팀 간에 복사되며, 개발자가 실제로 필요로 하는 집중된 동기화보다 느려집니다.

세 계층을 하나로 모으기

빌드 없는 인덱스, 컴파일러 기반 파이프라인, 그리고 메타데이터 우선 빌드 통합이 함께, 수백만 줄 규모의 Bazel 코드베이스에서 저지연 코드 인텔리전스를 제공합니다. 이를 Apache 2.0으로 오픈소스화함으로써, 우리는 더 넓은 생태계에 이전에는 없던 선택지를 주고자 합니다. 기존 JVM 언어 서버가 신뢰할 만한 길을 제시하지 못하던 규모에서, 코딩 에이전트와 경량 에디터를 풍부한 Scala·Java 탐색과 짝지을 수 있는 선택지 말입니다.