Тестирование функциональности Direct I/O — задача сама по себе нетривиальная. Сложность возрастает, если проверить работу функциональности можно только на ненагруженной базе данных, а тестируемое приложение предназначено для работы с высоконагруженными системами.

Можно, конечно, ограничиться проверкой того, что на неактивной базе приложение не использует кеш. Но это не даёт ответа на главные вопросы клиента: «Будет ли реальная выгода в промышленной эксплуатации и не случится ли так, что активное резервное копирование снизит производительность базы данных?»

Меня зовут Наталья Лабчук, я занимаюсь тестированием Platform V CopyWala — системы резервного копирования и восстановления данных от СберТеха. Расскажу, как мы убедились в том, что функциональность Direct I/O в CopyWala при снятии резервной копии с высоконагруженной базы не ухудшает производительность кластера. Надеюсь, что почитать об этой задаче будет полезно тем, кто работает в разработке и тестировании Postgres-подобных баз данных, а также инженерам, которые отслеживают производительность и администрируют PostgreSQL.


Для начала давайте определимся с тем, что мы понимаем под непосредственным вводом-выводом — это понятие дальше встретится не раз.

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

Флаг O_DIRECT передаётся в системный вызов open() и включает режим непосредственного ввода-вывода для файла или устройства. Этот режим позволяет обходить буферную кеш-память ядра.

Для более полного понимания механики работы O_DIRECT на уровне ОС можно обратиться к схеме «Обзор буферизации ввода-вывода», предложенной в работе Майкла Керриска («Linux API. Исчерпывающее руководство» — СПб.: Питер, 2022. — С. 279.).

В правой части схемы показаны вызовы, которые можно применять для автоматического сброса выключением буферизации в библиотеке stdio, либо включением синхронного режима выполнения для системных вызовов файлового вывода, чтобы при каждом вызове write() происходил немедленный сброс на диск. Это гарантирует немедленную запись, но данные всё равно проходят через системный кеш и остаются в нём, занимая оперативную память.

Механика ввода-вывода позволяет понять, зачем вообще в приложении для резервного копирования понадобился именно O_DIRECT (иногда для удобства называют Direct I/O).

Прежде чем ответить, договоримся: далее в тексте мы будем использовать термин PostgreSQL, подразумевая любые СУБД на основе этого ядра.

Итак, всё дело в том, как работает PostgreSQL. Любая работа со страницами данных проходит через кеш, причём сначала ведётся поиск в буферном кеше (shared buffers), затем идёт обращение к операционной системе (далее ОС) с просьбой прочитать эту страницу. Сама ОС может прочитать эту страницу с диска, а может обнаружить её в собственном кеше. Если при этом есть сторонние программы, которые тоже для своих нужд обращаются к кешу ОС, то у ядра для PostgreSQL остаётся меньше ресурсов. Это приведёт к меньшему запасу данных СУБД в кеше ядра и увеличит длительность запроса, отдачи и записи информации.

Если на малых нагрузках такое поведение ещё можно простить, то при увеличении базы до серьёзных размеров нехватка ресурсов ведёт к значительным задержкам в ответах. Помимо того, что никто не хочет ждать дольше, с точки зрения системы превышение запланированного времени может привести, например, к обрыву ожидания на интеграции.

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

То есть O_DIRECT в инструменте резервного копирования нужен для решения проблемы конкуренции за кеш. Функциональность не должна снижать производительность работающей базы при резервном копировании.

Клиент запросил разработку Direct I/O для инструмента резервного копирования CopyWala, с возможностью настройки отдельных параметров для операций чтения и записи. Отделу тестирования поставили задачу проверить корректность реализации этого механизма.

Пара слов о функциональности, которую будем рассматривать. Агент резервного копирования — далее CopyWala — устанавливается на хост с СУБД. Базовая команда для запуска резервной копии — copywala create-backup. Схема работы снятия копии представлена на диаграмме.

Базовая команда для восстановления из копии — copywala restore. Схема работы восстановления представлена на диаграмме.

