Я использую logrotate с опцией copytruncate . Это хорошо работает, создавая разреженный файл, который начинается с растущего числа "виртуальных" нулевых блоков, которые не занимают места на диске.

Проблема заключается в скопированных файлах: хотя они занимают мало места на диске, попытка изучить их с использованием less занимает вечность, поскольку "виртуальные" нулевые блоки расширяются до реальных нулей. Я действительно хотел бы удалить начальные разреженные нулевые блоки с начала скопированного файла.

Вот что я знаю до сих пор: ls -ls и du могут сказать мне, какая часть файла "настоящая". И я думаю, что dd можно использовать для копирования без ведущих пустых блоков. Но я не могу собрать все это во что-то, что я могу поместить в раздел postrotate моего файла logrotate.conf .

Я нашел методы, которые используют tr или sed для удаления пустых значений, но для этого необходимо расширить файл (сделать виртуальные нулевые значения физическими), и со временем размер файла может превысить терабайт! Мне нужен более «хирургический» подход, который работает без расширения файла. Это должно потребовать только возиться с инодами, поскольку именно там живут разреженные блоки (не в фактически выделенной области).

Конечно, "реальное" исправление - заставить генерирующую программу использовать SIGHUP для повторного открытия выходного файла, но в этом случае это невозможно.

Какой самый простой и быстрый способ напрямую удалить ведущие нулевые блоки из разреженного файла?


Приложение: Вот как сделать свой собственный редкий файл для игры:

$ dd if=/dev/zero of=sparse.txt bs=1 count=0 seek=8G
0+0 records in
0+0 records out
0 bytes (0 B) copied, 0.000226785 s, 0.0 kB/s

$ echo 'Hello, World!' >>sparse.txt

$ ls -ls sparse.txt
4 -rwxrwxrwx 1 me me 8589934606 Nov  6 10:20 sparse.txt

$ ls -lsh sparse.txt 
4.0K -rwxrwxrwx 1 me me 8.1G Nov  6 10:20 sparse.txt

Этот "огромный" файл почти не занимает места на диске. Теперь попробуйте less sparse.txt . Вам придется пробраться через 8G нулей, чтобы добраться до персонажей в конце. Даже tail -n 1 sparse.txt занимает много времени.

2 ответа2

0

Вот моя первая попытка «работать», использующая stat и dd , которая работает только для ведущих разреженных файлов:

#! /bin/bash
for f in $@; do
  echo -n "$f : "
  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 of=$f.new bs=1 skip=$null_xfer_bytes count=$non_null_bytes"
    echo $cmd
    exec $cmd
  else
    echo "Nothing to do: File is not sparse."
  fi
done

Как вы думаете?

0

Я создал здесь аккаунт, чтобы поблагодарить @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 поскольку оно приводит к чистым журналам, которые можно легко сжать.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .