Проверка наличия 350 миллионов файлов по сети
У меня есть таблица SQL Server с ~ 300 000 000 абсолютных UNC-путей, и я пытаюсь (быстро) проверить их, чтобы убедиться, что путь в таблице SQL Server фактически существует как файл на диске.
При значении лица я запрашиваю таблицу в партиях по 50 000 и увеличиваю счетчик, чтобы продвигать свою партию, когда я иду.
Затем я использую объект для чтения данных для хранения текущего набора пакетов и цикла через пакет, проверяя каждый файл командой File.Exists(path)
, как в следующем примере.
Проблема в том, что я обрабатываю прибл. 1000 файлов в секунду максимум на четырехъядерном ядре 3.4ghz i5 с 16-гигабайтным барабаном, который займет несколько дней. Есть ли более быстрый способ сделать это?
У меня есть индекс columnstore в таблице SQL Server, и я профилировал его. Я получаю пакеты из 50 тыс. Записей в < 1s, поэтому это не узкое место SQL при выпуске пакетов в консольное приложение .net.
while (counter <= MaxRowNum)
{
command.CommandText = "SELECT id, dbname, location FROM table where ID BETWEEN " + counter + " AND " + (counter+50000).ToString();
connection.Open();
using (var reader = command.ExecuteReader())
{
var indexOfColumn1 = reader.GetOrdinal("ID");
var indexOfColumn2 = reader.GetOrdinal("dbname");
var indexOfColumn3 = reader.GetOrdinal("location");
while (reader.Read())
{
var ID = reader.GetValue(indexOfColumn1);
var DBName = reader.GetValue(indexOfColumn2);
var Location = reader.GetValue(indexOfColumn3);
if (!File.Exists(@Location.ToString()))
{
//log entry to logging table
}
}
}
// increment counter to grab next batch
counter += 50000;
// report on progress, I realize this might be off and should be incremented based on ID
Console.WriteLine("Last Record Processed: " + counter.ToString());
connection.Close();
}
Console.WriteLine("Done");
Console.Read();
EDIT: добавление дополнительной информации:
думал об этом все через саму базу; это серверное предприятие sql с 2tb оперативной памяти и 64 ядрами. Проблема заключается в том, что учетная запись службы sql-сервера не имеет доступа к путям nas, на которых размещаются данные, поэтому моя cmdshell работает через SP с ошибкой (я не контролирую материал AD), а UNC-пути имеют сотни тысяч отдельных подсетей каталоги на основе хеша MD5 файла. Таким образом, перечисление содержимого каталогов не будет полезным, потому что у вас может быть файл 10 каталогов, в котором находится только 1 файл. Вот почему я должен выполнить буквальный полный путь/проверить.
О, и пути очень длинные вообще. Я фактически попробовал загрузить их все в список в памяти, прежде чем я понял, что это эквивалент 90gb данных (lol, oops). Полностью согласен с другими комментариями по его разметке. База данных очень быстро, не беспокоится вообще. Если бы не рассматривалась болтовня SMB, это очень хорошо, возможно, это то, с чем я столкнулся. - JRats 13 часов назад
О! И я также обновляю базу данных только в том случае, если файл не существует. Если это произойдет, мне все равно. Таким образом, мои работы базы данных сводятся к минимуму, чтобы захватить партии путей. В принципе, мы перенесли кучу данных из более медленного хранилища в это проворное устройство, и меня попросили убедиться, что все на самом деле закончило это, написав что-то, чтобы проверить существование на файл.
Threading помог совсем немного. Я включил проверку файлов на 4 потока и получил мощность обработки до 3300 записей в секунду, что намного лучше, но я все еще надеюсь получить еще быстрее, если смогу. Есть ли хороший способ узнать, связан ли я с трафиком SMB? Я заметил, как только я попытался увеличить количество потоков до 4 или 5, моя скорость упала до струйки; Я думал, может быть, я где-то зашел в тупик, но нет.
О, и я не могу выполнить проверку FileOnNetwork по той причине, о которой вы сказали, там 3 или 4 раза больше файлов, размещенных там по сравнению с тем, что я хочу проверить. На этом проворном устройстве, вероятно, есть 1.5b файлов.
Ответы
Ответ 1
Оптимизация SQL-команды здесь спорна, потому что вы связаны с файлом IO.
Я бы использовал Directory.EnumerateFiles
для получения списка всех существующих файлов. Перечисление файлов в каталоге должно быть намного быстрее, чем тестирование каждого файла по отдельности.
Вы можете даже полностью инвертировать проблему, и массовая вставка этого списка файлов в таблицу temp базы данных, чтобы вы могли выполнять SQL-обработку набора непосредственно в базе данных.
Если вы хотите продолжить и проверить индивидуально, вы, вероятно, должны сделать это параллельно. Непонятно, что процесс действительно связан с диском. Возможно, это связано с сетью или процессором.
Parallelism поможет здесь, перекрывая несколько запросов. Это сетевая латентность, а не полоса пропускания, которая может быть проблемой. В DOP 1 по крайней мере одна машина бездействует в любой момент времени. Бывают моменты, когда оба бездействия.
там 3 или 4x столько файлов, которые были там размещены, по сравнению с тем, что я хочу проверить
Используйте команду dir /b
, чтобы передать список всех имен файлов в файл .txt. Выполните это локально на машине с файлами, но если это невозможно, выполните удаленно. Затем используйте bcp
для массового ввода их в таблицу в базу данных. Затем вы можете выполнить быструю проверку существования в одном SQL-запросе, который будет сильно оптимизирован. Вы получите хеш-соединение.
Если вы хотите parallelism фазу dir
этой стратегии, вы можете написать для нее программу. Но, возможно, нет необходимости, и dir достаточно быстро, несмотря на однопоточность.
Ответ 2
Узким местом, скорее всего, является сетевой трафик, а точнее: SMB-трафик. Ваша машина говорит SMB, чтобы получить информацию о файле из сетевого хранилища. SMB-трафик "chatty", вам нужно несколько сообщений для проверки существования файла и вашего разрешения на его чтение.
Для чего это стоит, в моей сети я могу запросить существование около сотни файлов в секунду по SMB, в то время как перечисление 15K файлов рекурсивно занимает 10 секунд.
Что может быть быстрее - предварительно извлечь список удаленных каталогов. Это будет тривиально, если структура каталогов будет предсказуемой - и, если хранилище не содержит много ненужных файлов в этих каталогах.
Тогда ваш код будет выглядеть так:
HashSet<string> filesOnNetwork = new HashSet<string>(Directory.EnumerateFiles(
baseDirectory, "*.*", SearchOption.AllDirectories));
foreach (var fileToCheck in filesFromDatabase)
{
fileToCheckExists = filesOnNetwork.Contains(fileToCheck);
}
Это может ухудшиться, если в сети есть больше файлов, чем вам нужно проверить, так как заполнение и поиск через filesOnNetwork
станет узким местом вашего приложения.
Ответ 3
В вашем текущем решении, получающем партии в 50 000, и открытии и закрытии соединения служит НЕТ, но для замедления работы. Потоки DataReader. Просто откройте его один раз и прочитайте их все по одному. Под обложками Reader будет отправлять партии одновременно. DataReader не будет пытаться заклинить клиента с 300 000 000 строк, если вы прочитали только 10.
Я думаю, вы беспокоитесь об оптимизации самого быстрого шага - чтение из SQL
Проверка пути к файлу будет самым медленным шагом
Мне нравится ответ от CodeCaster, но на 350 миллионов вы попадете в пределы размера объекта с помощью .NET. И, читая в HashSet, он не начинает работать, пока этот шаг не будет выполнен.
Я бы использовал BlockingCollection с двумя коллекциями
- перечислять файлы
- записать в db
Самый медленный шаг - это имена файлов для чтения, поэтому сделайте это как можно быстрее и не прерывайте. Сделайте это на устройстве, расположенном рядом с устройством хранения. Запустите программу на подключенном к SAN устройстве.
Я знаю, что вы скажете, что запись в db происходит медленно, но она должна быть быстрее, чем перечислить файл. Просто найдите двоичные столбцы для поиска - не записывайте полное имя файла в #temp. Я поставил доллары на пончики (оптимизированное) обновление быстрее, чем перечислять файлы. Сократите свои обновления, например, 10 000 строк за раз, чтобы не допустить округления. И я сделаю обновление asynch, чтобы вы могли создать следующее обновление во время обработки текущего.
Затем в конце вы проверяете БД для любого файла, который не был помечен как найденный.
Не переходите к промежуточной коллекции. Обработайте перечисление напрямую. Это позволяет немедленно начать работу и сохранить память.
foreach (string fileName in Directory.EnumerateFiles(baseDirectory, "*.*", SearchOption.AllDirectories))
{
// write filename to blocking collection
}
Ответ 4
Быстрая идея, если подход CodeCaster не работает из-за слишком большого количества файлов на удаленных серверах и если вы можете установить новые программы на удаленных серверах: Напишите программу, которую вы устанавливаете на каждом сервере, и который прослушивает какой-либо порт для HTTP-запросов (или какую бы технологию веб-сервисов вы предпочитаете). Программа, которая запрашивает базу данных, должна загружать имена файлов на сервер и отправлять запрос на каждый сервер со всеми именами файлов, которые расположены на этом сервере. Веб-служба проверяет существование файла (который должен быть быстрым, поскольку он теперь является локальной операцией) и отвечает, например, список, содержащий только имена файлов, которые действительно существовали. Это должно устранить большую часть служебных данных протокола и задержки в сети, поскольку количество запросов значительно сокращено.
Ответ 5
Если я сделаю такую задачу, я знаю, что узкие места:
- латентность доступа к дискам (~ 1 мс)
- время ожидания доступа к сети (для 100mbps ~ 0.2ms)
- база данных ограничена диском
Самая быстрая вещь - кеш процессора, второй - оперативная память.
Я предполагаю, что я мог бы использовать дополнительную таблицу базы данных для хранения временных данных.
База данных, где теперь данные я буду называть основной базой данных.
Я буду выполнять задачи параллельно:
- рекурсивное чтение каталога и сохранение второй базы данных в кусках для файлов размером около 50 тыс.
- получить фрагменты записей из основной базы данных и сравнить ONE chunk с ONE из второй базы данных - все файлы, которые не были найдены, будут записываться в третью базу данных (а также отмечать файлы в первой базе данных).
- после того, как все куски из основной базы данных по сравнению со второй базой данных - проверьте все куски из третьей базы данных со второй базой данных и удалите найденные файлы.
В конце в третьей базе данных останутся только несуществующие файлы, поэтому я могу просто получить строки из него и пометить данные в основной базе данных.
Могут быть дополнительные улучшения, можно обсудить, если интерес.
Ответ 6
Как насчет сортировки местоположений по мере их получения из БД (db хороши при сортировке). Тогда чеки могут быть полезны из кэшированной информации о каталоге в клиенте cifs,
вы можете получить список каталогов для следующей строки в наборе результатов, а затем проверить эту строку для существования в каталоге dir-list, а затем повторить проверку, находится ли следующая строка в результирующем наборе в том же каталоге, и если да, то проверьте уже выбранный список-дир, если не повторить внешний цикл.