블로그5분 읽기

자바스크립트 검문소가 아홉 배 빨라졌습니다

Zod 4.5의 z.compile()은 스키마를 미리 컴파일해 파싱을 3~9배 빠르게 만들며, 기존 코드 수정 없이 임포트 한 줄로 적용됩니다.

자바스크립트 검문소가 아홉 배 빨라졌습니다 대표 이미지

Zod 4.5가 나왔습니다. 8월 28일, 만든 사람은 콜린 맥도널입니다. 간판 기능은 하나예요. z.compile(). 스키마를 미리 컴파일해두면 파싱이 3~9배 빨라집니다.

Zod는 자바스크립트와 타입스크립트에서 데이터 모양을 검사하는 라이브러리입니다. 받은 값이 약속한 형태가 맞는지 문 앞에서 걸러내는 물건이에요. 깃허브 스타는 43.6k. npm 주간 다운로드로 봐도 이 바닥에서 가장 많이 쓰이는 검문소예요.

발표 트윗은 조회 112,982를 받았습니다. 숫자보다 눈에 걸린 건 발표문 한 줄이었어요. "컴파일된 스키마에는 특별한 규칙이 없습니다. 그냥 더 빠를 뿐입니다."

컴파일이 뭘 바꾸길래 아홉 배가 나올까요?

검사기는 두 가지 일을 합니다. 스키마를 읽어서 무엇을 봐야 하는지 정하는 일, 그리고 실제 데이터를 그 기준에 대보는 일. 지금까지는 파싱할 때마다 이 두 일을 같이 했습니다.

z.compile()은 앞의 일을 미리 끝내둡니다. 스키마를 한 번 훑어 검사 절차를 굳혀두고, 이후 파스는 굳어진 절차만 돌려요. 같은 스키마로 백만 번 검사한다면, 백만 번 하던 준비를 한 번으로 줄이는 셈입니다.

이득은 스키마가 복잡할수록 커집니다. 객체, 배열, 유니온이 모두 빨라지는데 발표문 예시로 나온 20개 넘는 필드짜리 객체는 약 9배가 나왔어요. 단순한 스키마는 3배 쪽에 가깝고요. 준비 작업이 무거운 쪽일수록 미리 해두는 값어치가 커지니까요.

적용은 얼마나 손이 갑니까?

한 줄입니다.

엔트리 파일 맨 위에 import "zod/compile"을 넣으면 그 뒤로 만들어지는 모든 스키마가 첫 파스 때 알아서 컴파일됩니다. 노드는 node --import zod/compile 플래그로, Bun은 preload 설정으로 같은 결과를 냅니다. 기존 스키마 코드는 한 글자도 안 고칩니다.

컴파일은 첫 파스에 붙습니다. 이득이 나오는 자리도 여기서 갈립니다. 같은 스키마를 몇 번이고 다시 쓰는 코드예요. API 응답 검사, 큐에서 꺼낸 메시지 검사, 모델이 돌려준 JSON 검사처럼 하루에 수만 번 같은 문을 지나는 자리요. 한 번 쓰고 버리는 스키마라면 굳이 켤 이유가 없습니다.

이 대목이 이번 발표에서 제일 실무적인 부분이라고 봅니다. 성능 개선이 보통 어떻게 오는지 생각해보면 그렇습니다. 새 API를 배우고, 기존 코드를 갈아엎고, 갈아엎은 코드가 예전과 같게 도는지 다시 확인하는 순서예요. 그 세 단계를 한 줄이 지웠습니다.

왜 테스트를 두 번 돌렸을까요?

빨라졌다는 말에는 늘 각주가 붙습니다. "단, 이 경우에는 동작이 조금 다릅니다" 같은 각주요. 검문소에서 그런 각주는 사고로 이어집니다. 통과시키면 안 될 값을 통과시키니까요.

Zod는 그 각주를 아예 없애는 쪽으로 갔습니다. 전체 테스트 스위트를 두 번 돌렸어요. 한 번은 평소대로, 한 번은 전역 자동 컴파일을 켠 채로. 양쪽이 똑같이 통과해야 "특별한 규칙이 없다"는 문장을 쓸 수 있습니다.

저는 이 절차가 이번 발표에서 가장 배울 만한 대목이라고 봅니다. 빠르게 만드는 일과 같음을 증명하는 일은 다른 작업이에요. 속도만 올리고 내놓으면, 확인 부담은 전부 쓰는 사람에게 넘어갑니다. 테스트를 두 번 돌리는 비용은 만든 쪽에서 한 번 치르면 끝나는데 말입니다.

파싱 말고 또 뭐가 빨라졌습니까?

z.validate()가 새로 들어왔습니다. 풀 파스를 건너뛰고 맞다 아니다만 돌려주는 함수예요. 무효 데이터에서 최대 16배 빠릅니다.

