← Назад к вопросам

Как обеспечить устойчивость базы данных

2.0 Middle🔥 131 комментариев
#ООП#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Как обеспечить устойчивость базы данных

Что такое устойчивость базы данных

Устойчивость (resilience) БД — это способность системы продолжать работать, восстанавливаться и сохранять данные при сбоях, перегрузках и непредвиденных ситуациях.

1. Репликация и резервное копирование

Синхронная репликация

// Spring Data JPA с несколькими источниками данных
@Configuration
public class DataSourceConfig {
    
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://db1.example.com/mydb");
        config.setUsername("user");
        config.setPassword("pass");
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        return new HikariDataSource(config);
    }
    
    @Bean
    public DataSource replicaDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://db2.example.com/mydb");
        config.setUsername("user");
        config.setPassword("pass");
        config.setMaximumPoolSize(20);
        return new HikariDataSource(config);
    }
}

// Использование читаемой реплики
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;  // Write на primary
    
    @Autowired
    private UserReadRepository userReadRepository;  // Read из replica
    
    public User getUserForWrite(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    public User getUserForRead(Long id) {
        return userReadRepository.findById(id).orElse(null);
    }
}

Асинхронная репликация с восстановлением

@Service
public class ReplicationService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    // Проверка состояния реплики
    public boolean isReplicaHealthy() {
        try {
            Map<String, Object> result = jdbcTemplate.queryForMap(
                "SELECT status FROM replication_status"
            );
            return "healthy".equals(result.get("status"));
        } catch (Exception e) {
            return false;
        }
    }
    
    // Переключение на备用сервер при отказе
    public void failoverToSecondary() {
        try {
            // Остановить запись на primary
            // Повысить secondary до primary
            // Обновить конфигурацию приложения
            System.out.println("Failover to secondary database completed");
        } catch (Exception e) {
            System.err.println("Failover failed: " + e.getMessage());
        }
    }
}

2. Пулирование соединений и управление ресурсами

// HikariCP — лучший пул соединений
@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:postgresql://localhost/mydb");
    config.setUsername("user");
    config.setPassword("pass");
    
    // Критические параметры для устойчивости
    config.setMaximumPoolSize(20);              // Макс соединений
    config.setMinimumIdle(5);                   // Мин свободных
    config.setConnectionTimeout(30000);         // 30 сек ожидание
    config.setIdleTimeout(600000);              // 10 мин неактивности
    config.setMaxLifetime(1800000);             // 30 мин жизни
    config.setLeakDetectionThreshold(60000);    // Обнаружение утечек
    config.setConnectionTestQuery("SELECT 1");  // Health check
    config.setAutoCommit(false);
    
    return new HikariDataSource(config);
}

3. Обработка ошибок и retry логика

@Service
public class ResilientDatabaseService {
    
    private static final int MAX_RETRIES = 3;
    private static final long RETRY_DELAY = 1000;  // 1 сек
    
    @Autowired
    private UserRepository userRepository;
    
    // Retry с экспоненциальным увеличением задержки
    @Retryable(
        value = {DataAccessException.class, SQLException.class},
        maxAttempts = MAX_RETRIES,
        backoff = @Backoff(
            delay = RETRY_DELAY,
            multiplier = 2.0  // 1s, 2s, 4s
        )
    )
    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    // Fallback при отказе
    @Recover
    public User getUserFallback(DataAccessException e, Long id) {
        System.err.println("All retries failed for user " + id);
        // Вернуть кэшированные данные, null или выбросить исключение
        return null;
    }
    
    // Сохранение с гарантией
    public User saveUserWithRetry(User user) {
        int attempts = 0;
        while (attempts < MAX_RETRIES) {
            try {
                return userRepository.save(user);
            } catch (DataAccessException e) {
                attempts++;
                if (attempts >= MAX_RETRIES) {
                    throw new RuntimeException("Failed to save user after " + 
                        MAX_RETRIES + " attempts", e);
                }
                try {
                    Thread.sleep((long) Math.pow(2, attempts) * RETRY_DELAY);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(ie);
                }
            }
        }
        return null;
    }
}

