Павел «Xemul» Емельянов

Введение

Будучи большим хранилищем данных, ScyllaDB выполняет множество внутренних действий, конкурирующих за дисковый ввод-вывод. Существуют средства записи данных (например, запоминаемый код сброса или журнал фиксации) и средства чтения данных (которые, скорее всего, извлекают данные из SSTables для обслуживания клиентских запросов). Кроме того, существуют фоновые действия, такие как уплотнение или восстановление, которые могут выполнять ввод-вывод в обоих направлениях. Это соревнование очень важно решать; в противном случае просто выдача дискового ввода-вывода без какой-либо справедливости или балансировки, запрос, обслуживающий запрос на чтение, может оказаться похороненным в середине кучи фоновых операций записи. К тому времени, когда у него появится возможность запуститься, все это ожидание приведет к увеличению задержки чтения.

Поддерживая параллелизм на диске на достаточно высоком уровне для использования внутреннего параллелизма диска, для базы данных лучше не отправлять все эти запросы на диск в первую очередь, а держать их в очереди внутри базы данных. Находясь внутри базы данных, ScyllaDB затем может пометить эти запросы и с помощью приоритизации гарантировать качество обслуживания среди различных классов запросов ввода-вывода, которые должна обслуживать база данных: чтение запросов CQL, запись журнала фиксации, ввод-вывод с уплотнением в фоновом режиме и т. д.

При этом в низкоуровневом движке ScyllaDB есть компонент, называемый планировщиком ввода-вывода, который управляет несколькими очередями запросов ввода-вывода и пытается поддерживать баланс между двумя неэффективными действиями — переполнением диска запросами и недостаточным его использованием. С первым мы достигаем хорошей пропускной способности, но в конечном итоге ставим под угрозу задержку. В последнем случае задержка хороша, но страдает пропускная способность. Наша цель — найти баланс, при котором мы можем извлечь большую часть доступной пропускной способности диска, сохраняя при этом хорошую задержку.

Текущее состояние / Предыдущее большое изменение

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

Примечание: Seastar использует подход ничего не делиться, что означает, что любые решения, принимаемые ядрами ЦП (часто называемыми сегментами), не синхронизируются. друг с другом. В редких случаях, когда одному сегменту требуется помощь другого сегмента, они явно взаимодействуют друг с другом.

Существенной частью решения было введение общей пары счетчиков, которая помогла достичь двух целей: не допустить, чтобы осколки потребляли больше пропускной способности, чем может предоставить диск, а также действовать как «резервирование», когда каждый осколок мог претендовать на емкость, и ждать. чтобы он стал доступным в случае необходимости — все «честным» образом.

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

К моделированию диска

Скорее всего, при оценке диска нужно смотреть на его 4 параметра — чтение/запись IOPS и скорость чтения/записи (например, в МБ/с). Сравнение этих чисел друг с другом — популярный способ заявить, что один диск лучше другого, и оценить вышеупомянутую пропускную способность диска, применяя закон Литтла. При этом задача планировщика состоит в том, чтобы обеспечить определенный уровень параллелизма внутри диска, чтобы получить от него максимальную пропускную способность, но не делать этот параллелизм слишком высоким, чтобы диск не помещал внутренние запросы в очередь дольше, чем необходимо.

На заре своего существования ScyllaDB восприняла эту концепцию буквально и представила параметр конфигурации для управления максимальным количеством запросов, которые могут быть отправлены на диск за один раз. Позже эта концепция была доработана с учетом пропускной способности диска и того факта, что операции чтения и записи имеют разную стоимость. Таким образом, планировщик поддерживал не только «максимальное количество запросов», но и «максимальное количество байтов», отправляемых на диск, и по-разному оценивал эти числа для операций чтения и записи.

Эта модель, однако, не принимала во внимание потенциальное взаимодействие потоков чтения и записи друг с другом. Предполагалось, что этим влиянием можно пренебречь. Дальнейшие эксперименты со смешанным вводом-выводом показали, что модель можно еще улучшить.

Данные

