惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Privacy International News Feed
I
Intezer
T
Tenable Blog
S
Schneier on Security
Project Zero
Project Zero
G
GRAHAM CLULEY
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
Know Your Adversary
Know Your Adversary
博客园 - 司徒正美
The Cloudflare Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
News and Events Feed by Topic
博客园 - 叶小钗
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题
aimingoo的专栏
aimingoo的专栏
S
Secure Thoughts
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
D
Darknet – Hacking Tools, Hacker News & Cyber Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
罗磊的独立博客
IT之家
IT之家
H
Hacker News: Front Page
I
InfoQ
云风的 BLOG
云风的 BLOG
S
Security Affairs
M
MIT News - Artificial intelligence
GbyAI
GbyAI
Jina AI
Jina AI
Help Net Security
Help Net Security
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog
L
Lohrmann on Cybersecurity
A
About on SuperTechFans
Attack and Defense Labs
Attack and Defense Labs
The Register - Security
The Register - Security
V
V2EX
G
Google Developers Blog
D
DataBreaches.Net
Apple Machine Learning Research
Apple Machine Learning Research
C
Cybersecurity and Infrastructure Security Agency CISA
J
Java Code Geeks
W
WeLiveSecurity
Cloudbric
Cloudbric
T
Tor Project blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Локальное нагрузочное тестирование в Java с использованием Virtual Threads
MishaBucha · 2026-05-14 · via Все публикации подряд на Хабре

Всем привет! Меня зовут Михаил, я работаю главным экспертом в ОТП Банке.

Я люблю тестировать свои решения и почти всегда пишу unit- и integration-тесты. Но вот с нагрузочным тестированием ситуация обычно совсем другая: о нем вспоминают ближе к релизу, когда архитектуру уже поздно менять.

В какой-то момент я поймал себя на мысли:

А как вообще заранее понять, сколько ресурсов будет потреблять сервис под нагрузкой?

Сколько памяти съест приложение? Когда упрется в CPU? Как поведет себя БД при разном кол-ве запросов?

Чтобы ответить на эти вопросы, я написал небольшую библиотеку для локального нагрузочного тестирования на Java Virtual Threads. Она запускает большое количество задач, собирает метрики и формирует отчет - прямо в консоли или в CSV.

Сегодня я покажу сам подход, разберу код библиотеки и оставлю ссылку на GitHub-репозиторий, чтобы вы могли попробовать ее у себя или адаптировать под свои задачи.

Что тестируем сегодня

Я написал небольшой и очень простой код. Создаем пользователя в бд и отправляем запрос на регистрацию в смежную систему:

@Service
@RequiredArgsConstructor
public class UserService {

  private final UserRepository userRepository;

  private final OtherSystemClient otherSystemClient;

  @Transactional
  public void createUser() {
    String randomEmail = UUID.randomUUID() + "@gmail.com";
    User user = buildUser(randomEmail);
    otherSystemClient.registrationUser(new RegistrationDto(randomEmail));
    userRepository.save(user);
  }

  private User buildUser(String randomEmail) {
    return User.builder()
        .email(randomEmail)
        .status(UserStatus.NEW)
        .isActive(true)
        .name("Легенда")
        .build();
  }
}

@Component
public class OtherSystemClient {

  public RegistrationResponseDto registrationUser(RegistrationDto dto) {
    LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
    return new RegistrationResponseDto(UserStatus.SUCCESS.name(), null);
  }
}

Самый очевидный вариант - поднять приложение и начать дергать HTTP endpoint через Postman, JMeter или любой другой инструмент.

Но здесь появляются проблемы:

  • не вся логика доступна через endpoint;

  • иногда хочется протестировать конкретный сервис или даже небольшой участок кода;

  • для локальной проверки поднимать полноценный нагрузочный стенд часто слишком дорого и долго.

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

High-load-tester библиотека

Идея была простой:
хотелось получить нагрузочный тест буквально в несколько строк кода и запускать его на любом участке приложения - не только на HTTP endpoint.

Например:

  • сервис;

  • repository;

  • интеграция с БД;

  • вызов внешнего API;

  • или даже отдельный метод.

При этом тест можно запускать:

  • локально;

  • внутри integration tests;

  • или как часть CI.

Ниже - пример integration-теста.

