jaysnote
13분

웹 터미널의 입력 지연과 WebRTC를 검토한 이유

브라우저와 셸의 연결, OTP와 권한 검사, 입력 지연 측정, WebRTC의 직접 연결과 TURN 중계를 칠판 만화로 설명합니다. 실제 운영 정보는 일반화했습니다.

키를 눌렀는데 글자가 한 박자 늦게 나타납니다. 같은 컴퓨터에 SSH로 접속했을 때는 빠릿한데, 웹 터미널에서는 손끝의 리듬이 조금 어긋납니다. 브라우저가 느린 걸까요, 화면을 그리는 방식이 문제일까요?

웹 터미널을 만들고 점검하면서 이 질문을 따라갔습니다. 화면 렌더러를 바꾸는 실험도 했고, 브라우저끼리 실시간 통신할 때 쓰는 WebRTC도 검토했습니다. 먼저 알아둘 점은 키를 누른 순간과 그 키의 결과가 화면에 나타나는 순간 사이에 여러 단계가 있다는 것입니다.

이 글의 그림은 실제 구현을 학습용으로 일반화했습니다. 접속 주소, 계정, 내부 IP와 포트, 토큰, 장비 식별자, 운영 설정값은 싣지 않았습니다. WebRTC 그림은 검토한 구조이며 현재 운영에 적용된 구조가 아닙니다.

웹 터미널은 화면과 셸이 떨어져 있습니다

보통 터미널 앱을 열면 글자를 입력하는 화면과 명령을 실행하는 셸이 같은 컴퓨터에 있습니다. 웹 터미널에서는 화면이 브라우저에 있고, 셸은 원격 컴퓨터에서 실행됩니다.

브라우저의 xterm.js는 터미널 화면을 그리고 키 입력을 읽습니다. 명령을 실행하는 프로그램은 아닙니다. 입력을 받은 서버가 가상 터미널인 PTY를 통해 셸과 연결합니다. 셸은 명령을 실행하고, 결과를 PTY로 내보냅니다. tmux는 그 셸의 작업 세션을 유지합니다. 브라우저 창이 닫혀도 원격 작업이 바로 사라지지 않는 이유입니다.

현재 웹 터미널의 연결 경로와 인증, 작업 보존을 설명하는 칠판 만화

현재 사용하는 연결은 WSS, 즉 TLS로 보호하는 WebSocket입니다. 연결을 한 번 맺으면 입력과 출력을 양방향으로 계속 주고받습니다. 키를 누를 때마다 새 로그인이나 새 HTTP 연결을 만드는 방식은 아닙니다.

웹 페이지와 인증은 Cloudflare Access가 보호합니다. 인증을 통과한 연결은 Cloudflare Tunnel을 통해 컴퓨터 안의 터미널 서비스로 전달됩니다. 터미널 서비스는 로컬 경계 안에서 실행되고, 일반 웹 서버처럼 원본 서비스를 그대로 인터넷에 열어 두지 않습니다. 서비스에서도 전달받은 인증 증명이 올바른지 다시 검사합니다. Cloudflare WebSocket 설명

OTP로 들어온 뒤에도 권한 확인은 계속됩니다

로그인은 “누구인가”를 확인하고, 조작권은 “지금 이 연결이 명령을 보낼 수 있는가”를 판단합니다. 두 질문은 다릅니다.

계정과 OTP로 인증한 뒤에는 서버가 인증 증명의 서명과 유효기간, 소유자와 대상 서비스가 맞는지 확인합니다. WebSocket을 시작할 때도 짧게 유효한 일회용 접속권을 확인합니다. 누군가 과거의 접속권을 다시 제출하거나 다른 사이트에서 연결을 시도한다고 같은 권한을 얻어서는 안 됩니다.

연결된 뒤에도 입력과 출력 처리 전에 현재 조작권과 만료 상태를 검사합니다. 입력이 오래 없으면 화면을 잠그고, 인증이 만료되면 다시 인증하도록 합니다. 연결 확인 메시지가 오간다는 이유만으로 사용자가 계속 타이핑하고 있다고 취급하지 않습니다. 여러 브라우저가 같은 세션을 동시에 제어하는 일도 막습니다.

이 검사는 터미널을 느리게 만들기 위한 장치가 아닙니다. 대부분은 이미 검증한 상태와 시간을 비교하는 작은 작업입니다. 아래 실험에서 이 검사를 포함한 로컬 전달 경로는 1ms보다 짧았습니다.

한 가지 신뢰 경계는 분명히 해야 합니다. WSS를 쓴다는 말이 중간 서비스도 내용을 볼 수 없다는 뜻은 아닙니다. 현재 구조에서는 Cloudflare가 TLS를 종료하고 다시 전달하므로, 통신을 처리하는 서비스 제공자를 신뢰합니다. 브라우저와 컴퓨터 자체의 안전도 중요합니다. 터미널 페이지에서 실행되는 악성 스크립트나 과도한 권한을 가진 확장 프로그램은 입력을 위협할 수 있습니다. xterm.js 보안 지침

글자가 늦게 뜨는 시간을 나눠 봤습니다

일반 명령줄에서 키를 누르면 브라우저가 입력을 보냅니다. 원격 셸이 입력을 받아 표시할 문자를 돌려보내면, 브라우저가 이를 그립니다. 이 되돌아오는 출력을 흔히 echo라고 부릅니다. 비밀번호처럼 echo를 끄는 입력도 있으므로, 브라우저가 모든 키를 임의로 먼저 그릴 수는 없습니다.

체감 시간에는 입력 이벤트 처리, 네트워크 왕복, 서버와 셸의 처리, 브라우저 갱신이 모두 들어갑니다. 한 구간만 빨라졌다고 전체 입력 반응이 같은 만큼 좋아지지는 않습니다.

키 입력부터 화면 표시까지의 지연과 실제 측정 범위를 설명하는 칠판 만화

2026년 9월 5일, 실제 사용자 작업과 분리한 로컬 테스트에서 다음 값을 얻었습니다. 앞의 두 행은 일반 문자 입력 각각 80표본입니다. 렌더러 비교는 같은 크기의 Chrome 화면에서 조건별로 100표본씩 세 번 실행했습니다.

측정한 구간결과이 수치가 말해 주지 않는 것
PTY, tmux, 셸의 로컬 입력 왕복중앙값 0.215ms, p95 0.323ms외부 인터넷 경로
로컬 WebSocket과 서버 검사를 포함한 입력 왕복중앙값 0.617ms, p95 0.880ms실제 접속자의 외부 WebSocket 왕복
기본 렌더러의 출력 전달부터 렌더 이벤트까지세 실행의 p95 평균 9.067ms실제 모니터에 픽셀이 표시되는 정확한 순간
GPU 렌더러의 같은 구간세 실행의 p95 평균 8.833ms네트워크 왕복 시간의 개선

p95는 표본의 95%가 그 시간 이내에 들어왔다는 뜻입니다. 여기서 화면 비교의 값은 세 실행에서 각각 계산한 p95의 평균입니다. 서로 다른 구간의 값을 더해서 실제 접속 지연이라고 계산하지는 않았습니다.

GPU 렌더러가 줄인 화면 갱신 시간은 약 0.23ms였습니다. 로컬 셸의 echo까지 포함한 비교에서는 기본 렌더러가 약 9.17ms, GPU 렌더러가 약 9.37ms로, GPU 쪽이 오히려 조금 길었습니다. 이 조건에서는 키 반응을 바꿀 만한 이득이 확인되지 않아 기본 렌더러를 유지했습니다.

외부 요청에서 우회하는 경로가 관측되기는 했습니다. 하지만 그것만으로 실제 사용 중인 WebSocket에서 네트워크가 가장 느린 구간이라고 확정한 것은 아닙니다. 페이지를 처음 여는 시간에는 DNS와 연결 설정, 인증 응답도 들어갑니다. 이미 열린 WebSocket의 왕복 시간과는 구분해야 합니다.

현재 추가한 연결 진단은 인증된 연결로 작은 확인 메시지를 왕복시켜 시간을 잽니다. 화면 갱신은 출력이 있을 때 따로 관찰합니다. 이 숫자는 원인을 좁히기 위한 자료이지, 진단 기능 자체가 통신을 빠르게 만드는 것은 아닙니다.

SSH가 빠르다는 말도 연결 경로를 함께 봐야 합니다

cmux나 Ghostty 같은 네이티브 터미널은 화면을 효율적으로 그립니다. 그렇다고 웹과 SSH의 반응 차이가 모두 GPU 때문인 것은 아닙니다. SSH가 짧은 사설망 경로를 쓰고 웹이 별도 중계 경로를 쓴다면, 같은 컴퓨터에 접속하더라도 왕복하는 길이 다릅니다.

다른 웹 터미널의 구현도 살펴봤습니다. ttyd는 입력을 즉시 전송하고 출력량을 제어합니다. WeTTY도 GPU 렌더링과 출력 흐름 제어를 사용합니다. xterm.js에는 사용자 입력 직후 돌아온 첫 출력을 신속하게 처리하는 경로가 이미 있습니다. 이런 방법은 대량 출력이나 화면 처리에는 도움이 될 수 있지만, 긴 네트워크 왕복을 없애지는 못합니다. ttyd 구현, WeTTY 출력 제어, xterm.js 출력 버퍼

