SSOT (Single Source of Truth)

sinjuk1·2025년 5월 18일

개요

상품 목록 미션을 하면서 서버의 장바구니 목록과 로컬에서의 내 장바구니 목록이 일치하지 않은 문제가 있었다.

로컬에서 내가 콜라를 장바구니에 담았다면 서버에도 콜라가 담긴다.
그러나 로컬에서 재실행 될 때 서버에서의 장바구니 현황을 내 로컬 장바구니 현황에 동기화시키지 않아서 다음과 같은 문제가 발생했었다. 서버에는 콜라가 있어서 장바구니에서 뺄 수 있는데 로컬에는 콜라가 없어서 장바구니에서 뺄 수 없었다.

이런 문제를 겪지 않기 위해서는 SSOT라는 개념이 중요하다.

1. SSOT (Single Source of Truth)란 무엇일까?

SSOT는 말 그대로 '하나의 진실된 출처'라는 뜻이다. 애플리케이션에서 특정 데이터나 정보의 정확하고 신뢰할 수 있는 유일한 출처가 존재해야 한다는 원칙을 의미한다. 프로그래밍에서는 모든 데이터가 한 곳에서만 제어되거나 편집되고, 다른 곳에서는 이 주된 출처를 단순히 참조만 하는 것을 말한다. 즉, '진실된' 상태나 데이터는 단 한 곳에서만 관리되어야 한다는 생각이다.

예를 들어 React의 경우, 단방향 데이터 흐름에서 부모 컴포넌트의 상태가 자식 컴포넌트에게 props로 전달될 때 그 부모 컴포넌트의 상태가 해당 데이터의 SSOT로 간주될 수 있다.

2. SSOT, 왜 이렇게 중요할까?

SSOT 원칙을 지키는 것은 안정적이고 예측 가능한 애플리케이션을 만드는 데 필수적이다.

  • 데이터의 일관성 보장: 데이터가 여러 곳에 중복되어 저장될 때 발생할 수 있는 불일치 문제를 근본적으로 방지한다.

  • 항상 최신 상태 유지: 모든 데이터가 한 곳에서만 관리되므로, 언제나 최신 정보를 참조할 수 있다.

  • 유지 관리와 디버깅 용이: 데이터의 흐름이 명확해지면서 어디서 상태가 변경되었는지 추적하기 쉽다. 이는 코드의 유지 보수와 버그를 찾는 디버깅 시간을 크게 줄여준다.

  • 예측 가능성: 데이터 흐름이 단방향으로 통제되므로 애플리케이션의 동작을 예측하기 쉬워진다.

  • 오류 및 버그 발생 가능성 감소: 복잡한 애플리케이션에서 데이터 불일치로 인한 오류나 버그 발생 가능성을 현저히 줄여준다.

3. '반대로 생각하기'로 알아보는 SSOT 원칙 (React 중심)

찰리 멍거의 '반대로 생각하기' 원칙은 복잡한 문제를 해결할 때 "무엇을 하지 말아야 할까?"를 먼저 생각함으로써 문제의 본질에 더 쉽게 접근할 수 있도록 돕는다. React에서 SSOT 원칙을 지키는 방법을 이해하기 위해, 이 원칙을 어기는 나쁜 관행들과 그로 인해 발생하는 문제 상황에 집중해서 설명해 보려고 한다.

다음은 React에서 SSOT 원칙을 어기는 대표적인 나쁜 관행들이다.

자세한 상황과 실행 예제 코드는 리액트 공식문서에서 확인할 수 있다.

3.1 불필요한 상태 / 다른 상태나 props로부터 파생 가능한 데이터를 상태로 저장하는 것

상황: firstName과 lastName 상태가 있는데, 이 둘을 합쳐서 fullName이라는 별도의 상태로 또 저장하는 경우다.

자세한 코드와 실행할 수 있는 예제는 리액트 공식문서에서 더 살펴볼 수 있다.

3.2 데이터 중복 / 전체 객체를 상태로 저장하는 것

상황: 여러 항목(items)의 목록 상태(items)를 가지고 있으면서, 동시에 선택된 항목 객체 전체를 selectedItem이라는 별도의 상태로 저장하는 경우다.

자세한 코드와 실행할 수 있는 예제는 리액트 공식문서에서 더 살펴볼 수 있다.

3.3 모순된 상태

상황: 서로 배타적인 상태를 나타내는 여러 개의 boolean 상태 변수를 사용하는 경우다. 예: isSending과 isSent 상태 변수를 동시에 사용하는 경우.

자세한 코드와 실행할 수 있는 예제는 리액트 공식문서에서 더 살펴볼 수 있다.

3.4 깊게 중첩된 상태

상황: 데이터 구조가 행성 > 대륙 > 국가와 같이 깊게 중첩된 객체나 배열 형태로 상태를 관리하는 경우다.

자세한 코드와 실행할 수 있는 예제는 리액트 공식문서에서 더 살펴볼 수 있다.

3.5 상태 끌어올리기를 하지 않는 경우

상황: 두 개 이상의 컴포넌트가 동기화되어야 하는 동일한 상태를 각자 로컬 상태로 가지고 있는 경우다.

  • 예: 여러 개의 패널 컴포넌트가 각각 자신이 열려 있는지 아닌지에 대한 상태(isActive)를 가지고 있고, 요구사항이 "한 번에 하나의 패널만 열려야 한다"로 바뀐 경우.

