Вокруг резервного копирования 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/