Как можно решить проблему, связанную с исключением JpaSystemException: Unable to rollback against JDBC Connection в Spring Batch?

Добрый день.  
У меня возникла следующая проблема:  
В своем проекте я использую Spring Batch. Клиент инициирует запуск джоба, после чего возвращается id, по которому затем фетчится результат. Завершение джоба позволяет получить результат, сохраненный в Redis.  
Я применяю tasklet и имею два этапа:  
1. Этап оптимизации, на котором Spring сохраняет статус джоба в БД PostgreSQL.  
2. Этап записи результата в Redis.  
Обычно выполнение запроса занимает от 20 до 30 минут. Проблем не возникает, когда БД и приложение расположены в одной сети. Однако если они находятся в разных сетях (например, в Kubernetes, Swarm и т.д.), через некоторое время соединение с БД теряется, из-за чего транзакция не может быть завершена. Статус джоба изменяется на UNKNOWN, и в логах появляется исключение: JpaSystemException: Unable to rollback against JDBC Connection.  

Полный лог и логи БД я предоставлю позже.  
Предполагаю, что происходит следующее: при запуске джоба открывается транзакция, но из-за разрыва соединения Spring Batch не может ее завершить.  

Я предпринял несколько шагов для решения проблемы:  
1. Заменил PlatformTransactionManager на ResourcelessTransactionManager:  
```java
@Bean
public Job optimizationJob() {
    Step step = new StepBuilder("firstStep", jobRepository)
            .tasklet(optimizationTasklet, transactionManager)
            .build();
    return new JobBuilder("firstJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(step)
            .next(redisWriteStep())
            .build();
}
```  
Однако спустя некоторое время снова возникают проблемы.
  • 27 декабря 2024 г. 12:23
Ответы на вопрос 1
Добрый день!

Проблема, с которой вы сталкиваетесь, связана с тем, что при длительном выполнении задач в Spring Batch соединение с базой данных может быть потеряно. Ваша проблема — это типичная проблема, возникающая из-за time-out соединений или потери соединения в сетевых окружениях, таких как Kubernetes или Swarm. Давайте обсудим несколько подходов к решению данной проблемы:

### 1. Увеличение времени ожидания (timeout)

Убедитесь, что настройки вашего пула соединений (например, HikariCP, если вы его используете) правильно настроены. Увеличьте параметры `connectionTimeout`, `idleTimeout` и `maxLifetime`. Это поможет избежать разрыва соединений в ситуации длительного простоя.

```yaml
spring.datasource.hikari.connection-timeout: 30000  # 30 секунд
spring.datasource.hikari.idle-timeout: 600000        # 10 минут
spring.datasource.hikari.max-lifetime: 1800000       # 30 минут
```

### 2. Использование `@Transactional` и управление транзакциями

Помните, что при использовании транзакций для длительных операций лучше избегать слишком долгих транзакций. Попробуйте разбить вашу логику на более мелкие транзакции. Например, вы можете использовать `@Transactional` и выполнять сохранение результата на более коротких этапах.

### 3. Использование Spring Retry

Интегрируйте Spring Retry для ваших операций, связанных с базой данных. В этом случае при возникновении исключения попытка будет автоматически повторяться несколько раз.

```java
@EnableAspectJAutoProxy
@Configuration
public class AppConfig {

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(2000);  // 2 секунды задержки между попытками
        
        retryTemplate.setBackOffPolicy(backOffPolicy);
        
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(5); // максимальное число попыток
        
        retryTemplate.setRetryPolicy(retryPolicy);
        
        return retryTemplate;
    }
}
```

### 4. Использование `JdbcTemplate` для контроля потока

Вместо того чтобы полагаться на Spring Batch для управления транзакциями за длительные операции, вы можете выполнить более управляемый подход, используя `JdbcTemplate` и проводить более короткие транзакции по частям.

### 5. Мониторинг соединений

Используйте такие инструменты, как `pgBouncer` для управления соединениями с PostgreSQL. Он может помочь вам управлять соединениями и обработкой времени ожидания.

### 6. Обработка ошибок

Проверьте, как ваша система обрабатывает ошибки. Убедитесь, что ошибка при выполнении операции несколько раз перезапускает задачу и не оставляет статус джоба в `UNKNOWN`.

### 7. Кэши и промежуточные результаты

Если операция длительная, рассмотрите возможность кэширования промежуточных результатов в Redis или временной базе данных, чтобы избежать повторного выполнения.

Если у вас есть возможность работать с асинхронными подходами, вы также можете рассмотреть использование `CompletableFuture`, чтобы ваша задача не блокировала поток, что может предотвратить вероятность потери соединения.

---

Эти подходы могут значительно помочь в решении или минимизации проблемы с потерей соединений в вашей среде выполнения Spring Batch. Если ни один из этих методов не сработает, пожалуйста, предоставьте дополнительную информацию о конфигурациях, ошибках и логах, чтобы можно было более точно диагностировать проблему.
Похожие вопросы