cmux의 네이티브 GPU 코드를 브라우저에 그대로 넣는 방식도 선택하지 않았습니다. 브라우저용 엔진으로 바꾸려면 한글 입력, 검색, 제어문자 처리, 위험한 출력 차단까지 다시 검증해야 합니다. 화면 비교에서 작은 차이만 확인된 상황에서는 교체 비용을 정당화하기 어려웠습니다.

WebRTC는 영상 통화만을 위한 기술은 아닙니다

WebRTC는 브라우저 등 서로 다른 기기가 실시간 통신을 할 수 있게 해 주는 기술입니다. 영상과 음성뿐 아니라 RTCDataChannel로 텍스트와 바이너리 데이터도 보낼 수 있습니다. 터미널의 키 입력과 출력 역시 데이터이므로 기술적으로 이 채널에 실을 수 있습니다.

여기서 검토한 아이디어는 인증과 데이터 경로를 나누는 것이었습니다. 로그인은 기존 웹 인증으로 처리하고, 인증된 두 끝점이 WebRTC 연결을 협의합니다. 직접 연결에 성공하면 실제 터미널 데이터는 브라우저와 PC 사이를 오갑니다. 접속하는 PC에 Tailscale 같은 별도 클라이언트를 설치하게 만들 필요는 없습니다.

다만 WebRTC가 로그인까지 알아서 해 주지는 않습니다. 누가 연결 상대인지, 그 사람이 터미널을 조작해도 되는지는 서비스가 판단해야 합니다. 두 끝점이 연결 정보를 교환하는 signaling도 따로 필요합니다. WebRTC 연결 설명, DataChannel 설명

WebRTC 검토안의 인증과 연결 협의, 직접 연결과 TURN 중계를 구분한 칠판 만화

그림에서 점선은 연결을 준비하는 정보 교환입니다. 실선은 준비가 끝난 뒤 실제 데이터가 흐를 수 있는 경로입니다. 그림 전체가 검토안이며, 현재 터미널이 WebRTC로 동작한다는 뜻은 아닙니다.

STUN과 TURN은 역할이 다릅니다

집이나 회사의 공유기 뒤에 있는 기기는 인터넷에서 바로 연결하기 어려울 수 있습니다. WebRTC는 ICE라는 절차로 후보 경로를 모으고 실제로 연결되는 경로를 검사합니다.

STUN은 공유기 밖에서 자신이 어떤 주소로 보이는지 알아내는 데 도움을 줍니다. 터미널 데이터를 계속 대신 전달하는 중계 서버가 아닙니다. TURN은 직접 연결이 어려울 때 데이터를 대신 전달할 중계 서버입니다. TURN 경유에서도 WebRTC DataChannel의 데이터는 끝점 사이에서 DTLS로 암호화됩니다. 중계자가 일반적으로 평문 터미널 내용을 읽도록 설계된 방식은 아닙니다.

공용 Wi-Fi라고 무조건 직접 연결이 안 되는 것은 아닙니다. UDP 차단과 NAT, 방화벽 정책에 따라 결과가 달라집니다. 반대로 가정용 인터넷이라고 항상 직접 연결되는 것도 아닙니다. 네트워크의 이름만 보고 성공률을 정할 수는 없습니다. ICE 표준, STUN 표준, TURN 표준

상황가능한 경로기억할 점
직접 경로 검사에 성공WebRTC 직접 연결기존 중계보다 짧은 길이면 지연이 줄 수 있음
직접 연결이 어렵고 TURN을 구성함TURN 중계서버 위치, 혼잡, 전송 방식에 따라 속도가 달라짐
사용할 직접 경로와 TURN이 없음연결 실패 또는 별도로 구현한 기존 연결 복귀WebRTC가 TURN 서버를 자동으로 마련해 주지는 않음

TURN 중계가 기존 WebSocket보다 반드시 느린 것도, 반드시 빠른 것도 아닙니다. 비교해야 하는 것은 기술 이름이 아니라 실제로 선택된 두 경로의 왕복 시간입니다.

빠른 경로를 얻는 대신 무엇을 더 책임져야 할까

WebRTC가 원래 불안전한 기술이라는 뜻은 아닙니다. 다만 현재 터미널에 새 전송 경로를 붙이면 검증해야 할 경계가 늘어납니다.

