BASE
Basically Available, Soft state, Eventually consistent
분산 시스템을 위한 데이터 일관성 모델입니다. ACID의 대안으로, 높은 가용성과 확장성을 위해 즉각적인 강한 일관성 대신 최종 일관성을 선택합니다.
Basically Available, Soft state, Eventually consistent
분산 시스템을 위한 데이터 일관성 모델입니다. ACID의 대안으로, 높은 가용성과 확장성을 위해 즉각적인 강한 일관성 대신 최종 일관성을 선택합니다.
기본 가용성(Basically Available)은 시스템이 항상 응답을 반환함을 의미합니다. 일부 노드에 장애가 발생해도 전체 시스템은 계속 동작하며, 최신 데이터가 아닐 수 있지만 항상 응답을 제공합니다. 가용성을 일관성보다 우선시하는 것이 핵심입니다.
유연한 상태(Soft state)는 시스템의 상태가 외부 입력 없이도 시간에 따라 변할 수 있음을 의미합니다. 데이터 복제본들 사이에서 동기화가 진행되는 동안 시스템 상태는 일시적으로 불일치할 수 있으며, 이는 정상적인 상태로 간주됩니다.
최종 일관성(Eventually consistent)은 충분한 시간이 지나면 모든 복제본이 동일한 값을 가지게 됨을 보장합니다. 즉각적인 일관성은 포기하지만, 결국에는 모든 노드가 같은 데이터를 갖게 됩니다. 이 "결국"의 시간은 밀리초에서 수 초까지 다양합니다.
BASE는 CAP 정리에서 영감을 받았습니다. 네트워크 분할(Partition)이 발생할 때, ACID 시스템은 일관성(Consistency)을 선택하고 가용성을 포기하지만, BASE 시스템은 가용성(Availability)을 선택하고 일관성을 느슨하게 합니다.
Amazon DynamoDB, Apache Cassandra, MongoDB(기본 설정), Redis 클러스터 등 대부분의 대규모 분산 NoSQL 데이터베이스가 BASE 모델을 채택합니다. 소셜 미디어 피드, 좋아요 수, 쇼핑 카트 등 약간의 불일치가 허용되는 시나리오에 적합합니다.
# BASE 모델 - Python + DynamoDB 예제
import boto3
from botocore.config import Config
from decimal import Decimal
import time
class EventuallyConsistentCart:
"""
BASE 원칙을 따르는 장바구니 서비스
- 최종 일관성 읽기로 빠른 응답
- 강한 일관성 읽기는 필요할 때만 사용
"""
def __init__(self, table_name: str):
self.dynamodb = boto3.resource('dynamodb')
self.table = self.dynamodb.Table(table_name)
def add_item(self, user_id: str, product_id: str, quantity: int):
"""
장바구니에 상품 추가
DynamoDB는 자동으로 여러 리전에 복제 (최종 일관성)
"""
self.table.update_item(
Key={'user_id': user_id},
UpdateExpression='SET items.#pid = if_not_exists(items.#pid, :zero) + :qty',
ExpressionAttributeNames={'#pid': product_id},
ExpressionAttributeValues={
':qty': quantity,
':zero': 0
}
)
# 쓰기는 즉시 반환되지만, 읽기에 반영되기까지 시간 필요
print(f"✅ 상품 추가됨 (최종 일관성으로 전파 중)")
def get_cart_eventually_consistent(self, user_id: str) -> dict:
"""
최종 일관성 읽기 - 빠르지만 최신 데이터 아닐 수 있음
대부분의 경우 충분 (밀리초 내 동기화)
"""
response = self.table.get_item(
Key={'user_id': user_id},
ConsistentRead=False # 기본값, 최종 일관성
)
return response.get('Item', {})
def get_cart_strongly_consistent(self, user_id: str) -> dict:
"""
강한 일관성 읽기 - 결제 직전 등 정확성이 중요할 때
약간 느리고 비용 2배, 하지만 최신 데이터 보장
"""
response = self.table.get_item(
Key={'user_id': user_id},
ConsistentRead=True # 강한 일관성
)
return response.get('Item', {})
# BASE 원칙을 활용한 좋아요 카운터
class EventuallyConsistentCounter:
"""
SNS 좋아요 수처럼 정확한 일관성보다 가용성이 중요한 경우
"""
def __init__(self, table_name: str):
self.dynamodb = boto3.resource('dynamodb')
self.table = self.dynamodb.Table(table_name)
def increment_like(self, post_id: str):
"""
좋아요 증가 - 원자적 카운터 업데이트
여러 리전에서 동시 증가해도 결국 합쳐짐
"""
self.table.update_item(
Key={'post_id': post_id},
UpdateExpression='SET like_count = if_not_exists(like_count, :zero) + :inc',
ExpressionAttributeValues={
':inc': 1,
':zero': 0
}
)
def get_approximate_count(self, post_id: str) -> int:
"""
대략적인 좋아요 수 - UI 표시용
"약 1.2K 좋아요" 처럼 표시하면 정확성 불필요
"""
response = self.table.get_item(
Key={'post_id': post_id},
ConsistentRead=False,
ProjectionExpression='like_count'
)
return response.get('Item', {}).get('like_count', 0)
# 사용 예시
if __name__ == "__main__":
cart = EventuallyConsistentCart('shopping-carts')
# 상품 추가 (빠른 쓰기)
cart.add_item('user123', 'product-abc', 2)
# 최종 일관성 읽기 (일반 조회)
cart_data = cart.get_cart_eventually_consistent('user123')
print(f"장바구니 (최종 일관성): {cart_data}")
# 결제 전 강한 일관성 읽기 (정확성 필요)
cart_data = cart.get_cart_strongly_consistent('user123')
print(f"장바구니 (강한 일관성): {cart_data}")
// BASE 모델 - Node.js + Cassandra 예제
const cassandra = require('cassandra-driver');
class EventuallyConsistentUserService {
constructor() {
// Cassandra는 분산 설계로 BASE 모델 기본 지원
this.client = new cassandra.Client({
contactPoints: ['node1', 'node2', 'node3'],
localDataCenter: 'datacenter1',
keyspace: 'users_ks'
});
}
/**
* 사용자 프로필 업데이트
* Cassandra는 "Last Write Wins" 전략 사용
*/
async updateProfile(userId, profileData) {
const query = `
UPDATE users
SET name = ?, bio = ?, updated_at = toTimestamp(now())
WHERE user_id = ?
`;
// 일관성 수준 선택
await this.client.execute(query,
[profileData.name, profileData.bio, userId],
{
// ONE: 한 노드 확인 후 반환 (빠름, 낮은 일관성)
// QUORUM: 과반수 노드 확인 (균형)
// ALL: 모든 노드 확인 (느림, 높은 일관성)
consistency: cassandra.types.consistencies.one
}
);
console.log('✅ 프로필 업데이트됨 (최종 일관성으로 전파 중)');
}
/**
* 프로필 조회 - 일관성 수준 선택 가능
*/
async getProfile(userId, strongConsistency = false) {
const query = 'SELECT * FROM users WHERE user_id = ?';
const result = await this.client.execute(query, [userId], {
consistency: strongConsistency
? cassandra.types.consistencies.quorum
: cassandra.types.consistencies.one
});
return result.rows[0];
}
/**
* 팔로워 수 카운터 - 분산 카운터 사용
* Cassandra Counter는 최종 일관성으로 동작
*/
async incrementFollowers(userId) {
const query = `
UPDATE user_stats
SET follower_count = follower_count + 1
WHERE user_id = ?
`;
await this.client.execute(query, [userId], {
consistency: cassandra.types.consistencies.one
});
}
}
// 읽기 복구 (Read Repair) 시뮬레이션
class ReadRepairExample {
/**
* Cassandra의 Read Repair 동작 원리
* 읽기 시 불일치 발견하면 자동으로 복구
*/
async readWithRepair(client, userId) {
// QUORUM 읽기: 여러 복제본 비교
const query = 'SELECT * FROM users WHERE user_id = ?';
const result = await client.execute(query, [userId], {
consistency: cassandra.types.consistencies.quorum
});
// 내부적으로:
// 1. 여러 노드에서 데이터 읽기
// 2. 타임스탬프 비교
// 3. 오래된 데이터 가진 노드에 최신 데이터 전파
// => "최종 일관성"이 실현되는 메커니즘
return result.rows[0];
}
}
// 사용 예시
async function main() {
const userService = new EventuallyConsistentUserService();
// 프로필 업데이트 (빠른 쓰기)
await userService.updateProfile('user123', {
name: '홍길동',
bio: '소프트웨어 개발자'
});
// 일반 조회 (최종 일관성, 빠름)
const profile = await userService.getProfile('user123', false);
console.log('프로필:', profile);
// 중요한 조회 (강한 일관성, 느림)
const accurateProfile = await userService.getProfile('user123', true);
console.log('정확한 프로필:', accurateProfile);
}
┌─────────────────────────────────────────────────────────────────────┐
│ ACID vs BASE 비교 │
├─────────────────────────────────────────────────────────────────────┤
ACID (관계형 DB) BASE (NoSQL/분산 시스템)
═══════════════ ═══════════════════════
┌─────────────┐ ┌─────────────┐
│ Atomicity │ │ Basically │
│ 원자성 │ │ Available │
│ 전부/전무 │ │ 기본가용성 │
└─────────────┘ └─────────────┘
↓ ↓
트랜잭션 전체 성공 장애 시에도 응답 반환
또는 전체 롤백 (오래된 데이터일 수 있음)
┌─────────────┐ ┌─────────────┐
│ Consistency │ │ Soft State │
│ 일관성 │ │ 유연한상태 │
│ 즉각적 반영 │ │ 중간상태허용 │
└─────────────┘ └─────────────┘
↓ ↓
쓰기 후 읽기는 복제 중 일시적
항상 최신 데이터 불일치 허용
┌─────────────┐ ┌─────────────┐
│ Isolation │ │ Eventually │
│ 격리성 │ │ Consistent │
│ 동시성제어 │ │ 최종일관성 │
└─────────────┘ └─────────────┘
↓ ↓
트랜잭션 간 시간이 지나면
간섭 없음 모든 노드 동기화
┌─────────────┐
│ Durability │
│ 지속성 │
│ 영구 저장 │
└─────────────┘
═══════════════════════════════════════════════════════════════
적합한 사용 사례
───────────────
ACID 선택 BASE 선택
────────── ──────────
• 금융 거래 • 소셜 미디어 피드
• 결제 시스템 • 좋아요/조회수 카운터
• 재고 관리 (정확성 중요) • 쇼핑 카트 (결제 전)
• 예약 시스템 • 로그/분석 데이터
• 사용자 인증 • 추천 시스템
═══════════════════════════════════════════════════════════════
최종 일관성의 시간 흐름
─────────────────────
T=0ms (쓰기) T=50ms T=200ms (최종 일관성 달성)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node A │ │ Node A │ │ Node A │
│ v=100 ✓ │ ──→ │ v=100 │ ──→ │ v=100 │
└─────────┘ └─────────┘ └─────────┘
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node B │ │ Node B │ │ Node B │
│ v=99 │ ──→ │ v=100 ✓ │ ──→ │ v=100 │
└─────────┘ └─────────┘ └─────────┘
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Node C │ │ Node C │ │ Node C │
│ v=99 │ ──→ │ v=99 │ ──→ │ v=100 ✓ │
└─────────┘ └─────────┘ └─────────┘
불일치 상태 복제 진행 중 모든 노드 동기화!
(Soft State) (Soft State) (Eventually Consistent)
└─────────────────────────────────────────────────────────────────────┘
"SNS 피드는 BASE 모델이 적합합니다. 친구의 새 글이 0.5초 늦게 보여도 사용자 경험에 큰 문제 없어요. 대신 수백만 동시 사용자에게 빠르게 응답할 수 있죠. 반면 결제 기능은 ACID가 필수입니다."
"좋아요 수가 잠깐 다르게 보이는 건 BASE의 특성입니다. 최종 일관성이라 복제 지연이 있을 수 있어요. 1-2초 후면 동기화됩니다. 심각한 버그가 아니라 설계된 동작이에요."
"Cassandra를 쓰면 일관성 수준을 조절할 수 있어요. 일반 조회는 ONE으로 빠르게, 중요한 작업은 QUORUM으로 일관성을 높이면 됩니다. BASE 시스템도 상황에 따라 일관성을 튜닝할 수 있어요."
결제, 송금, 재고 차감 등 정확성이 필수인 작업에는 ACID 트랜잭션을 사용하세요. 1원이라도 차이나면 안 됩니다.
사용자가 글을 작성 후 바로 조회할 때 안 보이면 혼란스러워합니다. 쓰기 후 자신의 데이터는 강한 일관성 읽기를 사용하세요.
여러 노드에서 동시에 같은 데이터를 수정하면 충돌이 발생합니다. Last-Write-Wins, 버전 벡터 등 충돌 해결 전략을 미리 정하세요.
하나의 시스템 내에서도 기능별로 ACID/BASE를 구분하세요. 장바구니 조회는 BASE, 결제 처리는 ACID로 분리하면 양쪽 장점을 취할 수 있습니다.