Какие production-риски есть у Spring Boot: blocking code, connection pooling, config, auth, observability, deploy или graceful shutdown?
Ключевые production-риски: блокирующий I/O в реактивном стеке, исчерпание пула соединений, утечки конфигурации через Actuator, отсутствие graceful shutdown и неполная observability без трейсинга.
Production-риски Spring Boot: полный разбор
Spring Boot значительно упрощает старт, но в продакшне каждый из его удобных дефолтов может стать источником аварии. Рассмотрим каждую категорию рисков с конкретными примерами и способами их устранения.
1. Blocking code в реактивном стеке
Если вы используете Spring WebFlux (Netty), любой блокирующий вызов в event loop-потоке (JdbcTemplate, синхронный RestTemplate, Thread.sleep) моментально останавливает обработку всех запросов.
// ПЛОХО: блокирующий вызов в реактивной цепочке
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
// jdbcTemplate.queryForObject — блокирует Netty event loop!
return Mono.just(jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id=?", User.class, id));
}
// ХОРОШО: оборачиваем в boundedElastic-шедулер
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return Mono.fromCallable(() ->
jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", User.class, id)
).subscribeOn(Schedulers.boundedElastic());
}
В WebFlux используйте R2DBC вместо JDBC, WebClient вместо RestTemplate.
2. Connection pooling
Spring Boot по умолчанию использует HikariCP. Типичная проблема — пул слишком мал для нагрузки или слишком велик для базы данных.
spring:
datasource:
hikari:
maximum-pool-size: 20 # НЕ ставьте 200+ — Postgres не выдержит
minimum-idle: 5
connection-timeout: 3000 # 3 сек — не ждать вечно
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 5000 # логировать утечки соединений
Формула для Postgres: max_connections = (cores * 2) + effective_spindle_count. При нескольких подах суммарный пул не должен превышать max_connections базы.
3. Утечка конфигурации
Actuator по умолчанию (до 2.x) открывал /env, /configprops, /beans без авторизации. Закройте всё лишнее явно:
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
env:
enabled: false
configprops:
enabled: false
Никогда не кладите секреты в application.yml в репозитории — используйте Spring Cloud Config, AWS Secrets Manager или Vault.
4. Аутентификация и авторизация
Spring Security по умолчанию генерирует случайный пароль при старте — это нормально для разработки, но не для продакшна. Убедитесь что:
- Статeless JWT-фильтры не хранят состояние в памяти пода (иначе горизонтальное масштабирование сломает сессии).
- CSRF защита включена для браузерных клиентов, отключена для API-only эндпоинтов.
- Методы
@PreAuthorizeактивированы через@EnableMethodSecurity— без этой аннотации они молча игнорируются.
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable) // REST API
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
}
5. Observability
Spring Boot 3.x поставляется с Micrometer Tracing (OpenTelemetry / Brave). Без трейсинга отладка медленных запросов в распределённой системе невозможна:
management:
tracing:
sampling:
probability: 0.1 # 10% в продакшне
metrics:
export:
prometheus:
enabled: true
6. Graceful Shutdown
По умолчанию Spring Boot немедленно убивает контекст при SIGTERM. Включите graceful shutdown и задайте таймаут:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
Kubernetes должен отправлять SIGTERM и выждать terminationGracePeriodSeconds (ставьте не меньше 35 секунд) перед SIGKILL. Без этого in-flight транзакции обрываются.
7. Deploy-риски
Fat JAR запускается медленно — типичный старт 8–15 секунд. При rolling update Kubernetes может направить трафик на под, который ещё не готов. Настройте readinessProbe:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 5
failureThreshold: 6
Подводные камни
- spring.jpa.open-in-view=true по умолчанию — держит транзакцию открытой на всё время HTTP-запроса, включая сериализацию JSON. Ведёт к утечкам соединений при медленных клиентах. Отключите явно.
- Actuator /heapdump и /threaddump открыты — позволяют выгрузить всю память процесса. Никогда не открывайте их в публичную сеть.
- @Async без настройки ThreadPoolTaskExecutor — по умолчанию Spring создаёт неограниченный пул, который при нагрузке создаёт тысячи потоков и убивает JVM.
- Circular bean dependencies — Spring Boot 2.6+ запрещает их по умолчанию. Если ваш старый код это обходит через
spring.main.allow-circular-references=true, это тикающая бомба при рефакторинге. - DevTools в продакшне —
spring-boot-devtoolsв classpath меняет поведение кэширования и перезапускает контекст. Убедитесь, что scoperuntimeне попадает в fat JAR. - JVM heap не ограничен в контейнере — без
-XX:MaxRAMPercentage=75.0JVM видит память хоста и выделяет heap больше лимита контейнера, что приводит к OOMKill. - Flyway/Liquibase миграции при rolling update — если две версии приложения работают одновременно и миграция несовместима с предыдущей схемой, старые поды упадут с ошибкой SQL.
What hurts your answer
- Говорить только о запуске Spring Boot, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Spring Boot
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию