과학은 일관성을 가진 세계를 보고, 본질은 정합성을 가진 세계를 본다.
데이터베이스 일관성(Consistency) vs 정합성(Consistency/Integrity)
1. 일관성(Consistency)
- 정의: 데이터베이스 시스템이 유효한 상태를 유지하도록 DB 엔진이 강제하는 데이터 규칙(ACID의 C)
- 특징: DB가 직접 보장할 수 있는 제약 조건을 위반하면 에러가 발생하고 해당 트랜잭션이 실패함.
- 예시: 데이터 타입(INT 등), NOT NULL, UNIQUE, FK, CHECK(나이 >= 0) 등의 제약 조건(Constraint)
2. 정합성(Integrity)
- 정의: 데이터 간의 관계와 상태가 비즈니스적으로 모순 없이 올바른 상태를 유지하는 것
- 특징: DB 제약 조건만으로 표현하기 어려운 비즈니스 규칙까지 포함함. 이러한 규칙이 깨져도 DB 자체에서는 정상적인 데이터로 판단할 수 있음.
- 예시: 동시성 문제로 인한 Lost Update(갱신 분실), 재고와 주문 수량의 불일치, 요청 처리 누락 등
3. 핵심 차이 및 결론
- DB의 제약 조건을 모두 만족하더라도 비즈니스 흐름상 데이터가 서로 모순될 수 있음.
- 따라서 DB 레벨의 일관성을 만족한다고 해서 반드시 비즈니스 데이터의 정합성까지 보장되는 것은 아님.
- 데이터 정합성의 본질: 데이터 간에 모순이 없는 상태. 즉, 비즈니스 흐름상 앞뒤 데이터의 논리와 맥락이 올바르게 연결되어 있는 상태를 의미함.
규칙의 주체 데이터베이스 시스템이 관리하는 규칙 개발자(비즈니스 로직)가 관리하는 규칙 검증 방식 DB 엔진이 제약 조건(FK, UNIQUE, CHECK 등)으로 자동 검증 개발자가 락(Lock)이나 동시성 제어 코드로 직접 검증 깨지는 상황 예시 나이 컬럼에 문자를 넣거나, 중복 이메일을 넣으려고 할 때 동시에 수정하다가 다른 사람의 결제 내역을 덮어씌워 지워버렸을 때
아키텍처 관점에서 보는 일관성(Consistency)의 종류
'일관성'이라는 단어는 사용하는 맥락에 따라 의미가 달라지므로 구분해서 이해해야 함.
1. ACID의 일관성 (Consistency)
- 개념: 트랜잭션 수행 전후로 데이터베이스가 정의한 제약 조건과 규칙을 만족하는 유효한 상태를 유지하는 것
- 특징: 트랜잭션이 DB를 하나의 유효한 상태에서 또 다른 유효한 상태로 변경하도록 함.
2. 강한 일관성 (Strong Consistency)
- 개념: 데이터가 변경된 이후 다른 사용자가 데이터를 읽었을 때 항상 최신 상태를 관찰할 수 있도록 보장하는 일관성 모델
- 특징: 분산 시스템에서는 여러 노드 사이의 동기화가 필요하기 때문에 성능이나 가용성 측면에서 추가적인 비용이 발생할 수 있음.
3. 최종적 일관성 (Eventual Consistency)
- 개념: 데이터 변경 이후 일정 시간 동안 일시적인 불일치를 허용하지만, 추가적인 변경이 없다면 시간이 지나면서 결국 동일한 상태에 도달하도록 하는 일관성 모델
- 특징: 즉각적인 일치를 포기하는 대신 처리량, 확장성, 가용성을 높일 수 있음.
- 발생 케이스:
- 여러 DB 또는 Replica 사이의 데이터 동기화 지연
- 서비스 간 이벤트 기반 데이터 동기화
- 메인 로직 처리 후 @Async나 메시지 큐를 이용해 후속 작업을 별도로 처리하는 경우
일관성과 애그리게이트(Aggregate) 설계의 관계
1. 하나의 트랜잭션에서 반드시 지켜야 하는 불변식 = 하나의 애그리게이트
애그리게이트를 나누는 핵심 기준은 단순히 생명주기가 같은가가 아니라, **"이 비즈니스 규칙을 하나의 트랜잭션 안에서 반드시 지켜야 하는가?"**임.
- 원칙: 여러 객체 사이의 불변식을 하나의 트랜잭션에서 원자적으로 보장해야 한다면 하나의 애그리게이트로 묶는 것을 우선적으로 고려함.
- 이유: 애그리게이트는 하나의 트랜잭션 일관성 경계(Transaction Consistency Boundary) 역할을 하기 때문임.
- 애그리게이트 내부의 불변식은 트랜잭션이 커밋되는 시점에 항상 만족되어야 함.
예시: 주문(Order)과 주문 항목(OrderItem)
다음과 같은 규칙이 있다고 가정함.
Order.totalPrice = 모든 OrderItem의 가격 × 수량의 합
주문 항목이 변경됐는데 주문 총액이 변경되지 않은 상태로 트랜잭션이 커밋되면 불변식이 깨짐.
따라서 주문과 주문 항목 사이의 이러한 규칙을 하나의 트랜잭션에서 반드시 보장해야 한다면, Order를 Aggregate Root로 하고 OrderItem을 내부 엔티티로 두는 방식이 자연스러움.
즉, 강한 불변식, 하나의 트랜잭션에서 보장, 하나의 애그리게이트라는 흐름으로 이해할 수 있음.
2. 즉시 맞지 않아도 되는 규칙 = 애그리게이트 분리 + 단계적/최종적 일관성
반대로 두 데이터가 항상 같은 순간에 변경될 필요가 없고 각자의 불변식을 독립적으로 지킬 수 있다면 서로 다른 애그리게이트로 분리할 수 있음.
- 원칙: 각 애그리게이트는 자신의 트랜잭션 안에서 자신의 불변식만 책임짐.
- 다른 애그리게이트와의 관계는 Application Service의 조율, 이벤트, 메시지 큐 등의 방식으로 연결할 수 있음.
- 이 과정에서 두 애그리게이트 사이에 일시적인 상태 차이가 존재할 수 있음.
- 중요한 것은 일시적인 차이 자체가 아니라 비즈니스적으로 허용되지 않는 불변식이 깨지는가임.
즉, 독립적인 불변식, 독립적인 트랜잭션, 서로 다른 애그리게이트로 볼 수 있음.
예시: TimeDeal과 TimeDealStock
TimeDeal과 TimeDealStock을 생각해 보면 두 객체의 상태 변화 기준이 다름.
TimeDeal의 주요 규칙
- 시작 시간이 되면 SCHEDULED → ACTIVE
- 종료 시간이 되면 ACTIVE → ENDED
- 관리자가 강제 중지하면 ACTIVE → STOPPED
TimeDealStock의 주요 규칙
- 구매 선점 시 available 감소
- 결제 확정 시 reserved → sold
- 결제 취소/만료 시 reserved → available
- 재고는 음수가 될 수 없음
즉, 두 객체가 서로 연관되어 있더라도 지켜야 하는 불변식 자체는 다를 수 있음.
예를 들어 재고가 감소할 때마다 반드시 같은 트랜잭션 안에서 TimeDeal의 상태까지 변경해야 하는 강한 불변식이 없다면, 두 객체를 하나의 애그리게이트로 묶을 필요는 없음.
TimeDealStock은 자신의 트랜잭션에서 available >= 0과 같은 재고 불변식을 보장함.
TimeDeal은 자신의 트랜잭션에서 허용된 상태 전이만 가능이라는 상태 불변식을 보장할 수 있음.
이후 재고가 0이 되었을 때 타임딜을 ENDED 상태로 변경해야 한다면 이벤트나 후속 로직을 통해 단계적으로 반영할 수 있음.
따라서 중요한 질문은 **"TimeDeal과 TimeDealStock이 관련되어 있는가?"**가 아니라 **"두 객체 사이에 하나의 트랜잭션에서 반드시 지켜야 하는 불변식이 존재하는가?"**임.
최종 설계 기준
애그리게이트를 판단할 때 다음 순서로 생각하면 됨.
1. 어떤 비즈니스 불변식이 존재하는가?
2. 그 불변식을 하나의 트랜잭션에서 반드시 보장해야 하는가?
YES인 경우
하나의 트랜잭션 일관성 경계가 필요함
하나의 애그리게이트로 묶는 것을 우선적으로 고려함.
NO인 경우
각 객체가 자신의 불변식을 독립적으로 지킬 수 있음
서로 다른 애그리게이트로 분리하고 Application Service, 이벤트, 메시지 등을 통해 조율함.
필요하다면 단계적·최종적 일관성으로 연결함.
핵심 공식
애그리게이트의 핵심 기준 = 하나의 트랜잭션에서 반드시 지켜야 하는 불변식의 경계
따라서 단순히 **"생명주기가 같다 = 같은 애그리게이트"**라고 판단하기보다는, **"하나의 트랜잭션에서 반드시 함께 지켜야 하는 불변식이 있다 = 같은 애그리게이트를 고려한다"**라고 이해하는 것이 더 정확함.
생명주기와 상태 변경 기준은 애그리게이트를 판단하는 중요한 단서이지만, 최종적인 핵심 기준은 트랜잭션 안에서 보장해야 하는 불변식임.
'공부 > DB' 카테고리의 다른 글
| 전통 락킹(2PL) vs MVCC :동시성 제어 방식 비교 (0) | 2025.11.19 |
|---|