Как понять, что проблема в сервисе связана с Spring Framework, а не с базой, сетью, инфраструктурой или бизнес-логикой?
Изолируйте слой через Micrometer/Zipkin трейсинг (span покажет где задержка), thread dump (BLOCKED = lock/pool exhaustion), SQL-логирование Hibernate и метрики HikariCP. Spring виновен редко — чаще БД, пул соединений или blocking-код в reactive pipeline.
Диагностика источника проблемы в Spring-сервисе
Когда сервис деградирует, первый вопрос — где именно происходит задержка или ошибка. Spring Framework сам по себе редко является виновником; чаще это база данных, сеть, неправильная конфигурация пула соединений или бизнес-логика. Ниже — систематический подход к изоляции слоя.
Шаг 1: Метрики и трейсинг
Включите Spring Boot Actuator и Micrometer с экспортом в Prometheus/Grafana. Ключевые метрики:
# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus,threaddump,httptrace
metrics:
distribution:
percentiles-histogram:
http.server.requests: true
http.server.requests— latency по endpoint'ам (p50, p95, p99).hikaricp.connections.pending— очередь ожидания соединений с БД.hikaricp.connections.acquire— время получения соединения из пула.jvm.threads.states— наличие BLOCKED/WAITING потоков.
Шаг 2: Распределённый трейсинг
Добавьте Micrometer Tracing (Spring Boot 3.x) + Zipkin/Jaeger. Trace покажет span'ы по каждому слою: HTTP → Service → Repository → SQL.
# application.yml
management:
tracing:
sampling:
probability: 1.0
spring:
zipkin:
base-url: http://zipkin:9411
Если span SELECT занимает 900ms из 1000ms total — проблема в БД, не в Spring. Если span HTTP-запроса к внешнему API занимает 800ms — проблема в сети/downstream сервисе.
Шаг 3: Проверка Spring-специфичных причин
Spring является виновником, если:
- AOP overhead: большое количество аспектов (
@Transactional,@Cacheable,@Async, security interceptors) на одном методе создаёт цепочку прокси. Проверьте через-Dspring.aop.auto=falseв тесте. - Context refresh: в тестах
@SpringBootTestкаждый тест-класс с разным контекстом поднимает новый ApplicationContext. Это медленно, но не проблема в проде. - Blocking в WebFlux: вызов
JdbcTemplateилиThread.sleep()внутри reactive pipeline блокирует event-loop Netty. Детектируется черезBlockHound. - @Scheduled зависание: если scheduled task зависает, следующий запуск откладывается (single-thread executor по умолчанию).
// Диагностика: включить SQL-логирование
// application.yml
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
com.zaxxer.hikari: DEBUG
Шаг 4: Изоляция через thread dump
curl http://localhost:8080/actuator/threaddump | jq '.threads[] | select(.threadState == "BLOCKED")'
# Или через jstack:
jstack $(pgrep -f 'spring') | grep -A 20 'BLOCKED'
BLOCKED-потоки указывают на lock contention в бизнес-логике или синхронизированные блоки. WAITING с HikariPool в стектрейсе — пул соединений исчерпан.
Шаг 5: Проверка инфраструктуры
- БД:
EXPLAIN ANALYZEдля медленных запросов,pg_stat_activityдля активных сессий. - Сеть:
ping,traceroute, RTT к downstream сервисам. - Redis/кэш:
redis-cli INFO stats | grep keyspace_misses— высокий miss rate означает бесполезный кэш. - GC:
jstat -gcutil— если GC занимает >5% CPU, тюнингуйте heap или переходите на G1/ZGC.1000
Шаг 6: Контрольный эксперимент
Напишите минимальный endpoint, который делает только то, что подозревается как проблема (например, только запрос к БД без бизнес-логики), и сравните его latency с полным endpoint'ом. Разница — overhead Spring-слоёв (сериализация, валидация, security).
Подводные камни
- Actuator по умолчанию не экспортирует
/actuator/httptraceбез явного включения и бинаHttpTraceRepository. - Micrometer percentile histogram потребляет память — не включайте с
1.0на highRPS проде без ограничений. - Thread dump через Actuator даёт JSON, но
jstackдаёт более читаемый формат для анализа deadlock. - Spring AOP работает через JDK-прокси для интерфейсов и CGLIB для классов — убедитесь, что
spring.aop.proxy-target-class=trueесли ожидаете CGLIB. - Логирование Hibernate TRACE в проде генерирует гигабайты логов — включайте только временно.
BlockHoundнесовместим с production-режимом — только для разработки и тестов.- HikariCP по умолчанию имеет
maximumPoolSize=10— для высоконагруженного сервиса этого недостаточно. - Трейсинг с 100% sampling (probability=1.0) создаёт значительный overhead — в проде используйте 0.01–0.1.
What hurts your answer
- Сразу обвинять Spring Framework, не проверив соседние слои системы
- Чинить симптом без минимального воспроизведения и evidence
- Не учитывать версии, конфигурацию, окружение и recent changes
What they're listening for
- Умеет локализовать проблему вокруг Spring Framework
- Двигается от симптома к гипотезам и проверкам
- Отличает баг инструмента от ошибки использования или окружения