자체 호스팅 Convex를 운영하는 개발자가 TypeScript로 작성한 Convex 함수 일부를 WebAssembly(Wasm)로 컴파일해 실행하는 도구와 백엔드 변경 사항을 공개했다. 개발자는 자신의 서버에서 쿼리와 뮤테이션의 평균 실행 시간이 거의 절반으로 줄었고, 실행 환경에 남는 메모리도 감소했다고 보고했다.
도구 convex-wasm-compiler는 TypeScript 애플리케이션 코드를 분석하고 JavaScript로 묶은 뒤, Static Hermes를 통해 Wasm으로 컴파일한다. 함께 공개한 convex-backend 변경 사항은 Wasmtime을 Convex의 Tokio 런타임에 연결해 컴파일된 함수를 실행한다. 애플리케이션 코드는 계속 TypeScript와 convex-js SDK를 사용한다.
개발자가 제시한 전체 쿼리·뮤테이션 실행 지연 시간은 V8에서 평균 38.53ms, p90 62ms, p99 329ms, 최대 7,463ms였다. Wasm에서는 각각 19.61ms, 34ms, 139ms, 2,242ms였다. 비교는 개발자의 자체 호스팅 서버에서 측정한 결과이며, 모든 Convex 애플리케이션에 적용되는 성능 보장은 아니다.
| 런타임 | 평균 | p90 | p99 | 최대 |
|---|---|---|---|---|
| V8 | 38.53ms | 62ms | 329ms | 7,463ms |
| Wasm | 19.61ms | 34ms | 139ms | 2,242ms |
메모리 측면에서 Wasm 실행 환경 하나가 사용하는 비공유 메모리는 평균 12MB로, V8 컨텍스트의 35MB보다 66% 적었다. 개발자의 서버에서는 Wasm 환경 130개를 유지했고, V8 컨텍스트는 약 400개였다. Wasm 환경은 컴파일된 코드와 초기화된 메모리를 여러 격리 환경이 공유하도록 설계됐다. 배포 전에 초기화를 마친 환경을 준비해, 배포 뒤 V8 컨텍스트를 다시 만들고 예열할 때 발생하던 작업 급증도 줄이려는 구조다.
CPU 압박이 가장 컸던 5분 구간의 CPU pressure는 Wasm 실행이 주가 됐을 때 15%, V8 실행이 주가 됐을 때 29%였다. 다만 애플리케이션 전체를 Wasm으로 옮긴 것은 아니다. 전체 모듈의 약 20%를 골랐고, 이 모듈들이 쿼리와 뮤테이션 실행의 90% 이상을 차지한다. 나머지 모듈은 V8에서 실행되므로 V8 컨텍스트 재생성이나 백그라운드 스레드 문제가 모두 사라진 것은 아니다.
성능 향상도 함수마다 같지 않았다. 비동기 데이터베이스 작업을 많이 하는 함수에서는 Wasm의 상대적 이점이 작아지는 경향이 있었으며, 그 원인은 아직 진단되지 않았다고 개발자는 설명했다. 10만 개가 넘는 원소의 Float64Array를 반복 처리하는 뮤테이션 하나는 Wasm에서 함수 실행 시간은 짧아졌지만, 게스트 런타임에서 보낸 시간은 V8보다 평균 14% 길었다. 개발자는 이를 V8의 최적화 기능이 Hermes-Wasm 경로에 아직 없을 가능성으로 제시했다.
운영 비용도 고려해야 한다. 선택한 모듈 57개에 포함된 Convex 함수 631개를 컴파일하기 위해 실제로는 의존성을 포함한 JavaScript 모듈 528개가 처리됐다. 메모리 12GB와 AOT 컴파일 작업자 16개를 사용했을 때 콜드 컴파일은 약 6분, 웜 컴파일은 약 2분이 걸렸다. 배포 압축 파일은 약 50MB로, Wasm을 사용하지 않을 때의 약 15MB보다 세 배가량 컸다. 개발자는 컴파일 속도와 캐싱을 계속 개선 중이라고 밝혔다.
개발자는 V8과 Wasm에서 함수를 함께 실행하고 결과를 비교하는 섀도 실행으로 차이를 확인했다고 설명했다. 표본 실행의 5%를 섀도 실행에 할당할 수 있으며, 주 실행은 기다리지 않는다. 뮤테이션의 경우 두 번째 실행에서 실제 데이터베이스 행을 다시 덮어쓰지 않고 상태 변경 호출의 기록을 비교한다. Wasm을 주 실행 경로로 전환한 뒤에도 V8을 5% 섀도 실행으로 유지하고 있다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요