원문: Alex Russell: The Absolute State of Management
Typos and other content © 2001-2026, Alex Russell, infrequently.org
리액트 시대¹에 등장한 표현 가운데 가장 심각한 언어적 범죄까지는 아니겠지만, "상태 관리" 라는 말은 칠판을 손톱으로 긁는 소리만큼이나 거슬립니다. 이 표현을 둘러싸고 제가 반복해서 하게 되는 소크라테스식 대화는 대략 이런 식입니다.
- 나: 이 UI는 하는 일에 비해 너무 무거운데요. 리액트를 쓰지 않는 방법은 고려해 보셨나요?
- 상대: 그럼 리액트 없이 상태 관리는 어떻게 하죠?
- 나: 무슨 말씀이신가요? <대안들>도 충분히 반응형으로 동작하는데요.
- 상대: 리액트가 우리 애플리케이션의 상태를 관리하잖아요.
- 나: 그래요? 그럼 <여러 상태 관리 라이브러리>는 뭘 하는 거죠?
- 상대: 아, 걔네가 상태를 관리하죠.
- 나:

이런 '1루수가 누구야(Who's on First?)' 같은 혼란스러운 대화를 불러오는 라이브러리들은 다음과 같습니다.
- MobX는 "간단하고 확장 가능한 상태 관리"를 제공합니다.
- Satchel은 "뷰 업데이트를 쉽고 효율적으로 만들어 주는 옵저버블 상태를 노출하는 것이 특징"이라고 소개합니다.
- Recoil은 "리액트를 위한 상태 관리 라이브러리"를 표방합니다.
- Jotai는 "리액트를 위한 원시적이면서도 유연한 상태 관리"를 약속합니다.
- XState는 "자바스크립트와 타입스크립트 애플리케이션을 위한 상태 관리 및 오케스트레이션 솔루션"이라고 자신을 소개합니다.
- Apollo는 "자바스크립트를 위한 포괄적인 GraphQL 상태 관리 라이브러리"를 내세웁니다.
- Redux의 슬로건은 "자바스크립트 애플리케이션을 위한 예측 가능한 상태 컨테이너"입니다.
- TanStack Store는 "불변 업데이트를 위한 프레임워크 독립적인 상태 프리미티브"라고 설명합니다.
- Zustand는 자신을 "리액트에서 상태 관리를 위한 최소한의 필수 요소"라고 소개합니다.
참으로 선명한 대비입니다. 부드러운 버터도 썰어버릴 만큼 말이죠.²
어쨌든 한 가지 분명한 사실은, 프런트엔드 생태계는 아직 어느 하나를 정답으로 선택하지 못했다는 것입니다. 실제로 저는 하나의 페이지에서 이런 라이브러리 여러 개가 함께 사용되는 경우를 자주 봅니다. 하지만 리액트가 상태 관리 기능에 대해 상당한 분량의 문서를 할애하며 자랑하고 있다는 점을 생각하면, 이 라이브러리들은 하나만 추가되어도 많아 보입니다. 결국 '상태 관리 시스템'을 하나 더 덧붙이고 있는 셈이니까요.
그렇다면 다음 셋 중 하나는 사실이어야 합니다.
- 리액트가 상태 관리 시스템이라면, 누군가 중복되는 의존성을 몰래 추가한 것이니 그 라이브러리는 과감히 제거해도 됩니다.
- 리액트가 불완전한 상태 관리 시스템이라면, '상태 관리'라는 문제의 애매한 일부만 리액트에 맡기는 일을 이제는 그만둬야 합니다.
- 리액트는 애초에 상태 관리 시스템이 아니라면, 이제는 그런 척하는 것도 그만둬야 합니다.
하지만 컨설팅 엔지니어링 현장에서는 프레임워크에 대한 신념을 건드려 괜한 반감을 살까 봐 이런 말을 대놓고 하기는 어렵습니다.³
다행히 여기는 제 블로그이니, 돌려 말하지 않고 결론부터 이야기하겠습니다. 이 도구들 가운데 상태를 관리하는 것은 하나도 없습니다.
물론 이들은 상태를 캡슐화할 수는 있습니다. 상태 업데이트를 필터링하거나 변환할 수도 있죠. 하지만 어떤 의미에서도 상태를 관리한다고 보기는 어렵습니다. 대신 이들은 상태 변경에 대한 알림을 국소적으로 전파하고, 경우에 따라 그 변경을 후처리하거나 캐시 역할을 수행합니다.⁴ 개념적으로 보면 이들은 이벤트 버스나 Pub/Sub 시스템과 표현력이 동일하며, 결국 메시지를 전달하는 백플레인으로 이해하는 편이 더 정확합니다.⁵
상태 전파(state propagation) 와 상태 관리의 차이를 이해하려면 시간이라는 차원을 함께 고려해야 합니다. 리액트 중심의 '상태 관리' 라이브러리들이 시간을 다루는 방식은 멀티스레드 C++가 시간을 다루는 방식과 비슷합니다. 다시 말해, 독자가 알아서 해결해야 할 문제로 남겨 둔다는 것이죠.⁶
시간과 순서에 대한 개념이 내장되어 있지 않은 시스템은 시간적 효과(temporal effects) 의 영향을 받을 수 있습니다. 심지어 순서도 없고 타임스탬프도 없는 업데이트들을 조합해 하나의 상태를 만들어낼 수도 있습니다. 하지만 그런 시스템이 상태를 관리한다고 주장하는 것은 희망 사항에 가깝습니다. 상태를 관리한다는 것은 시간이 흐르면서 발생하는 변경을 올바르게 적용하고, 충돌을 적절히 해결하는 것을 의미하기 때문입니다.
그때, 지금, 그리고 앞으로
리액트와 리덕스를 이용해 비대해진 '상태 관리' 코드에서 발생하는 문제의 상당수는 같은 근본 원인에서 비롯됩니다. 상태 전파 시스템으로 들어오는 업데이트는 출처도 제각각이고, 발생 빈도와 범위도 서로 다릅니다. 그런데 순수한 상태 전파 시스템은 이벤트 버스라는 추상화만으로 이러한 변화의 결과를 처리하려 하기 때문에, 그 과정에서 생기는 부수적인 복잡성을 감당하지 못하고 무너지게 됩니다.
본질적으로 이것은 여러 컴퓨터에 걸쳐 데이터를 관리하는 문제와 동일합니다. 다시 말해, 과거부터 현재, 그리고 미래까지 이어지는 시간을 가로질러 데이터를 관리하는 문제인 것입니다.
이러한 관점은 매우 중요합니다. 덕분에 지금 메모리에 존재하는 애플리케이션과 나중에 다시 실행된 동일한 애플리케이션을 구분해서 생각할 수 있기 때문입니다. 미래의 어떤 시점에 실행된 애플리케이션 인스턴스는 개념적으로 완전히 다른 컴퓨터에서 실행된 동일한 애플리케이션과 다르지 않습니다. 이 점을 명확히 이해하는 것이야말로 '상태 관리'라는 단순한 사고방식을 버리고, 문제에 적합한 도구를 선택하는 첫걸음입니다.
단순히 상태를 전파하거나 필터링하는 것이 아니라, 정말로 상태를 관리하는 시스템은 시간과 순서에 대한 개념을 내장하고 있습니다. 모든 변경이 우리가 대충 '지금'이라고 부르는 어느 한 시점에 일어난다고 가정하는 대신, 상태 변경을 관리하는 시스템은 변경의 순서를 재정렬하고 충돌을 해결할 수 있는 메커니즘을 갖추고 있습니다. 그리고 이러한 일관성을 바탕으로, 시간의 흐름에 따른 데이터 변경을 신뢰할 수 있고 성능 좋은 형태로 구성해 냅니다.
이러한 시스템은 내부 구현을 살펴보면 벡터 클록(Vector Clock)과 유사한 메커니즘을 통해 구분할 수 있습니다. 상태 변경에 시간과 순서에 대한 메타데이터를 함께 기록해야 전체 시스템에서 변경의 순서를 일관되게 결정할 수 있기 때문입니다. 그리고 이러한 정보 없이는 진정한 의미의 상태 관리는 불가능합니다.⁷
이러한 개념적 구분은 앞서 던졌던 질문에도 답을 줍니다. 지금까지 살펴본 라이브러리 가운데 상태를 실제로 관리하는 것은 없습니다. 리액트 역시 마찬가지입니다.⁸
진지하게 바라보기
이제 이러한 시스템들이 무엇을 하고, 무엇을 하지 않는지를 보다 정확하게 설명할 수 있게 되었으니, '상태 관리' 라는 표현은 대부분 혼란을 불러오는 용어처럼 보입니다. 그렇다고 너무 비판적으로만 볼 필요는 없습니다. 이처럼 뒤섞인 용어를 사용하는 사람들도 나름의 이유가 있기 때문입니다.
애플리케이션의 상태를 진정으로 관리하는 시스템은 아직 널리 사용되지 않고 있으며, 대부분의 개발팀도 이를 제대로 구현하는 알고리즘과 시스템에 익숙하지 않습니다. CRDT, Operational Transform, Prolly Tree처럼 일관성을 유지하기 위한 접근 방식은 아직도 프런트엔드 커뮤니티에서 활발하게 논의되는 주제가 아닙니다.
다행히 이러한 이론을 이해하는 것과 실제로 더 나은 패턴을 적용하는 것 사이의 간극은 생각보다 크지 않습니다. 이미 실무에서 사용할 수 있는 구현체들이 존재하며, 각각의 장단점은 있지만 모두 시간을 고려해 데이터를 관리한다는 공통점을 가지고 있습니다.
- Y.js: CRDT를 텍스트 편집에 적용한 대표적인 사례입니다. 데이터 구조의 성능이 크게 발전하면서, 몇 년 전만 해도 현실성이 떨어졌던 완전한 CRDT 기반 접근 방식이 이제는 충분히 실용적인 선택지가 되었습니다.
- Zero: Replicache의 후속 프로젝트로, 데이터 동기화를 넘어 실시간 협업까지 지원하는 것을 목표로 합니다. 오픈소스로 직접 운영할 수도 있고 호스팅 서비스를 이용할 수도 있으며, 지금 새 프로젝트를 시작한다면 제가 가장 먼저 고려할 선택지입니다.
- Fluid: Microsoft가 개발한 오픈소스 협업 및 동기화 시스템으로, Operational Transform의 아이디어를 기반으로 설계되었습니다.
이러한 시스템이 만들어내는 가장 아름다운 결과 중 하나는, 시간과 충돌을 시스템 차원에서 다루기 때문에 '지금'의 양쪽 경계를 모두 자연스럽게 처리할 수 있다는 점입니다. 그 결과 실시간 협업은 물론이고, 오프라인 우선(offline-first) 애플리케이션도 자연스럽게 구현할 수 있습니다.⁹ Y.js나 Replicache처럼 더 나아가 로컬 우선(local-first) 개발을 가능하게 하는 시스템도 있습니다.
어떤 구현 방식을 선택하든 이러한 시스템은 패러다임의 전환을 가져옵니다. 데이터 모델은 더 이상 막연한 희망에 기대어 억지로 이어 붙인 구조가 아니라, 시간의 흐름에 따라 데이터 변경을 올바른 순서로 적용하는 시스템이 됩니다. 그리고 그 결과로 오프라인 지원과 실시간 협업은 별도의 기능이 아니라 자연스럽게 따라오는 효과가 됩니다.
이것이 바로 지금 우리가 선택할 수 있는 미래입니다. 먼저 '상태 관리'라는 이름이 실제로는 상태를 관리하지 않는다는 사실을 인정해야 합니다. 그리고 그다음, 정말 상태를 관리하는 시스템을 선택하면 됩니다.
각주
1. 제 생각에 리액트 문화가 퍼뜨린 가장 심각한 용어 오용은 '서버 사이드 렌더링', '순수 함수', 그리고 '동시성' 입니다. 이 용어들은 원래 오랫동안 연구되어 온 개념을 가져와 훨씬 덜 엄밀한 의미로 다시 정의한 뒤, 그보다 더 중요하고 큰 개념의 권위를 빌려 저렴한 모조품을 정당화하는 데 사용되었습니다.
리액트 진영이 리액트 생태계의 MVC 구성 요소들에 붙여 놓은 이름들도 마찬가지입니다. 전역 변수를 사용하는 아주 전형적인 MVC 패턴을 함수형 프로그래밍을 흉내 낸 새로운 개념처럼 포장했지만, 실제로는 트랜스파일러 오버헤드만 늘어난 기존 방식에 불과했습니다.
솔직히 이런 건 엉망입니다. 진지한 엔지니어라면 이런 값싼 마케팅 연출에 혼란을 느끼거나 불쾌해하는 것이 당연합니다.
2. Apollo의 엄청난 덩치를 생각하면, 차라리 나머지 여덟 개 라이브러리를 전부 흡수해 버리지 않는 이유가 궁금해질 수도 있습니다.
3. 누군가에게 상태 관리에 대한 이해가 완전히 잘못됐다고 정면으로 말하는 것은 생산적이지도 않고(무례하기도 합니다), 그래서 저는 보통 서로 모순되는 주장들을 나란히 놓아 그 모순이 자연스럽게 드러나도록 하는 정도에서 그칩니다.
리액트가 말하는 상태 관리와 다른 라이브러리들이 말하는 상태 관리를 함께 보여주면, 적어도 저에게는 자연스럽게 떠오르는 의문들이 다른 사람들에게도 생기길 바랍니다. 하지만 안타깝게도 이런 방법은 거의 효과가 없습니다.
대부분의 개발자는 실시간 협업 기능을 추가해야 하는 상황이 되어서야 비로소 더 나은 접근 방식이 있다는 사실을 깨닫습니다. 이해는 가지만, 한편으로는 Apollo나 리덕스를 데이터 동기화 엔진처럼 억지로 활용하려고 쏟아붓는 노력들을 생각하면 다소 답답한 일입니다.
4. 이러한 시스템은 어느 정도 캐시 역할을 하기는 하지만 데이터베이스는 아닙니다. 데이터를 질의할 수 있는 기능을 제공하지 않기 때문입니다. 또한 대부분은 로컬 영속성도 제대로 지원하지 않습니다. 여기에 저장 기능을 억지로 덧붙인 사례들을 보면, 애초에 확장 가능한 데이터 접근 계층으로 설계된 것이 아니라는 사실이 더욱 분명해집니다.
효율적인 조회를 위한 데이터 구조와 메타데이터가 없는 상태에서, 데이터 계층의 얇은 스냅샷 하나를 범용 접근 추상화처럼 사용하는 것은 결국 헛수고입니다. 최선의 경우에도 O(n²) 이상의 비효율을 피할 수 없기 때문입니다.
실제로 규모가 큰 제품들조차 이러한 설계 때문에 성능이 크게 저하된 상태로 운영되며, 특히 헤비 유저들에게 큰 불편을 안겨주었습니다. 어떤 애플리케이션은 리덕스를 JSON으로 직렬화한 데이터를 읽어 애플리케이션을 부팅하려다 실패하기도 했습니다.
그런 방식이 개발자의 성능 좋은 컴퓨터에서라도 동작했다는 사실은 현대 소프트웨어 교육이나 프런트엔드 문화의 성과라기보다, 하드웨어 엔지니어들의 뛰어난 기술력 덕분이라고 보는 편이 맞습니다.
5. 단일 호스트에서 실행되는 RabbitMQ 하나만으로도 표현력은 동일하며, 일반적으로 성능도 더 뛰어날 것입니다.
6.다소 비꼬는 표현입니다.
C++와 자바스크립트를 모두 잘 아는 사람이라면 이해하겠지만, 시간의 흐름에 따라 데이터를 접근하고 갱신하는 능력은 사실 C++가 JavaScript보다 훨씬 뛰어납니다. 다만 그 기능이 C++11 Memory Model이라는 복잡한 규격 속에 숨어 있을 뿐입니다.
반면 자바스크립트는 프로그램이나 클래스 초기화 과정은 점점 더 세밀하게 다듬으면서도, 실행의 시간적 차원을 다루기 위한 문법이나 표준 라이브러리는 거의 제공하지 않습니다.
7. 벡터 클록을 기반으로 한 업데이트 시스템에 대해 가장 흔히 나오는 반론은, 많은 경우에는 지금, 과거, 미래를 구분할 필요가 없다는 것입니다.
그리고 그 말은 때로는 맞습니다.
많은 UI 컴포넌트의 내부 상태는 시간에 따른 변경 이력을 관리할 정도로 복잡하지 않습니다. 이런 컴포넌트들은 하나의 거대한 데이터 관리 체계에 깊게 통합되기보다, 시간 개념이 없는 상태 머신이나 이력이 없는 상태 차트만으로도 충분합니다. 그것은 아주 합리적인 선택입니다.
문제는 그런 작은 컴포넌트의 범위를 벗어난 뒤에도, 애플리케이션 전체에서 발생하는 충돌까지 동일한 방식으로 처리할 수 있다고 믿는 것입니다.
이 둘은 근본적으로 다른 문제입니다. 모든 것을 컴포넌트의 무한한 재귀 구조로만 바라보고, 횡단 관심사는 필요 없다고 여기는 리액트식 사고방식은 정작 리액트 커뮤니티의 실무 사례만 봐도 성립하지 않는다는 사실을 알 수 있습니다.
다르게 말하면, 리덕스는 도움을 요청하는 비명입니다. 그리고 우리는 리덕스가 만들어 낸 고통을 더 적절한 방식으로 해결할 수 있는 도구를 이미 가지고 있습니다. 다만 그러려면 시간이 존재하지 않는 세계를 벗어나, 시간의 흐름을 받아들이는 세계로 들어가야 합니다.
8. 이 라이브러리들을 양심적으로 '상태 관리 도구'라고 부르기는 어렵습니다. 그렇다고 당장 더 적절한 이름이 떠오르는 것도 아닙니다.
예를 들면 이런 이름들이 더 어울릴지도 모르겠습니다.
- 이벤트 블렌더(Event blenders)
- 상태 체(State strainers)
- 모델 뒤죽박죽기(Model muddlers)
- 변경 섞기(Mutation mixers)
- 변경의 늪(Modification marshes)
- 컴포넌트 복잡도 생성기(Component complexifiers)
- 변화 신비주의(Metamorphosis mystifiers)
- 혼란 캐시(Confusion caches)
- 변환의 수렁(Transform tarpits)
- 힙 뒤죽박죽(Heap hodgepodges)
- 업데이트 대혼란(Update imbroglios)
- 상태 대참사(Status SNAFUs)
더 좋은 이름이 떠오른다면 Mastodon이나 Bluesky로 알려주세요.
9. 조금 덜 구조적인 방식으로 데이터 동기화를 해결하려는 시스템들도 언급할 만합니다.
- PouchDB: 이 분야의 고전적인 라이브러리입니다. 지금도 꾸준히 개선되고 있지만, 충돌 해결 전략이 너무 단순해서 새로운 프로덕션 프로젝트에는 추천하기 어렵습니다.
- RxDB: 충돌 해결을 개발자에게 맡기는 점은 아쉽습니다. 다만 CRDT를 통합할 수 있는 구조를 제공하기 때문에 그 방식을 사용하는 것을 추천합니다.
- Replicache: 완전한 상태 동기화와 우수한 충돌 제어를 제공합니다. 오픈소스이지만 현재는 Rocicorp의 지원이 다소 줄어든 상태입니다. Y.js가 너무 무겁다고 느끼지만 오프라인 퍼스트 또는 로컬 퍼스트 개발과 클라우드의 권위 있는 저장소를 모두 원한다면 좋은 선택입니다.
마지막으로 빼놓을 수 없는 것이 Google Docs API입니다. 사실상 Google Docs와 Docs Offline을 구동하는 실시간 OT 시스템의 외부 진입점이라고 볼 수 있습니다. 다만 Google이 수정한 OT 시스템은 서버와 클라이언트 구현 모두 오픈소스가 아니고 자체 호스팅도 불가능하기 때문에, 추천하기는 어렵습니다.