마술 공학
동시성의 두 축
백엔드 엔지니어에게 동시성이란, 보통 누가 왔는가의 문제다. 몇 명이, 언제, 어디서. 공간을 격리하고, 점유를 선언하고, 경합을 중재한다. 락, 트랜잭션, 세마포어. 전부 ‘이 자리에 지금 누가 앉아있는가’를 통제하는 도구들이다.
프론트엔드에서 동시성은 다른 축 위에 놓인다. 누가 들어왔는가는 문제가 아니다. 문제는 언제 무엇이 도착하는가다. 사용자 입력이 언제 올지, 네트워크 응답이 언제 돌아올지, 애니메이션 프레임이 언제 찍힐지. 디바운싱, 스로틀링, AbortController, React의 배치 처리, useTransition. 전부 시간축 위의 이벤트 도착 순서와 처리 우선순위를 통제하는 도구들이다.
이 구조는 우연이 아닐 것이다. 웹 프론트엔드는 단일 UI 스레드 중심 모델 위에 세워져 있어 공유 메모리 경합보다는 입력, 렌더링, 응답, 취소, 우선순위 같은 시간축 문제로 복잡도가 집중되는 경향이 있다. 따라서 프론트엔드의 복잡도는 주로 시간적 비결정성의 방향으로 수렴한다.
실패 탐색 경로
주요 복잡도를 다루는 측면에서는 대칭, 역전이 일어나곤 한다. 백엔드는 처리 경로의 비용과 일관성에 민감하고, 프론트는 표현 표면의 비용과 지각 연속성에 민감하다. 이 교차 구조가 두 분야가 서로를 잘 이해하지 못하는 근본 원인일 것이다. 같은 단어를 쓰면서 직교하는 축을 가리키고 있으니까.
물론, 이 구분은 절대적인 분할이 아니라 주된 관심 축의 차이에서 기인한다. 어느 축에서 실패가 먼저 드러나고, 어느 축을 먼저 제어해야 시스템이 성립하는가가 다르다. 백엔드도 공간을 다루고, 프론트엔드도 시간을 다룬다. 다만 백엔드는 공유 자원의 경합을 제어해 시스템의 일관성을 지키고, 프론트엔드는 이벤트의 도착 순서와 우선순위를 제어해 사용자 지각의 연속성을 지킨다. 백엔드는 압도적인 복잡도를 겪는다면, 프론트엔드는 보이지 않고 잡히지 않는 복잡도를 다룬다. 서로가 복잡도를 다루지 않는 것이 아니라, 무엇을 먼저 문제로 인식하느냐가 다르다.
이처럼 주된 관심 축과 실패가 먼저 드러나는 표면이 다르다면, 각 분야가 주로 상대하는 비결정성의 양상도 달라질 수밖에 없다. 백엔드도 매우 어려운 비결정성을 다뤄야 한다. 분산 시스템의 합의 문제만 봐도 절대 쉬운 문제가 아니다. 비단 비잔틴 합의라고 하면, 신뢰할 수 없는 참여자 사이에서 합의를 이끌어내야 하는 블록체인 상의 합의 알고리즘인데, 이 얼마나 철학적으로 모호한 주제를 기술적으로 증명해야함인가. 다만 그 비결정성을 다룸에, 서버 간 통신은 프로토콜이 정해져 있고, 스키마가 합의되어 있고, 타임아웃이 명시되어 있다. 어렵더라도 조건과 제약을 정의할 수 있고, 정의된 제약 안에서 결정론적인 분산과 동작을 추구할 수 있다.
프론트엔드의 비결정성은 성격이 다르다. 인간의 입력은 그 어떤 것도 보장하지 않고 믿을 수 없다. 터치 좌표의 정밀도, 입력 속도, 의도의 모호성, 접근성 요구사항, 문화적 맥락까지. 조건과 제약을 형식적으로 정의하는 것 자체가 난해하다. 무한 엔트로피를 유한한 상태 머신으로 수렴시킴이 필요하다.
프론트엔드는 무엇을 최적화하는가
Rob Pike는 말했다. ‘Concurrency is about dealing with lots of things at once, parallelism is about doing lots of things at once.’ 이 시간적 비결정성을 더 정밀하게 들여다보면 이 구분에서 병렬성보다 동시성 쪽에 프론트엔드는 가까이 있다. 여러 일을 실제로 동시에 하는 것이 아니라, 여러 일을 동시에 다루는 것. 시분할 컴퓨팅 모델이 정확히 이것이고, React의 Concurrent Rendering은 이를 UI 스케줄링에 적용한 것이다. requestIdleCallback과 Fiber 아키텍처는 ‘CPU 시간이라는 단일 자원을 어떤 작업 단위에 얼마나 할당할 것인가’라는 OS 스케줄러의 문제를 풀어오던 과정과 유사하다.
스케줄링이 시간의 배분이라면, 그 배분된 시간 안에서 상태를 다루는 방식도 시간을 의식할 수밖에 없다. 프론트엔드에서는 상태를 누가 소유하는가보다, 상태가 어떤 순서로 흐르는가가 중요하다. 흐르는 ‘객체’가 중요하다고 보는 것보다, 객체의 ‘흐름’이 더 중요하다. 참조 투명성이 프론트엔드에서 특별히 가치 있는 이유도 여기에 있다. 같은 입력이 항상 같은 출력을 낸다는 것은 시간적 불변성의 지향이다.
JS에서는 클로저로 특정 시점의 값을 렉시컬 스코프에 가둬서, 외부 시간이 흘러도 캡처된 값을 변하지 않게 한다. React의 이벤트 핸들러가 특정 렌더 시점의 state를 캡처하는 것이 전형적이다. 실행 시점은 미래에 위임하되, 컨텍스트는 등록 시점에 고정한다. 클로저를 통한 시간의 동결이다. 부수효과는 이 동결을 깨트리고, useEffect가 그렇게 다루기 어려운 이유는 그것이 시간의 비예측성을 프로그래머에게 직접 노출하는 표면으로 작용하기 때문이다.
이 비결정적인 도구와 환경 위에서, 프론트엔드에 요구되는 역할은 오히려 넓어져만 간다. 프론트엔드가 함수형 모델을 택하고, UML보다 플로우 차트를 선호하는 것은 취향이 아니라 환경에서 기인한 전략이라고 생각한다. JS조차 TS가 있음에도 브라우저 런타임과 사용자 입력이 만들어내는 비결정성까지 제거하지는 못하고, 브라우저 벤더사들마다 구현도 다른, 그런 환경에서 살아남을 수 있도록 진화한 것이 아닐까.
현대 프론트엔드가 난해한 이유
지금의 프론트엔드는 그저 JSP, ASP 등에서 뷰어 역할만 하던 프론트엔드와는 완전히 달라졌다.
Service Worker, SharedWebWorker, IndexedDB, PWA, SPA/SSR 등으로 촉발된 역할 확장부터, 서버리스와 엣지 컴퓨팅, CDN, SSG/ISR, 크로스플랫폼 클라이언트 등 인프라 레벨의 성숙도를 요구하고, BFF라는 백엔드 엔지니어링도 일부 수행한다. 클라이언트도 하나였다는 전제는 이제 반만 맞다. 심리학적이고 데이터과학적인 사용자의 행동 분석과 제품 분석마저 요구한다. 제품의 VOC로서 가장 먼저 작용하는 것은 여전히 프론트엔드인데다, 애자일 개발 패러다임은 이미 당연하고 만연해졌으니까. 연역과 귀납이라는 두 접근 방식 중 하나가 아닌, 둘 다 이미 강요받고 있다.
웹이라는 통신 프로토콜과 브라우저라는 VM 위에서, 현대 프론트엔드는 단순 UI 구현을 넘어 클라이언트 운영 전반을 다루게 되었다. 그렇기에 ‘ClientOps’라는 표현이 더 적확할지도 모른다. 입문 장벽이 낮다던 프론트엔드의 인식은 이제 유효하지 않다. 그저 시각적으로 쉬워보이는 마술만이 남았다.
그런데 이렇게 복잡해졌음에도 프론트엔드 엔지니어링은 대등하게 다루어지지 않는 이미지가 있었다. 역사도 짧고, 프론트엔드가 다루는 ’좋은 사용자 경험’은 형식적으로 정의하기 어렵기 때문이다. Core Web Vitals 같은 메트릭이 있지만, LCP가 2.5초 이하라고 해서 그것이 좋은 경험인지는 여전히 인간의 판단에 의존한다. 어떻게 측정할 것인가라는 정의조차 난해하고 편차가 크다. 하드웨어의 스펙과 성능 대비 비용은 평균적으로 수준이 좋아지고 있지만, 역설적으로 프론트엔드 엔지니어링은 어려워져만 간다.
LLM 시대로
그럼에도 React의 선언적 이벤트 관리 모델과 Flux 기반의 상태 관리 모델로 어느 정도 수렴되는 것처럼 보이기도 한다. 기술 변화가 빠르고 아는 것이 많아야 한다던 프론트엔드의 인식은, 사실 이 수렴의 과정이었을 것이다.
이 수렴의 끝에서, 현대 프론트엔드는 LLM 애플리케이션을 다루는 것과 다르지 않은 구조를 갖게 되었다. 비결정론적 출력을 결정론적 UX로 수렴시키는 것. 스트리밍 응답의 점진적 렌더링, 환각에 대한 우아한 처리, 에이전트의 중간 상태를 사용자에게 어떻게 노출할 것인가. 전부 프론트엔드가 늘 다뤄왔던 근본 과제와 동형이다.
결론
결국, 이 모든 복잡도를 프론트엔드가 다루는 방식은 하나로 수렴한다. 추상화다. 모든 공학이 추상화를 한다. 하지만 추상화의 수신자가 다르면 실패가 인지되는 방식이 달라진다.
어떤 추상화든 실패하면 신뢰가 무너진다. 결제가 이중 처리되어도, 데이터가 유실되어도 신뢰는 무너진다. 차이는 실패의 심각성이 아니라 실패가 드러나는 경로에 있다. 시스템을 수신자로 한 추상화는, 실패가 로그와 메트릭을 통해 드러나고, 엔지니어가 사후에 추적하고 복구할 수 있다. 인간을 수신자로 한 추상화는, 실패가 실시간으로 인지된다. 로딩이 3초를 넘으면 이탈하고, 에러 메시지가 기술적이면 불안해하고, 일관되지 않은 상태의 반복은 사용자에게 신뢰를 줄 수 없다. 사후 복구의 기회가 주어지기 전에 신뢰가 먼저 소실된다.
프론트엔드의 추상화는 기술적 정확성뿐 아니라 인지적 연속성을 보장해야 하는 이유다. 모든 복잡도를 흡수하고, 그 흡수의 흔적마저 투명하게 지워야 한다.
Arthur C. Clarke는 말했다. ‘충분히 발전한 기술은 마술과 구별할 수 없다.’ 그것은 기술의 한계를 말한 것이 아니라, 기술의 최고 형태를 말한 것이다. 성공할수록 존재가 투명해지는 공학. 프론트엔드 엔지니어링은 이 진화의 한가운데에 서 있고, HCI의 꽃 중 하나이다.
그야말로 마술 공학, 현대 프론트엔드 엔지니어링이 걸어가고 있는 길을 첨예하게 일컫는 표현이 아닐까. 피땀 흘려 이룩해준 백엔드의 무대와 질서 위에서, 시스템의 완결을 위해서, 가장 투명해보이는 마술을 부려야 한다.