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

Какие знаешь способы защиты от сбоев базы данных в одном экземпляре?

3.0 Senior🔥 81 комментариев
#Docker, Kubernetes и DevOps#Базы данных и SQL

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

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

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

Защита от сбоев базы данных — архитектура высокой доступности

Одним из критических компонентов любого приложения является база данных. Сбой БД может привести к полной потере функциональности. Рассмотрим стратегии защиты в случае одного экземпляра БД.

Connection Pooling

Connection Pooling — управление пулом соединений для оптимизации использования ресурсов:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
        config.setUsername("user");
        config.setPassword("password");
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000); // 30 сек
        config.setIdleTimeout(600000);      // 10 минут
        config.setMaxLifetime(1800000);     // 30 минут
        config.setAutoCommit(true);
        config.setConnectionTestQuery("SELECT 1");
        
        return new HikariDataSource(config);
    }
}

Преимущества: Повторное использование соединений, меньше overhead Ограничения: Защищает только от истощения соединений, не от сбоев

Circuit Breaker Pattern

Circuit Breaker — прерывает запросы при обнаружении сбоя, чтобы дать БД восстановиться:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private CircuitBreakerFactory circuitBreakerFactory;

    public User getUserById(Long id) {
        return circuitBreakerFactory.create("userService")
            .run(
                () -> userRepository.findById(id).orElse(null),
                throwable -> getDefaultUser() // fallback
            );
    }
}

@Configuration
public class CircuitBreakerConfig {
    @Bean
    public Resilience4jCircuitBreakerFactory resilience4jCircuitBreakerFactory() {
        return new Resilience4jCircuitBreakerFactory();
    }
}

@Configuration
public class CircuitBreakerProperties {
    @Bean
    public Customizer<Resilience4jCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4jConfigBuilder(id)
            .circuitBreakerConfig(CircuitBreakerConfig.custom()
                .failureRateThreshold(50.0f)
                .waitDurationInOpenState(Duration.ofSeconds(10))
                .automaticTransitionFromOpenToHalfOpenEnabled(true)
                .build())
            .timeLimiterConfig(TimeLimiterConfig.custom()
                .timeoutDuration(Duration.ofSeconds(5))
                .build())
            .build());
    }
}

Состояния Circuit Breaker:

  • CLOSED — нормальная работа, запросы проходят
  • OPEN — обнаружен сбой, запросы блокируются
  • HALF_OPEN — попытка восстановления, некоторые запросы пропускаются

Retry с экспоненциальной задержкой

Retry Logic — повтор запросов при временных сбоях:

@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    @Retryable(
        value = {TemporaryDataAccessException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public Order saveOrder(Order order) {
        return orderRepository.save(order);
    }

    @Recover
    public Order recoverSaveOrder(TemporaryDataAccessException e, Order order) {
        logger.error("Failed to save order after retries", e);
        // Fallback стратегия
        return new Order();
    }
}

@Configuration
@EnableRetry
public class RetryConfig {
    // Конфигурация Retry с экспоненциальной задержкой
}

Преимущества: Восстанавливает временные сбои, прост в реализации Недостатки: Не помогает при постоянных сбоях, может замедлить отклик

Timeout Management

Timeout — ограничение времени ожидания ответа от БД:

@Configuration
public class JdbcConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
        // Socket timeout (зависит от драйвера)
        config.addDataSourceProperty("socketTimeout", "30");
        // Connection timeout
        config.setConnectionTimeout(10000);
        return new HikariDataSource(config);
    }
}

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Transactional(timeout = 10) // 10 секунд на транзакцию
    public List<Product> getProducts() {
        return productRepository.findAll();
    }
}

Преимущества: Предотвращает зависание запросов Недостатки: Может прерывать долгие, но необходимые операции

Bulkhead Pattern (Изоляция ресурсов)

Bulkhead — изолирует критичные операции от менее важных:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Bulkhead(name = "criticalOperation", type = Bulkhead.Type.THREADPOOL)
    public User getCriticalUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @Bulkhead(name = "nonCriticalOperation", type = Bulkhead.Type.SEMAPHORE)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
}

@Configuration
public class BulkheadConfig {
    @Bean
    public Customizer<Resilience4jBulkheadFactory> customizer() {
        return factory -> factory.configureDefault(id -> {
            if ("criticalOperation".equals(id)) {
                return new Resilience4jConfigBuilder(id)
                    .bulkheadConfig(ThreadPoolBulkheadConfig.custom()
                        .maxThreadPoolSize(5)
                        .coreThreadPoolSize(2)
                        .build())
                    .build();
            }
            return new Resilience4jConfigBuilder(id)
                .bulkheadConfig(BulkheadConfig.custom()
                    .maxConcurrentCalls(10)
                    .maxWaitDuration(Duration.ofSeconds(1))
                    .build())
                .build();
        });
    }
}

