Резервное копирование MS SQL в «Бересте»: как мы используем VDI

от автора

Вокруг резервного копирования Microsoft SQL Server обычно обсуждают либо штатные BACKUP DATABASE … TO DISK, либо интеграцию с большими корпоративными системами защиты данных. Между этими двумя мирами есть важный слой: VDI (Virtual Device Interface). Именно через него внешнее приложение может встроиться в процесс резервного копирования и восстановления так, чтобы SQL Server писал не в обычный .bak по своему усмотрению, а в управляемый приложением поток данных.

В этой статье разберем небольшой, но вполне рабочий проект на C++, который реализует РК и ВД для MS SQL Server через VDI в ПО «Береста».

Утилита поддерживает:

• полный, дифференциальный и логический backup;

• restore одной базы или всех найденных;

• striped backup/restore в несколько потоков;

• Windows-аутентификацию и SQL-аутентификацию;

• работу с SQL Server 2008-2022.

Почему VDI?

Если задача ограничивается локальным резервным копированием на диск, VDI не нужен: достаточно стандартных T-SQL команд. Но как только появляется внешняя система резервного копирования, картина меняется.

СРК обычно хочет сама управлять:

• жизненным циклом задания;

• маршрутом потока данных;

• параллелизмом;

• политиками хранения;

• журналированием и обработкой ошибок.

И здесь VDI становится мостом между SQL Server и внешним приложением. SQL Server продолжает выполнять привычные BACKUP и RESTORE, но вместо физического файла работает с виртуальными устройствами. А уже клиент VDI читает или записывает данные туда, куда считает нужным: в локальные файлы, сетевое хранилище, object storage, дедуп-слой или собственный медиасервер.

В рассматриваемом проекте в качестве целевого носителя используются обычные файлы .bak, но архитектурно это именно VDI-клиент.

 Утилита

В проекте есть простой CLI-интерфейс:

mssql_vdi backup|restore [опции]

Параметры описаны в Config. Из коробки поддерживаются:

• режимы backup и restore;

• типы full, diff, log;

• число полос  — stripes;

• глубина буферизации — buffers;

• размеры — maxtransfer и — blocksize;

• компрессия SQL Server;

• выбор одной базы или списка баз;

• восстановление из каталога с наборами backup-файлов.

Это важный момент: проект сразу создавался не под один «счастливый путь», а под типовые эксплуатационные сценарии.

Логика делится на две части:

1. Подготовка задания;

2. Выполнение VDI-потока для конкретной базы.

На этапе подготовки утилита:

• валидирует аргументы;

• подключается к SQL Server через ODBC;

• получает список БД, если пользователь не указал конкретную;

• определяет, какие наборы файлов использовать при restore;

• для diff подбирает цепочку full + diff по общему timestamp.

Это уже дает важный практический элемент: учтена не только запись backup, но и логика последующего восстановления, а значит проект ориентирован именно на полный цикл РК/ВД.

Backup через VDI

Сценарий выглядит так:

1. Приложение инициализирует COM через CoInitializeEx.

2. Создает объект IClientVirtualDeviceSet2 через CoCreateInstance.

3. Генерирует GUID-базу имен виртуальных устройств.

4. Формирует VDConfig: число устройств, размер блока, размер передачи, таймаут, глубину I/O.

5. Вызывает CreateEx, публикуя набор виртуальных устройств для SQL Server.

6. В отдельном потоке запускает T-SQL команду BACKUP DATABASE или BACKUP LOG … TO VIRTUAL_DEVICE=….

7. После получения конфигурации от SQL Server поднимает рабочие потоки передачи данных, по одному на каждый stripe.

Это и есть ключевая идея VDI: SQL Server и внешнее приложение работают синхронно, но не в одном потоке исполнения. SQL Server отдает команды виртуальному устройству, а клиент подтверждает их выполнением через CompleteCommand.

Внутри transferThreadProc происходит наиболее важная часть:

• открывается конкретное виртуальное устройство через OpenDevice;

• открывается целевой файл;

• в цикле вызывается GetCommand;

• команды VDC_Write записываются в файл при backup;

• команды VDC_Read читаются из файла при restore;

• VDC_Flush завершает буферизацию;

• после каждой операции вызывается CompleteCommand.