Настройка пути хранения копии и WAL-файлов, стратегии пользователя для подключения к базе — в конфигурационном файле агента. Конфигурация позволяет управлять возможностями агента, опираясь на потребности клиента и возможности хоста. Я предлагаю абстрагироваться от тонкостей настройки конфигурации, изменяя только настройку непосредственного ввода-вывода для агента CopyWala (далее Direct I/O True/False). В тестах, описанных ниже, выполняется горячее полное копирование базы PostgreSQL с локальным хранением.

При планировании тестирования функциональности вопросов было больше, чем ответов. В одних источниках предполагали ухудшение производительности приложения, снижение его скорости работы, ухудшение показателей по дискам. В других — улучшение показателей по дискам, снижение нагрузки на память. Кроме сложности в планировании — что и как тестировать — не было понимания, на каких стендах это делать: какие нужны процессоры, память и диски, какой объём базы, нагрузка, настройки приложения использовать. Снятие показателей приложения и дисков с использованием привычных метрик Grafana не давало статистически значимых результатов и оставалось на уровне погрешности (с использованием новой функциональности и без CPU и RAM меняется в пределах 2%, скорость снятия копии меняется в пределах 5-6%).

Для получения доказуемых показателей решили идти от простого. Что ожидается в CopyWala с использованием Direct I/O? При Direct I/O True CopyWala не использует кеширование операционной системы и обращается непосредственно к файловой системе, минуя слой кеш-памяти.

Мы проверили на стенде с 32 ядрами, 32 Гб памяти и диском на 1 Тб — условно большой стенд, чтобы можно было оперировать широким диапазоном нагрузки и величины базы. На хосте установлена СУБД PostgreSQL, общий размер кластера 750 Гб.

Самый очевидный способ тестирования (далее «Тест на ненагруженной базе»):

эксперимент:

  1. Принудительно очистить Page Cache (например, через echo 3 > /proc/sys/vm/drop_caches).

  2. Измерить свободный кеш.

  3. Настроить CopyWala (Direct I/O True).

  4. Создать копию базы с помощью copywala create-backup.

  5. Измерить свободный кеш. Вычислить размер использованного кеша.

контрольный замер:

  1. Принудительно очистить Page Cache (например, через echo 3 > /proc/sys/vm/drop_caches).

  2. Измерить свободный кеш.

  3. Выполнить настройку CopyWala (Direct I/O False).

  4. Создать копию базы с помощью copywala create-backup.

  5. Измерить свободный кеш. Вычислить размер использованного кеша.

  6. Сравнить результаты для Direct I/O True/False (размер использованного кеша при эксперименте и контрольном замере) с размером базы данных (далее БД).

Ожидаемый результат: при copywala create-backup с флагом O_DIRECT размер использованного кеша не превышает 1% от размеров БД; размер использованного кеша без флага O_DIRECT сравним с размером RAM на стенде.

После тестирования без нагрузки измерение подтвердило этот ожидаемый результат с небольшой поправкой на сопутствующие процессы:

  • При Direct I/O True операции идут в обход файлового кеша ОС, поэтому система использует всего ~559 МБ кеша (0,0729% от размера БД).

  • При Direct I/O False данные проходят через файловый кеш ОС, для этого чего система задействует около 26 ГБ кеша (оперативная память 32ГБ, 3,6% от размера БД).

То есть, когда Direct I/O включен — кеш почти не используется, а когда выключен — используется существенно больше.

Результат измерения на графике:

Тест на ненагруженной базе, copywala create-backup Direct I/O True
Тест на ненагруженной базе, copywala create-backup Direct I/O True
Тест на ненагруженной базе, copywala create-backup Direct I/O False
Тест на ненагруженной базе, copywala create-backup Direct I/O False

По шкале x отложено время, 1 час; по шкале y — объём оперативной памяти в Гб.

