데우스랠리 / 2025.06 ~
데우스랠리 상태관리 아키텍처
TL;DR
데우스랠리는 데우스 위에서 동작하는 실시간 모션 편집 플러그인이었어요. 타임라인에서 duration과 delay를 드래그하면 조작 패널과 프리뷰, 코드젠 입력이 같은 상태를 바라봐야 했고, 그래서 MobX 기반 클래스형 store로 상태관리 모델을 설계했어요.
문제 정의 Problem
처음부터 새로 만드는 플러그인이라 상태관리를 어떻게 가져갈지가 중요한 결정이었어요. 익숙한 Zustand를 쓸 수도 있었지만, 데우스 자체가 MobX 기반 observable 데이터를 제공했고 데우스랠리도 그 데이터와 계속 동기화되어야 했어요. 여기서 중요하게 본 건 반응성의 단위였어요. Coarse-grained reactivity가 비교적 큰 상태 단위의 변경을 구독하고 필요한 값은 selector로 골라 쓰는 방식이라면, fine-grained reactivity는 실제로 읽힌 observable 값 단위로 변경을 추적해요. 타임라인 드래그처럼 작은 값이 계속 바뀌고 그 값이 패널과 프리뷰에 바로 반영되어야 하는 편집 환경에는 fine-grained reactivity가 더 잘 맞는다고 판단했어요.
이 선택은 익숙한 상태관리 라이브러리를 고르는 문제가 아니었어요. 호스트 에디터인 데우스의 데이터 모델과 실시간 편집 경험을 함께 고려해야 했고, 데이터 정합성과 렌더링 반응성을 동시에 맞춰야 했어요.
접근 방식 Approach
상태 감시와 파생값 계산은 MobX store에 맡기고, React 컴포넌트는 store에서 필요한 computed 값만 읽는 구조로 나눴어요. 데우스 observable 데이터와 플러그인 내부 편집 데이터를 한 흐름에서 다루려고 MobX 기반 class store 구조를 골랐어요. 좌측 조작 패널, 하단 타임라인, 프리뷰, 코드젠은 section store로 나누고 여러 섹션이 함께 봐야 하는 값은 bridge store에 두었어요. 초기 상태관리 구조를 동료와 함께 설계한 뒤, 조작 패널과 코드젠 store 구현을 맡았어요.
처음에는 데우스에서 직렬화된 데이터를 받아 root store에서 hydrate했어요. 사용자가 값을 바꾸면 데우스 storage 업데이트 요청을 보내고, 동시에 MobX action으로 메모리 상태도 갱신했어요. 가공이 필요한 데이터는 store 안에 action과 computed로 모아두었고, 패널과 프리뷰는 필요한 computed 값만 읽도록 했어요. 패널 open 상태와 프리뷰 접힘 상태, 다중 노드 선택처럼 여러 섹션이 함께 써야 하는 값은 root store 하위 bridge store에서 공유했어요.
핵심 결정 Decision
데우스의 observable 모델과 이어지도록 MobX 기반 클래스형 store, 곧 fine-grained reactivity를 골랐어요. 당시에는 MobX와 class store에 익숙하지 않았어요. 그래도 데우스가 이미 observable 기반으로 동작했고, 데우스랠리 역시 실시간 편집 중 작은 값이 자주 바뀌는 환경이었기 때문에 MobX를 골랐어요. 기술 숙련도보다 프로덕트 상황에 맞는 반응성 모델을 고르는 쪽을 더 중요하게 봤어요.
MobX proxy 객체 때문에 로그로 데이터를 확인하기 번거로웠고, observable 관계가 어디서 끊겼는지 추적하기 어려운 디버깅 비용이 있었어요. Deus storage 업데이트에 debounce나 throttle을 두지 않았어요. 사용자 변경 시 Deus storage 업데이트 요청과 메모리 상태 갱신을 함께 했어요. 실제 쓰는 데 큰 문제가 없어서 당시에는 추가 최적화를 넣지 않았어요. 돌아보면 저장 동기화 빈도를 제어하지 않은 점은 보완할 여지로 남아 있어요.
해결 결과 Result
약 1년 가까이 운영하는 동안 데이터 정합성 이슈는 0건이었어요. section store 구조 덕분에 기능을 추가할 때 책임 영역을 찾아 확장하기 쉬웠어요. 패널과 프리뷰, 코드젠이 같은 데이터를 기준으로 움직였어요. 데우스에서 받은 직렬화 데이터와 플러그인 내부 편집 상태를 하나의 흐름으로 다룰 수 있었어요.
배운 점 Learning
이 작업에서 가장 크게 배운 건 기술이 먼저가 아니라 프로덕트가 먼저라는 점이에요. 예전에는 내가 쓸 수 있는 Redux나 Zustand를 먼저 떠올리는 편이었지만, 이 케이스에서는 문제를 먼저 정의하고 그 상황에 더 맞는 기술을 골랐어요. 나중에 돌아보니 MobX 선택에는 분명 비용도 있었어요. 로그를 찍으면 proxy 객체로 보여서 데이터를 확인하기 번거로웠고, observable 관계가 어디서 끊겼는지 추적하기도 어려웠어요. 이후 성능 병목을 크롬 프로파일링으로 확인했을 때도 가장 큰 병목은 MobX가 아니라 프리뷰 렌더링 쪽이었어요.
이 작업을 다시 한다면 Next
다시 만든다면 Zustand도 진지하게 고려할 것 같아요. 중간에 직렬화가 계속 들어가는 구조라면 observable state의 장점을 100% 누리기 어렵고, 디버깅 가능성도 무시 못 할 기준이고요. 다음에는 저장 동기화와 렌더링 병목, 디버깅 흐름까지 같이 놓고 상태관리 방식을 정해보고 싶어요. 프리뷰 렌더링 병목과 transition 기반 지연 렌더링은 별도 프리뷰 구현 케이스스터디에서 더 자세히 정리할 예정이에요.