Увеличьте размер буфера, возвращаемого SQL VDI (интерфейс виртуального устройства) SQL Backup для Azure Blob - PullRequest
0 голосов
/ 06 ноября 2019

Мы создали проект на основе этого проекта кода и обновили его до Visual Studio 2017. Но мы изменили часть C # DotNet для резервного копирования в блочный BLOB-объект Azure. Это должен быть блочный блоб, чтобы позже добавить шифрование. Код CPP DotNet передает буферы части C # в буферах по 64 КБ. Часть C # объединяет несколько блоков по 64 КБ в один большой блок по 50 МБ перед загрузкой в ​​Azure. Объединение буферов требуется из-за ограничения приблизительно в 50 КБ на количество блоков в BLOB-объекте Azure. Загрузка в виде блоков размером 64 КБ заставит нас очень быстро превысить это ограничение для большой базы данных.

Все это работает как шарм. Однако это мучительно медленно: примерно в 3 раза больше времени для резервного копирования базы данных объемом 80 ГБ, чем эквивалент BACKUP TO URL в Management Studio.

Эта команда передается из C # в подсистему VDI CPP:

BACKUP DATABASE [bench1] TO VIRTUAL_DEVICE='<deviceGUID>' WITH FORMAT, COMPRESSION

По-видимому, снижение производительности связано с объединением блоков по 64 КБ в блоки по 50 МБ. Мой вопрос: есть ли способ заставить подсистему VDI возвращать буферы размером более 64 КБ или это "встроено в соус"?

Вот очень урезанный код VDIDotNet.CPP. Для краткости большая часть обработки ошибок была удалена.

#include "vdi.h"
#include "vdierror.h"
#include "vdiguid.h"

using namespace System;
using namespace System::Data;
using namespace System::Data::Odbc;
using namespace System::Globalization;
using namespace System::IO;
using namespace System::Runtime::InteropServices;
using namespace System::Threading;
using namespace System::Reflection;

namespace VdiDotNet {

    public ref class VdiEngine
    {
        private: Void SqlServerConnection_InfoMessage(Object^ sender, OdbcInfoMessageEventArgs^ e)
                {
                    VdiDotNet::InfoMessageEventArgs^  i = gcnew VdiDotNet::InfoMessageEventArgs(e->Message);
                    InfoMessageReceived(this, i);
                }
        private: Void ThreadFunc(Object^ data)
        {
            try
            {
                String^ connString = "Driver={SQL Server Native Client 11.0};Server=(local);Trusted_Connection=Yes;";

                //Create and configure an ODBC connection to the local SQL Server
                OdbcConnection^ SqlServerConnection = gcnew OdbcConnection(connString);
                SqlServerConnection->InfoMessage += gcnew OdbcInfoMessageEventHandler(this, &VdiDotNet::VdiEngine::SqlServerConnection_InfoMessage);

                //Create and configure the command to be issued to SQL Server
                OdbcCommand^ SqlServerCommand = gcnew OdbcCommand(data->ToString(), SqlServerConnection);
                SqlServerCommand->CommandType = CommandType::Text;
                SqlServerCommand->CommandTimeout = 0;

                //Notify the user of the command issued
                CommandIssued(this, gcnew CommandIssuedEventArgs(data->ToString()));

                //Open the connection
                SqlServerConnection->Open();

                //Execute the command
                SqlServerCommand->ExecuteNonQuery();
            }
            catch (Exception ^ex)
            {
                LogException(ex);
                throw gcnew ApplicationException(ex->Message);
            }
        }

        private: static Void ExecuteDataTransfer (IClientVirtualDevice* vd, Stream^ s)
        {
            VDC_Command *   cmd;
            DWORD           completionCode;
            DWORD           bytesTransferred;
            HRESULT         hr;

            while (SUCCEEDED(hr = vd->GetCommand(INFINITE, &cmd)))
            {
                array<System::Byte>^ arr = gcnew array<System::Byte>(cmd->size);
                bytesTransferred = 0;
                switch (cmd->commandCode)
                {
                    case VDC_Read:
                        // ... stuff ...
                    case VDC_Write:
                        //Copy the data from the cmd object to a CLR array
                        Marshal::Copy((IntPtr)cmd->buffer, arr, 0, cmd->size);

                        //Write the data to the stream
                        s->Write(arr, 0, cmd->size);

                        //Set the number of bytes transferred
                        bytesTransferred = cmd->size;

                        //Set the completion code
                        completionCode = ERROR_SUCCESS;
                        break;

                    case VDC_Flush:
                        //Flush the stream
                        s->Flush();

                        //Set the completion code
                        completionCode = ERROR_SUCCESS;
                        break;

                    case VDC_ClearError:
                        //Set the completion code
                        completionCode = ERROR_SUCCESS;
                        break;

                    default:
                        //Set the completion code
                        completionCode = ERROR_NOT_SUPPORTED;
                        break;
                }

                //Complete the command
                hr = vd->CompleteCommand(cmd, completionCode, bytesTransferred, 0);
            }
        }

        public: Void ExecuteCommand(System::String^ command, Stream^ commandStream)
        {
            try
            {
                //Initialize COM
                HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
                //Get an interface to the virtual device set
                IClientVirtualDeviceSet2* vds = NULL;
                hr = CoCreateInstance(CLSID_MSSQL_ClientVirtualDeviceSet, NULL, CLSCTX_INPROC_SERVER, IID_IClientVirtualDeviceSet2, (void**)&vds);

                //Configure the device configuration
                VDConfig config = { 0 };
                vds->GetConfiguration(INFINITE, &config);
                config.deviceCount = 1; //The number of virtual devices to create

                //Create a name for the device using a GUID
                String^ DeviceName = System::Guid::NewGuid().ToString()->ToUpper(gcnew CultureInfo("en-US"));
                WCHAR wVdsName[37] = { 0 };
                Marshal::Copy(DeviceName->ToCharArray(), 0, (IntPtr)wVdsName, DeviceName->Length);

                //Create the virtual device set
                hr = vds->CreateEx(NULL, wVdsName, &config);

                //Format the command
                command = String::Format(gcnew CultureInfo("en-US"), command, DeviceName);

                //Create and execute a new thread to execute the command
                Thread^ OdbcThread = gcnew Thread(gcnew ParameterizedThreadStart(this, &VdiDotNet::VdiEngine::ThreadFunc));
                OdbcThread->Start(command);

                //Configure the virtual device set
                hr = vds->GetConfiguration(INFINITE, &config);

                //Open the one device on the device set
                IClientVirtualDevice* vd = NULL;
                hr = vds->OpenDevice(wVdsName, &vd);

                //Execute the data transfer
                ExecuteDataTransfer(vd, commandStream);

                //Wait for the thread that issued the backup / restore command to SQL Server to complete.
                OdbcThread->Join();
            }
            catch (Exception ^ex)
            {
                LogException(ex);
                throw gcnew ApplicationException(ex->Message);

            }
        }
    };
}

