Рекомендации по итерации по массивным CSV-файлам в PHP

хорошо, я постараюсь сохранить это коротким, сладким и по существу.

мы делаем массивные обновления GeoIP в нашей системе, загружая массивный CSV-файл на нашу PHP-CMS. Эта вещь обычно имеет более 100k записей информации об IP-адресе. Теперь простой импорт этих данных не является проблемой, но мы должны проверить наши текущие региональные сопоставления IP-адресов.

Это означает, что мы должны проверить данные, сравнить и разделить перекрывающиеся IP-адрес, так далее.. И эти проверки должны быть сделаны для каждой записи.

не только это, но я только что создал решение для отображения полей, которое позволит другим поставщикам реализовать свои обновления GeoIP в разных форматах. Это делается путем применения правил к записям IPs в обновлении CSV.

например, правило может выглядеть так:

Если 'countryName' == 'Австралия' на 'австралийский пул IP'

там может быть несколько правил, которые для запуска и каждой записи IP необходимо применить их все. Например, 100k записей для проверки против 10 правил будет 1 миллион итераций; не весело.

мы находим 2 правила для 100k записей занимает до 10 минут, чтобы обработать. Я полностью осознаю узкое место здесь, которое представляет собой количество итераций сдвига,которые должны произойти для успешного импорта; просто не полностью осведомлены о любых других вариантах, которые мы можем немного ускорить.

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

Итак, вопрос в том, учитывая то, что я только что написал, Какой лучший способ ускорить этот процесс? Обновление оборудования сервера только для этого инструмента, к сожалению, не вариант, но они довольно высокого класса коробки для начинаться.

