Я создал здесь аккаунт, чтобы поблагодарить @BobC за его ответ (и его вопрос). Это был катализатор, который мне нужен для решения нашей давней проблемы с журналами Solr.
Я изменил сценарий BobC, чтобы немного оптимизировать его для случая использования logrotate (используя $xfer_block_size
для ibs
и произвольно большие (8M) obs
, за которыми следует tr -d "\000"
для удаления оставшихся нулей), а затем использовал это в firstaction
части моего конфига logrotate
.
Полагаю, моё решение немного хакерское, но это гораздо лучше, чем отказывать критически важным рабочим сервисам, когда файл журнала размером более 80 ГБ угрожает заполнить диск ...
Вот чем я закончил:
#! /bin/bash
# truncat.sh
# Adapted from @BobC's script http://superuser.com/a/836950/539429
#
# Efficiently cat log files that have been previously truncated.
# They are sparse -- many null blocks before the interesting content.
# This script skips the null blocks in bulk (except for the last)
# and then uses tr to filter the remaining nulls.
#
for f in $@; do
fields=( `stat -c "%o %B %b %s" $f` )
xfer_block_size=${fields[0]}
alloc_block_size=${fields[1]}
blocks_alloc=${fields[2]}
size_bytes=${fields[3]}
bytes_alloc=$(( $blocks_alloc * $alloc_block_size ))
alloc_in_xfer_blocks=$(( ($bytes_alloc + ($xfer_block_size - 1))/$xfer_block_size ))
size_in_xfer_blocks=$(( ($size_bytes + ($xfer_block_size - 1))/$xfer_block_size ))
null_xfer_blocks=$(( $size_in_xfer_blocks - $alloc_in_xfer_blocks ))
null_xfer_bytes=$(( $null_xfer_blocks * $xfer_block_size ))
non_null_bytes=$(( $size_bytes - $null_xfer_bytes ))
if [ "$non_null_bytes" -gt "0" -a "$non_null_bytes" -lt "$size_bytes" ]; then
cmd="dd if=$f ibs=$xfer_block_size obs=8M skip=$null_xfer_blocks "
$cmd | tr -d "\000"
else
cat $f
fi
done
Использование больших блоков ускоряет порядок на dd
. dd
сначала вырезает, затем tr
обрезает остальные нули. Для справки, для разреженного файла 87 ГиБ (содержащего данные 392 МБ):
# ls -l 2015_10_12-025600113.start.log
-rw-r--r-- 1 solr solr 93153627360 Dec 31 10:34 2015_10_12-025600113.start.log
# du -shx 2015_10_12-025600113.start.log
392M 2015_10_12-025600113.start.log
#
# time truncat.sh 2015_10_12-025600113.start.log > test1
93275+1 records in
45+1 records out
382055799 bytes (382 MB) copied, 1.53881 seconds, 248 MB/s
real 0m1.545s
user 0m0.677s
sys 0m1.076s
# time cp --sparse=always 2015_10_12-025600113.start.log test2
real 1m37.057s
user 0m8.309s
sys 1m18.926s
# ls -l test1 test2
-rw-r--r-- 1 root root 381670701 Dec 31 10:07 test1
-rw-r--r-- 1 root root 93129872210 Dec 31 10:11 test2
# du -shx test1 test2
365M test1
369M test2
Когда я позволил logrotate
обработать это с помощью copytruncate
, это заняло большую часть часа и привело к созданию полностью материализованного не разреженного файла, который затем занял gzip
более часа.
Вот мое окончательное решение logrotate
:
/var/log/solr/rotated.start.log {
rotate 14
daily
missingok
dateext
compress
create
firstaction
# this actually does the rotation. At this point we expect
# an empty rotated.start.log file.
rm -f /var/log/solr/rotated.start.log
# Now, cat the contents of the log file (skipping leading nulls)
# onto the new rotated.start.log
for i in /var/log/solr/20[0-9][0-9]_*.start.log ; do
/usr/local/bin/truncat.sh $i >> /var/log/solr/rotated.start.log
> $i # truncate the real log
done
endscript
}
Хакерский момент заключается в том, что при первой настройке вы должны создать пустой файл rotated.start.log
, в противном случае logrotate
никогда не поднимет его и не запустит сценарий firstaction
.
Я видел ваш тикет об ошибке в logrotate
для которого было выпущено исправление в logrotate 3.9.0
. К сожалению, если я правильно читаю, реализованное исправление решает только часть проблемы. Он правильно копирует файл разреженного журнала для создания другого разреженного файла. Но, как вы заметили, это не совсем то, что мы хотим; мы хотим, чтобы копия исключала все ненужные нулевые блоки и сохраняла только записи журнала. После copytruncate
logrotate
еще должна gzip
файла и gzip
не обрабатывают разреженные файлы эффективно (он считывает и обрабатывает каждые нулевые байты.)
Наше решение лучше, чем исправление copytruncate
в logrotate 3.9.x
поскольку оно приводит к чистым журналам, которые можно легко сжать.