Легенда графика:

  • Total (Всего): общий объем оперативной памяти (RAM).

  • Used (Использовано): память, занятая запущенными программами и процессами. На графике — жёлтая зона.

  • Cache + Buffer (Кеш и буферы): память, которую операционная система временно использует для ускорения работы с диском. На графике — тёмно-синяя зона.

  • Free (Свободно): чистая, не занятая ничем память. На графике — зелёная зона.

  • Swap used (Использовано подкачки): на графике — красная зона.

Метрики показали отличный результат: приложение явно не использует кеш, новая настройка работает. С точки зрения тестирования приложения вопросов нет: у нас есть требование не использовать кеш, мы его не используем. Можно составить отчёт о неиспользовании кеша, вставить картинки с «До», «После» и сделать вывод об успешной реализации Direct I/O.

Но! Наша Platform V CopyWala не будет работать на неактивной базе, приложение разработано для горячего копирования высоконагруженной базы. Значит, надо нагружать.

Для испытаний под нагрузкой скорректировали план тестирования. Провели три типа измерения:

  • контрольное измерение на базе под нагрузкой (без копии);

  • контрольное измерение на базе под нагрузкой со снятием резервной копии с Direct I/O False на чтение и запись;

  • эксперимент на базе под нагрузкой со снятием резервной копии с Direct I/O True на чтение и запись.

Ожидаемый результат:

  1. Подтверждение обхода кеша: экспериментально доказать, что copywala create-backup с Direct I/O True на чтение и запись не использует кеширование операционной системы и обращается непосредственно к файловой системе, минуя слой кеш-памяти.

  2. Отсутствие влияния на производительность БД: получить количественные подтверждения того, что copywala create-backup с использованием флага O_DIRECT не снижает производительности сервера БД.

Нагружаем PostgreSQL

Для нагрузки будем использовать BenchBase — это фреймворк для нагрузочного тестирования баз данных. Он позволяет создать реалистичную OLTP-нагрузку на базу данных с использованием классического теста на основе TPC-C, эмулирующего работу склада с транзакциями: создание заказов, обработка платежей, управление поставками и инвентарём.

Повторим измерение с высокой нагрузкой (второй тест — на нагруженной базе), выполнив замер:

  • PostgreSQL с нагрузкой, без работы резервного копирования CopyWala;

  • PostgreSQL с нагрузкой, с copywala create-backup с Direct I/O True;

  • PostgreSQL с нагрузкой, с copywala create-backup с Direct I/O False.

Тест на нагруженной базе PostgreSQL + нагрузка
Тест на нагруженной базе PostgreSQL + нагрузка
Тест на нагруженной базе PostgreSQL + нагрузка + copywala create-backup Direct I/O True
Тест на нагруженной базе PostgreSQL + нагрузка + copywala create-backup Direct I/O True
Тест на нагруженной базе PostgreSQL + нагрузка + copywala create-backup Direct I/O False
Тест на нагруженной базе PostgreSQL + нагрузка + copywala create-backup Direct I/O False

По шкале x отложено время, 1 час; по шкале y — объём оперативной памяти в Гб.

Легенда графика:

  • Total (Всего): общий объем оперативной памяти (RAM).

  • Used (Использовано): память, занятая запущенными программами и процессами. На графике — жёлтая зона.

  • Cache + Buffer (Кеш и буферы): память, которую операционная система временно использует для ускорения работы с диском. На графике — тёмно-синяя зона.

  • Free (Свободно): чистая, не занятая ничем память. На графике — зелёная зона.

  • Swap used (Использовано подкачки): на графике — красная зона.

Как видите, теперь у нас нет достоверной информации о том, что CopyWala с Direct I/O работает, минуя кеш операционной системы. Тогда как присутствует разница между CopyWala с Direct I/O и без (есть освобождение кеша): при сравнении чистого PostgreSQL и PostgreSQL с CopyWala память во втором случае явно расходуется быстрее.

Вернёмся к первоначальной идее: зачем Direct I/O приложению для резервного копирования и восстановления? Чтобы работающее приложение не использовало кеширование операционной системы, тем самым не снижая производительность работающей базы. Но те графики, которые мы получили, не позволяют говорить о выгоде от функциональности для клиента. По данным свободного кеша получается, что резервное копирование влияет на работающую базу, что косвенно может повлиять на производительность.

По всем показателям получается, что или функциональность не работает так, как запланировано, или тестирование не привело к достоверным результатам. Тут у нас возникли два вопроса:

  1. Куда девается кеш?

  2. Как точно понять, что PostgreSQL не пострадал?

Ответ на первый вопрос получили довольно просто. Если ненагруженная база copywala create-backup не замечена в потреблении кеша, то, скорее всего, и на нагруженной базе она его потреблять не начнёт.

Гипотеза следующая: при чистой нагрузке, без бэкапа, PostgreSQL читает из файла несколько страниц, которые ему нужны сейчас для обслуживания нагрузки, и они попадают в кеш ОС. При подключении бэкапа — copywala create-backup — происходит полное чтение файла (не нескольких страниц, а целого файла). Если в этот момент PostgreSQL запрашивает страницу из этого же файла для своих нужд, то ОС видит, что к файлу идёт активное обращение, и сохраняет соседние блоки в кеш, считая их «горячими».

Чтобы проверить гипотезу, увеличили оперативную память так, чтобы её размер превышал базу данных, которая находится под нагрузкой (размер базы данных — 28 Гб, оперативная память для теста — 48 Гб). При повторном тесте (с нагрузкой; размер оперативной памяти значительно превышает размер базы, которая находится под нагрузкой) получили графики:

Тест на нагруженной базе (размер оперативной памяти значительно превышает размер базы под нагрузкой):

PostgreSQL + нагрузка
PostgreSQL + нагрузка
PostgreSQL + нагрузка + copywala create-backup Direct I/O True
PostgreSQL + нагрузка + copywala create-backup Direct I/O True
PostgreSQL + нагрузка + copywala create-backup Direct I/O False
PostgreSQL + нагрузка + copywala create-backup Direct I/O False

По шкале x отложено время, 1 час; по шкале y — объём оперативной памяти в Гб.

Легенда графика:

  • Total (Всего): общий объем оперативной памяти (RAM).

  • Used (Использовано): память, занятая запущенными программами и процессами. На графике — жёлтая зона.

  • Cache + Buffer (Кеш и буферы): память, которую операционная система временно использует для ускорения работы с диском. На графике — тёмно-синяя зона.

  • Free (Свободно): чистая, не занятая ничем память. На графике — зелёная зона.

  • Swap used (Использовано подкачки): на графике — красная зона.

Подтвердили гипотезу, что размер использованного Page Cache не превышает размер базы. Такое поведение означает, что в кеше находится база данных, и сделал это сам PostgreSQL в связи с особенностью собственной работы с кешем ОС.

Как точно понять, что PostgreSQL не пострадал? На второй вопрос удалось ответить на основании постановки задачи. PostgreSQL использует кеширование для обращения к часто используемым данным — информацию из кеша забрать намного быстрее, чем с диска. Если кеш ОС нужен PostgreSQL для стабильной высокой производительности, значит косвенным доказательством того, что PostgreSQL не страдает от снятия резервной копии, будет скорость его работы. Если точнее, стабильное состояние TPS (Transactions Per Second) — PostgreSQL работает со страницами данных через буферный кеш и для скорости работы ему важно, чтобы другие приложения не вытесняли страницы из кеша. Так, подтверждением того, что PostgreSQL не ущемлён в правах и свободах, станет неизменная скорость транзакции — и с CopyWala, и без.

Для измерений использовали нагрузчик BenchBase. Замеряли при одинаковых начальных условиях нагрузки за одинаковый отрезок времени.

BenchBase запускали с конфигурационным файлом:

