Общие потоки - новая фича акторной системы YDB, кардинально улучшает работу на ограниченных ресурсах:
Общие потоки уже доступны в версии YDB 25-1-3, и скоро будут включены на всех малых конфигурациях внутренних кластеров.
YDB опирается на собственный акторный фреймворк. Акторы (лёгкие изолированные объекты) пересылают сообщения и обрабатывают их. Каждый актор закреплён за конкретным пулом задач.
YDB использует 4 основных пула задач, предназначенных для выполнения акторов с различными требованиями к длительности обработки:
Раньше каждый поток быз закреплен за каким-либо одним пулом задач, при этом количество потоков соответствовало количеству vCPU, чтобы избежать вытеснения потоками друг друга и роста времени отклика. Теперь мы отделили потоки от пулов. Есть общие потоки на все пулы и каждый поток сам выбирает, из какого пула взять задачу, руководствуясь приоритетами:
IC > System > User > Batch.
Когда ядер достаточно, все работает плавно. Но как только ядер становится меньше, очереди растут, а задачи начинают дожидаться освобождения потоков. В итоге латентность возрастает, а пропускная способность падает — это особенно заметно на 1-о и 2-х ядерных конфигурациях. Можно увидеть на графике ниже:
График выше показывает производительность в расчете на одно ядро. Метрика измеряется в транзакциях в секунду бенчмарка ydb workload stock. Подробнее в разделе Тестирование.
Теперь посмотрим небольшую демонстрацию работы 6-ти ядерной конфигурации:
На ней изображены 4 очереди (User, Batch, System, IC) и 6 потоков, распределённых между пулами: 1 поток для User, 1 для Batch, 2 для System и 2 для IC.
Каждый поток берёт из своей очереди задачу (обработку сообщения актора). После выполнения задача может создать новые задачи:
В демонстрациях мы замеряем время последовательности (Sequence time) — время от появления задачи в User-пуле до выполнения последней созданной задачи в IC-пуле (показано внизу анимации).
Эта анимация показывает упрощённый конвейер YDB: пользовательский запрос разбивается на несколько задач в пуле System; каждая из них отправляет сообщения по сети, создавая задачи в пуле IC. Параллельная работа пулов и обеспечивает системе высокую пропускную способность.
Давайте посмотрим, как влияет уменьшение конфигурации до 4 ядер:
Можно заметить, что время последовательности увеличилось с 508 до 746, а так же что ядра начали простаивать.
Когда пулов больше, чем ядер (а это типично для 1-о и 2-х ядерных конфигураций), ядра приходится делить. Старая акторная система при работе на 2 и менее ядрах объединяет все пулы в один Common-пул.
Рассмотрим теперь 1-о ядерную конфигурацию слева и 2-х ядерную конфигурацию справа:
Особо интересен момент, когда фоновая задача из Batch пула начинает выполняться на тех же потоках, что и остальной пайплайн, заметно тормозя его — особенно на одном ядре.
С однопоточной машиной понятно: улучшить невозможно. А вот на 2-x ядерной можно попробовать отселить Batch из Common.
Увы, выходит хуже: поток Batch простаивает, а пайплайн лишь удлиняется.
Мы разделили потоки и пулы задач, в новой акторной системе мы создаем потоки по количеству vCPU и каждый поток периодически выбирает, из какого пула брать задачи.
Посмотрим как это повлияло для 1-о ядерной (слева) и 2-х ядерной (справа) конфигураций:
В наивной реализации задачи Batch пула могли бы постоянно откладываться, но наши общие потоки гарантируют выделение каждому пулу процессорного времени. В какой-то момент попадётся «неудачный» запрос, который столкнётся с Batch-задачей, но в среднем баланс справедливый.
Общие потоки также отлично разгребают микробёрсты нагрузки. Например, в нашем пайплайне актор из User пула отправляет 4 сообщения разным акторам из System пула. Эти 4 сообщения одновременно попадают в очередь System пула, что и создает микроберст. На 4-х ядерной конфигурации, общие потоки могут разом обработать этот микроберст нагрузки.
Можно сравнить, как изменилась обработка с общими потоками (слева) по сравнению со старой актор системой (справа):
Время последовательности уменьшилось практически в 2 раза, c 746 до 389!
Таким образом, общие потоки решают ключевую проблему малых конфигураций — неэффективное использование ограниченных ресурсов процессора. Теперь в системе любой свободный поток может взять самую приоритетную на данный момент задачу.
Это позволяет одновременно достичь двух целей:
С общими потоками система становится более отзывчивой, а её пропускная способность на ядро сохраняется даже при сильном дефиците ядер.
При оценке эффективности общих потоков важно понимать, что простое измерение предельной пропускной способности без учета времени отклика не даёт полной картины. В реальных условиях задержки критически важны для многих наших пользователей — база данных должна не только обрабатывать много запросов, но и отвечать быстро. Поэтому мы выбрали подход, при котором измеряем пропускную способность при заданном времени отклика.
Тестовая нагрузка: бенчмарк ydb workload stock.
Ограничение: 99-й перцентиль задержки не более 25 мс.
Метрика: количество транзакций в секунду при соблюдении лимита задержки.
Кластер: тестовый однодатацентровый кластер c NVMe дисками.
Рассматривали следующие конфигурации вычислительного узла:
Команды для запуска нагрузки:
ydb workload stock init
ydb workload stock run add-rand-order -s 3600 -t <inflight>
Параметр inflight подбирался для каждой конфигурации отдельно с учетом ограничений на 99-й перцентиль задержки.
График сверху показывает абсолютную пропускную способность (транзакций в секунду) для разных конфигураций. Видно значительный рост производительности с общими потоками на всех малых конфигурациях.
Следующий график - производительность в расчёте на одно ядро.
Именно здесь видны улучшения от внедрения общих потоков. Удельная производительность ядра в старой акторной системе стремительно падала по мере уменьшения числа ядер. При использовании общих потоков существенное падение удельной производительности наблюдается только на 1-о ядерной конфигурации.
Важно отметить, что хотя общие потоки разрабатывались в первую очередь для решения проблем малых конфигураций, они также принесли пользу и большим системам: 10-ти ядерная конфигурация показала прирост производительности на 6%.
Останавливаться не собираемся — будем ещё эффективнее использовать процессор и улучшим работу общих потоков на многоядерных машинах. В планах дальнейшая оптимизация алгоритмов планирования и адаптация под специфические нагрузки.
Примечание: кликайте по изображениям, чтобы открыть их в полном размере (PNG).