Read Replicas (Масштабирование чтения)

Отделение операций чтения на отдельный экземпляр БД (secondary):

@Configuration
public class RoutingDataSourceConfig {
    @Bean(name = "primaryDataSource")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://primary-db:5432/mydb")
            .username("user")
            .password("password")
            .driverClassName("org.postgresql.Driver")
            .build();
    }

    @Bean(name = "replicaDataSource")
    public DataSource replicaDataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:postgresql://replica-db:5432/mydb")
            .username("user")
            .password("password")
            .driverClassName("org.postgresql.Driver")
            .build();
    }

    @Bean
    public DataSource routingDataSource(
            @Qualifier("primaryDataSource") DataSource primary,
            @Qualifier("replicaDataSource") DataSource replica) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("primary", primary);
        targetDataSources.put("replica", replica);

        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(primary);
        return routingDataSource;
    }
}

@Component
public class DbContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setReadOnly() {
        contextHolder.set("replica");
    }

    public static void setReadWrite() {
        contextHolder.set("primary");
    }

    public static String getDataSourceKey() {
        return contextHolder.get() != null ? contextHolder.get() : "primary";
    }

    public static void clear() {
        contextHolder.remove();
    }
}

public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDataSourceKey();
    }
}

@Aspect
@Component
public class DataSourceAspect {
    @Before("execution(* *.find*(..")) // все find методы
    public void setReadOnly() {
        DbContextHolder.setReadOnly();
    }

    @Before("execution(* *.save*(..)) || execution(* *.update*(..))")
    public void setReadWrite() {
        DbContextHolder.setReadWrite();
    }

    @After("execution(* *.*(.."))
    public void clearContext() {
        DbContextHolder.clear();
    }
}

Health Checks и Monitoring

Health Checks — периодическая проверка доступности БД:

@Component
public class DatabaseHealthIndicator extends AbstractHealthIndicator {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    protected void doHealthCheck(Health.Builder builder) {
        try {
            Integer result = jdbcTemplate.queryForObject(
                "SELECT 1",
                Integer.class
            );
            if (result != null) {
                builder.up().withDetail("database", "OK");
            } else {
                builder.down().withDetail("database", "Invalid response");
            }
        } catch (Exception e) {
            builder.down()
                .withDetail("database", "Connection failed")
                .withException(e);
        }
    }
}

@Configuration
public class ActuatorConfig {
    // Spring Boot автоматически включит /actuator/health endpoint
}

Кэширование

Caching — уменьшает нагрузку на БД за счет кэширования часто запрашиваемых данных:

@Service
@CacheConfig(cacheNames = "users")
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Cacheable(key = "#id", unless = "#result == null")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @CacheEvict(key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }

    @CacheEvict(allEntries = true)
    public void invalidateCache() {
    }
}

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "products");
    }
}

Backup и Recovery

Backup стратегия — обеспечивает восстановление при потере данных:

@Service
public class BackupService {
    private final String backupCommand = 
        "pg_dump -h localhost -U user mydb | gzip > /backup/db_$(date +%Y%m%d_%H%M%S).sql.gz";

    @Scheduled(cron = "0 2 * * *") // Каждый день в 2:00 AM
    public void dailyBackup() {
        try {
            Runtime.getRuntime().exec(backupCommand);
            logger.info("Daily backup completed successfully");
        } catch (IOException e) {
            logger.error("Backup failed", e);
            sendAlert("Database backup failed");
        }
    }
}

Сравнение методов

МетодУровеньСложностьOverhead
Connection PoolingНизкийНизкаяМинимальный
Circuit BreakerСреднийСредняяНизкий
RetryСреднийНизкаяЗависит
TimeoutСреднийНизкаяМинимальный
BulkheadВысокийСредняяСредний
Read ReplicasВысокийВысокаяСредний
CachingВысокийСредняяЗначительный
Health ChecksНизкийНизкаяМинимальный

Комплексная стратегия

Для надежной защиты применяй комбинацию:

  1. Connection Pooling — базовая защита
  2. Health Checks + Monitoring — раннее обнаружение
  3. Retry + Circuit Breaker — восстановление при временных сбоях
  4. Timeout — предотвращение зависания
  5. Caching — снижение нагрузки
  6. Read Replicas — масштабирование
  7. Backup & Recovery — восстановление при катастрофе
Какие знаешь способы защиты от сбоев базы данных в одном экземпляре? | PrepBro