Если смотреть на это архитектурно, то здесь SQL Server воспринимается как producer/consumer блочных данных, а приложение выступает транспортным адаптером между SQL-движком и носителем.

Striped backup в коде

Для реальной СРК поддержка одного потока быстро становится узким местом. Именно поэтому в проекте есть параметр —stripes, а в логике backup/restore создается несколько виртуальных устройств и несколько файлов:

db_full_20260412_101530_s0.bak

db_full_20260412_101530_s1.bak

db_full_20260412_101530_s2.bak

Такой подход дает сразу несколько преимуществ:

• можно распараллелить запись и чтение;

• проще масштабировать;

• ближе модель интеграции с промышленными СРК, где потоков почти всегда больше одного;

• легче переносить транспорт с локального диска на внешний storage backend.

Для каждого stripe создается отдельный поток _beginthreadex, которому передается свой deviceName и свой filePath. Это достаточно простой, но правильный способ показать параллельный VDI pipeline без лишнего усложнения.

Дополнительно в конфиге вынесены параметры, которые реально влияют на производительность:

• blockSize;

• maxTransferSize;

• buffersPerStripe;

• vdiTimeoutSec.

То есть проект уже позволяет не только сделать backup, но и экспериментировать с профилем I/O.

Формирование команд SQL Server

Подключение выполняется по ODBC. Что особенно полезно, код не зашит в один-единственный драйвер, а пробует несколько вариантов:

• ODBC Driver 18 for SQL Server;

• ODBC Driver 17 for SQL Server;

• старый SQL Server.

Для проекта это хорошее решение: оно снижает число ложных отказов на стендах с разным окружением.

Сама SQL-команда собирается программно.

Для backup:

• BACKUP DATABASE [db] TO VIRTUAL_DEVICE=’…’;

• или BACKUP LOG [db] TO VIRTUAL_DEVICE=’…’.

Дополнительно могут добавляться:

• COMPRESSION;

• DIFFERENTIAL;

• NO_TRUNCATE для аварийного log backup;

• BLOCKSIZE=….

Для restore используется:

• RESTORE DATABASE [db] FROM VIRTUAL_DEVICE=’…’ WITH REPLACE;

• далее RECOVERY или NORECOVERY.

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

ALTER DATABASE [db] SET SINGLE_USER WITH ROLLBACK IMMEDIATE

Это нужно, чтобы активные подключения не мешали восстановлению. После успешного RECOVERY база возвращается в MULTI_USER.

Учитываем differential и log backup

Проект не ограничивается full backup.

• для differential backup ищется последний full для той же базы;

• timestamp включается в имя файла и связывает цепочку full + diff;

• при restore differential сначала выполняется full WITH NORECOVERY, потом diff WITH RECOVERY;

• log backup разрешается только для баз в моделях восстановления FULL или BULK_LOGGED;

• master пропускается для differential backup, потому что SQL Server это не поддерживает;

• master можно пропускать при restore, поскольку для него нужны отдельные условия запуска SQL Server в single-user mode.

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

Как устроен поиск наборов для восстановления

Для restore одного факта наличия файлов недостаточно. Нужно понять, какие файлы относятся к одному набору.

Именно поэтому в проекте используется соглашение об именовании:

{db}_{type}_{timestamp}_s{n}.bak

Например:

Sales_full_20260412_101530_s0.bak

Sales_diff_20260412_101530_s0.bak

Функции сканируют каталог и строят наборы восстановления:

• для full restore выбирается последний набор по timestamp;

• для diff restore берутся только такие наборы, у которых одновременно существуют и full, и diff с одинаковой меткой времени;

• число stripe определяется по максимальному найденному индексу sN.

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

Итог

Проект наглядно демонстрирует, как может выглядеть интеграция MS SQL Server с внешней системой резервного копирования через VDI.

С одной стороны, утилита делает понятные вещи и делает их достаточно прямолинейно. С другой стороны, в ней уже есть те элементы, без которых сложно обсуждать реальное РК и ВД:

• параллельные потоки;

• full/diff/log сценарии;

• цепочки восстановления;

• управление состоянием базы при restore;

• работа через виртуальные устройства, а не через обычные файлы SQL Server.

Этот проект закрывает самый важный вопрос: как построить поток между SQL Server и внешним приложением через VDI и довести его до работающего backup/restore цикла.

 

ссылка на оригинал статьи https://habr.com/ru/articles/1023254/