Внутри TestContext поднимается PostgreSQL через Testcontainers, а сам тест запускается в обычном @SpringBootTest.

class UserServiceLoadCurveIT extends TestContext {

  @Autowired
  private UserService userService;

  @Test
  public void shouldCreateUsersWithMediumRpsCurrentWork() {
    LoadTestReport report = RunnableChecker.run(
        RunnableTesting.builder()
            .requestCount(1000)
            .task(userService::createUser)
            .build()
    );
    Assertions.assertTrue(report.getErrors().isEmpty());
  }
}

И все, чтобы после этого мы получили полную сводку метрик, пример:

================= LOAD TEST REPORT =================

Total requests: 10000
Completed requests: 10000

Total duration: 1630 ms
Throughput: 6134.97 requests/sec

---------------- LATENCY ----------------

Average latency: 1165.37 ms
P95 latency: 1482.79 ms
P99 latency: 1491.56 ms

---------------- RESOURCES ----------------

Peak CPU usage: 51.45 %
Peak heap memory usage: 389 MB
Heap limit (MB): —

---------------- SNAPSHOTS ----------------

Collected metrics snapshots: 17

---------------- ERRORS ----------------

Errors count: 9763

====================================================

Какие метрики собираются

Идея простая - не перегружать отчет сотнями метрик, а дать набор ключевых:

  • насколько быстро система обрабатывает нагрузку;

  • где находится latency (avg / p95 / p99);

  • упирается ли она в CPU или память;

  • есть ли ошибки и в каком объеме.

@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LoadTestReport {

  /** Запрошенное число выполнений задачи (как в конфигурации). */
  private long totalRequests;

  /** Фактически завершённых задач после блока finally исполнителя. */
  private long completedRequests;

  /** Длительность всего прогона по настенным часам, миллисекунды. */
  private long durationMs;

  /** Завершённые запросы в секунду (число завершённых / длительность в секундах). */
  private double throughput;

  /** Средняя латентность одной задачи, миллисекунды. */
  private double avgLatencyMs;

  /** Оценка 95-го персентиля латентности, миллисекунды. */
  private double p95LatencyMs;

  /** Оценка 99-го персентиля латентности, миллисекунды. */
  private double p99LatencyMs;

  /** Максимальная оценка загрузки CPU по снимкам метрик, в процентах. */
  private double peakCpuLoad;

  /** Максимальный зарегистрированный объём heap по снимкам, мегабайты. */
  private long peakMemoryMb;

  /** Временной ряд снимков метрик во время прогона (может быть пустым). */
  private List<MetricsSnapshot> snapshots;

  /** Краткие сообщения об ошибках (ожидание future, лимит памяти и т.п.). */
  private List<String> errors;

  /** Лимит heap (МБ), скопированный из конфигурации; если не задавали — null. */
  private Long heapLimitMb;
  // логика сбора метрик
}

Сам тест описывается через простой конфиг:

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RunnableTesting {

  /** Код одной единицы нагрузки; вызывается столько раз, сколько задано в поле ниже. */
  private Runnable task;

  /** Сколько раз отправить задачу в пул виртуальных потоков. */
  private int requestCount;

  /** Верхний предел используемого heap (МБ), или null — не проверять. */
  private Long heapLimitMb;
}

Сейчас библиотека поддерживает три базовых параметра:

  • сама нагрузочная задача;

  • количество запусков;

  • лимит heap (если нужно симулировать ограниченные условия).

Зачем это все нужно?

Давайте смоделируем довольно типичную ситуацию.

У нас есть приложение, которое работает с базой данных. И рано или поздно именно база становится узким местом - не CPU, не код, а количество одновременных подключений.

Хочется быстро ответить на вопросы:

  • что будет при высокой конкуренции за соединения?

  • как система деградирует под нагрузкой?

  • где начинаются блокировки и ожидания?

  • как ведет себя код при параллельных транзакциях?

Для примера ограничим пул подключений к БД:

spring:
  jpa:
    hibernate:
      ddl-auto: create-drop
  datasource:
    hikari:
      # Для демонстрации исчерпания пула: мало слотов + короткое ожидание выдачи соединения
      maximum-pool-size: 20
      minimum-idle: 0
      connection-timeout: 500

Теперь запускаем 1000 виртуальных потоков, каждый из которых пытается выполнить операцию с базой:

