Самый быстрый способ скопировать файлы (но не весь каталог) из одного места в другое - PullRequest
0 голосов
/ 19 февраля 2020

Сводка

  • В настоящее время мне поручено перенести около 6 ТБ данных на облачный сервер, и я пытаюсь оптимизировать, насколько быстро это можно сделать.
  • Я бы использовал стандартную Robocopy, чтобы сделать это обычно, но есть требование, что я должен передавать только файлы, которые присутствуют в файловой таблице в SQL, а не целые каталоги (из-за большого количества мусор, находящийся внутри этих папок, который мы не хотим переносить).

То, что я пробовал

Подача отдельных файлов из массива в Robocopy нецелесообразна медленно, поскольку экземпляры Robocopy запускались последовательно для каждого файла, поэтому я попытался ускорить этот процесс двумя способами.

  1. Было бы бессмысленно устанавливать / MT выше 1, если только один файл был передан, поэтому я попытался смоделировать функцию многопоточности. Я сделал это с помощью новой функции ForEach-Object –Parallel в PowerShell 7.0 и установив предел газа равным 4. Благодаря этому я смог передать массив и запустить 4 задания Robocopy параллельно ( все еще запускается и останавливается для каждого файла), что немного увеличивает скорость.

  2. Во-вторых, я разделил массив на 4 равных массива и запустил вышеупомянутую функцию для каждого массива в качестве задания, что снова увеличило скорость совсем немного. Для ясности, у меня было 4 равных массива, поданных в 4 блока кода ForEach-Object -Parallel, в которых выполнялось 4 экземпляра Robocopy, поэтому в общей сложности 16 экземпляров Robocopy одновременно.

Проблемы

Я столкнулся с несколькими проблемами.

  1. Мое моделирование функции многопоточности не работало , так как флаг / MT работает в Robocopy. При проверке запущенных процессов мой код выполняет одновременно 16 экземпляров Robocopy, в то время как флаг Robocopy / MT: 16 запускает только один экземпляр Robocopy (но все равно будет многопоточным).

  2. Во-вторых, код вызывает утечка памяти . Использование памяти начинает увеличиваться при выполнении заданий и накапливается со временем, пока не будет использована большая часть памяти. После завершения заданий использование памяти остается высоким, пока я не закрою PowerShell и не освободлю память. Обычная Робокопия не делала этого.

  3. Наконец, я решила сравнить время, затраченное на мой метод, и затем стандартную Робокопию всего каталога тестирования, и нормальная Робокопия все еще была закончена. в 10 раз быстрее и имели лучший показатель успеха (многие файлы не были скопированы с моим кодом, и большую часть времени я получал сообщения об ошибках о том, что файлы в настоящее время используются и не могут не может быть Robocopied, предположительно потому, что они находились в процессе Robocopied).

Существуют ли более быстрые альтернативы или есть способ вручную создать многопоточный экземпляр Robocopy, который бы выполнять как / MT флаг стандартной Robocopy? Я ценю любые идеи / альтернативные способы взглянуть на это. Спасибо!

#Item(0) is the Source excluding the filename, Item(2) is the Destination, Item(1) is the filename
$robocopy0 = $tables.Tables[0].Rows
$robocopy1 = $tables.Tables[1].Rows
$robocopy0 | ForEach-Object -Parallel {robocopy $_.Item(0) $_.Item(2) $_.Item(1) /e /w:1 /r:1 /tee /NP /xo /mt:1 /njh /njs /ns 
                                      } -ThrottleLimit 4 -AsJob
$robocopy1 | ForEach-Object -Parallel {robocopy $_.Item(0) $_.Item(2) $_.Item(1) /e /w:1 /r:1 /tee /NP /xo /mt:1 /njh /njs /ns 
                                      } -ThrottleLimit 4 -AsJob
#*8 for 8 arrays

1 Ответ

0 голосов
/ 19 февраля 2020

Многопоточность RunspaceFactory может оптимально подходить для этого типа работы - с одним ОГРОМНЫМ предупреждением. На net есть довольно много статей об этом. По сути, вы создаете блок сценариев, который принимает параметры для исходного файла, который нужно скопировать, и место назначения для записи и использует эти параметры для выполнения робокопии против него. Вы создаете отдельные экземпляры PowerShell для выполнения каждого варианта блока сценариев и добавляете его в RunspaceFactory. RunspaceFactory будет помещать в очередь задания и работать с числом X заданий, возможно, миллионами за раз, где X равно количеству потоков, выделенных для фабрики.

ПРЕДУПРЕЖДЕНИЕ. Прежде всего, чтобы поставить в очередь миллионы заданий относительно вероятных миллионов файлов в 6 ТБ, вам, вероятно, понадобятся огромные объемы памяти. Предполагая, что средняя длина пути для источника и места назначения составляет 40 символов (вероятно, очень великодушна) * WAG из 50 миллионов файлов занимает почти 4 ГБ в памяти, что не включает структурные издержки объекта, экземпляры PowerShell и т. Д. c. Вы можете преодолеть это либо разбив работу на более мелкие куски, либо использовать сервер с 128 ГБ ОЗУ или лучше. Кроме того, если вы не прервите задания после их обработки, вы также столкнетесь с утечкой памяти, но это будут только ваши задания, выдающие информацию, которую вы не закрываете после завершения.