기존 방식에서는 외부 연결을 앞단 서비스가 받고, PC 안의 서비스는 제한된 경계에서 전달받은 요청을 처리합니다. WebRTC를 추가하면 PC의 통신 모듈이 ICE, DTLS, SCTP 패킷을 처리하게 됩니다. 인증 후에 연결을 만들더라도, 열린 소켓에 도달하는 패킷과 암호화 협상 코드를 관리해야 합니다.

연결마다 자원과 메시지 크기를 제한하고, 암호화 구현과 하위 라이브러리도 업데이트해야 합니다. 새 경로가 생겼다고 기존의 소유자 확인, 조작권, 만료 검사, 입력 크기 제한을 생략할 수는 없습니다.

연결이 끊기는 순간도 까다롭습니다. 직접 연결에서 보낸 명령이 실행됐는지 모르는 상태로 중계 연결에 같은 명령을 다시 보내면 중복 실행될 수 있습니다. 한 시점에는 한 경로만 입력을 처리하고, 전송 여부가 불확실한 입력을 자동 재전송하지 않는 설계가 필요합니다.

DTLS가 데이터를 암호화해도 브라우저에 악성 코드가 들어오면 입력 자체를 훔칠 수 있습니다. WebRTC로 전환한다고 웹 페이지와 인증, signaling을 제공하는 쪽에 대한 신뢰가 사라지는 것도 아닙니다. WebRTC 보안 구조, DataChannel 프로토콜

이번에는 기존 연결을 유지했습니다

검토 결과 WebRTC는 운영에 넣지 않았습니다. 브라우저만으로 접속한다는 조건은 유지하면서, 먼저 기존 연결의 실제 왕복을 측정하기로 했습니다. 화면 렌더러 교체에서는 체감할 만한 개선이 확인되지 않았고, 보안 점검 중 발견한 만료 처리와 진단 기능을 보완했습니다.

설계 원리를 설명하는 것과 접속에 필요한 정보를 공개하는 것은 다릅니다. 이 글과 그림, NotebookLM 자료에는 실제 접속 정보나 인증 비밀을 넣지 않았습니다. 그렇다고 설명에서 정보를 뺐다는 사실만으로 시스템 안전이 보장되는 것은 아닙니다. 안전은 서버의 인증과 권한 검사, 격리와 업데이트에서 유지해야 합니다.

다음 판단은 실제 연결에서 얻은 값에 따라 달라집니다. 왕복이 길면 네트워크 경로를, 출력이 쌓일 때만 느리면 출력 제어를, 화면 갱신만 오래 걸리면 렌더러를 봅니다. 서로 다른 문제를 하나의 “웹이라 느리다”로 묶지 않는 것이 이번 점검에서 얻은 기준입니다.

NotebookLM 학습 노트: 이 한 장으로 다시 보기

아래 그림은 본문을 읽지 않아도 연결 구조를 따라갈 수 있도록 만든 독립 학습 노트입니다. 같은 내용의 별도 노트를 NotebookLM에도 작성했습니다. 공개 글에는 계정별 NotebookLM 접근 주소 대신 학습 내용과 그림만 싣습니다.

현재 웹 터미널, 지연 원인 구분, WebRTC와 STUN 및 TURN, 보안 판단을 한 장으로 정리한 독립 학습 노트

현재 연결. 브라우저는 화면과 키 입력을 담당하고, 원격 PC의 셸이 명령을 실행합니다. WSS는 연결을 유지하며 양방향으로 데이터를 주고받습니다. OTP 인증, 원본 서비스의 권한 재검사, 일회용 접속권, 만료와 조작권 검사가 함께 필요합니다. tmux 작업 보존과 브라우저 연결 유지도 서로 다른 기능입니다.

지연 읽기. 로컬 서버 처리 약 0.6ms와 화면 갱신 약 9ms는 측정한 구간의 값입니다. 실제 외부 WebSocket의 왕복 시간은 따로 재야 합니다. GPU 실험에서 화면 차이는 약 0.23ms에 그쳤고, 이것을 전체 입력 속도 개선으로 해석하지 않았습니다.

WebRTC 검토. DataChannel은 브라우저와 PC가 데이터를 주고받을 수 있는 통로입니다. Signaling은 연결 정보 교환, ICE는 경로 검사, STUN은 외부에서 보이는 주소 발견, TURN은 데이터 중계입니다. 직접 연결은 지연을 줄일 수 있지만 환경마다 성공하는 것은 아닙니다. 암호화와 사용자 권한 확인은 둘 다 필요합니다.

현재 결정. WebRTC, 예측 입력, 새 렌더러 교체는 적용하지 않았습니다. 기존 인증과 WSS를 유지하고, 측정으로 병목을 구분합니다. 학습 노트 텍스트 내려받기

관련 글

← 목록으로