================= LOAD TEST REPORT =================

Total requests: 1000
Completed requests: 1000

Total duration: 587 ms
Throughput: 1703.58 requests/sec

---------------- LATENCY ----------------

Average latency: 440.37 ms
P95 latency: 566.43 ms
P99 latency: 567.26 ms

---------------- RESOURCES ----------------

Peak CPU usage: 27.73 %
Peak heap memory usage: 68 MB
Heap limit (MB): —

---------------- SNAPSHOTS ----------------

Collected metrics snapshots: 6

---------------- ERRORS ----------------

Errors count: 517

====================================================

Также я еще вывывел ошибки:
[org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction и бла бла бла]

С помощью такого подхода можно очень быстро менять условия эксперимента:

  • уменьшать или увеличивать pool;

  • менять количество конкурентных запросов;

  • проверять поведение кода при перегрузке БД;

  • находить узкие места до того, как это случится в проде.

Про ресурсы и честность локальных тестов

Когда начинаешь гонять нагрузочные тесты локально, быстро всплывает очевидная проблема:
твоя машина ≠ продакшен.

И это важно понимать.

Есть разные способы приблизить локальные тесты к реальности:

  • ограничение CPU (cgroups / Docker)

  • лимиты памяти (-Xmx, container limits)

  • имитация задержек сети

  • throttling через rate limit

  • запуск в контейнерах

  • использование выделенных стендов

Но важно другое: эта библиотека не пытается заменить полноценный load testing инструмент.

Ее задача другая:

быстро понять, как ведет себя конкретный кусок кода под конкурентной нагрузкой прямо в процессе разработки.

при необходимости тест можно дополнительно “приземлить” к реальным условиям - через Docker лимиты, настройку JVM или запуск в CI-окружении, но это уже слой над библиотекой, а не её ответственность.

Как все это работает

Теперь коротко разберем внутреннее устройство.

1. Запуск нагрузки через virtual threads

Все задачи отправляются в Executors.newVirtualThreadPerTaskExecutor():

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    ...
}

Это позволяет дешево создать тысячи конкурентных задач без классических thread pool ограничений.

2. Сбор метрик во время выполнения

Параллельно запускается отдельный ScheduledExecutorService, который каждые 100 мс снимает:

  • CPU usage

  • heap memory

  • количество активных задач

  • прогресс выполнения

3. Постобработка результатов

После завершения прогона считаются:

  • latency (avg / p95 / p99)

  • throughput

  • пики CPU и памяти

  • ошибки выполнения

Возможность собирать отчеты в виде CSV

Иногда одного вывода в консоль недостаточно.

Например, когда нужно:

  • сравнить несколько прогонов;

  • зафиксировать результаты для анализа;

  • или просто сохранить историю изменений производительности.

Для этого в библиотеке есть возможность сохранить отчет в CSV:

  @Test
  public void currentTest() {
    LoadTestReport report = RunnableChecker.run(
        RunnableTesting.builder()
            .requestCount(1000)
            .task(userService::createUser)
            .build()
    );
    CsvReportGenerator.generateRunnableReport("report/load-report.csv", report);
  }

CSV содержит все ключевые метрики прогона, поэтому его можно:

  • открыть в Excel / Google Sheets;

  • сравнить разные конфигурации;

  • построить свои графики поверх данных.

Итог

Эта библиотека - не попытка заменить полноценные инструменты нагрузочного тестирования.

Она про другое: быстрые локальные эксперименты, когда нужно понять, как ведет себя конкретный кусок кода под конкурентной нагрузкой, без подготовки стендов и сложной инфраструктуры.

По сути, это способ задать себе несколько простых вопросов прямо во время разработки:

  • что будет, если увеличить нагрузку в 10–100 раз?

  • где упрется система: CPU, память или база?

  • как быстро деградирует код при конкуренции?

Virtual threads здесь выступают просто как удобный механизм для генерации высокой конкуренции без накладных расходов на потоки.

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

Мне было интересно исследовать, как virtual threads ведут себя под высокой нагрузкой и можно ли сделать JVM-native performance framework для тестирования Runnable/Callable без HTTP-слоя.

В процессе получился небольшой experimental framework, которого пока нет в maven.

Сама библиотечка - https://github.com/MishaBucha/high-load-tester/tree/develop

Всем спасибо за внимание!)