Software Design

Single Source of Truth, 같은 정보를 두 곳에 두면 반드시 어긋난다

배포 직전에 발견되는 버그 중에는 유형이 정해져 있는 것들이 있습니다. 프론트엔드는 할인율을 10%로 계산했는데 백엔드는 15%로 계산한다든가, 문서에는 필수라고 적힌 필드가 실제 API에서는 사라져 있다든가. 코드 자체는 둘 다 멀쩡합니다. 문제는 같은 정보가 두 곳에…

이석우iOS Developer5분 읽기
Single Source of Truth, 같은 정보를 두 곳에 두면 반드시 어긋난다 대표 이미지

배포 직전에 발견되는 버그 중에는 유형이 정해져 있는 것들이 있습니다. 프론트엔드는 할인율을 10%로 계산했는데 백엔드는 15%로 계산한다든가, 문서에는 필수라고 적힌 필드가 실제 API에서는 사라져 있다든가. 코드 자체는 둘 다 멀쩡합니다. 문제는 같은 정보가 두 곳에 존재했고, 한쪽만 바뀌었다는 것뿐이에요.

이런 사고를 구조적으로 막는 원칙이 Single Source of Truth, 줄여서 SSOT입니다. 이름은 거창하지만 내용은 한 문장입니다. 모든 정보 조각에는 권위 있는 원본이 정확히 하나만 있어야 한다.

Single Source of Truth 원칙을 나타내는 하나의 데이터베이스 원본과 파생 화면들 히어로 이미지
원본은 하나, 나머지는 전부 파생이어야 합니다

핵심 요약입니다.

  1. SSOT는 “정보의 원본이 하나”라는 원칙이다. 저장소가 하나여야 한다는 뜻이 아니다
  2. 중복된 정보는 반드시 어긋난다. 문제는 어긋나는 순간이 아니라, 어느 쪽이 맞는지 아무도 모르게 되는 순간이다
  3. 복사가 필요하면 “파생”으로 만들어라. 손으로 두 번 쓰는 대신 원본에서 자동 생성한다
  4. 캐시·복제본은 SSOT 위반이 아니다. 원본이 어디인지, 갱신 방향이 어느 쪽인지만 분명하면 된다

중복은 왜 반드시 어긋나는가

정보를 두 곳에 두는 순간, 둘을 같게 유지하는 책임은 사람에게 넘어옵니다. 도구도 컴파일러도 그 약속을 모르니까요. 수정할 때마다 “이 값이 또 어디에 있더라”를 기억해야 하고 한 번이라도 잊으면 그때부터 두 값은 다른 길을 갑니다.

더 고약한 건 어긋난 다음입니다. 상수가 코드 두 곳에서 다른 값으로 발견됐을 때, 어느 쪽이 맞는지는 코드만 봐서는 알 수 없습니다. git 히스토리를 뒤지고, 당시 담당자를 찾고, 기획 문서를 확인하는 고고학이 시작되죠. SSOT가 무너졌을 때 치르는 진짜 비용은 버그 수정이 아니라 이 “어느 게 진실인가”를 판정하는 시간입니다.

흔한 사례를 몇 개만 짚어 볼게요.

  • 상수 중복: 최대 업로드 크기 10MB가 프론트 검증, 백엔드 검증, 에러 메시지 문자열에 각각 하드코딩
  • 검증 로직 중복: 이메일 형식 검사를 클라이언트와 서버가 서로 다른 정규식으로 수행
  • 문서와 코드: API 명세 문서 따로, 실제 구현 따로. 몇 달 뒤 문서는 소설이 된다
  • DB와 캐시: 원본 갱신 후 캐시 무효화를 빠뜨려 옛 데이터가 계속 서빙
  • 디자인과 코드: 디자인 시안의 색상 값과 앱에 하드코딩된 색상 값이 미묘하게 다름

형태는 달라도 구조는 같습니다. 원본이 둘이고, 동기화가 사람 손에 달려 있다는 것.

SSOT 없이 수동 동기화로 값이 어긋나는 구조와 공용 원본에서 파생하는 구조 비교 다이어그램
사람의 기억에 맡긴 동기화와 원본에서 파생시키는 구조의 차이입니다

원칙은 “복사 금지”가 아니라 “파생”이다