이 차이가 나는 이유는 하는 일이 다르기 때문입니다. 파스는 값을 검사하고, 변환하고, 실패하면 어디가 왜 틀렸는지 오류 객체까지 만들어 돌려줍니다. 들어온 요청을 버릴지 말지만 정하는 자리에서는 그 오류 객체를 아무도 안 씁니다. 불리언 하나면 되는 자리에 파스를 부르던 습관을 겨눈 함수입니다.

메모리 사용량은 9분의 1로 내려갔습니다. 스키마 수천 개를 띄워두는 서버라면 이쪽이 더 반가운 숫자일 겁니다.

부가 기능도 여럿 왔습니다. z.creditCard()는 카드번호를 룬 체크섬으로 검사합니다. z.properties()가 들어왔고, Zod 4에서 빠졌던 .deepPartial()이 돌아왔어요. .exactPartial()은 키가 아예 없는 경우와 키에 undefined가 박힌 경우를 갈라, 뒤쪽을 거부합니다. 로케일은 8종이 늘어 벵골어, 힌디어, 브라질 포르투갈어로도 오류 문구가 나옵니다.

에이전트한테 이게 왜 큰 뉴스입니까?

Zod가 요즘 서 있는 자리는 폼 검증기 옆이 아닙니다. 모델 출구예요.

LLM에 구조화 출력을 시킬 때, 툴콜 인자를 정의할 때, 그 스키마를 적는 자리가 대체로 Zod예요. 모델이 뱉은 JSON이 약속한 모양인지 확인하는 문도 Zod고요. 에이전트 한 턴에 이 문을 몇 번 지나는지 세어보면 감이 옵니다. 툴 하나 부를 때마다 인자 검사 한 번, 결과 검사 한 번, 상태를 다시 넣을 때 또 한 번.

루프가 길어질수록 이 횟수는 선형으로 늘어납니다. 초당 수천 번 도는 검사라면 3배도 손에 잡힐 차이예요. 모델을 바꾸지 않고, 프롬프트를 손대지 않고, 임포트 한 줄로 얻는 3배라면 더 그렇습니다.

한 가지는 짚어둬야 합니다. 에이전트 루프에서 제일 오래 걸리는 구간은 여전히 모델 응답을 기다리는 시간입니다. 파싱이 아홉 배 빨라져도 전체 응답 시간이 아홉 배 줄지는 않아요. 다만 대량 배치, 검증 파이프라인, 스트리밍 파싱처럼 모델을 기다리지 않는 구간에서는 이 숫자가 그대로 남습니다.

남는 생각

저는 이 발표를 속도 소식이 아니라 검증 소식으로 읽습니다.

이 계정에서 계속 추적해온 축이 있습니다. 에이전트가 만든 결과를 누가 확인하느냐, 그 확인을 기계가 대신 도느냐는 축이에요. z.compile()은 그 축의 맨 앞자리, 그러니까 데이터가 시스템에 들어오는 문에 붙은 개선입니다.

이 라이브러리를 만든 쪽은 자기 개선을 자기 규칙으로 검사했습니다. 빨라진 물건이 조용히 다르게 굴지 않는다는 증거를 먼저 만들고, 그다음에 3~9배를 말했어요. 저는 이 순서가 숫자보다 오래 남는다고 봅니다.

빠르게 만드는 사람은 많습니다. 같음을 증명하고 내놓는 사람은 드뭅니다.


원문·소스

FAQ

자주 묻는 질문

컴파일이 뭘 바꾸길래 아홉 배가 나올까요?
스키마를 읽어 검사 절차를 미리 굳혀두고, 이후 파스는 굳어진 절차만 돌립니다. 같은 스키마로 백만 번 검사한다면 백만 번 하던 준비를 한 번으로 줄이는 셈이에요. 20개 넘는 필드짜리 객체는 약 9배, 단순한 스키마는 3배 쪽에 가깝습니다.
적용은 얼마나 손이 갑니까?
한 줄입니다. 엔트리 파일 맨 위에 import "zod/compile"을 넣으면 그 뒤로 만들어지는 모든 스키마가 첫 파스 때 알아서 컴파일돼요. 기존 스키마 코드는 한 글자도 안 고칩니다.
왜 테스트를 두 번 돌렸을까요?
빨라진 물건이 조용히 다르게 굴지 않는다는 증거를 만들기 위해서입니다. 전체 테스트 스위트를 한 번은 평소대로, 한 번은 전역 자동 컴파일을 켠 채로 돌렸어요. 양쪽이 똑같이 통과해야 '특별한 규칙이 없다'는 문장을 쓸 수 있습니다.
에이전트한테 이게 왜 큰 뉴스입니까?
LLM 구조화 출력, 툴콜 인자 정의, 모델이 뱉은 JSON 검사 자리가 대체로 Zod이기 때문입니다. 툴 하나 부를 때마다 인자 검사, 결과 검사, 상태 재삽입까지 검문을 여러 번 지나요. 모델을 바꾸지 않고 임포트 한 줄로 얻는 3배라면 손에 잡히는 차이입니다.