Вот пример из недавнего проекта, в котором я перенес файлы со старого NAS домена на новый NAS домена - я использую Quest SecureCopy вместо RoboCopy, но вы сможете легко заменить эти биты:

## MaxThreads is an arbitrary number I use relative to the hardware I have available to run jobs I'm working on.
$FileRSpace_MaxThreads = 15
$FileRSpace = [runspacefactory]::CreateRunspacePool(1, $FileRSpace_MaxThreads, ([System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()), $Host)
$FileRSpace.ApartmentState = 'MTA'
$FileRSpace.Open()

## The scriptblock that does the actual work.
$sb = {
param(
    $sp,
    $dp
)

    ## This is my output object I'll emit through STDOUT so I can consume the status of the job in the main thread after each instance is completed.
    $n = [pscustomobject]@{
        'source'  = $sp
        'dest'    = $dp
        'status'  = $null
        'sdtm'    = [datetime]::Now
        'edtm'    = $null
        'elapsed' = $null
    }

    ## Remove the Import-Module and SecureCopy cmdlet and replace it with the RoboCopy version
    try {
        Import-Module "C:\Program Files\Quest\Secure Copy 7\SCYPowerShellCore.dll" -ErrorAction Stop
        Start-SecureCopyJob -Database "C:\Program Files\Quest\Secure Copy 7\SecureCopy.ssd" -JobName "Default" -Source $sp -Target $dp -CopySubFolders $true -Quiet $true -ErrorAction Stop | Out-Null
        $n.status = $true
    } catch {
        $n.status = $_
    }
    $n.edtm    = [datetime]::Now
    $n.elapsed = ("{0:N2} minutes" -f (($n.edtm - $n.sdtm).TotalMinutes))
    $n        
}

## The array to hold the individual runspaces and ulitimately iterate over to watch for completion.
$FileWorkers = @()

$js = [datetime]::now
log "Job starting at $js"

## $peers is a [pscustomobject] I precreate that just contains every source (property 's') and the destination (property 'd') -- modify to suit your needs as necessary
foreach ($c in $peers) {

    try {

        log "Configuring migration job for '$($c.s)' and '$($c.d)'"
        $runspace = [powershell]::Create()
        [void]$runspace.AddScript($sb)

        [void]$runspace.AddArgument($c.s)
        [void]$runspace.AddArgument($c.d)

        $runspace.RunspacePool = $FileRSpace
        $FileWorkers += [pscustomobject]@{
            'Pipe'   = $runspace
            'Async'  = $runspace.BeginInvoke()
        }

        log "Successfully created a multi-threading job for '$($c.s)' and '$($c.d)'"
    } catch {
        log "An error occurred creating a multi-threading job for '$($c.s)' and '$($c.d)'"
    }
}

while ($FileWorkers.Async.IsCompleted -contains $false) {
    $Completed = $FileWorkers | ? { $_.Async.IsCompleted -eq $true }
    [pscustomobject]@{
        'Numbers' = ("{0}/{1}" -f $Completed.Count, $FileWorkers.Count)
        'PercComplete' = ("{0:P2}" -f ($Completed.Count / $FileWorkers.Count))
        'ElapsedMins' = ("{0:N2}" -f ([datetime]::Now - $js).TotalMinutes)
    }

    $Completed | % { $_.Pipe.EndInvoke($_.Async) } | Export-Csv -NoTypeInformation ".\$($DtmStamp)_SecureCopy_Results.csv"

    Start-Sleep -Seconds 15
}

## This is to handle a race-condition where the final job(s) aren't completed before the sleep but do when the while is re-eval'd
$FileWorkers | % { $_.Pipe.EndInvoke($_.Async) } | Export-Csv -NoTypeInformation ".\$($DtmStamp)_SecureCopy_Results.csv"

Предлагаемые стратегии, если у вас нет мощного сервера для одновременной постановки всех заданий в очередь, - это либо пакетировать файлы в блоки статического размера (например, 100 000 или что-то еще, что может взять ваш hw), либо вы можете сгруппировать файлы вместе для отправки к каждому блоку сценария (например, 100 файлов в блоке сценария), что минимизирует количество заданий в очереди в фабрике пространства выполнения (но потребует некоторого изменения кода).

HTH

Редактировать 1: Для адресации при создании объекта ввода я использую

$destRoot = '\\destinationserver.com\share'

$peers = @()

$children = @()
$children += (get-childitem '\\sourceserver\share' -Force) | Select -ExpandProperty FullName 

foreach ($c in $children) {

    $peers += [pscustomobject]@{
        's' = $c
        'd' = "$($destRoot)\$($c.Split('\')[3])\$($c | Split-Path -Leaf)"
    }
}

В моем случае я брал вещи из \ s erver1 \ share1 \ subfolder1 и переместить его в нечто вроде \ server2 \ share1 \ subfolder1 \ subfolder2. Таким образом, по сути, все, что делает массив $ peers, - это создание объекта, который принимает полное имя целевой цели и создает соответствующий путь назначения (поскольку имена серверов источника / назначения разные и, возможно, также имеют общее имя).

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

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

...