Модель была разработана путем разработки инструмента Diskplorer для сбора полного профиля поведения диска при всех видах нагрузки. Что делает инструмент, так это загружает диск операциями чтения и записи разной интенсивности (включая чистые рабочие нагрузки, когда одно из измерений буквально равно нулю) и собирает полученные задержки запросов. Результатом является 5-мерное пространство, показывающее, как задержка диска зависит от значений {чтение, запись} x {количество операций ввода-вывода в секунду, пропускная способность}. Нарисовать такую ​​сложную вещь на экране само по себе сложно. Вместо этого инструмент отображает набор плоских срезов, некоторые примеры которых показаны ниже.

Например, именно так задержка запроса на чтение зависит от интенсивности небольших операций чтения (сложная работа диска в IOPS) по сравнению с интенсивностью больших операций записи (с учетом пропускной способности диска). Значение задержки выделено цветом, «интересная область» окрашена в голубой цвет — здесь задержка не превышает 1 миллисекунды. Измеряемым диском является диск NVMe, поставляемый с экземпляром AWS EC2 i3en.3xlarge.

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

Точно так же выглядит график для еще одного экземпляра AWS EC2 — i3.3xlarge.

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

Менее актуален для ScyllaDB, но все же очень интересен тот же сюжет для какого-то локального HDD. Ось и расцветка те же, но масштаб отличается, в частности, голубым цветом обведена область ~100 миллисекунд.

Этот диск также является полудуплексным, но при пропускной способности записи выше ~100 МБ/с смешанная рабочая нагрузка не может выдержать, несмотря на то, что на нем вполне возможна чистая рабочая нагрузка записи.

Видно, что «зона безопасности», в которой мы можем ожидать, что задержка запросов к диску останется ниже желаемого значения, представляет собой вогнутую треугольную область с вершинами, расположенными на нуле и вблизи максимальной пропускной способности диска и IOPS.

Математика

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

Где Bx — максимальные значения пропускной способности при чтении и записи, Ox — максимальные значения IOPS, а K — константа с точным значением, взятым из собранный профиль. Принимая во внимание, что пропускная способность и IOPS являются производными по времени от соответствующих байтов и количества операций, т.е.

и

и введение отношений между максимальной пропускной способностью и значениями IOPS

приведенное выше уравнение превращается в более простую форму

Давайте измерим каждый запрос с помощью кортежа из двух значений T= {1, bytes} для операций чтения и T= {Mo,Mb · bytes} для записи и определить операцию нормализации

Теперь окончательное уравнение выглядит так

Чем он лучше или проще оригинального? Дело в том, что измерить пропускную способность (или IOPS) непросто. Если вы посмотрите на любой инструмент мониторинга, будь то простая команда Linux top или сложный стек Grafana, вы обнаружите, что все такие скоростные значения рассчитываются в течение некоторого заранее определенного периода времени, скажем, 1 секунды. Ограничение чего-то, что можно измерить только с определенным промежутком времени, — еще более сложная задача.

В своей окончательной форме уравнение планирования имеет большое преимущество — оно должно накапливать некоторые мгновенные числовые значения — вышеупомянутые кортежи запросов. Единственная сложность здесь в том, что алгоритм должен ограничивать не само накопленное значение, а скорость, с которой оно растет. Эта задача имеет известное решение.

Код

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

По сути, алгоритм поддерживает корзину с двумя входами и одним выходом. Первым входом является поток пакетов, которые необходимо передать. Второй вход — это ограниченный по скорости поток токенов. Ограничение скорости здесь означает, что в отличие от пакетов, которые поступают в корзину по мере их появления, токены помещаются в корзину с некоторой фиксированной скоростью, скажем, N токенов в секунду. Результатом является ограниченный по скорости поток пакетов, недавно поступивших в корзину. Каждый исходящий пакет несет с собой один или несколько токенов, и количество токенов соответствует тому, какая «мера» ограничена по скорости. Если ведро токенов должно обеспечить скорость передачи пакетов в секунду, то каждый пакет забирает один токен. Если цель состоит в том, чтобы ограничить вывод байтами в секунду, то каждый пакет должен содержать столько токенов, сколько байтов составляет его длина.

Этот алгоритм можно применить к нашим нуждам. Помните, что цель состоит в том, чтобы ограничить нормализованную стоимость кортежей исходящих запросов. Соответственно, каждый запрос отправляется только тогда, когда он может получить с ним N(T) токенов, и токены поступают в корзину со скоростью K.

