Naga와 같은 만능 번역기의 등장은 개발자들에게 전례 없는 자유를 주었다. 하지만 그 자유는, 브라우저 엔진 개발자들에게는 새로운 종류의 재앙을 예고하고 있었다. 그 재앙의 실체는 한 게임 스트리밍 서비스 회사와의 기술 협력 과정에서 드러났다.
그 회사는 사용자의 게임 화면을 실시간으로 인코딩하여 다른 사용자에게 전송하는 서비스를 제공하고 있었다. 그들은 이 비디오 인코딩 과정을 WebGPU 컴퓨트 셰이더로 가속화하고 있었다.
서비스의 CTO, ‘사라’가 드미트리에게 문제를 제기했다.
“드미트리, 저희 서비스의 핵심은 안정적인 프레임 속도입니다. 저희는 매 프레임을 16.67밀리초(60fps) 안에 반드시 처리해야 합니다. 하지만 저희 인코딩 셰이더의 실행 시간은 예측이 불가능할 정도로 널뛰기를 합니다.”
그녀는 성능 분석 데이터를 보여주었다.
대부분의 프레임에서, 인코딩 셰이더는 5밀리초 안에 작업을 마쳤다. 하지만 주기적으로, 어떤 명확한 이유도 없이 실행 시간이 20~30밀리초까지 급증하는 ‘스파이크(Spike)’ 현상이 발생했다. 이 스파이크 때문에 영상은 주기적으로 끊기고 버벅거렸다.
“저희 셰이더 코드는 매 프레임 거의 동일한 작업을 수행합니다. 입력되는 비디오 데이터의 복잡성도 거의 일정하고요. 대체 왜 이런 예측 불가능한 지연이 발생하는 거죠? 이건 마치 유령과 싸우는 기분입니다.”
드미트리는 이 현상이 단순한 버그가 아님을 직감했다.
그는 Dawn의 가장 깊은 곳, GPU 드라이버와 직접 통신하는 계층에 로깅 코드를 추가하여, 셰이더가 실행되는 동안 GPU 내부에서 무슨 일이 벌어지는지를 추적하기 시작했다.
며칠간의 분석 끝에, 그는 마침내 그 유령의 정체를 찾아냈다.
원인은 셰이더 자체가 아니었다. 문제는 운영체제(OS)와 GPU 드라이버의 ‘스케줄링 정책’에 있었다.
그는 사라에게 그림을 그려가며 설명했다.
“사라, 당신의 컴퓨터는 지금 이 순간에도 수많은 일들을 하고 있습니다. 운영체제는 창을 움직이고, 백그라운드에서는 이메일을 확인하죠. 이 모든 작업들이 GPU 자원을 조금씩 나누어 사용합니다.”
“GPU 드라이버의 스케줄러는 이 모든 작업 요청들을 받아서, 우선순위에 따라 줄을 세우고, 시분할(Time-slicing) 방식으로 처리합니다. 문제는, 이 스케줄링 정책이 저희나 당신 같은 애플리케이션 개발자들에게는 완전히 ‘블랙박스’라는 겁니다.”
그는 분석 로그를 가리켰다.
“바로 이 순간을 보십시오. 당신의 인코딩 셰이더가 한창 실행되던 중에, 운영체제의 윈도우 매니저가 ‘화면 전체에 반투명 알림창을 띄워라’는, 더 높은 우선순위의 작업을 끼워 넣었습니다. 드라이버는 당신의 셰이더 실행을 잠시 ‘중단(Preempt)’시키고, 알림창을 먼저 그린 뒤에야, 다시 당신의 작업을 재개했습니다. 이 중단과 재개 과정에서 발생하는 오버헤드가 바로 당신이 본 성능 스파이크의 원인입니다.”
사라는 망연자실했다.
“그렇다면… 저희는 아무것도 할 수 없다는 말인가요? 저희 작업이 언제 중단될지 예측할 수도, 막을 수도 없다는 거잖아요.”
“현재로서는… 그렇습니다.”
드미트리의 대답은 무거웠다.
이것은 WebGPU API의 설계 범위를 넘어서는, 운영체제 레벨의 문제였다.
웹이라는 ‘손님’은, 운영체제라는 ‘집주인’의 규칙을 따라야만 했다. 그리고 그 규칙은 실시간 처리를 보장하지 않았다.
이 문제는 W3C 회의에서도 격렬한 논쟁을 불러일으켰다.
“이건 웹 표준이 해결할 수 있는 문제가 아니다. 운영체제 제조사들이 실시간 처리를 보장하는 새로운 종류의 드라이버 모델을 제공해야 한다.”
“하지만 그건 수십 년이 걸릴 이야기다. 우리는 지금 당장 개발자들이 예측 가능한 성능을 얻을 수 있도록 도와야 한다.”
모두가 뾰족한 해결책을 찾지 못하고 있을 때, 드미트리는 다시 한번 관점을 바꾸었다.
‘만약 우리가 미래를 예측할 수 없다면, 미래를 예측할 필요가 없도록 만들면 어떨까?’
그는 새로운 개념을 제안했다.
‘비동기 컴퓨트(Asynchronous Compute)’.
“현재의 모델은, 렌더링 작업과 계산 작업이 모두 동일한 큐(Queue)에서 순차적으로 실행됩니다. 하지만 대부분의 GPU는 그래픽 처리와 계산 처리를 동시에 수행할 수 있는, 별도의 엔진을 가지고 있습니다. 만약 우리가 이 두 종류의 작업을 위한 별도의 큐를 API 레벨에서 제공한다면?”
그는 두 개의 큐를 그렸다.
하나는 Graphics Queue
, 다른 하나는 Compute Queue
.
“개발자는 실시간성이 중요한 렌더링 작업은 그래픽 큐에 보내고, 시간이 좀 걸려도 괜찮은 무거운 계산 작업(비디오 인코딩, AI 추론 등)은 컴퓨트 큐에 보내는 겁니다. 컴퓨트 큐의 작업이 운영체제 때문에 잠시 중단되더라도, 그래픽 큐의 렌더링 작업에는 아무런 영향을 미치지 않습니다. 두 작업은 서로 다른 엔진에서 비동기적으로 실행되니까요.”
이것은 완벽한 해결책은 아니었다. 여전히 개별 작업의 실행 시간을 예측할 수는 없었다.
하지만 이것은, 예측 불가능한 지연이 전체 시스템을 마비시키는 최악의 상황을 막아주는, 강력한 ‘방화벽’ 역할을 할 수 있었다.
이 아이디어는 ‘다중 큐(Multi-Queue)’ 아키텍처라는 이름으로, WebGPU의 다음 로드맵에 중요한 이정표로 기록되었다.
드미트리는 사라에게 말했다.
“아직은 갈 길이 멉니다. 하지만 우리는 당신과 같은 개발자들이, 예측의 종말이라는 혼돈 속에서도 안정적인 서비스를 구축할 수 있도록, 새로운 항해술을 만들어낼 겁니다.”
그는 이제 단순히 빠른 길을 찾는 탐험가가 아니었다.
그는 예측 불가능한 폭풍우 속에서도 배가 침몰하지 않도록, 선체의 구조를 바꾸고 새로운 돛을 다는, 노련한 조선공이 되어가고 있었다.