<parameters>
	<type>POSTGRES</type>
	<driver>org.postgresql.Driver</driver>
	<url>путь</url>
	<username>пользователь</username>
	<password>пароль</password>
	<isolation>TRANSACTION_READ_COMMITTED</isolation>
	<batchsize>128</batchsize>
	<enable_adaptive_scaling>false</enable_adaptive_scaling>
        <ignore_cpu_threshold>95</ignore_cpu_threshold>
	<scalefactor>175</scalefactor>
	<loaderThreads>12</loaderThreads>
	<terminals>175</terminals>
	<works>
		<work>
			<time>3700</time>
			<rate>1125</rate>
			<weights>45,43,4,4,4</weights>
		</work>
	</works>
	<transactiontypes>
		<transactiontype>
			<name>NewOrder</name>
		</transactiontype>
		<transactiontype>
			<name>Payment</name>
		</transactiontype>
		<transactiontype>
			<name>OrderStatus</name>
		</transactiontype>
		<transactiontype>
			<name>Delivery</name>
		</transactiontype>
		<transactiontype>
			<name>StockLevel</name>
		</transactiontype>
	</transactiontypes>
</parameters>

В команде запуска BenchBase включен вывод итогового отчёта --collect=true --json --histograms -s 5.

Сценарий измерения (тест на нагруженной базе):

  1. Принудительно очистить Page Cache (например, через echo 3 > /proc/sys/vm/drop_caches).

  2. Настроить CopyWala (direct_io.read, direct_io.write = True/False).

  3. Запустить нагрузку (средством BenchBase) или нагрузку и copywala create-backup.

  4. Получить отчёт BenchBase.

  5. Проанализировать замеры PostgreSQL с нагрузкой, PostgreSQL с нагрузкой иcopywala create-backup с новой функциональностью Direct I/O, PostgreSQL с нагрузкой и copywala create-backup.

Ожидаемый результат: copywala create-backup с опцией Direct I/O оказывает минимальное влияние на PostgreSQL.

Полученные данные подтвердили гипотезу: Direct I/O позволяет существенно снизить негативное влияние на производительность базы данных.

Тест на нагруженной базе. Отчёт нагрузчика BenchBase, tpcc.summary.json

PostgreSQL + нагрузка, без резервного копирования

PostgreSQL + нагрузка + copywala create-backup, Direct I/O True

PostgreSQL + нагрузка + copywala create-backup, Direct I/O False

{

 "scalefactor": "175",

 "Current Timestamp (milliseconds)": 1770103710484,

 "Benchmark Type": "tpcc",

 "isolation": "TRANSACTION_READ_COMMITTED",

 "Goodput (requests/second)": 1093.0553140897919,

 "terminals": "175",

 "DBMS Type": "POSTGRES",

 "Latency Distribution": {

  "95th Percentile Latency (microseconds)": 15817,

  "Maximum Latency (microseconds)": 211802626,

  "Median Latency (microseconds)": 4907,

  "Minimum Latency (microseconds)": 483,

  "25th Percentile Latency (microseconds)": 2890,

  "90th Percentile Latency (microseconds)": 10953,

  "99th Percentile Latency (microseconds)": 84370,

  "75th Percentile Latency (microseconds)": 7521,

  "Average Latency (microseconds)": 15310

 },

 "Throughput (requests/second)": 1098.0261244853332

}

{

 "scalefactor": "175",

 "Current Timestamp (milliseconds)": 1770148449101,

 "Benchmark Type": "tpcc",

 "isolation": "TRANSACTION_READ_COMMITTED",

 "Goodput (requests/second)": 1094.4628716952243,

 "terminals": "175",

 "DBMS Type": "POSTGRES",

 "Latency Distribution": {

  "95th Percentile Latency (microseconds)": 29527,

  "Maximum Latency (microseconds)": 211876035,

  "Median Latency (microseconds)": 6238,

  "Minimum Latency (microseconds)": 494,

  "25th Percentile Latency (microseconds)": 3503,

  "90th Percentile Latency (microseconds)": 16662,

  "99th Percentile Latency (microseconds)": 64830,

  "75th Percentile Latency (microseconds)": 9565,

  "Average Latency (microseconds)": 16612

 },

 "Throughput (requests/second)": 1099.410979345452

}

