Более эффективный способ поиска и архивирования миллионов файлов

У меня на сервере уже два дня выполняется задание из командной строки:

find data/ -name filepattern-*2009* -exec tar uf 2009.tar {} ;

Это займет вечность, а потом еще немного. Да, в целевом каталоге миллионы файлов. (Каждый файл представляет собой жалкие 8 байтов в хорошо хэшированной структуре каталогов.) Но просто запущен ...

find data/ -name filepattern-*2009* -print > filesOfInterest.txt

... занимает всего два часа или около того. Учитывая скорость выполнения моей работы, она не будет завершена через пару недель .. Это кажется неразумным. Есть ли более эффективный способ сделать это? Может быть, с помощью более сложного сценария bash?

Второй вопрос: "Почему мой нынешний подход такой медленный?"


person Stu Thompson    schedule 23.04.2010    source источник
comment
Параметр exec запускает новый процесс tar для каждого найденного файла. Кроме того, операция обновления tar стоит дорого.   -  person theomega    schedule 23.04.2010
comment
сколько у вас файлов и какого размера они? влияние вызова tar для каждого из файлов имеет большое значение для файлов размером 10 петабайт или 10 миллиардов небольших файлов   -  person sfussenegger    schedule 23.04.2010
comment
@sfussenegger: по 8 байтов ... обновили вопрос. Как указано в q, существует миллионы файлов.   -  person Stu Thompson    schedule 23.04.2010
comment
@theomega, @Stu Thompson: именно поэтому существует второй вариант -exec с + вместо ;. Если быть точным: find data/ -name 'filepattern-*2009*' -exec tar uf 2009.tar '{}' +   -  person janmoesen    schedule 29.04.2010


Ответы (9)


Если вы уже выполнили вторую команду, которая создала список файлов, просто используйте параметр -T, чтобы программа tar считывала имена файлов из этого сохраненного списка файлов. Намного лучше будет запустить 1 команду tar против N команд tar.

person frankc    schedule 23.04.2010
comment
Поработав некоторое время с xargs, я попробовал этот подход ... и он оказался намного быстрее! - person Stu Thompson; 28.04.2010
comment
будьте осторожны с xargs в этой ситуации: если ему передано много имен файлов, он выполняет tar несколько раз для подмножеств списка файлов. В вашем случае с tar -u это, вероятно, работает, но если вы создаете tar-файл tar -c, после его завершения там будет только последнее подмножество файлов ... - person drevicko; 06.05.2013

Один из вариантов - использовать cpio для создания архива в формате tar:

$ find data/ -name "filepattern-*2009*" | cpio -ov --format=ustar > 2009.tar

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

person Matthew Mott    schedule 23.04.2010
comment
это элегантное решение. и вы можете запустить его по сети. заменить > 2009.tar на | ssh host tar xf - - person ; 12.03.2011
comment
find data/ -print0 | tar -T - --null --create -f archive.tar читает список файлов из стандартного вывода и использует нулевой разделитель файлов - person Felipe Alvarez; 04.05.2012
comment
Моему Ubuntu tar не нравится, когда --null после -T. Мне пришлось использовать: find data/ -print0 | tar --null -T - --create -f archive.tar - person Jake Biesinger; 29.08.2012
comment
Осторожно: форматы архивов cpio, такие как tar, имеют максимальную длину имени файла. Предполагаемая максимальная длина - 256; Тем не менее, у меня был сбой с сообщением об ошибке слишком длинного имени файла при обработке файла с именем длиной 101 символ. - person l3x; 03.07.2014

Вот комбинация find-tar, которая может делать то, что вы хотите, без использования xargs или exec (что должно привести к заметному ускорению):

tar --version    # tar (GNU tar) 1.14 

# FreeBSD find (on Mac OS X)
find -x data -name "filepattern-*2009*" -print0 | tar --null --no-recursion -uf 2009.tar --files-from -

# for GNU find use -xdev instead of -x
gfind data -xdev -name "filepattern-*2009*" -print0 | tar --null --no-recursion -uf 2009.tar --files-from -

# added: set permissions via tar
find -x data -name "filepattern-*2009*" -print0 | \
    tar --null --no-recursion --owner=... --group=... --mode=... -uf 2009.tar --files-from -
person bashfu    schedule 23.04.2010

Для этого есть xargs:

find data/ -name filepattern-*2009* -print0 | xargs -0 tar uf 2009.tar

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

person Michal Čihař    schedule 23.04.2010
comment
Каталог хорошо хэширован. ext3, кстати. Как я уже упоминал, одна команда find выполняется быстро, поэтому я считаю, что файловая система, структура каталогов и т. Д. Не является проблемой. - person Stu Thompson; 23.04.2010
comment
Я думаю, вам придется добавить --max-args=n (сокращенно -n n), где n - максимальное количество аргументов, которое tar (или любая другая программа) может принимать. getconf ARG_MAX должен показать, насколько высок этот предел (131 072 на моей машине). Вполне возможно, что xargs сам позаботится об этом. - person sfussenegger; 23.04.2010
comment
Ух ты! Итак, я выполнил другую команду с xargs, как вы сказали 15 минут назад, и итоговый файл tar уже составляет 25% от размера моей исходной команды. Спасибо. - person Stu Thompson; 23.04.2010
comment
@Stu no arg list too long ошибка? если бы нет, я бы переборщил ... еще раз :) - person sfussenegger; 23.04.2010
comment
@Stu Эй, это снова я :) Вы можете просто заменить ; в исходной команде на +, чтобы получить тот же эффект. Просто посмотрите соответствующую запись на странице руководства по -exec - person sfussenegger; 23.04.2010
comment
@sfussenegger Ошибок нет (пока) 273 838 файлов tar'd, и их количество продолжает расти. RHEL4 64-бит. getconf ARG_MAX сообщает о 131 тысяче таких, как вы. - person Stu Thompson; 23.04.2010
comment
@Stu, в любом случае, он сразу бы потерпел неудачу. Например, это происходит, когда вы выполняете tar -uf 2009.tar filepattern-*2009* в каталоге с более чем 132 КБ файлов. - person sfussenegger; 23.04.2010
comment
xargs знает, какое максимальное количество аргументов нужно передать, это цель, вам нужен --max-args, только если вы хотите передать меньше - person Michal Čihař; 23.04.2010
comment
Оказывается, хотя xargs быстрее, чем мой первый подход, запуск tar со списком ввода через -T намного, намного быстрее, чем оба. - person Stu Thompson; 28.04.2010