А вот очень урезанный VDI.H

#include "rpc.h"
#include "rpcndr.h"

#include "windows.h"
#include "ole2.h"

#pragma pack(8)
struct VDConfig
    {
    unsigned long deviceCount;
    unsigned long features;
    unsigned long prefixZoneSize;
    unsigned long alignment;
    unsigned long softFileMarkBlockSize;
    unsigned long EOMWarningSize;
    unsigned long serverTimeOut;
    unsigned long blockSize;
    unsigned long maxIODepth;
    unsigned long maxTransferSize;
    unsigned long bufferAreaSize;
    } ;

enum VDCommands
    {   VDC_Read    = 1,
    VDC_Write   = ( VDC_Read + 1 ) ,
    VDC_ClearError  = ( VDC_Write + 1 ) ,
    VDC_Rewind  = ( VDC_ClearError + 1 ) ,
    VDC_WriteMark   = ( VDC_Rewind + 1 ) ,
    VDC_SkipMarks   = ( VDC_WriteMark + 1 ) ,
    VDC_SkipBlocks  = ( VDC_SkipMarks + 1 ) ,
    VDC_Load    = ( VDC_SkipBlocks + 1 ) ,
    VDC_GetPosition = ( VDC_Load + 1 ) ,
    VDC_SetPosition = ( VDC_GetPosition + 1 ) ,
    VDC_Discard = ( VDC_SetPosition + 1 ) ,
    VDC_Flush   = ( VDC_Discard + 1 ) ,
    VDC_Snapshot    = ( VDC_Flush + 1 ) ,
    VDC_MountSnapshot   = ( VDC_Snapshot + 1 ) ,
    VDC_PrepareToFreeze = ( VDC_MountSnapshot + 1 ) ,
    VDC_FileInfoBegin   = ( VDC_PrepareToFreeze + 1 ) ,
    VDC_FileInfoEnd = ( VDC_FileInfoBegin + 1 ) 
    } ;

struct VDC_Command
    {
    DWORD commandCode;
    DWORD size;
    DWORDLONG position;
    BYTE *buffer;
    } ;

Спасибо за любые предложения.

1 Ответ

0 голосов
/ 07 ноября 2019

Я наконец понял это. Отправка ответа на случай, если кто-то столкнется с подобной проблемой

Я использовал MAXTRANSFERSIZE=4194304 (максимум) в команде BACKUP, например:

BACKUP DATABASE [bench1] TO VIRTUAL_DEVICE='<deviceGUID>' WITH FORMAT, COMPRESSION, 
    MAXTRANSFERSIZE=4194304

После этого SQLVDI прошелОбратные буферы в 4 МБ на блок.

...