{

 "scalefactor": "175",

 "Current Timestamp (milliseconds)": 1770129586066,

 "Benchmark Type": "tpcc",

 "isolation": "TRANSACTION_READ_COMMITTED",

 "Goodput (requests/second)": 1083.788388648986,

 "terminals": "175",

 "DBMS Type": "POSTGRES",

 "Latency Distribution": {

  "95th Percentile Latency (microseconds)": 40199,

  "Maximum Latency (microseconds)": 238485879,

  "Median Latency (microseconds)": 8496,

  "Minimum Latency (microseconds)": 571,

  "25th Percentile Latency (microseconds)": 5040,

  "90th Percentile Latency (microseconds)": 28924,

  "99th Percentile Latency (microseconds)": 89558,

  "75th Percentile Latency (microseconds)": 14016,

  "Average Latency (microseconds)": 22521

 },

 "Throughput (requests/second)": 1088.7146036834083

}

Сравнение результатов даёт представление о значительном улучшении производительности базы с использованием непосредственного ввода-вывода.

Тест на нагруженной базе: отчёт нагрузчика BenchBase, tpcc.summary.json

Параметр

PostgreSQL, без резервного копирования

PostgreSQL + copywala create-backup, Direct I/O True

PostgreSQL + copywala create-backup, Direct I/O  False

Положительное влияние Direct I/O

Goodput (запросов в секунду)

 

 

1093.06

 

1094.46

 

 

1083.78

 

 

незначительно

 

95-й процентиль задержки (мкс)

 

 

15817

 

29527

(+86,6%)

 

40199

(+154%)

 

задержка снижена

 

Максимальная задержка (мкс)

 

211802626

 

211876035

 

238485879

(+13%)

 

задержка снижена

 

Медиана (мкс)

4907

6238

(+27%)

8496

(+73%)

задержка снижена

Минимальная задержка (мкс)

483

 

 

494

 

571

 

 

незначительно

25-й процентиль задержки (мкс)

 

 

 

2890

 

3503

(+21%)

 

5040

(+74%)

 

задержка снижена

 

90-й процентиль задержки (мкс)

10953

16662

(+52%)

28924

(+164%)

задержка снижена

99-й процентиль задержки (мкс)

 

 

84370

 

64830

(-23%)

 

89558

(+6%)

 

задержка снижена

 

75-й процентиль задержки (мкс)

7521

 

9565

(+27%)

 

14016

(+86%)

задержка снижена

Средняя задержка (мкс)

15310

 

16612

(+8,5%)

 

 

22521

(+47%)

 

задержка снижена

 

Throughput (запросов в секунду)

1098.03

 

1099.41

 

1088.71

 

 

незначительно

Замер позволил получить информацию о влиянии снятия резервной копии на нагруженную базу. Опция Direct I/O при снятии резервной копии copywala create-backup работает и удовлетворяет важное требование клиента: резервное копирование не снижает производительности сервера.

Общие выводы по результатам тестирования:

  1. Тестирование на неактивной базе подтвердило, что CopyWala корректно реализует Direct I/O, обращаясь к устройствам хранения в обход системного кеша.

  2. На высоконагруженных стендах мы столкнулись с методическим противоречием: интенсивная работа самой PostgreSQL вызывает естественное разрастание кеша ОС. В этих условиях прямой мониторинг Page Cache становится неинформативным: шум от БД полностью перекрывает активность приложения бэкапа.

  3. Невозможность прямой фиксации кеша под нагрузкой позволила нам прийти к более значимому результату. Мы доказали эффективность Direct I/O косвенным, но критически важным для бизнеса методом: через стабильность показателей TPS и Latency.

По результатам тестов на нагруженных стендах в эксплуатационную документацию внесли рекомендации по мониторингу. Так, в качестве целевого показателя отсутствия негативного влияния CopyWala на БД предложили считать стабильность TPS, а не объём кеша ОС.

Подытожим

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

Буду рада вашим вопросам и размышлениям по задаче. Прошу в комментарии.