목록으로 돌아가기
기술
12 분
Savermatrix와 Game Status 통합 설계
영구 저장 상태와 실시간 세션 상태를 통합할 때 필요한 책임 분리, 동시성, 멱등성, 복구 전략을 살펴봅니다.
박효열 (Hyoyoul Park)마지막 수정
#Game Development#System Design#Data Management
이 글에서 Savermatrix는 영구 저장 계층을 설명하기 위한 개념적 이름입니다. 보안을 위해 특정 게임과 사내 구현 세부사항은 제외했으며, 코드는 설계 의도를 보여주는 의사 코드입니다.
게임 개발에서 데이터 저장과 게임 상태 관리는 핵심적인 요소입니다. Savermatrix와 Game Status 시스템을 결합하여 더 강력하고 효율적인 솔루션을 만들어봅시다.
시스템 개요
Savermatrix란?
Savermatrix는 게임 데이터를 효율적으로 저장하고 관리하는 시스템입니다. 플레이어의 진행 상황, 인벤토리, 설정 등을 영구적으로 보관합니다.
주요 특징:
- 영구 저장: 게임 데이터의 안전한 저장
- 버전 관리: 데이터 구조 변경 시 호환성 유지
- 백업 및 복구: 데이터 손실 방지
Game Status란?
Game Status는 현재 게임의 상태를 실시간으로 관리하는 시스템입니다. 플레이어의 현재 위치, HP, 상태 효과 등을 추적합니다.
주요 특징:
- 실시간 상태: 현재 게임 상태의 즉각적인 반영
- 상태 전환: 게임 상태 간의 부드러운 전환
- 동기화: 여러 시스템 간의 상태 동기화
결합의 필요성
두 시스템을 분리하면 다음과 같은 문제가 발생할 수 있습니다:
- 데이터 불일치: 저장된 데이터와 현재 상태가 다를 수 있습니다
- 중복 관리: 같은 데이터를 두 곳에서 관리해야 합니다
- 복잡성 증가: 두 시스템을 따로 관리하는 것이 복잡합니다
통합 전에 정해야 할 설계 조건
- 권위 있는 상태의 위치: 세션 메모리와 영구 저장소 중 어느 쪽이 source of truth인지 정의합니다
- 동시성 정책: 같은 캐릭터가 여러 서버나 디바이스에서 갱신될 때 낙관적 잠금, 버전 번호, 또는 단일 writer 중 하나를 선택합니다
- 멱등성: 재시도된 저장 요청이 아이템이나 재화를 중복 반영하지 않도록 command id를 기록합니다
- 스키마 진화: 이전 버전 save를 읽을 migration 순서와 rollback 가능 범위를 명시합니다
- crash consistency: 프로세스가 쓰기 도중 종료돼도 마지막 정상 snapshot이나 event log로 복구할 수 있어야 합니다
통합 아키텍처
1. 단일 소스 원칙
// 통합된 게임 상태 관리
interface GameState {
// 영구 저장 데이터
player: {
level: number;
experience: number;
inventory: Item[];
};
// 현재 세션 상태
session: {
currentHP: number;
currentLocation: string;
activeEffects: Effect[];
};
}
2. 상태 동기화 메커니즘
class UnifiedGameManager {
private state: GameState;
private saver: Savermatrix;
// 상태 변경 시 자동 저장
updateState(updates: Partial<GameState>) {
this.state = { ...this.state, ...updates };
this.autoSave(); // 변경사항 자동 저장
}
// 주기적 자동 저장
private autoSave() {
this.saver.save(this.state);
}
}
3. 이벤트 기반 아키텍처
// 상태 변경 이벤트
interface StateChangeEvent {
type: 'state_changed';
field: string;
oldValue: any;
newValue: any;
}
// 이벤트 리스너로 자동 저장
eventBus.on('state_changed', (event: StateChangeEvent) => {
if (event.field.startsWith('player.')) {
savermatrix.save(event.newValue);
}
});
구현 전략
전략 1: 계층적 구조
┌─────────────────────┐
│ Game Status │ (현재 상태)
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Savermatrix │ (영구 저장)
└─────────────────────┘
장점:
- 명확한 책임 분리
- 각 시스템의 독립적 테스트 가능
- 유연한 확장성
전략 2: 통합 모델
┌─────────────────────┐
│ Unified Game State │
│ (Status + Save) │
└─────────────────────┘
장점:
- 단일 데이터 모델
- 일관성 보장
- 간단한 구현
실제 구현 예시
저장 트리거 설정
// 중요한 상태 변경 시 자동 저장
const saveTriggers = [
'player.level_up',
'player.item_acquired',
'player.progress_milestone',
'game.checkpoint_reached'
];
saveTriggers.forEach(trigger => {
gameStatus.on(trigger, () => {
savermatrix.save(gameStatus.getState());
});
});
로드 시 상태 복원
// 게임 시작 시 저장된 데이터 로드
async function initializeGame() {
const savedData = await savermatrix.load();
if (savedData) {
// 영구 데이터 복원
gameStatus.restorePermanentState(savedData.player);
// 세션 상태 초기화
gameStatus.initializeSession(savedData.session);
} else {
// 새 게임 시작
gameStatus.startNewGame();
}
}
최적화 고려사항
1. 저장 빈도 최적화
- 즉시 저장: 중요한 변경사항 (레벨업, 아이템 획득)
- 주기적 저장: 일정 시간마다 자동 저장
- 체크포인트 저장: 특정 지점에서만 저장
2. 데이터 압축
// 저장 전 데이터 압축
const compressed = compress(gameState);
savermatrix.save(compressed);
3. 증분 저장
전체 상태를 매번 저장하지 않고 변경된 부분만 저장:
// 변경된 필드만 저장
const changes = diff(currentState, lastSavedState);
savermatrix.saveIncremental(changes);
에러 처리
저장 실패 시
try {
await savermatrix.save(state);
} catch (error) {
// 저장 실패 시 재시도 로직
console.error('Save failed, retrying...', error);
await retrySave(state, 3);
}
데이터 무결성 검증
function validateGameState(state: GameState): boolean {
// 상태 유효성 검사
if (state.player.level < 0) return false;
if (state.session.currentHP < 0) return false;
// ... 추가 검증
return true;
}
테스트 전략
단위 테스트
describe('Unified Game Manager', () => {
it('should save state when status changes', () => {
const manager = new UnifiedGameManager();
manager.updateState({ player: { level: 2 } });
expect(savermatrix.save).toHaveBeenCalled();
});
});
통합 테스트
it('should restore game state from save', async () => {
const savedState = createTestState();
await savermatrix.save(savedState);
const restored = await initializeGame();
expect(restored).toEqual(savedState);
});
장애·경합 테스트
- 저장 직전과 직후 프로세스를 강제 종료해 마지막 정상 상태로 복구되는지 확인합니다
- 같은 버전의 상태를 두 writer가 갱신했을 때 한 요청이 명확하게 충돌 처리되는지 확인합니다
- 동일한 command id를 반복 전송해 재화와 인벤토리가 한 번만 변경되는지 확인합니다
- 구버전 save fixture를 최신 schema로 migration한 뒤 핵심 불변식이 유지되는지 확인합니다
- 저장소 지연과 일시 장애를 주입해 retry가 서버 부하를 증폭시키지 않는지 확인합니다
마무리
Savermatrix와 Game Status를 결합하면 더 강력하고 일관된 게임 데이터 관리 시스템을 만들 수 있습니다.
핵심은:
- 명확한 책임 분리: 각 시스템의 역할을 명확히 정의
- 자동 동기화: 상태 변경 시 자동으로 저장
- 에러 처리: 저장 실패에 대한 대비책
- 성능 최적화: 불필요한 저장 최소화
이러한 통합을 통해 플레이어 경험을 향상시키고, 개발 과정을 단순화할 수 있습니다.