스키마가 자주 바뀌는 상황에서 문서형 데이터베이스가 왜 적합한지 배우세요: 유연한 스키마, 빠른 반복, 자연스러운 JSON 저장 방식과 고려할 트레이드오프.

문서형 데이터베이스는 데이터를 자체 포함된 ‘문서’로 저장하며, 보통 JSON 유사 형식을 사용합니다. 하나의 비즈니스 객체를 여러 테이블에 흩어놓는 대신, 단일 문서가 필드, 하위 필드, 배열까지 한꺼번에 담을 수 있습니다. 이는 많은 애플리케이션이 코드 내에서 데이터를 표현하는 방식과 유사합니다.
users 컬렉션이나 orders 컬렉션).같은 컬렉션의 문서들이 반드시 동일한 모양일 필요는 없습니다. 어떤 user 문서는 12개 필드를, 다른 문서는 18개 필드를 가질 수 있고 둘 다 공존할 수 있습니다.
사용자 프로필을 상상해보세요. 처음에는 name과 email만 있습니다. 다음 달에는 마케팅이 preferred_language를 원합니다. 이후 고객 성공팀이 timezone과 subscription_status를 요청합니다. 나중에는 social_links(배열)와 privacy_settings(중첩 객체)를 추가합니다.
문서형 데이터베이스에서는 보통 새 필드를 바로 쓰기 시작할 수 있습니다. 오래된 문서는 필요할 때까지 그대로 둘 수 있고, 원하면 백필할 수 있습니다(혹은 하지 않을 수도 있습니다).
이 유연성은 제품 작업 속도를 높여줄 수 있지만 책임을 애플리케이션과 팀에게 옮깁니다. 명확한 규약, 선택적 검증 규칙, 신중한 쿼리 설계가 필요하지 않으면 데이터가 엉망이 될 수 있습니다.
다음으로는 왜 어떤 모델이 자주 바뀌는지, 유연한 스키마가 마찰을 줄이는 방법, 문서가 실제 앱 쿼리와 어떻게 매핑되는지, 그리고 문서 저장소를 관계형 대신(또는 하이브리드로) 선택할 때 따져야 할 트레이드오프를 살펴보겠습니다.
데이터 모델은 거의 정지하지 않습니다. 제품이 정지하지 않기 때문입니다. ‘단지 사용자 프로필을 저장’하는 것으로 시작한 기능은 곧 선호도, 알림, 결제 메타데이터, 디바이스 정보, 동의 플래그 등 초기 버전에는 없던 수십 가지 세부로 확장됩니다.
대부분의 모델 변화는 학습의 결과입니다. 팀은 다음과 같은 경우 필드를 추가합니다:
이러한 변화는 보통 점진적이고 빈번합니다—작은 추가들이며 공식적인 ‘대규모 마이그레이션’으로 예약하기 어렵습니다.
실제 데이터베이스는 히스토리를 포함합니다. 오래된 레코드는 작성 당시의 형태를 유지하고, 새로운 레코드는 최신 형태를 채택합니다. 예를 들어 marketing_opt_in이 존재하기 전 생성된 고객, delivery_instructions가 지원되기 전 생성된 주문, 새 source 필드가 정의되기 전 기록된 이벤트 등이 있을 수 있습니다.
따라서 하나의 모델을 ‘변경’하는 것이 아니라 여러 버전을 동시에 지원하는 것이며, 때로는 몇 달간 지속됩니다.
여러 팀이 병렬로 배포하면 데이터 모델은 공유되는 표면 영역이 됩니다. 결제팀은 사기 신호를 추가하고, 성장팀은 어트리뷰션 데이터를 추가할 수 있습니다. 마이크로서비스 환경에서는 각 서비스가 서로 다른 필요를 가진 ‘고객’ 개념을 저장하고, 그 필요는 독립적으로 진화합니다.
조율이 없으면 ‘완벽한 단일 스키마’는 병목이 됩니다.
외부 시스템은 부분적으로 알려진, 중첩되었거나 일관성이 없는 페이로드를 보낼 때가 많습니다: 웹훅 이벤트, 파트너 메타데이터, 폼 제출, 디바이스 텔레메트리 등. 중요한 부분을 정규화하더라도, 감사나 디버깅, 미래 사용을 위해 원래 구조를 유지하고 싶을 때가 많습니다.
이런 모든 요인은 변경을 관용하는 저장소로 팀을 이끕니다—특히 빠른 배포 속도가 중요할 때.
제품이 아직 형태를 찾는 동안 데이터 모델은 거의 ‘완성’ 상태가 아닙니다. 새 필드가 생기고 기존 필드는 선택적이 되며 고객마다 약간 다른 정보가 필요할 수 있습니다. 문서형 데이터베이스는 이런 시점에서 각 변경을 매번 데이터베이스 마이그레이션 작업으로 만들지 않고 데이터를 진화시킬 수 있게 해주기 때문에 인기가 있습니다.
JSON 문서에서는 새 속성을 추가하는 것이 새 레코드에 그 속성을 쓰는 것만큼 간단할 수 있습니다. 기존 문서는 백필할 때까지 그대로 둘 수 있습니다. 즉, 새 선호 설정을 수집하는 작은 실험은 스키마 변경, 배포 창, 백필 작업을 조율하지 않아도 바로 학습을 시작할 수 있습니다.
때로는 실제로 변형(variant)이 존재합니다: ‘무료’ 계정은 설정이 적고 ‘엔터프라이즈’ 계정은 설정이 많거나, 제품 유형마다 추가 속성이 필요할 수 있습니다. 문서형 데이터베이스에서는 같은 컬렉션의 문서가 서로 다른 모양을 가져도 애플리케이션이 이를 해석할 줄 알면 허용될 수 있습니다.
강제적으로 모든 것을 하나의 엄격한 구조에 맞추는 대신 다음을 유지하세요:
id, userId, createdAt 같은)유연한 스키마가 ‘규칙 없음’을 의미하는 것은 아닙니다. 흔한 패턴은 누락된 필드를 ‘기본값 사용’으로 취급하는 것입니다. 애플리케이션은 읽기 시점에 합리적인 기본값을 적용하거나 쓰기 시점에 설정할 수 있으므로 오래된 문서도 올바르게 동작합니다.
기능 플래그는 일시적 필드와 부분적 롤아웃을 도입하는 경우가 많습니다. 유연한 스키마는 소규모 코호트로 변경을 배포하고, 플래그된 사용자에 대해서만 추가 상태를 저장하며, 스키마 작업에 막히지 않고 빠르게 반복할 수 있게 해줍니다.
많은 제품 팀은 ‘사용자가 화면에서 보는 것’이라는 관점으로 생각합니다. 프로필 페이지, 주문 상세, 프로젝트 대시보드—각각은 보통 예측 가능한 모양을 가진 단일 앱 객체에 대응합니다. 문서형 데이터베이스는 해당 객체를 단일 JSON 문서로 저장할 수 있게 해, 애플리케이션 코드와 저장소 간 변환을 크게 줄여 그 사고 모델을 지원합니다.
관계형 테이블에서는 동일한 기능이 여러 테이블, 외래키, 조인 로직으로 분리되는 경우가 많습니다. 그 구조는 강력하지만, 앱이 이미 중첩 객체로 데이터를 보유하고 있을 때는 불필요한 의례처럼 느껴질 수 있습니다.
문서형 데이터베이스에서는 객체를 거의 그대로 지속할 수 있습니다:
user 문서가 User 클래스/타입에 대응project 문서가 Project 상태 모델에 대응변환이 줄어들면 매핑 버그가 줄고 필드가 바뀔 때 더 빨리 반복할 수 있습니다.
실제 앱 데이터는 평면(flat)인 경우가 드뭅니다. 주소, 설정, 알림 설정, 저장된 필터, UI 플래그 등은 자연스럽게 중첩됩니다.
부모 문서 안에 중첩 객체를 저장하면 관련 값이 가깝게 유지되어 ‘한 레코드 = 한 화면’ 쿼리에 유리합니다: 문서 하나를 가져오면 뷰 하나를 렌더링할 수 있습니다. 이는 조인과 그로 인한 성능 이슈를 줄여줍니다.
각 기능 팀이 자신의 문서 형태를 소유하면 책임이 명확해집니다: 기능을 배포하는 팀이 그 데이터 모델도 진화시킵니다. 마이크로서비스나 모듈형 아키텍처에서 독립적 변경이 잦을 때 이는 잘 작동합니다.
문서형 데이터베이스는 작은 데이터 추가가 대개 조율된 ‘중단(차단)’ 데이터베이스 변경을 필요로 하지 않기 때문에 자주 배포하는 팀에 적합한 경우가 많습니다.
프로덕트 매니저가 ‘속성 하나만 더 추가’하자고 하면(예: preferredLanguage나 marketingConsentSource) 문서 모델은 보통 그 필드를 즉시 쓰기 시작하게 해줍니다. 항상 마이그레이션을 예약하거나 테이블을 잠그거나 여러 서비스에 걸쳐 릴리스 창을 협상할 필요가 없습니다.
이는 스프린트를 차단할 수 있는 작업 수를 줄입니다: 데이터베이스는 애플리케이션이 진화할 때도 계속 사용 가능합니다.
JSON 유사 문서에 선택적 필드를 추가하는 것은 일반적으로 역호환적입니다:
이 패턴은 배포를 더 안정적으로 만듭니다: 먼저 쓰기 경로를 롤아웃하고(새 필드 저장 시작), 이후 읽기 경로와 UI를 업데이트해도 됩니다—모든 기존 문서를 즉시 업데이트할 필요는 없습니다.
실제 시스템은 클라이언트를 한 번에 모두 업그레이드하지 않습니다. 다음과 같은 상황이 있을 수 있습니다:
문서형 데이터베이스에서는 필드를 추가적이고 선택적으로 취급해 ‘혼합 버전’을 설계하는 경우가 많습니다. 최신 라이터는 데이터를 추가해도 구버전 리더가 이를 깨뜨리지 않습니다.
실무적 배포 패턴은 다음과 같습니다:
이 접근법은 속도를 유지하면서 데이터베이스 변경과 애플리케이션 릴리스 사이의 조율 비용을 줄입니다.
팀들이 문서형 데이터베이스를 좋아하는 이유 중 하나는 애플리케이션이 실제로 ‘읽는 방식’에 맞춰 데이터를 모델링할 수 있기 때문입니다. 개념을 여러 테이블에 흩어놓고 나중에 다시 조립하는 대신, 보통 하나의 장소에 ‘전체’ 객체(주로 JSON 문서)를 저장할 수 있습니다.
비정규화는 일반적인 쿼리를 단일 문서 읽기로 해결하기 위해 관련 필드를 중복하거나 임베드하는 것을 의미합니다.
예를 들어 주문 문서에 고객 스냅샷 필드(구매 시점의 이름, 이메일)와 중첩된 라인 아이템 배열을 포함하면 “최근 10건의 주문 보기” 같은 동작이 빠르고 단순해집니다. UI가 페이지를 렌더링하기 위해 여러 조회를 할 필요가 없어집니다.
화면이나 API 응답에 필요한 데이터가 한 문서에 있을 때, 보통 다음을 얻습니다:
이는 특히 피드, 프로필, 장바구니, 대시보드 같은 읽기 중심 경로에서 지연을 줄여줍니다.
임베딩이 보통 도움이 되는 경우:
참조가 더 나은 경우:
보편적으로 ‘최고’인 문서 형태는 없습니다. 한 쿼리에 최적화된 모델이 다른 쿼리를 느리게 하거나(또는 업데이트를 더 비용 높게 만들 수 있음) 합니다. 가장 신뢰할 수 있는 접근법은 실제 쿼리—앱이 실제로 무엇을 가져와야 하는지—에서 시작해 그 읽기 경로에 맞게 문서를 설계하고 사용량이 진화함에 따라 모델을 재검토하는 것입니다.
스키마 온 리드는 모든 필드와 테이블 형태를 미리 정의하지 않아도 데이터를 저장할 수 있다는 뜻입니다. 대신 애플리케이션(또는 분석 쿼리)이 읽을 때 각 문서의 구조를 해석합니다. 실제로는 preferredPronouns나 중첩된 shipping.instructions 같은 필드를 스키마 마이그레이션을 조율하지 않고 도입할 수 있게 해줍니다.
대부분의 팀은 여전히 ‘기대되는 형태’를 염두에 둡니다—다만 이를 나중에, 더 선택적으로 강제합니다. 어떤 고객 문서는 phone을 가질 수 있고 다른 문서는 없을 수 있습니다. 오래된 주문은 discountCode를 문자열로 저장하고, 새 주문은 더 풍부한 discount 객체를 저장할 수 있습니다.
유연성이 혼돈을 의미할 필요는 없습니다. 일반적인 방식:
id, createdAt, status 같은 핵심 필드를 요구하고 고위험 필드의 타입을 제한작은 일관성 규칙이 큰 효과를 냅니다:
camelCase, ISO-8601 타임스탬프)schemaVersion: 3)로 리더가 오래된 형태와 새 형태를 안전하게 처리하게 함모델이 안정되기 시작하면—대개 핵심 필드가 무엇인지 학습한 후—그 필드들과 중요한 관계에 대해 더 강력한 검증을 도입하세요. 실험적 필드나 선택적 필드는 유연하게 두어 데이터베이스가 끊임없는 반복을 지원하도록 합니다.
제품이 주간 단위로 바뀌면 단지 ‘현재’ 데이터 형태만 중요한 것이 아닙니다. 그 형태가 어떻게 도달했는지도 필요합니다. 문서형 데이터베이스는 자체 포함된 레코드를 저장하므로 히스토리를 유지하기에 자연스럽습니다. 과거를 모두 재작성해야 할 필요 없이 문서가 진화할 수 있습니다.
일반적 접근법은 변경을 이벤트 스트림으로 저장하는 것입니다: 각 이벤트는 새 문서로 추가되며 기존 행을 제자리에서 덮어쓰지 않습니다. 예: UserEmailChanged, PlanUpgraded, AddressAdded.
각 이벤트 문서는 그 시점의 전체 컨텍스트(누가 했는지, 무엇이 트리거였는지, 나중에 필요할 메타데이터)를 캡처할 수 있습니다.
이벤트 정의도 안정적이지 않습니다. source="mobile", experimentVariant 또는 paymentRiskSignals 같은 중첩 객체를 추가할 수 있습니다. 문서 저장소에서는 오래된 이벤트가 단순히 해당 필드를 생략하고, 새 이벤트는 이를 포함할 수 있습니다.
리더(서비스, 잡, 대시보드)는 누락된 필드를 안전하게 기본값으로 처리할 수 있으므로 수백만 건의 역사적 레코드를 백필하거나 마이그레이션할 필요 없이 새 속성을 도입할 수 있습니다.
소비자의 예측 가능성을 유지하려면 많은 팀이 각 문서에 schemaVersion(또는 eventVersion) 필드를 포함합니다. 이는 점진적 롤아웃을 가능하게 합니다:
무엇이 일어났는지에 대한 내구성 있는 히스토리는 감사 외에도 유용합니다. 분석팀은 어떤 시점의 상태를 재구성할 수 있고, 서포트 엔지니어는 이벤트를 재생하거나 버그를 일으킨 정확한 페이로드를 조사해 회귀를 추적할 수 있습니다. 몇 달 동안 이는 근본 원인 분석을 빠르게 하고 리포팅 신뢰도를 높입니다.
문서형 데이터베이스는 변경을 더 쉽게 만들지만 설계 작업을 없애지는 않습니다—그 작업의 성격을 옮길 뿐입니다. 선택하기 전에 무엇을 거래하는지 명확히 하는 것이 도움이 됩니다.
많은 문서형 데이터베이스가 트랜잭션을 지원하지만, 다중 문서(엔티티)에 걸친 트랜잭션은 제한적이거나 느리거나 관계형보다 비용이 높을 수 있습니다—특히 대규모에서 그렇습니다. 주문, 재고, 원장 항목을 함께 ‘전부 또는 전무’ 방식으로 업데이트해야 하는 핵심 워크플로가 있다면 데이터베이스가 이를 어떻게 처리하는지, 성능이나 복잡성 관점에서 비용이 어떤지 확인하세요.
필드가 선택적이면 팀이 프로덕션에서 동일한 개념의 여러 ‘버전’을 실수로 만들 수 있습니다(예: address.zip vs address.postalCode). 이는 하위 기능을 망가뜨리고 버그를 찾기 어렵게 만들 수 있습니다.
실용적 완화책은 핵심 문서 타입에 대한 공유 계약을 정의하고(가벼운 수준이라도) 결제 상태, 가격, 권한 같은 곳에는 선택적 검증 규칙을 추가하는 것입니다.
문서가 자유롭게 진화하면 분석 쿼리가 지저분해질 수 있습니다: 분석가는 여러 필드 이름과 누락 값을 처리하는 로직을 작성해야 합니다. 리포팅에 의존하는 팀은 다음과 같은 계획이 필요할 수 있습니다:
주문 안에 고객 스냅샷 같은 관련 데이터를 임베드하면 읽기는 빨라지지만 정보가 중복됩니다. 공유 데이터가 변경될 때 모두 업데이트할지, 과거 상태를 그대로 둘지, 일시적 불일치를 허용할지 결정해야 합니다. 그 결정은 의도적이어야 합니다—그렇지 않으면 미묘한 데이터 드리프트를 초래할 수 있습니다.
문서형 데이터베이스는 변화가 잦은 상황에 적합하지만 모델링, 네이밍, 검증을 지속적인 제품 작업으로 취급하는 팀에게 보상이 돌아갑니다—한 번 설정하고 잊는 것이 아닙니다.
문서형 데이터베이스는 데이터를 JSON 문서로 저장하므로 필드가 선택적이거나 자주 바뀌거나 고객/디바이스/제품 라인마다 달라질 때 자연스럽습니다. 모든 레코드를 동일한 엄격한 테이블 형태로 강제하는 대신 점진적으로 데이터 모델을 진화시키며 팀의 속도를 유지할 수 있습니다.
제품 데이터는 거의 정지하지 않습니다: 새로운 사이즈, 재질, 규정 플래그, 번들, 지역별 설명, 마켓플레이스별 필드 등이 계속 생깁니다. JSON 문서의 중첩 데이터로 ‘제품’은 핵심 필드(SKU, 가격)을 유지하면서 카테고리별 속성을 허용할 수 있어 몇 주간의 스키마 재설계 없이도 대응할 수 있습니다.
프로필은 작게 시작해 성장합니다: 알림 설정, 마케팅 동의, 온보딩 답변, 기능 플래그, 개인화 신호 등. 문서형 데이터베이스에서는 사용자가 서로 다른 필드 집합을 가져도 기존 읽기를 깨뜨리지 않습니다. 스키마 유연성은 실험으로 필드를 빠르게 추가/제거하는 애자일 개발에도 도움이 됩니다.
현대 CMS의 콘텐츠는 단순한 ‘페이지’가 아닙니다. 히어로 섹션, FAQ, 제품 캐러셀, 임베드 등 각기 다른 구조를 가진 블록과 컴포넌트의 혼합입니다. 페이지를 JSON 문서로 저장하면 편집자와 개발자가 모든 과거 페이지를 즉시 마이그레이션하지 않고도 새 컴포넌트 유형을 도입할 수 있습니다.
텔레메트리는 펌웨어 버전, 센서 패키지, 제조사에 따라 달라지는 경우가 많습니다. 문서형 데이터베이스는 이러한 진화하는 데이터 모델을 잘 처리합니다: 각 이벤트는 디바이스가 아는 것만 포함하고, 스키마 온 리드를 통해 분석 도구가 필드가 있을 때 해석하도록 합니다.
NoSQL과 SQL을 비교할 때, 이런 시나리오에서 문서형 데이터베이스가 더 적은 마찰로 빠른 반복을 제공하는 경향이 있습니다.
데이터 모델이 아직 확정되지 않았을 때는 “종이상 완벽”보다 “충분히 좋고 변경하기 쉬움”이 낫습니다. 다음 습관들이 데이터베이스를 정크 서랍으로 만들지 않으면서도 속도를 유지하게 도와줍니다.
각 기능을 시작할 때 프로덕션에서 예상되는 주요 읽기와 쓰기를 적어보세요: 렌더링할 화면, 반환할 API 응답, 자주 수행할 업데이트 등.
한 사용자 동작이 정기적으로 “주문 + 아이템 + 배송 주소”를 필요로 한다면 한 번의 조회로 그 읽기를 제공하는 문서를 모델링하세요. 다른 동작이 “상태별 모든 주문”을 필요로 한다면 해당 경로를 쿼리하거나 인덱싱할 수 있어야 합니다.
임베딩(중첩)은 다음에 유리합니다:
참조는 다음에 안전합니다:
혼합도 가능합니다: 읽기 속도를 위해 스냅샷을 임베드하고, 업데이트를 위해 진짜 원본에 대한 참조를 유지하세요.
스키마 유연성에도 불구하고, 의존하는 필드에 대해 타입, 필수 ID, 허용 상태 같은 가벼운 규칙을 추가하세요. schemaVersion(또는 docVersion) 필드를 포함해 애플리케이션이 오래된 문서를 우아하게 처리하고 시간이 날 때 점진적 마이그레이션을 하게 하세요.
마이그레이션을 일회성 작업이 아니라 정기적 유지보수로 취급하세요. 모델이 성숙해지면 작은 백필과 정리(미사용 필드 제거, 키명 변경, 비정규화 스냅샷 정리)를 일정에 넣고 영향 전후를 측정하세요. 간단한 체크리스트와 가벼운 마이그레이션 스크립트가 큰 도움이 됩니다.
문서형 데이터베이스와 관계형 데이터베이스 중 선택하는 것은 ‘무엇이 더 낫다’의 문제가 아니라 제품이 어떤 종류의 변화를 주로 겪느냐의 문제입니다.
데이터 형태가 자주 바뀌거나 레코드마다 다른 필드를 가질 수 있거나 팀이 매 스프린트마다 스키마 마이그레이션을 조율하지 않고 기능을 배포해야 한다면 문서형 데이터베이스가 강력한 선택입니다.
또한 주문(고객 정보 + 아이템 + 배송 메모)이나 사용자 프로필(설정 + 선호 + 디바이스 정보)처럼 애플리케이션이 ‘전체 객체’로 자연스럽게 작업하는 경우에 적합합니다.
관계형 데이터베이스는 다음에 강합니다:
팀의 작업이 주로 교차 테이블 쿼리와 분석 최적화라면 장기적으로 SQL이 더 간단한 집이 되는 경우가 많습니다.
많은 팀은 둘 다 사용합니다: 관계형은 ‘시스템 오브 레코드’(청구, 재고, 권한)에, 문서 저장소는 빠르게 진화하거나 읽기 최적화된 뷰(프로필, 콘텐츠 메타데이터, 제품 카탈로그)에 사용합니다. 마이크로서비스에서는 각 서비스가 경계에 맞는 저장소 모델을 선택할 수 있습니다.
하이브리드는 관계형 데이터베이스 내부에서도 가능합니다. 예를 들어 PostgreSQL은 JSON/JSONB를 통해 반구조화 필드를 강타입 컬럼과 함께 저장할 수 있어 트랜잭셔널 일관성과 진화하는 속성 저장을 동시에 제공합니다.
스키마가 주단위로 바뀐다면 병목은 종종 전체 루프—모델 업데이트, API, UI, 마이그레이션(있다면)과 안전한 롤아웃—입니다. Koder.ai는 그런 반복을 위해 설계되었습니다. 기능과 데이터 형태를 채팅으로 설명하면 동작하는 웹/백엔드/모바일 구현을 생성하고 요구가 진화함에 따라 이를 다듬을 수 있습니다.
실무적으로 팀들은 종종 관계형 코어( Koder.ai의 백엔드 스택은 Go와 PostgreSQL)를 시작점으로 두고, 필요할 때 문서형 패턴(예: 유연한 속성을 위한 JSONB나 이벤트 페이로드)을 사용합니다. Koder.ai의 스냅샷과 롤백 기능은 실험적 데이터 형태를 빠르게 되돌려야 할 때도 도움을 줍니다.
헷갈릴 때는 커밋하기 전에 짧은 평가를 실행하세요:
비교할 때는 범위를 좁게 유지하고 시간박스를 설정한 다음, 어떤 모델이 더 적은 놀라움으로 배포를 돕는지 확인한 후 확장하세요. 자세한 평가 체크리스트는 /blog/document-vs-relational-checklist를 참조하세요.
문서형 데이터베이스는 각 레코드를 중첩 객체와 배열을 포함할 수 있는 JSON 유사 문서로 저장합니다. 하나의 비즈니스 객체를 여러 테이블로 나누는 대신, 보통 컬렉션(예: users, orders) 안에서 전체 객체를 한 번에 읽고 쓸 수 있습니다.
빠르게 움직이는 제품에서는 선호 설정, 결제 메타데이터, 동의 플래그, 실험용 필드 등 새로운 속성이 끊임없이 생깁니다. 유연한 스키마는 새 필드를 즉시 쓰기 시작할 수 있게 하고, 기존 문서는 그대로 둔 채 필요할 때만 백필(backfill)하면 되므로 작은 변경이 큰 마이그레이션 작업으로 번지지 않습니다.
그렇다고 스키마가 아예 없는 것은 아닙니다. 대부분 팀은 ‘기대되는 형태’를 유지하되 강제 방식이 더 후순위로 밀립니다. 일반적인 적용 방식은 다음과 같습니다:
이렇게 하면 유연성을 유지하면서도 혼란스럽고 일관성 없는 문서를 줄일 수 있습니다.
새 필드를 추가할 때는 보통 ‘추가적이고 선택적’으로 처리합니다:
이 패턴은 가동 중단이 필요한 대규모 마이그레이션 없이도 서로 다른 버전의 데이터를 함께 운영할 수 있게 해줍니다.
가장 흔한 읽기 경로를 기준으로 모델링하세요. 화면이나 API 응답에 ‘주문 + 아이템 + 배송지’가 필요하다면, 가능한 한 하나의 문서에 함께 저장해 여러 번의 조회를 줄이고 조인 비용을 피하도록 설계합니다. 이렇게 하면 읽기 중심 경로의 지연(latency)을 줄일 수 있습니다.
임베딩은 자식 데이터가 보통 부모와 함께 읽히고 크기가 한정적일 때 유용합니다(예: 최대 20개 아이템). 참조는 관련 데이터가 크거나 무한히 증가할 수 있거나 여러 부모가 공유하거나 자주 변경될 때 더 안전합니다.
또는 두 방식을 혼합할 수도 있습니다: 빠른 읽기를 위해 스냅샷을 임베드하고, 업데이트를 위해 원본에 대한 참조를 유지합니다.
‘필드를 하나 추가하는’ 배포를 더욱 역호환성 있게 만듭니다:
이 패턴은 여러 서비스나 구버전 모바일 클라이언트가 공존할 때 특히 유용합니다.
경량의 가드레일을 포함하세요:
id, createdAt, status)일반적인 방법은 변경을 이벤트 스트림으로 저장하는 것입니다: 각 이벤트를 새 문서로 추가(append-only)합니다. 예: UserEmailChanged, PlanUpgraded, AddressAdded.
각 이벤트 문서는 그 순간의 전체 컨텍스트(누가, 무엇을, 어떤 메타데이터와 함께)를 담을 수 있습니다. 또한 eventVersion/schemaVersion 같은 버전 필드를 포함해 소비자가 점진적으로 마이그레이션할 수 있게 합니다.
주요 고려사항은 다음과 같습니다:
많은 팀은 핵심 ‘시스템 오브 레코드’에는 관계형을, 빠르게 진화하거나 읽기 최적화된 모델에는 문서형을 사용하는 하이브리드를 채택합니다.
camelCaseschemaVersion/docVersion 필드이런 조치들은 address.zip vs address.postalCode 같은 표기 불일치를 방지합니다.