상태 끌어올리기는 props 전달을 늘릴 수 있지만, 데이터의 흐름을 명확히 하고 SSOT를 지키는 데 필수적인 패턴이다. Context API나 children prop을 활용하면 props drilling을 완화하면서도 SSOT를 유지할 수 있다.

  • 다만 "Context API를 사용하면 SSOT를 보장할 수 있을까?" 라는 질문에는 아니라고 생각한다.
  • Context API는 상태를 여러 컴포넌트에 효율적으로 분배하는 도구이지, 특정 상태의 유일한 소유자(SSOT)가 되지는 않는다고 생각한다. 여전히 Context에 제공되는 상태 자체는 외부(대부분 부모 컴포넌트의 상태)로부터 와야 하며, 그 상태의 진정한 SSOT는 Context를 감싸고 있는 컴포넌트가 된다. Context는 상태를 '만드는' 곳이 아니라 '제공하는' 통로인 셈이다.

자세한 코드와 실행할 수 있는 예제는 리액트 공식문서에서 더 살펴볼 수 있다.

4. 생각해보기

4.1 클라이언트 관점을 벗어나서 생각해보아요. API 서버는 언제나 진실의 원천인가?

API 서버는 맥락과 상황에 따라 진실의 원천이 될 수도 있고, 아닐 수도 있다.

클라이언트(프론트엔드 애플리케이션)의 관점에서 볼 때, API 서버는 보통 가장 직접적인 SSOT로 인식된다. 클라이언트는 API 서버가 제공하는 데이터가 가장 정확하고 최신이라고 가정하고 이를 기반으로 UI를 렌더링하고 비즈니스 로직을 수행한다.

하지만 API 서버의 내부 구조나 더 큰 시스템 아키텍처를 고려하면, API 서버 역시 다른 시스템으로부터 데이터를 가져오는 '중간 다리' 역할을 하는 경우가 많다. 이런 경우 API 서버는 진실의 원천이 아니라고 생각한다.

  • 예를 들어 다른 마이크로서비스, 레거시 시스템, 또는 외부 서비스(예: 결제 게이트웨이, CDN) 등으로부터 데이터를 가져 오면, 해당 데이터베이스나 외부 서비스가 특정 데이터에 대한 진정한 SSOT일 수 있다.

4.2 React 에서 data fetching 시에 SSOT 를 지키기 위해, 어떤 방법들을 실제로 사용하고 있을까?

1. 중앙 집중식 상태 관리 라이브러리: Redux, Zustand, Jotai 등

서버에서 가져온 데이터를 이들 라이브러리가 제공하는 전역 스토어에 저장한다. 여러 컴포넌트가 동일한 데이터를 필요로 할 때, 직접 API 호출을 하는 대신 스토어에서 데이터를 구독하여 사용한다. 데이터가 업데이트되면 스토어만 변경되고, 스토어를 구독하는 모든 컴포넌트가 자동으로 재렌더링되어 일관된 UI를 유지한다.

2. 데이터 페칭 및 캐싱 라이브러리: React Query (TanStack Query), SWR 등

이 라이브러리들은 비동기 서버 상태 관리에 특화되어 있으며, 클라이언트 측에서 서버 데이터의 SSOT를 유지하는 데 매우 효과적이다.

  • 자동 캐싱 및 데이터 중복 제거: 한 번 가져온 데이터를 캐시하고, 여러 컴포넌트가 동일한 데이터를 요청할 때 중복된 API 호출을 방지하며 캐시된 데이터를 제공한다.
  • stale-while-revalidate 전략: 캐시된(stale) 데이터를 즉시 보여주고, 동시에 백그라운드에서 최신 데이터를 가져와 UI를 업데이트한다. 사용자 경험 측면에서 매우 효율적이다.

4.3 서버 데이터는 항상 최신이어야 할까? 최신이 아닌 데이터를 유지해야 하는 경우가 있을까? 이럴 때 어떻게 구현하면 좋을까?

항상 실시간으로 최신일 필요는 없다.

1. 최신이 아닌 데이터를 유지해야 하는 경우

데이터의 변화 빈도가 낮은 경우:
예시: 제품 카테고리 목록, 국가 목록, 사용자 프로필 정보(자주 변경되지 않는 부분), 웹사이트 설정 등.

데이터의 즉각적인 최신성보다 사용자 경험이 중요한 경우 :
예시: 블로그 게시물 목록, 뉴스 피드, 댓글 목록 등.

성능 최적화를 위해 의도적으로 지연시키는 경우:
예시: 대규모 데이터 리스트의 페이지네이션, 필터링 결과, 검색 결과 등.

집계/보고서성 데이터:
예시: 어제의 매출 보고서, 주간 사용자 통계 등.

2. 최신이 아닌 데이터를 유지할 때의 구현 방법

클라이언트 사이드 캐싱 (with TTL/Stale-While-Revalidate):

  • 구현: React Query나 SWR과 같은 라이브러리를 적극적으로 사용합니다.

클라이언트 사이드 지속성 (Persistent State):

  • 구현: localStorage, sessionStorage 등을 사용하여 데이터를 로컬에 저장하고 애플리케이션 시작 시 로드한다.

낙관적 UI 업데이트 (Optimistic UI Updates):

  • 구현: 사용자가 데이터를 변경하는 액션(예: '좋아요' 버튼 클릭, 항목 추가)을 수행했을 때, 서버의 응답을 기다리지 않고 즉시 UI를 업데이트한다. 이후 서버 응답에 따라 성공하면 그대로 두고, 실패하면 이전 상태로 롤백한다.
profile
함께 성장하는 것을 지향합니다.

1개의 댓글

comment-user-thumbnail
2025년 5월 30일

동기화라는 개념이 정말 중요한것 같아요!
잘 보고갑니다 ㅎㅎ

답글 달기