SSOT를 오해하면 “정보를 한 곳에만 저장하라”로 읽힙니다. 그러면 캐시도, 리드 레플리카도, 빌드 산출물도 전부 위반처럼 보이죠. 실제 원칙은 다릅니다. 여러 곳에 존재해도 된다. 다만 원본은 하나이고, 나머지는 전부 원본에서 파생되어야 한다.

파생을 만드는 대표적인 방법이 코드 생성입니다.

  • 스키마에서 타입 생성: OpenAPI 명세에서 클라이언트 타입·서버 스텁을 생성하면 명세가 원본이 되고 문서와 코드가 어긋날 길이 사라집니다
  • 디자인 토큰: 색상·타이포그래피를 토큰 파일 하나에 정의하고 iOS·Android·웹 코드로 각각 생성합니다
  • 공용 상수 모듈: 프론트와 백엔드가 같은 패키지에서 상수를 import하게 만들면 하드코딩 복사가 원천 차단됩니다
  • 문서를 코드에서 추출: 주석·타입에서 API 문서를 생성하면 문서는 항상 코드를 따라옵니다

공통점은 “사람이 두 번 쓰는” 지점을 “기계가 한 번 생성하는” 지점으로 바꾼다는 겁니다. 동기화 책임이 사람의 기억에서 빌드 파이프라인으로 옮겨가고 어긋나면 CI가 먼저 압니다.

캐시와 복제본도 같은 틀로 정리됩니다. 원본이 어디인지 선언되어 있고, 갱신이 원본→사본 한 방향으로만 흐르고, 사본이 낡을 수 있는 시간(TTL·무효화 전략)이 정의되어 있다면 SSOT는 지켜지고 있는 겁니다. 위반은 “복사본이 존재할 때”가 아니라 “복사본을 직접 수정하기 시작할 때” 일어납니다.

조직에도 같은 원칙이 적용된다

SSOT는 코드만의 이야기가 아닙니다. 마이크로서비스 설계에서 “이 데이터의 주인은 어느 서비스인가”를 정하는 일, 사내 위키·노션·슬랙에 흩어진 정책 문서 중 “공식 버전”을 지정하는 일이 전부 같은 문제예요.

특히 문서는 코드보다 어긋나기 쉽습니다. 컴파일러도 테스트도 없으니까요. 그래서 조직 차원의 SSOT는 도구보다 합의가 먼저입니다. “온보딩 절차의 원본은 위키의 이 페이지 하나이고, 다른 곳에는 링크만 둔다” 같은 규칙이요. 복사 대신 링크를 두는 것, 이게 문서 세계의 파생입니다.

스키마 원본에서 타입 문서 디자인 토큰을 자동 생성하는 코드 생성 파이프라인 일러스트
사람이 두 번 쓰는 지점을 기계가 한 번 생성하는 지점으로 바꿉니다

실전 체크리스트

내일부터 적용할 수 있는 판단 기준으로 정리하면 이렇습니다.

  1. 같은 값을 두 번째로 타이핑하는 순간이 신호다: 상수·검증 규칙·설정값을 복사하고 있다면, 공용 모듈로 옮기거나 생성으로 바꿀 수 있는지 먼저 검토하세요
  2. 원본을 선언하라: 캐시·복제·요약본 자체는 문제가 아닙니다. “원본은 여기”가 코드와 문서에 명시되어 있는지, 갱신이 한 방향인지 확인하세요
  3. 어긋남을 사람이 아니라 도구가 잡게 하라: 스키마 검증, 계약 테스트, 생성 코드의 diff 체크를 CI에 넣으면 동기화 실패가 머지 전에 드러납니다
  4. 완벽주의는 경계하라: 모든 중복을 없애겠다고 과도한 추상화를 만들면 그것대로 비용입니다. 자주 바뀌는 정보, 어긋났을 때 비싼 정보부터 SSOT를 세우는 게 순서입니다

리팩토링에서 말하는 DRY(Don’t Repeat Yourself) 원칙과 뿌리가 같습니다. DRY가 코드 로직의 중복을 겨눈다면, SSOT는 데이터와 지식의 중복까지 확장한 상위 원칙이라고 볼 수 있어요. 다음에 “이 값 저쪽에도 있는데 같이 고쳐야 하나?“라는 질문이 나오면, 그게 바로 SSOT를 세울 자리입니다.