Перейти к основному содержимому
Версия: 5.x

Производительность и качество

Архитектурные решения, влияющие на пропускную способность​

  • Одна сериализация на рассылку. Рассылка сериализуется один раз, и одни и те же байты записываются каждому получателю.
  • Параллельный fanout. Локальным получателям данные записываются параллельно с одним общим дедлайном, поэтому медленный сокет прерывается, не задерживая остальных.
  • Ограниченный объём работы на соединение. Фиксированное число параллельных запросов и ограниченная очередь не дают одному занятому клиенту исчерпать ресурсы сервера, а на heartbeat отвечается немедленно.
  • Последовательная запись. У каждого сокета одновременно только один писатель; фреймы ответов и рассылок никогда не перемешиваются.
  • Слоты доставки Redis. На каждом инстансе параллельно выполняется до 16 доставок Redis.

Бенчмарки​

Микробенчмарки локального Release-прогона 5.0 на .NET 10.0.12, AMD Ryzen 7 3700X, Windows 11 (медиана пяти замеров, payload 364 байта). Транспорт — in-memory приёмник без сетевого I/O, поэтому эти числа показывают накладные расходы библиотеки, а не сквозную задержку.

СценарийПолучателиВремя на операциюПамять на операцию
Сериализация для каждого получателя1000.266 ms39.2 KB
Однократная сериализация1000.004 ms1.2 KB
In-memory fanout1000.021 ms3.5 KB
Сериализация для каждого получателя1 0002.901 ms385 KB
Однократная сериализация1 0000.009 ms1.2 KB
In-memory fanout1 0000.143 ms11.8 KB
Сериализация для каждого получателя10 0006.040 ms3.84 MB
Однократная сериализация10 0000.021 ms1.2 KB
In-memory fanout10 0002.202 ms102 KB
Разбор 1 000 запросов–4.438 ms984 KB
  • Сериализация для каждого получателя и однократная сериализация сравнивают наивную сериализацию под каждого клиента с общим payload в DarkWS.
  • In-memory fanout — это настоящий IBroadcaster.PublishAsync через in-memory backplane каждому получателю: около 0.14 µs и 12 байт на получателя при 1 000 получателей.
  • Разбор 1 000 запросов разбирает конверты запросов, около 4 µs на запрос.

Запустите их сами на ненагруженной машине:

dotnet run --project benchmarks/DarkWS.Benchmarks -c Release

Результаты зависят от железа и среды выполнения; сравнивайте прогоны на одной и той же машине. Они не моделируют TLS, медленных клиентов и работу обработчиков.

Тесты​

Каждое изменение прогоняет полный набор тестов на .NET 8, 9 и 10:

НаборТесты
Сервер (DarkWS)193
.NET-клиент56
Пакет для тестирования29
Redis backplane (настоящий Redis в Docker)27
Браузерный клиент (Vitest)161

Пороги покрытия проваливают сборку, если у любого пакета покрытие ниже 90% по строкам или 80% по ветвям. Текущее покрытие:

ПакетСтрокиВетви
DarkWS97.6%94.3%
DarkWS.Redis93.2%91.7%
DarkWS.Client96.3%92.3%
DarkWS.Client.DependencyInjection100%100%
DarkWS.Testing100%100%
darkws99.0%97.1%

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

  • запускает браузерный клиент, собранный из исходников, в Node.js против настоящего сервера DarkWS;
  • тестирует Redis backplane с StackExchange.Redis 2.13.17 и 3.2.1;
  • упаковывает каждый NuGet-пакет, проверяет его XML-документацию и символы, устанавливает в чистый проект-потребитель и запускает на всех целевых фреймворках;
  • упаковывает npm-пакет из чистой копии, чтобы проверить его сборку;
  • запускает регрессионный нагрузочный тест Redis, который проверяет незавершённую работу и удерживаемую память при зависшем получателе.

Стабильность API​

Публичные API отслеживаются с помощью PublicApiAnalyzers: любое изменение публичной поверхности должно быть задекларировано и проверено, иначе сборка падает. Ломающие изменения выходят только в мажорных версиях, с инструкциями по миграции в changelog и в разделе Обновление.