Чтобы правильно обрабатывать имена файлов со странными (но допустимыми) символами (такими как новые строки, ...), вы должны записать свой список файлов в filesOfInterest.txt, используя команду find -print0:

find -x data -name "filepattern-*2009*" -print0 > filesOfInterest.txt
tar --null --no-recursion -uf 2009.tar --files-from filesOfInterest.txt 
person bashfu    schedule 01.05.2010

Как и в настоящее время, вы вызываете команду tar каждый раз, когда она находит файл, что неудивительно медленно. Вместо того, чтобы потратить два часа на печать плюс время, необходимое для открытия tar-архива, посмотреть, не устарели ли файлы, и добавить их в архив, вы фактически умножаете это время вместе. Возможно, вы добьетесь большего успеха, вызвав команду tar один раз, после того, как вы объедините все имена, возможно, используя xargs для выполнения вызова. Кстати, я надеюсь, что вы используете 'filepattern- * 2009 *', а не filepattern- * 2009 *, поскольку звездочки будут расширяться оболочкой без кавычек.

person Michael Aaron Safyan    schedule 23.04.2010

Для этого есть утилита tarsplitter.

tarsplitter -m archive -i folder/*.json -o archive.tar -p 8

будет использовать 8 потоков для архивирования файлов, соответствующих "folder / *. json", в выходной архив "archive.tar"

https://github.com/AQUAOSOTech/tarsplitter

person ruffrey    schedule 20.11.2018

Я долго боролся с Linux, прежде чем нашел гораздо более простое и потенциально более быстрое решение с использованием библиотеки tarfile Python.

  1. Используйте glob.glob для поиска нужных путей к файлам
  2. Создать новый архив в режиме добавления
  3. Добавить каждый путь к файлу в этот архив
  4. Закрыть архив

Вот мой пример кода:

import tarfile
import glob
from tqdm import tqdm

filepaths = glob.glob("Images/7 *.jpeg")
n = len(filepaths)
print ("{} files found.".format(n))
print ("Creating Archive...")
out = tarfile.open("Images.tar.gz", mode = "a")
for filepath in tqdm(filepaths, "Appending files to the archive..."):
  try:
    out.add(filepath)
  except:
    print ("Failed to add: {}".format(filepath))

print ("Closing the archive...")
out.close()

Это заняло в общей сложности около 12 секунд, чтобы найти 16222 пути к файлам и создать архив, однако в основном это было занято простым поиском путей к файлам. На создание tar-архива с 16000 путей к файлам ушло всего 7 секунд. С некоторой многопоточностью это могло бы быть намного быстрее.

Если вы ищете многопоточную реализацию, я ее сделал и разместил здесь:

import tarfile
import glob
from tqdm import tqdm
import threading

filepaths = glob.glob("Images/7 *.jpeg")
n = len(filepaths)
print ("{} files found.".format(n))
print ("Creating Archive...")
out = tarfile.open("Images.tar.gz", mode = "a")

def add(filepath):
  try:
    out.add(filepath)
  except:
    print ("Failed to add: {}".format(filepath))

def add_multiple(filepaths):
  for filepath in filepaths:
    add(filepath)

max_threads = 16
filepaths_per_thread = 16

interval = max_threads * filepaths_per_thread

for i in tqdm(range(0, n, interval), "Appending files to the archive..."):
  threads = [threading.Thread(target = add_multiple, args = (filepaths[j:j + filepaths_per_thread],)) for j in range(i, min([n, i + interval]), filepaths_per_thread)]
  for thread in threads:
    thread.start()
  for thread in threads:
    thread.join()

print ("Closing the archive...")
out.close()

Конечно, вам нужно убедиться, что значения max_threads и filepaths_per_thread оптимизированы; для создания потоков требуется время, поэтому время может фактически увеличиваться для определенных значений. И последнее, что следует отметить: поскольку мы используем режим добавления, мы автоматически создаем новый архив с указанным именем, если он еще не существует. Однако, если один действительно уже существует, он просто добавится к уже существующему архиву, а не сбросит его или создаст новый.

person Ryan Rudes    schedule 10.08.2020
comment
Учитывая, что вопросу OP теперь десять лет, мне интересно, насколько SSD повлияли бы на первоначальное время, затраченное на выполнение задачи. То, что следует учитывать при сравнении старых проблем с новыми решениями, чтобы не сравнивать яблоки с апельсинами. - person theruss; 11.08.2020

Самый простой (также удалить файл после создания архива):

find *.1  -exec tar czf '{}.tgz' '{}' --remove-files \;
person Oleg Kuznetsov    schedule 13.07.2013
comment
Нет никакой разницы в исходном подходе автора, который, как сообщается, был слишком медленным. Кроме того, он без необходимости удаляет исходные файлы, которые не запрашивались и, безусловно, будут нежелательными. - person syneticon-dj; 22.07.2013