На самом деле, классический алгоритм ведра токенов пришлось немного скорректировать, чтобы он хорошо сочетался с непредсказуемой природой современных твердотельных накопителей. Дело в том, что, несмотря на все меры предосторожности и моделирование, диски все же могут неожиданно тормозить. Одна из причин — фоновая сборка мусора, которую выполняет FTL. По возможности следует избегать продолжения обслуживания операций ввода-вывода с начальной скоростью на диске с фоновой гигиеной. Чтобы компенсировать это, мы добавили в алгоритм обратную ссылку — токены, поступающие в ведро, не появляются из ниоткуда (даже строго лимитированным образом), а исходят из другого ведра. В свою очередь, это второе ведро получает свои токены с самого диска после выполнения запросов.

Модифицированный алгоритм гарантирует, что планировщик ввода-вывода обслуживает запросы со скоростью, не превышающей двух значений — прогнозируемой моделью и той, которую показывает диск под реальной нагрузкой.

Полученные результаты

Результаты можно увидеть с двух сторон. Во-первых, выполняет ли планировщик свою работу и гарантирует, что полоса пропускания и IOPS останутся в зоне безопасности по сравнению с первоначальными измерениями профиля. Во-вторых, действительно ли это помогает, то есть показывает ли диск хорошие задержки или нет. Чтобы проверить и то и другое, мы провели стресс-тест Cassandra на двух версиях ScyllaDB — 4.6 со старым планировщиком и 5.0 с новым. Было четыре прогона подряд для каждой версии — первый прогон заключался в заполнении ScyllaDB данными, а последующий — в обратном запросе этих данных, но нарушенный фоновым сжатием (потому что мы знаем, что обычная рабочая нагрузка слишком проста для современного диска NVMe). обрабатывать). Запрос производился с разной скоростью клиентских запросов — 10к, 5к и 7,5к запросов в секунду.

Управление пропускной способностью/IOPS

Давайте сначала посмотрим, как планировщик поддерживает потоки ввода-вывода и учитывает ожидаемые ограничения.

Пропускная способность населения: ScyllaDB 4,6 (всего) и 5,0 (разделено, зеленый — R, желтый — W)

На паре графиков выше вы можете увидеть пропускную способность для трех разных потоков — commitlog (только запись), уплотнение (как чтение, так и запись) и сброс памяти (только запись). Обратите внимание, что ScyllaDB 5.0 может независимо сообщать о пропускной способности чтения и записи.

ScyllaDB 4.6 поддерживает общую пропускную способность на уровне 800 МБ/с, что является пиком возможностей диска. ScyllaDB 5.0 не позволяет пропускной способности превышать несколько более низкое значение 710 МБ/с, потому что новая формула требует, чтобы сбалансированная сумма пропускной способности и IOPS, а не их отдельные значения, оставалась в пределах предела.

Подобные эффекты можно увидеть на этапе чтения теста на графиках ниже. Поскольку часть чтения включает в себя запросы на чтение небольшого размера, поступающие от обработки запросов CQL, на этот раз хорошо видеть не только графики пропускной способности, но и графики IOPS. Также обратите внимание, что на этот раз есть три разных части теста — частота входящих запросов была 10k, 5k и 7,5k, таким образом, на графиках есть три отдельные части. И последнее, но не менее важное: классы memtable flushing и commitlog здесь отсутствуют, потому что эта часть теста была доступна только для чтения, а задание уплотнения выполнялось в фоновом режиме.

Пропускная способность запроса: ScyllaDB 4.6 (всего) и 5.0 (разделено, зеленый — R, желтый — W)

Первое, что нужно отметить, это то, что планировщик 5.0 подавляет класс уплотнения намного тяжелее, чем в 4.6, позволяя классу запроса получить больше пропускной способности диска — это одна из целей, которых мы хотели достичь. Во-вторых, видно, что, как и на этапе заполнения, чистая пропускная способность планировщика 5.0 ниже, чем у планировщика 4.6 — опять же потому, что новый планировщик учитывает значение IOPS вместе с пропускной способностью, а не отдельно.

