본문으로 건너뛰기
면접 학습
복습세션 →

이력서 기반

토픽 2 · Phase 1

상태 관리 점진적 마이그레이션 (Recoil → Jotai)

질문 5개
  1. Q2-1

    왜 Recoil에서 Jotai로 마이그레이션했나요? Redux나 Zustand가 아니라 Jotai를 고른 이유는요?

    핵심 포인트

    • Recoil 마이그레이션 동기: 메인테이너 공식 지원 위축 + 번들 사이즈 부담 + atom key 충돌 리스크
    • 다른 후보(Zustand, Redux Toolkit, Valtio)와 비교했지만 기존 atom 모델을 그대로 옮길 수 있다는 점에서 Jotai 선택
    • 비대한 라이브 코드베이스(라이브 서비스)를 한 번에 못 갈아엎는 제약이 의사결정의 핵심
    • "atom → atom" 1:1 매핑이 가능해서 마이그레이션 비용이 가장 작은 선택지
    모범 답안먼저 답해보고 펼치기

    세 가지 동기가 있었어요. 첫째, Recoil의 메인테이너 지원이 위축되고 있다는 시그널이 명확했습니다. 메인 메인테이너(Meta) 분의 활동이 줄었고 이슈 응답이 느려졌어요. 라이브 서비스의 핵심 인프라를 그런 라이브러리에 두는 건 리스크였어요. 둘째, 번들 사이즈 부담. Recoil은 트리쉐이킹이 잘 안 돼서 번들에 통째로 들어가곤 했어요. 셋째, atom key가 전역 문자열이라 매크로 단위 모듈 분리가 늘어날수록 충돌 위험이 누적됐습니다.

    대안을 셋 비교했어요. Zustand는 가볍지만 사고 모델이 store 단위라 atom 단위로 잘게 쪼갠 코드를 옮기는 비용이 컸고, Redux Toolkit은 보일러플레이트 + immer 사고가 따라와서 전환이 너무 큰 충격이었어요. Jotai는 atom 모델 자체가 Recoil과 거의 동일하고, atom이 객체 참조라서 key 문자열 자체가 사라지는 등 Recoil의 약점만 깔끔하게 보완하는 선택지였습니다. 결국 마이그레이션 비용이 가장 작고 효과가 가장 큰 선택지여서 Jotai로 결정했어요.

    이 답변, 어땠나요?

    꼬리 질문
    • Q-a. Recoil이 정말 사망했다고 보세요?

      2024년에 메인 메인테이너의 Meta 퇴사 이후 사실상 unmaintained 상태가 됐다고 알려졌어요. 라이브 서비스에서는 그 정도 시그널이면 탈출 결정의 충분 조건이라고 봤습니다.

    • Q-b. React 19의 use() 와 Server Components 흐름에서 클라 상태 관리는 줄어들지 않을까요?

      서버 데이터는 React Query나 RSC가 가져가는 흐름은 맞지만, 본 프로젝트는 3D 에디터처럼 클라이언트가 풍부한 상태를 들고 있어야 하는 도메인이라 atom 단위 클라 상태가 여전히 필요했습니다. 도메인 특성에 따른 선택이었어요.

    • Q-c. Jotai의 단점은요?

      devtools 생태계가 Redux보다 빈약하고, atom 디펜던시 그래프가 복잡해지면 디버깅이 어렵습니다. 이를 보완하기 위해 atom 작명 규칙과 의존 그래프 docstring을 컨벤션으로 강제했어요.

    CS · 이론
    • 상태 관리 라이브러리의 4가지 모델
      • Flux/Redux: 단일 store + reducer
      • Atomic (Recoil/Jotai): 작은 atom 단위 분산
      • Proxy (Valtio/MobX): mutable 처럼 쓰지만 내부에서 추적
      • External store hook (Zustand): 함수형 store + 구독 훅
    • Tree-shaking 적합성: 모듈 시스템 + side-effect-free 빌드. 라이브러리의 sideEffects: false 명시 여부 중요
    • 번들 사이즈 측정: Bundlephobia, source-map-explorer, webpack-bundle-analyzer
    • Maintainership risk: OSS 채택 시 기여자 활동도, 릴리스 주기, 이슈 응답 시간을 의사결정 근거로 삼는 문화
  2. Q2-2

    atom key 충돌 리스크가 정확히 어떤 의미인가요? Jotai는 어떻게 다른가요?

    핵심 포인트

    • Recoil의 atom은 key: 'someString' 을 필수로 받음 — 전역 namespace에 등록됨
    • 다른 모듈에서 같은 key 쓰면 런타임 에러 또는 silent override
    • 모노레포·codegen·동적 atom 생성 환경에서 누적 위험
    • Jotai의 atom은 객체 참조 자체가 식별자 — key 문자열 불필요, 충돌 자체가 발생할 수 없음
    모범 답안먼저 답해보고 펼치기

    Recoil은 atom을 만들 때 key: 'userAtom' 같은 문자열을 강제로 받아요. 이 key는 전역 namespace에 등록돼서, 다른 파일에서 같은 key로 atom을 또 만들면 개발 모드에서는 경고가 뜨고 프로덕션에서는 silent하게 한쪽이 덮어써집니다. 모노레포라 패키지가 여러 개고, atomFamily처럼 동적으로 생성되는 atom도 있다 보니 시간이 흐를수록 충돌 가능성이 누적됐어요.

    Jotai에서는 atom이 그냥 자바스크립트 객체 참조로 식별돼요. const userAtom = atom(initial) 했으면 그 변수 자체가 atom의 정체성이고, 같은 이름의 변수를 두 모듈에서 만들면 그냥 다른 atom입니다. key 충돌이라는 개념 자체가 존재하지 않아요. 이게 Recoil 코드를 옮기면서 자연스럽게 사라진 부채여서, 마이그레이션의 부수 효과로 가장 컸던 부분이에요.

    이 답변, 어땠나요?

    꼬리 질문
    • Q-a. atom 객체 참조 식별이 SSR/RSC에서 문제 안 되나요?

      Jotai는 Provider 단위로 store가 분리되니까 SSR에서 요청별로 store를 새로 만들면 됩니다. atom 자체는 모듈 싱글턴이지만 값은 store별로 격리돼요.

    • Q-b. atom 정체성이 객체 참조라면 HMR(Hot Module Replacement) 에서 문제는요?

      atom 모듈이 다시 평가되면 새 객체가 만들어져서, 진짜로 다른 atom이 됩니다. devtools에서 잠깐 끊기는 현상은 있지만 의도된 동작이고, atomWithStorage 같은 persist atom으로 보완 가능합니다.

    CS · 이론
    • 모듈 싱글턴 패턴: ES Module의 단일 인스턴스 보장이 atom 정체성의 기반
    • Reference identity vs structural identity: JavaScript는 객체를 참조로 비교 (===). 이게 atom 정체성의 핵심 메커니즘
    • Namespace pollution: 전역 namespace에 식별자를 등록하는 모든 시스템(jQuery 플러그인, CSS 클래스, atom key)의 공통 문제
    • String literal vs object identity: 식별자 설계의 두 갈래. 직렬화 가능성을 포기하는 대신 충돌 위험을 0으로 만드는 트레이드오프
  3. Q2-3

    "라이브 서비스 무중단 점진적 마이그레이션"을 구체적으로 어떻게 진행하셨나요?

    핵심 포인트

    • 둘을 동시에 살려두는 공존(coexistence) 전략 — RecoilRoot 내부에 Provider(Jotai) 중첩
    • 신규 기능은 Jotai, 기존 코드는 유지보수 시점에 자연스럽게 전환 ("Strangler Fig" 패턴)
    • 모듈 단위로 잘라서 PR 사이즈를 작게 유지 → 리뷰·롤백 비용 최소화
    • 공존 기간 중 양쪽이 같은 데이터를 들고 있을 경우 단방향 동기화로 일관성 보장
    모범 답안먼저 답해보고 펼치기

    한 번에 다 갈아엎으면 라이브 서비스가 며칠 멈출 위험이 있어서, 둘을 공존시키는 전략을 짰어요. 앱 루트에 RecoilRoot가 있는 상태에서 그 내부에 Jotai의 Provider를 중첩으로 두고, 둘이 서로 간섭하지 않는다는 걸 먼저 검증했습니다.

    그 다음 룰을 정했어요. 신규 기능은 무조건 Jotai로, 기존 Recoil 코드는 유지보수 시점에 자연스럽게 전환. 이건 Strangler Fig 패턴이에요. 새 기능을 늘리면서 점진적으로 옛 시스템을 잠식하는 거죠. PR 한 개당 대략 atom 하나씩 옮겼고, 그 atom을 쓰던 컴포넌트를 같이 옮기되 외부에서 그 atom을 쓰는 다른 컴포넌트가 있다면 양쪽 데이터를 잠시 동기화하는 어댑터 atom을 두기도 했습니다.

    이 전략 덕에 마이그레이션 진행 중에도 매주 정상적으로 배포가 나갔고, 롤백이 필요한 상황이 한 번도 없었어요. 약 6개월에 걸쳐 자연스럽게 100% Jotai로 수렴했습니다. (※ 기간은 본인 기억에 맞게 보정)

    이 답변, 어땠나요?

    꼬리 질문
    • Q-a. 공존 기간에 양쪽이 같은 데이터를 들고 있으면 일관성이 깨지지 않나요?

      단방향 동기화로 막았어요. Recoil → Jotai 한 방향만 흐르게 하고, 신규 코드는 Jotai만 읽도록 가이드했습니다. 양방향 동기화는 race condition이 생겨서 일찍 포기했어요.

    • Q-b. 어떻게 진행 상황을 트래킹했나요?

      단순하게 git grep recoil로 남은 import 개수를 주차 단위로 KR로 보고했어요. 처음 100% → 0%까지 그래프로 시각화하면 팀에게도 진척감이 잡혔습니다.

    • Q-c. 마이그레이션이 이상하게 길어지면 어떻게 끊을지 정해 두셨나요?

      네, "신규 기능은 무조건 Jotai" 룰만 지키면 자연스럽게 끝나는 흐름이라 별도 데드라인은 두지 않았지만, 6개월 시점에 남은 atom들을 일괄 정리하는 spike를 한 번 넣었어요.

    CS · 이론
    • Strangler Fig 패턴 (Martin Fowler): 기존 시스템을 한 번에 교체하지 않고, 주변에 새 시스템을 키우면서 점진적으로 잠식하는 마이그레이션 전략
    • Coexistence / Bi-modal 운영: 두 시스템을 일시적으로 함께 운영하는 마이그레이션 형태
    • Feature flag 기반 마이그레이션: 필요 시 런타임 토글로 전환 — 본 케이스는 코드 레벨 분리로 충분해서 안 씀
    • API surface vs implementation: 외부에서 보는 인터페이스(컴포넌트 prop, 훅 시그니처)는 그대로 두고, 내부 구현만 교체하는 것이 무중단 마이그레이션의 핵심 사상
  4. Q2-4

    usePersistedState 훅을 새로 만드신 이유는요? localStorage 직접 쓰면 되지 않나요?

    핵심 포인트

    • 라이브러리 교체에 그치지 않고 코드 품질을 함께 끌어올린 사례 (단순 1:1 마이그가 아님)
    • 이슈 1: localStorage 쓰기/읽기 코드가 컴포넌트마다 흩어져 있던 문제
    • 이슈 2: 직렬화/역직렬화 실패, SSR 환경, 탭 간 동기화 등 엣지케이스가 매번 다르게 처리되던 문제
    • 해결: 훅으로 캡슐화 + Provider 패턴으로 라이프사이클 일원화
    모범 답안먼저 답해보고 펼치기

    처음엔 그냥 atom 옆에 useEffect 로 localStorage에 쓰는 코드를 넣었는데, 같은 패턴이 10군데 넘게 반복되면서 미묘하게 다 달랐어요. 어디는 try/catch가 있고 어디는 없고, JSON 파싱 실패 시 동작도 제각각이었습니다.

    usePersistedState라는 훅으로 통합했어요. 시그니처는 usePersistedState(atom, key) 같은 단순한 형태고, 내부에서 마운트 시 localStorage에서 읽어오기·atom 변경 시 throttle된 쓰기·읽기 실패 시 기본값 fallback·SSR에서는 noop·storage 이벤트로 다른 탭 변경 동기화 — 이 다섯 가지 엣지케이스를 한 번에 처리합니다. Provider 패턴으로 묶어서 같은 atom을 여러 컴포넌트가 구독해도 storage 이벤트 리스너는 한 번만 붙도록 했고요.

    결과적으로 관련 코드가 20% 정도 줄었고, "localStorage를 어떻게 다뤄야 안전하지?"라는 질문이 더 이상 PR 리뷰에 안 올라왔어요. 라이브러리 교체를 단순 작업으로 끝내지 않고, 코드 품질 개선의 기회로 활용한 사례였습니다.

    이 답변, 어땠나요?

    꼬리 질문
    • Q-a. Jotai의 atomWithStorage를 그냥 쓰면 되지 않나요?

      atomWithStorage도 좋은 옵션이지만, 우리 도메인에서는 (1) 쓰기 throttle 정책, (2) 마이그레이션 룰(키 이름이 바뀌었을 때 옛 키 → 새 키로 변환) 같은 도메인 룰이 필요해서 직접 만든 훅이 더 잘 맞았어요.

    • Q-b. SSR 환경에서 hydration mismatch 위험은 어떻게 처리했나요?

      첫 렌더에는 atom의 default 값으로 그리고, 마운트 이후 effect에서 localStorage 값을 읽어 setState 합니다. hydration mismatch는 첫 페인트 후 한 프레임 깜빡임으로 절충했어요.

    • Q-c. 탭 간 동기화는 정말 필요한 요구사항이었나요?

      사용자가 같은 에디터를 여러 탭으로 띄우는 시나리오가 실제로 있었고, 한쪽에서 수정한 컬러가 다른 탭에 반영 안 되어서 혼란을 일으킨 버그 리포트가 있었습니다. 그래서 storage 이벤트 동기화를 추가했어요.

    CS · 이론
    • Custom hook abstraction: 공통 패턴을 hook으로 묶어 컴포넌트 코드의 인지 부하를 줄이는 React식 추상화
    • Storage 이벤트: window storage 이벤트는 다른 탭의 localStorage 변경 만 발화. 자기 탭은 발화하지 않는다는 점 주의
    • SSR Hydration mismatch: 서버 렌더 결과와 클라이언트 첫 렌더가 다르면 React가 경고. localStorage는 서버에 없으므로 마운트 후에만 접근
    • Throttling vs Debouncing: 쓰기 빈도 제어. 본 케이스는 마지막 값 보장이 중요해서 trailing throttle
  5. Q2-5

    atom 단위가 작게 쪼개지면 디펜던시 그래프가 복잡해지는데, 어떻게 관리하셨나요?

    핵심 포인트

    • atom 작명 규칙 (도메인 prefix + 역할 suffix)
    • derived atom의 의존성을 docstring으로 명시
    • 순환 의존성이 생기면 ESLint로 막고, 도메인을 다시 보는 신호로 삼음
    • atom 단위 README 또는 storybook addon으로 시각화 (실제로 했는지에 따라 답변 톤 조정)
    모범 답안먼저 답해보고 펼치기

    atom이 작아지면 자유도는 올라가지만 그래프는 복잡해져요. 세 가지로 관리했습니다.

    첫째, 작명 규칙. domain_role 형태로 통일했어요. material_color, editor_selection 같은 식이라 IDE에서 검색이 쉬웠습니다. 둘째, derived atom의 의존성을 JSDoc 으로 명시. @dependsOn material_color 같은 태그를 남겨서 코드 리딩 시 그래프를 따라가기 쉽게 했어요. 셋째, 순환 의존이 생기면 ESLint로 빌드를 막았어요. eslint-plugin-import의 no-cycle 룰로 강제했고, 순환이 잡히면 도메인을 잘못 잘랐다는 신호로 받아들여서 atom 분리 자체를 다시 봤습니다.

    이렇게 해도 그래프 복잡도는 한계가 있어서, 다음 단계로 atom 그룹을 모듈 단위로 묶고 모듈 간 인터페이스를 selector atom 으로만 노출하는 패턴을 도입할 계획이었습니다.

    이 답변, 어땠나요?

    꼬리 질문
    • Q-a. atom이 너무 잘게 쪼개지면 오히려 코드 추적이 어려워지지 않나요?

      기준은 "독립적으로 변경되는가"였어요. 같은 변경 축에 놓인 데이터는 합치고, 독립 축이면 분리. 이 원칙으로 분류하면 atom 수가 폭주하지 않습니다.

    • Q-b. devtools 없이 디버깅을 어떻게 했나요?

      Jotai의 useAtomDevtools와 Redux DevTools 어댑터를 일부 화면에 붙였고, 핵심 atom에는 subscribe 미들웨어로 콘솔 로그를 찍었습니다. devtools가 빈약한 게 Jotai의 약점이라는 건 인정합니다.

    CS · 이론
    • High cohesion, low coupling: atom 분리 기준의 일반 원칙
    • Topological order in atom graphs: derived atom은 DAG. 순환이 생기면 그래프가 깨짐
    • Static analysis: ESLint·TypeScript로 컴파일 타임에 구조 룰을 강제하는 일반 기법