Как вставить Disaster Recovery в систему устойчивости базы данных
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как вставить Disaster Recovery в систему устойчивости базы данных
Этот вопрос касается архитектуры высокодоступных систем и восстановления после сбоев. Давайте разберемся в механизмах Disaster Recovery (DR).
Что такое Disaster Recovery (DR)?
Disaster Recovery — это набор процессов и технологий для восстановления системы после катастрофического сбоя:
RPO (Recovery Point Objective) = максимальный объем потерянных данных
RTO (Recovery Time Objective) = максимальное время на восстановление
Примеры:
- RPO = 1 час (потеряем максимум 1 час данных)
- RTO = 15 минут (восстановим систему за 15 минут)
Уровни Disaster Recovery
Уровень 1: Резервные копии (Backup)
// Простая схема: регулярное копирование БД
@Component
public class DatabaseBackupService {
@Autowired
private DataSource dataSource;
@Autowired
private StorageService storageService; // S3, GCS, etc.
@Scheduled(cron = "0 0 * * *") // Каждый день в 00:00
public void performDailyBackup() {
try {
// Создать dump всей БД
String backupFile = createDatabaseDump();
// Загрузить на удаленное хранилище
storageService.upload(
backupFile,
"backups/daily-" + LocalDate.now() + ".sql.gz"
);
log.info("Daily backup completed successfully");
} catch (Exception e) {
sendAlert("Backup failed: " + e.getMessage());
}
}
private String createDatabaseDump() throws Exception {
// Использовать mysqldump или pg_dump
Process process = Runtime.getRuntime().exec(
"mysqldump --all-databases > backup.sql"
);
process.waitFor();
return "backup.sql";
}
public void restoreFromBackup(String backupFile) throws Exception {
String sql = storageService.download(backupFile);
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
// Выполнить SQL из backup'а
stmt.executeUpdate(sql);
log.info("Database restored from: " + backupFile);
}
}
}
// Недостатки:
// - RPO = 1 день (потеряем все данные за день)
// - RTO = несколько часов (ручное восстановление)
Уровень 2: Логические резервные копии + WAL Archiving
// Более частое копирование + логирование транзакций
@Configuration
public class PostgresWALArchivingConfig {
// В postgresql.conf:
// wal_level = replica
// archive_mode = on
// archive_command = 'test ! -f "/backup/wal/%f" && cp "/var/lib/postgresql/wal/%f" "/backup/wal/%f"'
// Результат:
// - Каждые 16MB данных логируются в WAL
// - WAL архивируется на удаленное хранилище
// - RPO = несколько минут
// - RTO = 30 минут
}
// Java-side monitoring
@Component
public class WALArchiveMonitor {
@Scheduled(fixedRate = 60000) // Каждую минуту
public void checkWALArchiveHealth() {
long unachedWALFiles = getUnarchivedWALFileCount();
if (unachedWALFiles > 10) {
sendAlert("WAL archive falling behind: " + unachedWALFiles);
}
}
}
Уровень 3: Репликация (Replication)
// Активная репликация на вторичную БД
// PostgreSQL streaming replication:
// Primary DB (Production)
// ↓ (WAL stream)
// Secondary DB (Standby) - read-only копия
// ↓ (failover)
// Tertiary DB (Backup)
@Configuration
public class DatabaseReplicationConfig {
@Bean
public DataSource primaryDataSource() {
// Основная база данных для write'ов
return DataSourceBuilder.create()
.url("jdbc:postgresql://primary-db:5432/myapp")
.username("postgres")
.password("password")
.build();
}
@Bean
public DataSource readReplicaDataSource() {
// Read-only копия для read-only операций
return DataSourceBuilder.create()
.url("jdbc:postgresql://replica-db:5432/myapp")
.username("postgres")
.password("password")
.build();
}
}
@Service
public class UserService {
@Autowired
private DataSource primaryDataSource; // WRITE
@Autowired
private DataSource readReplicaDataSource; // READ
@Transactional
public User createUser(UserDTO dto) {
// Пишем в primary
try (Connection conn = primaryDataSource.getConnection()) {
insertUser(conn, dto);
}
}
@Transactional(readOnly = true)
public List<User> getAllUsers() {
// Читаем из replica (может быть задержка на несколько ms)
try (Connection conn = readReplicaDataSource.getConnection()) {
return queryUsers(conn);
}
}
}
// Преимущества:
// - RPO = несколько секунд
// - RTO = 1-5 минут (failover)
// - Масштабируемость на чтение (replica может обработать 10000 read'ов)
Уровень 4: Multi-Region / Multi-DC (Master-Master Replication)
// Активная репликация между несколькими data center'ами
// DC1 (US East)
// └─ Primary DB ←→ Primary DB (DC2, US West)
// Quorum commit
@Configuration
public class MultiRegionReplicationConfig {
@Bean
public DataSource usEastDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://us-east-db:5432/myapp")
.build();
}
@Bean
public DataSource usWestDataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://us-west-db:5432/myapp")
.build();
}
}
@Service
public class MultiRegionDataService {
@Autowired
private DataSource usEastDataSource;
@Autowired
private DataSource usWestDataSource;
public void saveWithQuorum(User user) {
// Quorum commit: требует одобрения от большинства DC'ов
int successCount = 0;
try {
if (saveToRegion(usEastDataSource, user)) successCount++;
if (saveToRegion(usWestDataSource, user)) successCount++;
// Требуем 2 из 2 успешных (или 2 из 3 если есть третий)
if (successCount < 2) {
throw new QuorumCommitFailedException();
}
} catch (Exception e) {
log.error("Quorum commit failed", e);
throw new RuntimeException(e);
}
}
private boolean saveToRegion(DataSource ds, User user) {
try (Connection conn = ds.getConnection()) {
// Вставить user
return true;
} catch (Exception e) {
log.error("Failed to save to region", e);
return false;
}
}
}
// Преимущества:
// - RPO = 0 (zero data loss)
// - RTO = 0 (instantaneous failover)
// - Может работать при потере одного DC
Failover Strategy
@Component
public class FailoverManager {
@Autowired
private PrimaryDatabaseHealthChecker healthChecker;
@Autowired
private LoadBalancer loadBalancer;
// Проверяем health primary DB каждые 10 секунд
@Scheduled(fixedRate = 10000)
public void checkPrimaryHealth() {
if (!healthChecker.isPrimaryHealthy()) {
log.error("Primary DB is down! Initiating failover...");
triggerFailover();
}
}
private void triggerFailover() {
try {
// 1. Убедиться, что replica синхронизирована
if (!isReplicaSynchronized()) {
log.warn("Replica not fully synchronized, some data may be lost");
}
// 2. Promote replica to primary
promoteReplicaToPrimary();
// 3. Переключить traffic на новый primary
loadBalancer.switchToPrimary("replica-server");
// 4. Уведомить Ops team
notifyOpsTeam("Failover completed. Check replication status.");
} catch (Exception e) {
log.error("Failover failed!", e);
sendCriticalAlert("CRITICAL: Failover mechanism failed");
}
}
private boolean isReplicaSynchronized() {
// Проверить replication lag
long replicationLagMs = getReplicationLag();
return replicationLagMs < 5000; // Максимум 5 секунд задержки
}
}
// Health check реализация
@Component
public class PrimaryDatabaseHealthChecker {
@Autowired
private DataSource primaryDataSource;
public boolean isPrimaryHealthy() {
try (Connection conn = primaryDataSource.getConnection()) {
// Выполнить простой query
try (Statement stmt = conn.createStatement()) {
stmt.execute("SELECT 1");
return true;
}
} catch (SQLException e) {
log.error("Primary DB health check failed", e);
return false;
}
}
}
Мониторинг и Alerting
@Component
public class DisasterRecoveryMonitoring {
@Autowired
private MetricsRegistry metricsRegistry;
@Scheduled(fixedRate = 60000) // Каждую минуту
public void monitorDisasterRecoveryHealth() {
// Метрика 1: Replication lag
long replicationLagMs = getReplicationLag();
metricsRegistry.gauge("database.replication.lag.ms", replicationLagMs);
if (replicationLagMs > 30000) {
sendAlert("WARNING: Replication lag > 30 seconds");
}
// Метрика 2: Backup freshness
long timeSinceLastBackup = getTimeSinceLastBackup();
metricsRegistry.gauge("database.backup.age.ms", timeSinceLastBackup);
if (timeSinceLastBackup > 86400000) { // > 1 дня
sendAlert("WARNING: Backup is > 1 day old");
}
// Метрика 3: WAL archive lag
long walArchiveLagFiles = getUnarchivedWALFiles();
metricsRegistry.gauge("database.wal.archive.lag.files", walArchiveLagFiles);
if (walArchiveLagFiles > 100) {
sendAlert("WARNING: " + walArchiveLagFiles + " WAL files not archived");
}
// Метрика 4: RTO и RPO
metricsRegistry.gauge("disaster.recovery.rto.minutes", calculateRTO());
metricsRegistry.gauge("disaster.recovery.rpo.minutes", calculateRPO());
}
}
Практический Disaster Recovery Plan
1. TIER 1 - LOCAL BACKUP (RPO: 24h, RTO: 4h)
- mysqldump / pg_dump раз в день на S3
- Хранить 30 дней
- Тестировать восстановление раз в месяц
2. TIER 2 - STREAMING REPLICATION (RPO: 5min, RTO: 5min)
- Replica на другой машине в том же DC
- Automated failover
- Непрерывное копирование WAL
3. TIER 3 - GEO-REPLICATION (RPO: 1min, RTO: 1min)
- Async replication в другой регион
- Manual failover для критичных сбоев
4. TIER 4 - MULTI-MASTER (RPO: 0, RTO: 0)
- Активная репликация в 2-3 регионах
- Quorum commit для consistency
- Автоматический failover
Тестирование DR
@SpringBootTest
public class DisasterRecoveryTest {
@Test
public void testBackupRestoration() throws Exception {
// 1. Создать backup
String backupFile = backupService.createBackup();
// 2. Удалить тестовые данные
testDataService.deleteAllTestData();
// 3. Восстановить из backup'а
backupService.restoreFromBackup(backupFile);
// 4. Проверить что данные восстановлены
List<User> users = userRepository.findAll();
assertThat(users).isNotEmpty();
}
@Test
public void testReplicationLag() throws Exception {
User user = new User("John Doe");
userRepository.save(user);
// Подождать пока replica синхронизируется
Thread.sleep(2000);
// Проверить что replica получила данные
User replicatedUser = replicaUserRepository.findById(user.getId());
assertThat(replicatedUser).isNotNull();
}
@Test
public void testFailover() throws Exception {
// 1. Отключить primary
primaryDatabase.shutdown();
// 2. Инициировать failover
failoverManager.triggerFailover();
// 3. Проверить что система работает
User user = new User("Test User");
User saved = userService.createUser(user);
assertThat(saved.getId()).isNotNull();
}
}
Заключение
Для полной disaster recovery системы нужно:
✅ Tier 1: Резервные копии (backup/restore) ✅ Tier 2: Репликация (RPO/RTO в минутах) ✅ Tier 3: Geo-репликация (RPO/RTO в секундах) ✅ Tier 4: Multi-master (RPO = 0, RTO = 0)
✅ Мониторинг replication lag и backup freshness ✅ Автоматические failover'ы ✅ Регулярное тестирование восстановления ✅ Документирование RTO/RPO для каждого tier'а
Выбор tier'а зависит от критичности данных и бюджета на инфраструктуру!