Всем привет!
В этой статье я хочу рассказать о том, как выбирал архиватор для сжатия логов нашей фронт-офисной системы.
Подразделение, в котором я работаю, занимается разработкой и сопровождением единой фронт офисной системы Банка. Я отвечаю за ее сопровождение, мониторинг и DevOps.
Наша Система — это высоконагруженное приложение, ежедневно обслуживающее более 5 000 уникальных пользователей. На сегодняшний день — это «монолит» со всеми своими достоинствами и недостатками. Но сейчас активно идет процесс выноса функционала в микросервисы.
Ежедневно наша система генерирует более 130 ГБ «сырых» логов и, несмотря на то, что мы используем ENG стек (Elasticsearch Nxlog Graylog), файловые логи содержат гораздо больше информации (например, stack trace ошибок), поэтому требуют архивирования и хранения.
Так как место хранения ограничено, встаёт вопрос: «А какой архиватор лучше всего справится с этой задачей».
Для решения этого вопроса я написал скрипт на языке PowerShell, который произвел анализ за меня.
Задача скрипта вызвать архиваторы rar, 7z и zip с разными параметрами компрессии, посчитать скорость формирования архива, а также занимаемое место на диске.
#Requires -Version 4.0
# Очищаем экран, чтобы не мешало ничего лишнего
Clear-Host
# Переходим в каталог, где базируется исполняемый файл
Set-Location $PSScriptRoot
# Объявляем переменные, которые будем использовать в работе
$Archive = "Archive"
$ArchFileName = "ArchFileName"
[array]$path = (Get-ChildItem '.\logs').DirectoryName|Select-Object -Unique
# Для архивных файлов нам нужна папка-архив. Создаём ее, если она еще не создана.
if ((Test-Path -Path ".\$Archive") -ne $true){
New-Item -Path .\ -Name $Archive -ItemType Directory -Force
} else {
# Если папка существует - удаляем ее содержимое
Get-ChildItem .\$Archive|Remove-Item -Recurse -Force
}
# Объявляем переменную для создания таблицы
[array]$table=@()
# Выполняем архивацию Rar
#rar
# m<0..5> Set compression level (0-store...3-default...5-maximal)
1..5|foreach{
$CompressionLevel = $("-m" + $_)
$mc = Measure-Command {cmd /c .\rar.exe a -ep1 -ed $CompressionLevel -o+ -tsc .\$Archive\$($ArchFileName + $_) "$path"}
[math]::Round(($mc.TotalMilliseconds), 0)
$ArchFileNamePath = ".\$Archive\$($ArchFileName + $_ + ".rar")"
$table += ""|Select-Object -Property @{name="ArchFileName"; expression={$ArchFileNamePath -split "\\"|Select-Object -Last 1}},`
@{name="CompressionLevel"; expression={$CompressionLevel}},@{name="Extension"; expression={"rar"}},@{name="Size"; expression={(Get-ChildItem $ArchFileNamePath).Length}},`
@{name="Time"; expression={[math]::Round(($mc.TotalMilliseconds), 0)}},`
@{name="Size %"; expression={0}},@{name="Time %"; expression={0}},@{name="Result %"; expression={0}}
}
# Выполняем архивацию 7z
#7z
# -mx[N] : set compression level: -mx1 (fastest) ... -mx9 (ultra)
#cmd /c "$env:ProgramFiles\7-Zip\7z.exe" a -mx="$MX" -mmt="$MMT" -t7z -ssw -spf $($ArchFileName + "Fastest") "$path"
1..9|foreach{
$CompressionLevel = $("-mx=" + $_)
$mc = Measure-Command {cmd /c "$env:ProgramFiles\7-Zip\7z.exe" a $CompressionLevel -t7z -ssw -spf .\$Archive\$($ArchFileName + $_) $path} #-mmt="$MMT"
[math]::Round(($mc.TotalMilliseconds), 0)
$ArchFileNamePath = ".\$Archive\$($ArchFileName + $_ + ".7z")"
$table += ""|Select-Object -Property @{name="ArchFileName"; expression={$ArchFileNamePath -split "\\"|Select-Object -Last 1}},`
@{name="CompressionLevel"; expression={$CompressionLevel}},@{name="Extension"; expression={"7z"}},@{name="Size"; expression={(Get-ChildItem $ArchFileNamePath).Length}},`
@{name="Time"; expression={[math]::Round(($mc.TotalMilliseconds), 0)}},`
@{name="Size %"; expression={0}},@{name="Time %"; expression={0}},@{name="Result %"; expression={0}}
}
# Выполняем архивацию zip (Встроенная функция PS "Compress-Archive")
#zip
1..2|foreach{
Switch ($_){
1{$CompressionLevel = "Fastest"}
2{$CompressionLevel = "Optimal"}
}
$mc = Measure-Command {Compress-Archive -Path $path -DestinationPath .\$Archive\$($ArchFileName + $_) -CompressionLevel $CompressionLevel -Force}
[math]::Round(($mc.TotalMilliseconds), 0)
$ArchFileNamePath = ".\$Archive\$($ArchFileName + $_ + ".zip")"
$table += ""|Select-Object -Property @{name="ArchFileName"; expression={$ArchFileNamePath -split "\\"|Select-Object -Last 1}},`
@{name="CompressionLevel"; expression={$CompressionLevel}},@{name="Extension"; expression={"zip"}},@{name="Size"; expression={(Get-ChildItem $ArchFileNamePath).Length}},`
@{name="Time"; expression={[math]::Round(($mc.TotalMilliseconds), 0)}},`
@{name="Size %"; expression={0}},@{name="Time %"; expression={0}},@{name="Result %"; expression={0}}
}
# Выполняем сортировку по полю Size и берем первое значение [0] - это самый сжатый файл
$Size = ($table|Sort-Object -Property Size)[0].Size / 100
# Выполняем сортировку по полю Time и берем первое значение [0] - это самый быстро сжатый файл
$Time = ($table|Sort-Object -Property Time)[0].Time / 100
# Формируем итоговую таблицу
$table|foreach {
$_.time
$_."Size %" = [math]::Round(($_.Size / $Size), 0)
$_."Time %" = [math]::Round(($_.Time / $Time), 0)
if ($_."Size %" -ge $_."Time %"){
$_."Result %" = $_."Size %" - $_."Time %"
} else {
$_."Result %" = $_."Time %" - $_."Size %"
}
}
# Выводим на экран таблицу с сортировкой по "Size %" - это самый сжатый файл
$table|Sort-Object -Property "Size %","Result %"|Select-Object -First 1|Format-Table -AutoSize
# Выводим на экран таблицу с сортировкой по "Result %" - оптимальный вариант между скоростью и занимаемым местом на диске
$table|Sort-Object -Property "Result %","Size %","Time %"|Select-Object -First 1|Format-Table -AutoSize
$table|Sort-Object -Property "Size %","Result %"|Select-Object -First 1|Format-Table -AutoSize
$table|Sort-Object -Property "Result %","Size %","Time %"|Select-Object -First 1|Format-Table -AutoSize
# Работа сделана! Удаляем созданные архивы, чтобы не занимали место
Get-ChildItem .\$Archive|Remove-Item -Force
Подготовительные работы:
# Запрещаем запускать скрипт, если версия PowerShell меньше 4.0 ($PSVersionTable):
#Requires -Version 4.0
# Очищаем экран, чтобы не мешало ничего лишнего
Clear-Host
# Переходим в каталог, где базируется исполняемый файл (возможность использовать путь .\)
Set-Location $PSScriptRoot
# Объявляем переменные, которые будем использовать в работе
$Archive = "Archive"
$ArchFileName = "ArchFileName"
[array]$path = (Get-ChildItem '.\logs').DirectoryName|Select-Object -Unique
# Для архивных файлов нам нужна папка-архив. Создаём ее, если она еще не создана
if ((Test-Path -Path ".\$Archive") -ne $true){
New-Item -Path .\ -Name $Archive -ItemType Directory -Force
} else {
# Если папка существует, то удаляем ее содержимое
Get-ChildItem .\$Archive|Remove-Item -Recurse -Force
}
# Объявляем переменную для создания таблицы
[array]$table=@()
Разбираем подробнее
# Формируем массив данных от 1 до 5 и передаем через конвейер в цикл foreach
1..5|foreach{
# Передаем в переменную $CompressionLevel значение от 1 до 5($_), это необходимо для формирования уровня компрессии
$CompressionLevel = $("-m" + $_)
# Командлет Measure-Command позволяет посчитать время выполнения команды, заключенной в {}
$mc = Measure-Command {cmd /c .\rar.exe a -ep1 -ed $CompressionLevel -o+ -tsc .\$Archive\$($ArchFileName + $_) "$path"}
# Выводим время выполнения запроса на экран
[math]::Round(($mc.TotalMilliseconds), 0)
# Формируем переменную вида $ArchFileName + $_ + ".rar": Archive1.rar
$ArchFileNamePath = ".\$Archive\$($ArchFileName + $_ + ".rar")"
# Формируем таблицу $table построчно(+=) добавляя данные
$table += ""|Select-Object -Property @{name="ArchFileName"; expression={$ArchFileNamePath -split "\\"|Select-Object -Last 1}},`
@{name="CompressionLevel"; expression={$CompressionLevel}},@{name="Extension"; expression={"rar"}},@{name="Size"; expression={(Get-ChildItem $ArchFileNamePath).Length}},`
@{name="SizeAVD"; expression={0}},@{name="Time"; expression={[math]::Round(($mc.TotalMilliseconds), 0)}},`
@{name="Size %"; expression={0}},@{name="Time %"; expression={0}},@{name="Result %"; expression={0}}
}
# Выполняем сортировку по полю Size и берем первое значение [0] - это самый сжатый файл
$Size = ($table|Sort-Object -Property Size)[0].Size / 100
# Выполняем сортировку по полю Time и берем первое значение [0] - это самый быстро сжатый файл
$Time = ($table|Sort-Object -Property Time)[0].Time / 100
# Формируем итоговую таблицу
$table|foreach {
$_.time
$_."Size %" = [math]::Round(($_.Size / $Size), 0)
$_."Time %" = [math]::Round(($_.Time / $Time), 0)
if ($_."Size %" -ge $_."Time %"){
$_."Result %" = $_."Size %" - $_."Time %"
} else {
$_."Result %" = $_."Time %" - $_."Size %"
}
}
# Выводим на экран таблицу с сортировкой по "Size %" - это самый сжатый файл
$table|Sort-Object -Property "Size %","Result %"|Select-Object -First 1|Format-Table -AutoSize
# Выводим на экран таблицу с сортировкой по "Result %" - оптимальный вариант между скоростью и занимаемым местом на диске
$table|Sort-Object -Property "Result %","Size %","Time %"|Select-Object -First 1|Format-Table -AutoSize
# Работа сделана! Удаляем созданные архивы, чтобы не занимали место.
Get-ChildItem .\$Archive|Remove-Item -Force
$table|Sort-Object -Property "Size %","Result %"|Format-Table -AutoSize
ArchFileName | Compression Level | Extension | Size | Time | Size % | Time % | Result % |
---|---|---|---|---|---|---|---|
ArchFileName8.7z | -mx=8 | 7z | 26511520 | 62404 | 100 | 1674 | 1574 |
ArchFileName9.7z | -mx=9 | 7z | 26511520 | 64614 | 100 | 1733 | 1633 |
ArchFileName7.7z | -mx=7 | 7z | 28948176 | 52832 | 109 | 1417 | 1308 |
ArchFileName6.7z | -mx=6 | 7z | 30051742 | 37339 | 113 | 1002 | 889 |
ArchFileName5.7z | -mx=5 | 7z | 31239355 | 35169 | 118 | 943 | 825 |
ArchFileName4.rar | -m4 | rar | 33514693 | 11426 | 126 | 306 | 180 |
ArchFileName5.rar | -m5 | rar | 33465152 | 12894 | 126 | 346 | 220 |
ArchFileName3.rar | -m3 | rar | 33698079 | 9835 | 127 | 264 | 137 |
ArchFileName2.rar | -m2 | rar | 34399885 | 8352 | 130 | 224 | 94 |
ArchFileName4.7z | -mx=4 | 7z | 38926348 | 6470 | 147 | 174 | 27 |
ArchFileName3.7z | -mx=3 | 7z | 44545819 | 5889 | 168 | 158 | 10 |
ArchFileName2.7z | -mx=2 | 7z | 51690114 | 4754 | 195 | 128 | 67 |
ArchFileName1.rar | -m1 | rar | 53605833 | 4600 | 202 | 123 | 79 |
ArchFileName1.7z | -mx=1 | 7z | 57472172 | 3728 | 217 | 100 | 117 |
ArchFileName2.zip | Optimal | zip | 65733242 | 14025 | 248 | 376 | 128 |
ArchFileName1.zip | Fastest | zip | 81556824 | 9031 | 308 | 242 | 66 |
$table|Sort-Object -Property "Result %","Size %","Time %"|Format-Table -AutoSize
ArchFileName | Compression Level | Extension | Size | Time | Size % | Time % | Result % |
---|---|---|---|---|---|---|---|
ArchFileName3.7z | -mx=3 | 7z | 44545819 | 5889 | 168 | 158 | 10 |
ArchFileName4.7z | -mx=4 | 7z | 38926348 | 6470 | 147 | 174 | 27 |
ArchFileName1.zip | Fastest | zip | 81556824 | 9031 | 308 | 242 | 66 |
ArchFileName2.7z | -mx=2 | 7z | 51690114 | 4754 | 195 | 128 | 67 |
ArchFileName1.rar | -m1 | rar | 53605833 | 4600 | 202 | 123 | 79 |
ArchFileName2.rar | -m2 | rar | 34399885 | 8352 | 130 | 224 | 94 |
ArchFileName1.7z | -mx=1 | 7z | 57472172 | 3728 | 217 | 100 | 117 |
ArchFileName2.zip | Optimal | zip | 65733242 | 14025 | 248 | 376 | 128 |
ArchFileName3.rar | -m3 | rar | 33698079 | 9835 | 127 | 264 | 137 |
ArchFileName4.rar | -m4 | rar | 33514693 | 11426 | 126 | 306 | 180 |
ArchFileName5.rar | -m5 | rar | 33465152 | 12894 | 126 | 346 | 220 |
ArchFileName5.7z | -mx=5 | 7z | 31239355 | 35169 | 118 | 943 | 825 |
ArchFileName6.7z | -mx=6 | 7z | 30051742 | 37339 | 113 | 1002 | 889 |
ArchFileName7.7z | -mx=7 | 7z | 28948176 | 52832 | 109 | 1417 | 1308 |
ArchFileName8.7z | -mx=8 | 7z | 26511520 | 62404 | 100 | 1674 | 1574 |
ArchFileName9.7z | -mx=9 | 7z | 26511520 | 64614 | 100 | 1733 | 1633 |
# Работа сделана! Удаляем созданные архивы, чтобы не занимали место.
Get-ChildItem .\$Archive|Remove-Item -Force
Итог:
Самым экономичным по месту на диске оказался 7z со степенью сжатия -mx=8 и -mx=9
Самым быстрым по времени создания архива оказался 7z со степенью сжатия -mx=1
Самым оптимальным по скорости и занимаемому месту оказался 7z со степенью сжатия -mx=3
Мы выбираем 7z со степенью сжатия -mx=8, так как по размеру архива он равен -mx 9, но при этом работает чуть быстрее.
Отлично, архиватор и степень сжатия выбраны, теперь давайте заархивируем логи!
Нам нужно решить следующие задачи:
- Избегать высокой нагрузки на сервер во время работы.
- Обрабатывать все папки с вложенной папкой Logs.
- Удалять архивы старше 30 дней (чтобы не заканчивалось место на диске).
- Создавать архивы по дням в зависимости от времени изменения файлов.
#Requires -Version 4.0
# Добавляем в планировщик заданий если необходимо задачу архивации логов
#SCHTASKS /Create /SC DAILY /ST 22:00 /TN ArchLogs /TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe C:\Scripts\ArchLogs.ps1" /RU "NT AUTHORITY\NETWORKSERVICE" /F /RL HIGHEST
# Очищаем экран, чтобы не мешало ничего лишнего
Clear-Host
# Переходим в каталог, где базируется исполняемый файл
Set-Location $PSScriptRoot
# Описываем функцию получения значения потоков в зависимости от количества ядер процессора.
# Условие 1
Function Set-MMTCore {
$Core = (Get-WmiObject –class Win32_processor|Measure-Object NumberOfLogicalProcessors -sum).Sum / 2
$CoreTMP = $Core / 4
IF ($Core -lt 4)
{
$Core = $Core -1
}
Else
{
$Core = $Core - $CoreTMP
}
[math]::Round($Core)
}
# Задаем степень сжатия
$MX = 8
# Получаем количество потоков работы
$MMT = Set-MMTCore
# Объявляем переменные, которые будем использовать в работе
$SearchFolder = "C:\inetpub"
# Получаем объекты из папки $SearchFolder
$InetpubFolder = Get-ChildItem $SearchFolder
# Ищем папки с подпапкой Logs
# Условие 2
$LogsFolder = $InetpubFolder|foreach {Get-ChildItem $_.Fullname|Where-Object{$_.PSIsContainer -eq $true}|Where-Object{$_.name -eq "logs"}}
# Объявляем переменные, которые будем использовать в работе
$Archive = "Archive"
$ArchiveFile = "ArchFiles.txt"
# Перебираем по одной паке из $LogsFolder передавая в $LogsFolderName полный путь
foreach($LogsFolderName in $LogsFolder.Fullname){
$LogsFolderName
# Создаем папку $Archive, если она не была создана ранее
IF ((Test-Path -Path "$LogsFolderName\$Archive") -ne $true){New-Item -Path $LogsFolderName -Name $Archive -ItemType Directory -Force}
# Удаляем файлы старше 30 дней
# Условие 3
Get-ChildItem "$LogsFolderName\$Archive"|Where-Object {$_.LastWriteTime -le (Get-Date).AddDays(-30)}| Remove-Item -Force
# Получаем файлы для архивирования
[Array]$ArchiveItems = Get-ChildItem -Path $LogsFolderName -Exclude $Archive
# Если файлы есть, продолжаем. В другом случае - выходим
IF ($ArchiveItems.Count -ne ^_^quotquot^_^){
# Нас интересуют файлы изменённые не сегодня
$AllLogsFiles = Get-ChildItem $ArchiveItems -Recurse <#-Filter *.log#>|Where-Object {$_.LastWriteTime -lt (Get-Date).Date}
# Получаем дату изменения файлов
# Условие 4
$AllData = ($AllLogsFiles|Sort-Object -Property LastWriteTime).LastWriteTime.Date|Select-Object -Unique
$AllData
foreach ($Data in $AllData){
IF ($ArchiveItems.Count -ne ^_^quotquot^_^){
# Формируем список файлов для архивации за определенную дату
($AllLogsFiles|Where-Object {$_.LastWriteTime -lt (Get-Date).Date}|Where-Object {$_.LastWriteTime -ge (Get-Date $Data) -and $_.LastWriteTime -lt (Get-Date $Data).AddDays(1)}).FullName|Out-File .\$ArchiveFile -Force -Encoding default
Write-Host "==="
$Data
Write-Host "==="
# Если файлы существуют архивируем их
IF ($(Get-Content .\ArchFiles.txt -Encoding default) -ne $null){
$ArchiveFileName =$(($LogsFolderName.Remove($LogsFolderName.LastIndexOf("\"))) -split "\\"|Select-Object -Last 1) + "_" + $(Get-Date $Data -Format dd-MM-yyyy)
cmd /c "$env:ProgramFiles\7-Zip\7z.exe" a -mx="$MX" -mmt="$MMT" -t7z -ssw -sdel "$LogsFolderName\$Archive\$ArchiveFileName" "@$ArchiveFile"
# Удаляем файл со списокм файлов для архивирования
IF(Test-Path ".\$ArchiveFile"){Remove-Item ".\$ArchiveFile" -Force}
# Удаляем пустые папки внутри папки Logs
$LogsFolderName|foreach {Get-ChildItem $_|Where-Object {(Get-ChildItem -Path $_.FullName) -eq $null}|Remove-Item -Force}
}
}
}
}
}
Здесь я не буду расписывать каждую строчку скрипта(описано в комментариях), а остановлюсь лишь на функции Set-MMTCore, которая позволяет нам вычислить количество потоков для 7z, чтобы не грузить процессор на нашем сервере:
Function Set-MMTCore {
# Получаем сумму ядер процессора и делим на 2
$Core = (Get-WmiObject –class Win32_processor|Measure-Object NumberOfLogicalProcessors -sum).Sum / 2
# Получившееся значение делим еще на 4
$CoreTMP = $Core / 4
IF ($Core -lt 4)
{
$Core = $Core -1
}
Else
{
$Core = $Core - $CoreTMP
}
#Округляем значение до целого
[math]::Round($Core)
}
Без использования функции Set-MMTCore
Видно, что CPU упирается в 100%. Это значит, что мы неизбежно вызовем проблемы на сервере, а также получим алерт от системы мониторинга.
При использовании функции Set-MMTCore
Видно, что CPU равен 30-35%. Это значит, использование функции Set-MMTCore позволяет архивировать файлы без влияния на работу сервера.
Итог работы скрипта архивации:
Crazyvlad
5000 пользователей в сутки? Может 5 млн?
Revertis
Пять миллионов в сутки для какого-то ООО «Хоум Кредит Энд Финанс Банк»?
Просто у всех разные представления о высокой нагрузке ;)
asmalyshev1 Автор
У нас приложение для сотрудников, это около 2500 одновременно работающих операторов за 5 мин