Не так коротко, как я думал, Но да. Halps? :(

7 ответов


выполните массовый импорт в базу данных (я использую SQL Server). Массовый импорт занимает буквально несколько секунд, а 100 000 записей-это мелочь для базы данных, чтобы хруст на бизнес-правилах. Я регулярно выполняю подобные данные на таблице с более чем 4 миллионами строк, и это не занимает 10 минут, которые вы указали.

EDIT: я должен отметить, да, я не рекомендую PHP для этого. Вы имеете дело с необработанными данными, используйте базу данных.. : P


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

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

Если у вас есть память, например, и это практично для приложения, отложите любой "выход" до окончания основной обработки. Кэшировать любые входные данные, если это практично. Это работает лучше всего для сводные данные или случайные данные.

В идеале, за исключением чтения файла CSV, сделайте как можно меньше ввода-вывода во время основной обработки.

предоставляет ли PHP доступ к объекту mmap Unix, что обычно является самым быстрым способом чтения файлов, особенно больших файлов.

еще одно соображение-пакетировать ваши вставки. Например, легко создавать инструкции INSERT в виде простых строк и отправлять их на сервер блоками по 10, 50 или 100 строк. Большинство баз данных имеют некоторые жесткие ограничения на размер оператора SQL (например, 64K или что-то еще), поэтому вам нужно иметь это в виду. Это значительно сократит ваши поездки туда и обратно в БД.

Если вы создаете первичные ключи с помощью простых приращений, сделайте это En массами (блоки 1000, 10000, что угодно). Это еще одна вещь, которую вы можете удалить из своего внутреннего цикла.

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


100k записей не большое количество. 10 минут - неплохое время обработки работы для одного потока. Количество сырья работы по прямой наверное около 10 минут, независимо от того, если вы используете PHP или C. Если вы хотите, чтобы это было быстрее, вам понадобится более сложное решение, чем цикл while.

вот как я бы решить это:

  1. используйте решение map / reduce для параллельного запуска процесса. Hadoop, вероятно, излишне. Свинья Латинская май сделать работу. Вы действительно просто хотите, чтобы часть карты карты / уменьшить проблему. IE: вы разветвляете кусок файла, который будет обрабатываться подпроцессом. Ваш редуктор наверное cat. Простая версия может иметь процессы PHP fork для каждого фрагмента записи 10K, ждать детей, а затем повторно собрать их вывод.
  2. используйте модель обработки очереди / сетки. Выстроить в очередь куски файла, а затем иметь кластер машин проверки, захвата заданий и отправки данных где-то. Это очень похоже на модель map / reduce, просто используя разные технологии, плюс вы можете масштабировать, добавляя больше машин в сетку.
  3. если вы можете написать свою логику как SQL, сделайте это в базе данных. Я бы избегал этого, потому что большинство веб-программистов не могут работать с SQL на этом уровне. Кроме того, SQL ограничен для выполнения таких вещей, как проверки RBL или поиск ARIN.

одна вещь, которую вы можете попробовать, - это запустить импорт CSV в командной строке PHP. Обычно это дает более быстрые результаты.


Если вы используете PHP для выполнения этой работы, переключите синтаксический анализ на Python, так как он намного быстрее, чем PHP, этот обмен должен ускорить процесс на 75% или даже больше.

Если вы используете MySQL, вы также можете использовать оператор загрузки данных INFILE, я не уверен, что вам нужно проверить данные, прежде чем вставлять их в базу данных.


работали над этой проблемой интенсивно в течение некоторого времени. И, да, лучшее решение-читать только часть файла в любой момент времени, анализировать его, выполнять проверку, выполнять фильтрацию, затем экспортировать его, а затем читать следующую часть файла. Я бы согласился, что это, вероятно, не решение для php, хотя вы, вероятно, можете сделать это на php. Пока у вас есть функция поиска, чтобы вы могли начать чтение из определенного места в файле. Вы правы, это добавляет больше уровень сложности, но стоит того, что немного дополнительных усилий. Это ваши данные чисты, т. е. правильно разделены, строка квалифицирована, свободна от ломаных линий и т. д. Затем, безусловно, массовая загрузка в базу данных sql. Иначе вы хотите знать, где, когда и почему возникают ошибки и уметь их обрабатывать.


Я работаю с чем-то похожи.

csv-файл, над которым я работаю, содержит португальские данные (dd/mm/yyyy), которые я должен преобразовать в mysql yyyy-mm-dd. Португальский денежный: R$ 1.000, 15, который должен был быть преобразован в MySQL decimal 1000,15. Обрезайте возможные пробелы и, наконец, добавьте косые черты.

Перед вставкой необходимо обработать 25 переменных.

Если я проверяю каждое значение $ notafiscal (выберите в таблице, чтобы узнать, существует ли и обновляется), дескриптор php около 60к строк. Но если я не проверю его, php обрабатывает более 1 миллиона строк.

сервер работает с памятью 4GB-scripting localhosting (память 2GB), он обрабатывает половину строк в обоих случаях.

mysqli_query($db,"SET AUTOCOMMIT=0");
mysqli_query($db, "BEGIN");
mysqli_query($db, "SET FOREIGN_KEY_CHECKS = 0");
fgets($handle); //ignore the header line of csv file

while (($data = fgetcsv($handle, 100000, ';')) !== FALSE):
 //if $notafiscal lower than 1, ignore the record
 $notafiscal = $data[0];  
 if ($notafiscal < 1):
  continue;
 else:
  $serie = trim($data[1]); 
  $data_emissao = converteDataBR($data[2]);
  $cond_pagamento = trim(addslashes($data[3]));
  //...
  $valor_total = trim(moeda($data[24]));
  //check if the $notafiscal already exist, if so, update, else, insert into table
  $query = "SELECT * FROM venda WHERE notafiscal = ". $notafiscal ;
  $rs = mysqli_query($db, $query);
  if (mysqli_num_rows($rs) > 0):
    //UPDATE TABLE
  else:
    //INSERT INTO TABLE
  endif;
endwhile;

mysqli_query($db,"COMMIT");
mysqli_query($db,"SET AUTOCOMMIT=1");
mysqli_query($db,"SET FOREIGN_KEY_CHECKS = 1");
mysqli_close($db);