Optimistic UI 도입으로 즉각적인 UI 반응성을 확보했으나, 실제 서버 상태와의 불일치 레이스 컨디션(Race Condition) 발생 가능성 내포
다섯 번의 빠른 클릭 시, 동일한 불리언(Boolean) 값에 대한 덮어쓰기 요청이 동시성 이슈(Concurrency Issue)를 일으켜 UI와 서버 상태 불일치 초래
아이템별 요청 상태 추적을 통해 중복 요청을 방지하고, UI와 서버 간의 데이터 정합성(Data Consistency)을 유지하는 해결책 제시
Optimistic UI는 사용자 경험 향상을 위해 즉각적인 UI 피드백(Immediate UI Feedback)을 제공하는 패턴이다. 사용자가 액션을 취하는 즉시 UI 상태를 변경하여 로딩 스피너나 지연 없이 반응하는 것처럼 보이게 한다. 하지만 이 방식은 서버 응답과 무관하게 클라이언트 상태를 먼저 업데이트하기 때문에, 여러 요청이 동시에 발생할 경우 데이터 불일치(Data Inconsistency)라는 치명적인 버그를 야기할 수 있다. 특히, 여러 항목에 대한 동시 업데이트 시 각 요청의 완료 순서가 불확실하여 예상치 못한 상태로 이어질 위험이 존재한다.
본문에서 제시된 레이스 컨디션은 동일한 데이터 항목에 대한 동시 요청으로 인해 발생한다. 사용자가 매우 빠르게 여러 번 클릭할 경우, 각 요청이 서버에 도달하는 순서가 달라지면서 마지막 요청의 결과만이 반영되거나 예상치 못한 상태가 된다. 이를 해결하기 위해 아이템별 요청 상태 추적(Per-item Request Tracking) 메커니즘을 도입한다. 각 아이템별로 요청이 진행 중임을 나타내는 상태(예: `pendingId`)를 관리하고, 해당 요청이 완료될 때까지 추가적인 액션을 비활성화(Disable)하여 중복 요청(Duplicate Request)을 원천적으로 차단한다. 이는 전역적인 UI 잠금(Global Lock) 대신 세분화된 상태 관리(Granular State Management)를 통해 사용자 경험을 유지하면서도 데이터 정합성을 보장하는 핵심 전략이다.
React의 `useOptimistic` 훅은 임시 상태(Temporary State)를 기존 상태 위에 덧씌우는 방식으로 작동하며, 비동기 작업이 완료되면 자동으로 이전 상태로 복귀한다. 따라서 일반적인 토글(Toggle) 작업의 경우, 서버 요청 실패 시 별도의 롤백 로직 없이도 UI는 자동으로 이전 상태로 돌아간다. 이는 상태 변경을 이벤트 로그로 기록하는 이벤트 소싱(Event Sourcing)과는 다른 접근 방식이다. 다만, 클라이언트에서만 존재하는 임시 데이터(예: 새로 추가된 댓글의 임시 ID)가 서버에서 실패할 경우, 해당 데이터를 명시적으로 제거하는 별도의 처리(Catch Block에서의 상태 초기화)가 필요할 수 있다. 즉, `useOptimistic`은 상태의 일시적 불일치를 관리하는 데 초점을 맞춘다.
Next.js 16에서는 서버 쓰기 작업 후 클라이언트 측 캐시를 관리하기 위한 세 가지 방법(`updateTag`, `revalidateTag`, `revalidatePath`)을 제공한다. 본문에서는 사용자가 실시간으로 상호작용하는 체크박스 업데이트의 경우, 즉각적인 반영이 중요하므로 `updateTag`를 사용하는 것이 적합하다고 설명한다. 이는 해당 태그와 관련된 캐시를 즉시 만료시켜 사용자가 현재 보고 있는 페이지의 데이터가 최신 상태로 유지되도록 한다. 반면, 실시간 업데이트가 중요하지 않은 사이드바 통계 등에는 `revalidateTag`를 사용하여 비동기적으로 캐시를 업데이트하는 것이 효율적이다. 또한, `revalidateTag` 함수의 변경 사항(두 번째 인자 추가)에 대한 주의를 환기시킨다.