Глядя на графики IOPS, можно заметить, что 4.6, кажется, обеспечивает более или менее одинаковые IOPS для разных скоростей запросов в классе запросов, в то время как выделенная емкость 5.0 соответствует скорости входящих запросов. Правильное замечание, ответ на него — 4.6 не выдержала даже скорость запросов 5к запросов в секунду, а 5.0 хорошо себя чувствовала даже на скорости 7,5к и умерла на 10к. Теперь это явный признак того, что новый планировщик действительно обеспечивает ожидаемые задержки ввода-вывода.

Последствия задержки

Seastar отслеживает две задержки — в очереди и на диске. Задача планировщика — поддерживать последний. Очередь в очереди может расти настолько высоко, насколько она хочет; с этим должны справиться верхние слои. Например, если запросы слишком долго ожидают в очереди, ScyllaDB активирует так называемый механизм «противодавления, который включает несколько методов и может привести к отмене некоторых запросов, находящихся в очереди.

Задержки класса журнала фиксации ScyllaDB 4.6 (зеленый — время ожидания, желтый — время выполнения)

Задержки класса журнала фиксации ScyllaDB 5.0 (зеленый — время ожидания, желтый — время выполнения)

Масштаб графика не делает его очевидным, но из всплывающего окна в правом нижнем углу видно, что задержка на диске класса commitlog снизилась в три раза — с 1,5 миллисекунды до примерно полмиллисекунды. Это прямое следствие общего снижения пропускной способности, как было показано выше.

Когда дело доходит до запроса рабочей нагрузки, все становится еще лучше, потому что этот планировщик времени удерживает объемные записи на диске, когда системе требуются короткие и чувствительные к задержке чтения.

Задержки класса запросов ScyllaDB 4.6 (зеленый — время ожидания, желтый — время выполнения)

Задержки класса запросов ScyllaDB 5.0 (зеленый — время ожидания, желтый — время выполнения)

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

Что дальше

Обновленный планировщик станет частью множества других замечательных улучшений в версии 5.0. Тем не менее, некоторые новые функции должны быть добавлены сверху. Например, список метрик будет расширен, чтобы сообщать об описанных накопленных затратах, собранных каждым классом планирования. Еще одна важная функция заключается в корректировке фактора ограничения скорости (вещь K) во время выполнения для решения проблемы устаревания дисков и любых потенциальных неправильных оценок, которые мы могли сделать. И, наконец, чтобы настроить планировщик для использования на медленных дисках, таких как постоянные облачные диски или вращающиеся жесткие диски, нам нужно настроить конфигурацию цели задержки, на которую нацелен планировщик.

Узнайте больше о ScyllaDB V

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

Веб-семинар: серия глубоких погружений разработчиков ScyllaDB V — повышение производительности + сравнительный анализ AWS I4i

4 августа 2022 г. | В прямом эфире | ScyllaDB

Мы рассмотрим новую модель ввода-вывода и планировщик, которые обеспечивают точную балансировку запросов на чтение/запись в зависимости от возможностей вашего диска. Затем мы рассмотрим, как близкая к аппаратному обеспечению конструкция ScyllaDB использует всю мощь высокопроизводительных экземпляров облачных вычислений, таких как новые экземпляры EC2 I4i.

Зарегистрироваться сейчас ›

Веб-семинар: Серия глубоких погружений разработчиков ScyllaDB V — отказоустойчивость и строгая согласованность с помощью Raft

11 августа 2022 г. | В прямом эфире | ScyllaDB

Реализация протокола консенсуса Raft в ScyllaDB приводит к сильным, немедленно согласованным обновлениям схемы, изменениям топологии, таблицам и индексам и многому другому. Узнайте, как был реализован алгоритм консенсуса Raft, что вы можете сделать с ним сегодня и какие радикальные новые возможности он откроет в ближайшие дни.

Зарегистрироваться сейчас ›

Веб-семинар: серия глубоких погружений для разработчиков ScyllaDB V — драйверы на основе Rust и определяемые пользователем функции с помощью WebAssembly

18 августа 2022 г. | В прямом эфире | ScyllaDB

Rust и Wasm идеально подходят для одержимости ScyllaDB высокой производительностью и низкой задержкой. Присоединяйтесь к этому вебинару, чтобы узнать мнение наших инженеров о том, как Rust и Wasm работают с ScyllaDB, и как вы можете извлечь из них пользу.

Зарегистрироваться сейчас ›