Синхронизация файловой системы и кэшированных данных при запуске программы

У меня есть программа, которая должна получить некоторые данные о наборе файлов (то есть, каталог и все файлы в нем и подкаталоги определенных типов). Данные (очень) дороги для вычисления, поэтому вместо того, чтобы обходить файловую систему и вычислять ее при запуске программы, я сохраняю кэш данных в базе данных SQLite и использую FilesystemWatcher для отслеживания изменений в файловой системе. Это прекрасно работает во время работы программы, но вопрос в том, как обновить / синхронизировать данные во время запуска программы. Если файлы были добавлены (или изменены - я предполагаю, что я могу обнаружить это по последнему измененному / размеру), данные должны быть пересчитаны в кеш, а если файлы были удалены, данные должны быть удалены из кеша (так как интерфейс пересекает кеш вместо файловой системы).

Итак, вопрос в том, какой хороший алгоритм для этого? Один из способов, который я могу придумать, - обойти файловую систему и собрать путь и последние изменения / размер всех файлов в словаре. Затем я просматриваю весь список в базе данных. Если совпадения нет, то я удаляю элемент из базы / кеша. Если есть совпадение, то я удаляю элемент из словаря. Затем словарь содержит все элементы, данные которых необходимо обновить. Это может сработать, однако, похоже, что при каждом запуске было бы достаточно много памяти и времени, поэтому мне было интересно, есть ли у кого идеи получше?

Если это имеет значение: программа написана только для Windows на C# в.NET CLR 3.5 и использует объект SQLite для ADO.NET, доступ к которому осуществляется через структуру сущностей /LINQ для ADO.NET.

3 ответа

Решение

Наше приложение является кроссплатформенным настольным приложением C++, но имеет очень похожие требования. Вот описание высокого уровня того, что я сделал:

  • В нашей базе данных SQLite есть Files стол, который хранит file_id, name, hash (в настоящее время мы используем дату последнего изменения в качестве значения хеша) и state,
  • Каждая другая запись относится к file_id, Это позволяет легко удалять "грязные" записи при изменении файла.

Наша процедура проверки файловой системы и обновления кэша разделена на несколько отдельных этапов, чтобы упростить тестирование и дать нам больше гибкости в отношении того, когда происходит кэширование (названия, выделенные курсивом, - это то, что я выбрал для имен классов):

На 1-й запуск

  • База данных пуста. Walker рекурсивно обходит файловую систему и добавляет записи в Files Таблица. state установлен в UNPROCESSED,
  • Затем загрузчик перебирает Files таблица ищет UNPARSED файлы. Они передаются парсеру (который выполняет фактический анализ и вставку данных)
  • Это занимает некоторое время, поэтому первый запуск может быть немного медленным.

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

n + 1 запуск

  • Скруббер перебирает Files таблица и ищет файлы, которые были удалены и файлы, которые были изменены. Это устанавливает state в DIRTY если файл существует, но был изменен или DELETED если файл больше не существует
  • Deleter (не самое оригинальное имя) затем перебирает Files таблица ищет DIRTY а также DELETED файлы. Удаляет другие связанные записи (связанные через file_id). После удаления соответствующих записей оригинал File запись либо удалена, либо установлена ​​обратно state=UNPARSED
  • Затем Walker обходит файловую систему, чтобы забрать новые файлы.
  • Наконец загрузчик загружает все UNPARSED файлы

В настоящее время "наихудший сценарий" (каждый файл изменяется) встречается очень редко - поэтому мы делаем это каждый раз при запуске приложения. Но, разделив процесс на эти этапы, мы можем легко расширить реализацию до:

  • Scrubber/Deleter может быть реорганизован, чтобы оставить грязные записи на месте до тех пор, пока не будут загружены новые данные (поэтому приложение "продолжает работать", пока новые данные кэшируются в базе данных)
  • Загрузчик может загружать / анализировать фоновый поток во время простоя в основном приложении
  • Если вы заранее знаете что-то о файлах данных, вы можете назначить им "вес", немедленно загрузить / проанализировать действительно важные файлы и поставить в очередь менее важные файлы для последующей обработки.

Просто некоторые мысли / предложения. Надеюсь, они помогут!

В Windows есть механизм журнала изменений, который делает то, что вы хотите: вы подписываетесь на изменения в некоторой части файловой системы и при запуске можете прочитать список изменений, которые произошли с момента последнего чтения. См.: http://msdn.microsoft.com/en-us/library/aa363798(VS.85).aspx

РЕДАКТИРОВАТЬ: Я думаю, что это требует довольно высоких привилегий, к сожалению

Первая очевидная вещь, которая приходит на ум, - это создание отдельного небольшого приложения, которое всегда будет запускаться (возможно, как служба) и создавать своего рода "журнал" изменений в файловой системе (не нужно работать с SQLite, просто напишите их). в файл). Затем, когда основное приложение запускается, оно может просмотреть журнал и точно знать, что изменилось (не забудьте впоследствии очистить журнал:-).

Однако, если это по какой-то причине неприемлемо для вас, давайте попробуем взглянуть на первоначальную проблему.

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

Во-вторых, если вам нужно принять решение по каждому файлу, который только вы знаете, как сделать, вероятно, нет другого пути, кроме как просмотреть все файлы.

Помещая вышесказанное другими словами, вы можете сказать, что проблема по своей сути сложна (и любая конкретная проблема не может быть решена с помощью алгоритма, который проще, чем сама проблема).

Поэтому ваша единственная надежда - сократить пространство поиска с помощью твиков и хаков. И у меня есть два из них на уме.

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

Во-вторых, вам вообще не нужно запрашивать базу данных:-) Просто сохраните точное время, когда ваше приложение где-то в последний раз выполнялось (в файле.settings?), И проверьте каждый файл, чтобы увидеть, не новее ли он того времени. Если это так, вы знаете, что это изменилось. Если это не так, вы знаете, что поймали это изменение в прошлый раз (с помощью FileSystemWatcher).

Надеюсь это поможет. Повеселись.

Другие вопросы по тегам