마지막 버그 픽스

672025년 10월 21일3

공식 릴리스를 불과 몇 주 앞둔 시점, React Core Team은 축제보다는 오히려 숨 막히는 긴장감에 휩싸여 있었다. 릴리스 후보(RC) 버전이 배포된 이후, 전 세계의 다양한 개발 환경에서 예상치 못한 버그들이 속속 보고되고 있었기 때문이다. 대부분은 사소한 호환성 문제였지만, 그중 하나는 팀 전체를 잠 못 이루게 만들었다.

버그 리포트는 한 금융 서비스 회사에서 근무하는 개발자로부터 접수되었다. 내용은 간단했다.

“React 19 RC.2 버전으로 업그레이드한 후, 저희의 복잡한 데이터 그리드 컴포넌트에서 간헐적으로 상태 업데이트가 유실되는 현상이 발생합니다. 특히, startTransitionSuspense를 함께 사용하는 특정 조건에서만 재현됩니다.”

‘간헐적으로’, ‘특정 조건에서만’. 개발자에게 이보다 더 끔찍한 말은 없었다.

앤드류 클라크와 세바스찬 마크바게가 직접 이 문제에 뛰어들었다. 그들은 보고된 코드를 분석했지만, 로직 상의 명백한 문제는 보이지 않았다. 문제는 React의 가장 깊은 곳, 바로 동시성 렌더러의 스케줄링 로직에 숨어 있는 것처럼 보였다.

며칠 밤낮으로 디버깅이 이어졌다. 그들은 React의 내부 상태를 시각화하는 특수 도구를 만들고, 수백만 분의 일초 단위로 렌더링 과정을 추적했다. 마침내 문제의 실마리가 잡혔다.

원인은 ‘전환(Transition)’ 업데이트의 처리 우선순위와 관련된 아주 미묘한 엣지 케이스였다.

  1. 사용자가 액션을 취해 startTransition으로 상태 업데이트 A를 시작한다.
  2. 이 업데이트로 인해 데이터 로딩이 필요해져 Suspense가 발동하고, 렌더링이 잠시 ‘일시 중단’된다.
  3. 바로 그 순간, 다른 비동기 이벤트로 인해 우선순위가 더 낮은 상태 업데이트 B가 큐에 추가된다.
  4. 데이터 로딩이 끝나고 렌더링이 재개될 때, 동시성 렌더러의 스케줄러가 특정 조건 하에서 우선순위가 낮은 B를 먼저 처리해버리고, 정작 원래의 목표였던 A를 누락시키는 버그였다.

이것은 동시성이라는 강력한 엔진의 가장 복잡하고 세밀한 톱니바퀴 하나가 잘못 맞물려 돌아간 결과였다.

원인을 파악하자, 해결책을 찾기 위한 치열한 토론이 벌어졌다. 스케줄링 로직을 수정하는 것은 자칫 다른 부분에 예기치 않은 부작용을 낳을 수 있는, 심장 수술과도 같은 위험한 작업이었다.

세바스찬은 화이트보드에 동시성 렌더러의 상태 전이 다이어그램을 그려놓고, 몇 시간 동안 아무 말 없이 그것을 응시했다. 다른 팀원들은 감히 그를 방해하지 못했다.

마침내, 그가 입을 열었다.

“해결책은 로직을 더하는 게 아니라, 빼는 거야.”

그는 다이어그램의 한 부분을 지우고, 두 개의 상태를 연결하는 화살표의 경로를 더 단순하게 수정했다. 업데이트가 재개될 때, 여러 개의 대기 중인 업데이트를 처리하는 로직을 더 단순화하여, 항상 가장 먼저 시작된 Transition의 컨텍스트를 유지하도록 보장하는 방식이었다.

코드는 단 몇 줄만 수정되었지만, 그 결정에 도달하기까지는 수십 시간의 고뇌가 필요했다.

수정된 코드로 다시 테스트를 진행했다. 수천 번의 반복 테스트에도, 상태 유실 버그는 더 이상 재현되지 않았다.

팀은 안도의 한숨을 내쉬었다. 자칫하면 React 19 전체의 신뢰도를 뒤흔들 뻔했던 치명적인 버그가 마침내 잡힌 것이다.

이 마지막 버그 픽스는 팀에게 중요한 교훈을 남겼다. 아무리 위대한 아키텍처도, 사소해 보이는 하나의 엣지 케이스에 의해 무너질 수 있다는 것. 그리고 그 엣지 케이스를 발견하고 해결하는 힘은, 결국 전 세계 개발자 커뮤니티와의 긴밀한 소통과 협력에서 나온다는 사실이었다. 이제 그들은 정말로, 세상에 나아갈 준비를 마쳤다.