Как быстро сравнить большое число прайс-листов с данными в БД?

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

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

1) скачивание файла, определение его размера, парсинг, сравнение
Самый медленный способ, который сразу отметается.

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

3) получение размера файла и даты его изменения дистанционно (по CURL), и на основании этого решать, обновился файл или нет, если да - скачивать и парсить
Более быстрый способ, чем предыдущий, но могут возникать ложные срабатывания. Хэш-сумма мне видится более надежной.

4) создание специфической структуры, например, на базе sphinx или elastic search, решать, парсить файлы или нет, на основе хэш-суммы или времени изменения
При этом файлы все-таки придется парсить, но сам поиск данных в базе данных на базе этого решения будет быстрее.

Верно ли я выбрал последний вариант как оптимальный, и лучше не сделать? Может, можно сделать решение быстрее, чтобы вообще парсить не пришлось (или свести этот процесс к минимуму) - создать какой-нибудь индексный файл, или использовать алгоритм поиска, который будет выполняться за одно и то же время вне зависимости от размера исходных данных (но я не уверен, применимо ли это к xml \ csv - там речь шла про массивы и деревья)? Возможно, использовать diff для вычленения измененных участков?

Как бы подошли к данной проблеме вы?
Заранее спасибо.
  • Вопрос задан
  • 250 просмотров
Пригласить эксперта
Ответы на вопрос 2
@lubezniy
Хэш и размер одновременно ИМХО достаточно. Парсить можно, если загрузка велика, например, на отдельной машинке (реальной или виртуальной) с копией БД.
Ответ написан
Комментировать
gzhegow
@gzhegow
aka "ОбнимиБизнесмена"
Скрипт у меня где-то был который сравнивает удаленный массив с локальной копией и возвращает дельту изменений (массивы для прямой загрузки в бд) без потерь старых данных.

Ща я тебе скрипт скину погодь
// получаем локальное хранилище (тут может быть запрос в БД)
// * функция _pluck работает примерно как array_column только умнее - обрезает массив именованных массивов оставляя в каждом ключе значение только одной ячейки вместо всего массива, в underscore/lodash такая штука есть), вторым параметром принимает массив нужных полей (в нашем случае - null = весь массив без изменений), в третьем - колонку, которая станет ключом - то есть здесь получилось чтобы просто значения колонки code, сделали ключами
$full_arr = _pluck(json_decode(file_get_contents($this->full_path), true), null, "code");

// получаем удаленное хранилище (тут запрос на другой сервер, потом переназначение колонок, проверка минимального их количества, проверка значений данных)
$current_arr = $this->getData();


// получаем УДАЛЕННЫЕ товары
// - в товаре не хватает полей
// - товар с таким кодом когда-то был, а теперь исчез
// - количество товара меньше требуемого

// для этого
// 1. получить плохие элементы из текущей выгрузки
// 2. получить элементы, которые раньше когда-либо попадались, а теперь их почему-то нету
// 3. получить элементы, в которых количество меньше требуемого
// 4. проставить им флаг "удаленный"
// 5. в массив для синхронизации добавить только те удаленные, которые не были удалены раньше
$process_current_arr = array_filter($current_arr, function ($v) {

  // тут функция проверяющая наличие колонок для сравнения - ну то есть сравнение производится по такому-то набору колонок id, code, param1, param2, нужно убедится что эти колонки есть, в противном случае элемент ставится как плохой
  return $this->filterData($v);

});
$deleted_arr = array_diff_key($current_arr, $process_current_arr);
$deleted_arr = array_replace($deleted_arr, array_diff_key($full_arr, $process_current_arr));
$deleted_arr = array_replace($deleted_arr, array_filter($process_current_arr, function ($v) {
  return ($v["count"] < $this->count);
}));
$deleted_arr = array_map(function ($v) {
  $v["deleted"] = 1;
  return $v;
}, $deleted_arr);
$process_current_arr = array_diff_key($process_current_arr, $deleted_arr);
$sync_arr = array_replace($sync_arr, array_intersect_key($deleted_arr, array_filter(array_replace($deleted_arr, array_intersect_key($full_arr, $deleted_arr)), function ($v) {
  return (empty($v["deleted"]));
})));


// получаем ВОССТАНОВЛЕННЫЕ товары
// - товары которые были удалены, а теперь они соответствуют требованиям

// для этого
// 1. находим удаленные товары в нашем хранилище
// 2. методом исключения определяем, какие товары есть в нашей выгрузке
$recovered_arr = array_intersect_key($process_current_arr, array_filter($full_arr, function ($v) {
  return (!empty($v["deleted"]) && (strval($v["deleted"]) === "1"));
}));
$recovered_arr = array_map(function ($v) {
  $v["deleted"] = 0;
  return $v;
}, $recovered_arr);
$sync_arr = array_replace($sync_arr, $recovered_arr);
$process_current_arr = array_diff_key($process_current_arr, $recovered_arr);


// получаем НОВЫЕ товары
// - товары которых в нашем хранилище нету

// для этого
// 1. методом исключения по коду сравниваем хранилище и оставшиеся для обработки элементы
$new_arr = array_diff_key($process_current_arr, $full_arr);
$sync_arr = array_replace($sync_arr, $new_arr);
$process_current_arr = array_diff_key($process_current_arr, $new_arr);


// получаем ИЗМЕНЕННЫЕ товары
// - товары которые есть и в нашем хранилище и в выгрузке, но данные отличаются

// для этого
// 1. методом соответствия по хешу сравниваем хранилище и оставшиеся для обработки элементы
// * hashData - функция которая отбирает из массива значения нужных колонок и по ним делает хеш, не учитывая остальное
$process_current_arr_md5 = array_combine(array_map(array($this, "hashData"), $process_current_arr), $process_current_arr);
$full_arr_md5 = array_combine(array_map(array($this, "hashData"), $full_arr), $full_arr);
$updated_arr = _pluck(array_diff_key($process_current_arr_md5, $full_arr_md5), null, "code");
$sync_arr = array_replace($sync_arr, $updated_arr);
$process_current_arr = array_diff_key($process_current_arr, $updated_arr);


// Соединяем старые данные с новыми, чтобы ничего не потерять
$filemtime = date("Y-m-d H:i:s", filemtime($this->sync_path));
$sync_arr = array_replace(array_intersect_key($full_arr, $sync_arr), $sync_arr);


// Тут в конце соединяем наш full и sync, сохраняем или просто sync выводим, чтобы увидеть отличающиеся записи в прайсе и бд


Попробуй потестить может какие глюки найдешь, то я тоже исправлю
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы