DB 마이그레이션
기존 데이터베이스를 새로운 DB로 이전하는 전체 과정(전략 + 기술 + 실행)
DB 변경은 단순히 “데이터만 옮기는 것”이 아니라:
- 스키마(SQL 문법) 차이 조정
- 애플리케이션 코드 수정
- 트랜잭션 처리 방식 변화
- 성능 설정 변경
- 데이터 정합성 검증
- 장애 대비 롤백 계획
DB 마이그레이션의 4가지 방법론
1) Big Bang 방식 — "한 번에 갈아끼우기"
서비스 중단
↓
dump → restore
↓
yml DB 주소 변경
↓
서비스 재실행
✔ 장점: 단순, 빠름
❌ 단점: 다운타임 있음, 실패하면 대형 사고
🎯 소규모 서비스에서 가장 많이 쓰임
2) Trickle Migration (점진적 전환)
구 DB ←→ 신 DB
↑ ↑
동시 저장, 동기화
방법
- 일정 기간 두 DB 모두에 저장 : 자바 코드에서 save 두 번
- 검증 완료 후 신 DB만 사용
- 실패 시 롤백 가능
장단점
- ✔ 무중단
- ✔ 안전
- ❌ 설정 복잡도 높음 → 개념 이해 필요
- 🎯 중대규모 서비스에서 많이 사용
방법론 : 멀티디비설정
📁 전체 구조
프로젝트
├── application.yml # 두 DB 연결 정보
├── config/
│ ├── MariaDbConfig.java # MariaDB 설정
│ └── MssqlDbConfig.java # MSSQL 설정
├── db/
│ ├── mariadb/ # MariaDB 전용
│ │ ├── repository/
│ │ └── entity/ (공통 도메인 사용)
│ └── mssql/ # MSSQL 전용
│ ├── repository/
│ └── entity/
└── service/
├── NexReal3DStatisticsService.java # MariaDB 저장
├── MssqlStatisticsService.java # MSSQL 저장
└── ThreadExecutorService.java # 양쪽 호출
---
1️⃣ application.yml (두 DB 연결)
spring:
datasource:
# MariaDB 설정
maria:
hikari:
pool-name: master
maximum-pool-size: 50
# MSSQL 설정
mssql:
driver-class-name: cohttp://m.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
pool-name: mssql
maximum-pool-size: 50
---
2️⃣ MariaDbConfig.java (MariaDB 전용)
@Configuration
@EnableJpaRepositories(
basePackages = "dains.platforhttp://m.batch.db.mariadb.repository", // ← 패키지 분리!
entityManagerFactoryRef = "mariaEntityManagerFactory",
transactionManagerRef = "mariaTransactionManager"
)
public class MariaDbConfig {
@Bean
@ConfigurationProperties("spring.datasource.maria")
public DataSource mariaDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean mariaEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.MariaDBDialect");
return builder
.dataSource(mariaDataSource())
.packages("dains.platforhttp://m.common.domain") // ← Entity 위치
.persistenceUnit("maria")
.properties(properties)
.build();
}
@Bean
public PlatformTransactionManager mariaTransactionManager(
@Qualifier("mariaEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
---
3️⃣ MssqlDbConfig.java (MSSQL 전용)
@Configuration
@EnableJpaRepositories(
basePackages = "dains.platforhttp://m.batch.db.mssql.repository", // ← 다른 패키지!
entityManagerFactoryRef = "mssqlEntityManagerFactory",
transactionManagerRef = "mssqlTransactionManager"
)
public class MssqlDbConfig {
@Bean
@ConfigurationProperties("spring.datasource.mssql")
public DataSource mssqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean mssqlEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");
return builder
.dataSource(mssqlDataSource())
.packages("dains.platforhttp://m.batch.db.mssql.entity") // ← 별도 Entity
.persistenceUnit("mssql")
.properties(properties)
.build();
}
@Bean
public PlatformTransactionManager mssqlTransactionManager(
@Qualifier("mssqlEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
---
4️⃣ Repository 계층 (패키지 분리)
MariaDB Repository
package dains.platforhttp://m.batch.db.mariadb.repository;
// MariaDB용 Repository
public interface Count15SecStatsRepository
extends JpaRepository<Count15SecStats, Count15SecStatsPk> {
}
MSSQL Repository
package dains.platforhttp://m.batch.db.mssql.repository;
// MSSQL용 Repository
public interface TUCountRepository
extends JpaRepository<TUCount, Long> {
}
---
5️⃣ Service 계층 (트랜잭션 분리)
MariaDB Service
@Service
@RequiredArgsConstructor
@Transactional(transactionManager = "mariaTransactionManager", readOnly = true)
public class NexReal3DStatisticsService {
private final Count15SecStatsRepository count15SecStatsRepository; // MariaDB
@Transactional(transactionManager = "mariaTransactionManager")
public void saveNexReal3D15SecStatistics(Integer cameraNo, NexRealDto dto) {
// MariaDB에 저장
Count15SecStats stats = Count15SecStats.builder()
.cameraNo(cameraNo)
.totalCount(1)
.build();
count15SecStatsRepository.save(stats);
}
}
MSSQL Service
@Service
@RequiredArgsConstructor
@Transactional(transactionManager = "mssqlTransactionManager", readOnly = true)
public class MssqlStatisticsService {
private final TUCountRepository tuCountRepository; // MSSQL
@Transactional(transactionManager = "mssqlTransactionManager")
public void saveMssqlStatistics(NexRealDto dto) {
// MSSQL에 저장
TUCount tuCount = TUCount.builder()
.cameraId(dto.getCameraId())
.cnt("1")
.build();
tuCountRepository.save(tuCount);
}
}
---
6️⃣ 양쪽 DB 동시 저장 (핵심!)
@Service
@RequiredArgsConstructor
public class ThreadExecutorService {
private final NexReal3DStatisticsService nexReal3DStatisticsService; // MariaDB
private final MssqlStatisticsService mssqlStatisticsService; // MSSQL
public void saveNexReal3D15SecStatistics(Integer cameraNo, NexRealDto dto) {
// MariaDB 저장
handleAsync(() ->
nexReal3DStatisticsService.saveNexReal3D15SecStatistics(cameraNo, dto)
);
// MSSQL 저장 (동시에!)
handleAsync(() ->
mssqlStatisticsService.saveMssqlStatistics(dto)
);
}
private void handleAsync(Runnable task) {
try {
task.run();
} catch (Exception e) {
log.error("Error: {}", e.getMessage());
}
}
}
---
7️⃣ Scheduler에서 호출
@Component
@RequiredArgsConstructor
public class NexRealScheduler {
private final ThreadExecutorService threadExecutorService;
@Scheduled(fixedRate = 10000)
public void process() {
NexRealDto dto = getDataFromRedis();
// 한 번 호출로 양쪽 DB에 저장!
threadExecutorService.saveNexReal3D15SecStatistics(cameraNo, dto);
}
}
---
핵심 포인트 정리
✅ Config 분리
MariaDbConfig → basePackages = "db.mariadb.repository"
MssqlDbConfig → basePackages = "db.mssql.repository"
✅ TransactionManager 분리
@Transactional(transactionManager = "mariaTransactionManager")
@Transactional(transactionManager = "mssqlTransactionManager")
✅ Repository 패키지 분리
db.mariadb.repository.Count15SecStatsRepository
db.mssql.repository.TUCountRepository
✅ 양쪽 동시 저장
nexReal3DStatisticsService.save(); // MariaDB
mssqlStatisticsService.save(); // MSSQL
이게 Trickle Migration의 핵심입니다!
두 DB에 동시에 저장하면서 검증하고, 안정화되면 MSSQL 코드를 제거하는 방식입니다.
3) CDC (Change Data Capture) 기반 Migration
기존 DB에서 발생하는 INSERT/UPDATE/DELETE를 실시간으로 읽어서 새로운 DB에 반영하는 기술
[구 DB] → 로그(Change Data Capture) → [신 DB]
- 구DB에서 binlog, redo log를 읽어서 새로운 DB로 실시간 동기화
- 먼저 기존 DB → 신규 DB로 전체 데이터를 한 번 복사함. Full copy가 끝난 시점부터는 다음 변경을 CDC(binlog, redo log, WAL 등) 로 캡처해서 지속적으로 신규 DB에 반영함
장단점 비교
- 애플리케이션 변경 없이 가능 :자바 코드에서 save 두 번 할 필요 없음
- ✔ 가장 안전하고 정교함
- ❌ 구축 난이도 + 비용
- 🎯 대규모, 금융/대기업에서 선호
'개발기술 > RDB' 카테고리의 다른 글
| 데이터모델링 방법론 : 정규화 (0) | 2026.01.04 |
|---|---|
| 데이터모델링 방법론 : DDD(도메인 주도 설계) (1) | 2025.12.29 |
| 데이터베이스 저장구조 (0) | 2025.08.18 |
| 데이터베이스 쿼리 동작분석 및 튜닝 (2) | 2025.07.14 |
| 데이터베이스 인덱스 종류와 전략 (MYSQL InnoDB) (0) | 2024.12.27 |