4. Транзакции и согласованность данных

@Service
@Transactional
public class TransactionService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    // Гарантирует ACID свойства
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        rollbackFor = Exception.class,
        timeout = 30  // 30 сек максимум
    )
    public void transferFunds(User from, User to, BigDecimal amount) {
        // Заблокировать строку для чтения и обновления
        User fromUser = userRepository.findById(from.getId())
            .orElseThrow(() -> new UserNotFound());
        User toUser = userRepository.findById(to.getId())
            .orElseThrow(() -> new UserNotFound());
        
        // Проверка и обновление (атомарно)
        if (fromUser.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException();
        }
        
        fromUser.setBalance(fromUser.getBalance().subtract(amount));
        toUser.setBalance(toUser.getBalance().add(amount));
        
        userRepository.save(fromUser);
        userRepository.save(toUser);
        
        // Логирование для аудита
        Order order = new Order();
        order.setFrom(from);
        order.setTo(to);
        order.setAmount(amount);
        order.setTimestamp(LocalDateTime.now(UTC));
        orderRepository.save(order);
        
        // При исключении — автоматический ROLLBACK
    }
    
    // Оптимистичная блокировка
    @Transactional
    public void updateWithVersionCheck(Long id, String newData) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new UserNotFound());
        
        // @Version поле автоматически управляется
        user.setData(newData);
        
        try {
            userRepository.save(user);
        } catch (OptimisticLockingFailureException e) {
            // Конфликт: другой процесс изменил строку
            // Повторить попытку или сообщить об ошибке
            throw new ConcurrentModificationException(e);
        }
    }
}

// Entity с оптимистичной блокировкой
@Entity
@Table(name = "users")
public class User {
    @Id
    private Long id;
    
    @Version
    private Long version;  // Управляется Hibernate
    
    private String name;
    private BigDecimal balance;
}

5. Мониторинг и метрики

@Configuration
public class MonitoringConfig {
    
    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(new PrometheusConfig());
    }
}

@Service
public class MonitoredDatabaseService {
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        Timer.Sample sample = Timer.start(meterRegistry);
        
        try {
            User user = userRepository.findById(id).orElse(null);
            meterRegistry.counter("db.query.success").increment();
            return user;
        } catch (DataAccessException e) {
            meterRegistry.counter("db.query.error").increment();
            throw e;
        } finally {
            sample.stop(
                Timer.builder("db.query.duration")
                    .description("Database query execution time")
                    .register(meterRegistry)
            );
        }
    }
}

// Health check для Spring Boot Actuator
@Component
public class DatabaseHealthIndicator extends AbstractHealthIndicator {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    @Override
    protected void doHealthCheck(Health.Builder builder) {
        try {
            String result = jdbcTemplate.queryForObject(
                "SELECT 1", 
                String.class
            );
            builder.up()
                .withDetail("database", "PostgreSQL")
                .withDetail("response", result);
        } catch (Exception e) {
            builder.down()
                .withDetail("error", e.getMessage());
        }
    }
}

6. Кэширование и локальное хранилище

@Service
@CacheConfig(cacheNames = "users")
public class CachedUserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Cacheable(key = "#id")
    public User getUser(Long id) {
        // При кэш-хите не выполняется
        return userRepository.findById(id).orElse(null);
    }
    
    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

7. Лучшие практики резюме

Репликация — первичный + резервные инстансы ✓ Пулирование — HikariCP с правильной конфигурацией ✓ Retry логика — экспоненциальная задержка ✓ Транзакции — ACID гарантии, оптимистичная блокировка ✓ Мониторинг — метрики, алерты, health checks ✓ Кэширование — уменьшение нагрузки ✓ Резервные копии — регулярное бэкапирование ✓ Тестирование отказов — хаос-тестирование

Устойчивость БД требует многоуровневого подхода, комбинирующего технологии, конфигурацию и мониторинг.