2015-12-29 2 views
2

У меня есть файл csv, который может варьироваться от 50 000 до более 100 тыс. Строк данных.Импорт больших CSV-файлов в MySQL с использованием Laravel

Я в настоящее время использую Laravel w/Laravel Forge, MySQL и Maatwebsite Laravel Excel.

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

{!! Form::open(
    array(
     'route' => 'import.store', 
     'class' => 'form', 
     'id' => 'upload', 
     'novalidate' => 'novalidate', 
     'files' => true)) !!} 

    <div class="form-group"> 
     <h3>CSV Product Import</h3> 
     {!! Form::file('upload_file', null, array('class' => 'file')) !!} 
    </div> 

    <div class="form-group"> 
     {!! Form::submit('Upload Products', array('class' => 'btn btn-success')) !!} 
    </div> 
{!! Form::close() !!} 

Это затем сохраняет файл на сервере успешно и я m теперь можно выполнить итерацию результатов, используя что-то вроде цикла foreach.

Теперь вот вопросы, я сталкиваюсь в хронологическом порядке и исправления/попытках: (10k строки тесты CSV файл)

  1. [вопрос] PHP таймаут.
  2. [средство] Изменено для асинхронного запуска с помощью команды задания.
  3. [результат] Импортирует до 1500 строк.
  4. [issue] У сервера заканчивается память.
  5. [средство правовой защиты] Добавлен swap-накопитель в 1gb.
  6. [результат] Импортирует до 3000 строк.
  7. [issue] У сервера заканчивается память.
  8. [средство правовой защиты] Включены результаты chunking из 250 строк каждого фрагмента.
  9. [результат] Импортирует до 5000 строк.
  10. [issue] У сервера заканчивается память.
  11. [средство правовой защиты] Удалена какая-либо трансаполяция/объединение таблиц.
  12. [результат] Импортирует до 7000 строк.

Как вы можете видеть, результаты незначительны и нигде вблизи 50k, я едва могу сделать это около 10k.

Я прочитал и посмотрел на возможные предложения, такие как:

  • Используйте необработанный запрос для запуска Загрузки данных Local входной_файла.
  • Разделить файлы перед импортом.
  • Храните на сервере, затем сервер разбивается на файлы и обрабатывает их cron.
  • Обновите мою капюшон размером 512 МБ до 1 ГБ в качестве последнего средства.

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

Разделение файлов перед импортом в порядке до 10 000, но для 50 тыс. Или более? Это было бы крайне непрактично.

Хранить на сервере, а затем сервер разбивать его и запускать отдельно, не беспокоя конечного пользователя? Возможно, но даже не уверен, как это сделать на PHP, но только кратко прочитал об этом.

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

Теперь я могу сдавать и просто обновлять память до 1 гб, но я чувствую, что в лучшем случае он может подпрыгнуть меня до 20 тыс. Строк до того, как он снова не сработает. Что-то нужно обрабатывать все эти строки быстро и эффективно.

Наконец здесь проблеск моей таблицы структуры:

Inventory 
+----+------------+-------------+-------+---------+ 
| id | profile_id | category_id | sku | title | 
+----+------------+-------------+-------+---------+ 
| 1 |   50 |  51234 | mysku | mytitle | 
+----+------------+-------------+-------+---------+ 

Profile 
+----+---------------+ 
| id |  name  | 
+----+---------------+ 
| 50 | myprofilename | 
+----+---------------+ 

Category 
+----+------------+--------+ 
| id | categoryId | name | 
+----+------------+--------+ 
| 1 |  51234 | brakes | 
+----+------------+--------+ 

Specifics 
+----+---------------------+------------+-------+ 
| id | specificsCategoryId | categoryId | name | 
+----+---------------------+------------+-------+ 
| 1 |     20 |  57357 | make | 
| 2 |     20 |  57357 | model | 
| 3 |     20 |  57357 | year | 
+----+---------------------+------------+-------+ 

SpecificsValues 
+----+-------------+-------+--------+ 
| id | inventoryId | name | value | 
+----+-------------+-------+--------+ 
| 1 |   1 | make | honda | 
| 2 |   1 | model | accord | 
| 3 |   1 | year | 1998 | 
+----+-------------+-------+--------+ 

Full CSV Sample 
+----+------------+-------------+-------+---------+-------+--------+------+ 
| id | profile_id | category_id | sku | title | make | model | year | 
+----+------------+-------------+-------+---------+-------+--------+------+ 
| 1 |   50 |  51234 | mysku | mytitle | honda | accord | 1998 | 
+----+------------+-------------+-------+---------+-------+--------+------+ 

Так быстро прогоне моей логики процесса как можно проще было бы:

  1. Загрузить файл в Maatwebsite/Laravel -Excel и итерации через цепочку с петлями
  2. Проверьте, не потеряны ли категории_id и sku, иначе игнорируют и регистрируют ошибку в массиве.
  3. Lookup category_id и вытащите все соответствующие поля столбца из всех связанных таблиц, которые он использует, а затем, если в базу данных не было нулевой вставки.
  4. Создайте собственное название, используя больше логики, используя поля, доступные в файле.
  5. Промыть и повторить.
  6. Наконец, экспортируйте массив ошибок в файл и запишите его в базу данных для загрузки, чтобы просмотреть ошибки в конце.

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

Спасибо!

+0

Все ли файлы csv вставлены в одну и ту же таблицу? Если это так, я не понимаю, почему использование 'load data local infile' будет проблемой - некоторые столбцы будут просто' NULL'. Вы можете использовать Python (выполняемый через дочерний процесс PHP через 'exec()') для анализа файла по мере необходимости после загрузки на сервер, но перед его вставкой в ​​таблицу. – Terry

+0

@Terry Это всего лишь один CSV-файл, но он вставлен в несколько таблиц, как указано выше, почему я не смог легко использовать локальную информацию о нагрузке. Кроме того, данные изменяются для каждого файла в зависимости от того, какая категория будет задействована, которая будет иметь разные столбцы. Также из-за этой переменной теперь будет сложно указать тип данных для каждого поля. – dmotors

+0

Если это просто файл CSV, то использование пакета Maatwebsite Laravel Excel и PHPExcel слишком велико, хотя пакет Maatwebsite Laravel Excel делает (я считаю) доступ к функциям chunking PHPExcel для загрузки файла –

ответ

4

Возможно, вы уже поняли логику интерпретации строк CSV и конвертируете их для вставки запросов в базу данных, поэтому я сосредоточу внимание на проблеме исчерпания памяти.

При работе с большими файлами на PHP любой подход, который загружает весь файл в память, либо потерпит неудачу, либо станет невыносимо медленным, либо потребует намного больше ОЗУ, чем у вас, Капля.

Так что мои советы являются:

Прочитайте файл построчно с помощью fgetcsv

$handle = fopen('file.csv', 'r'); 
if ($handle) { 
    while ($line = fgetcsv($handle)) { 
     // Process this line and save to database 
    } 
} 

Этот путь только один строки в то время, будет загружен в память. Затем вы можете обработать его, сохранить в базе данных и перезаписать его следующим.

Держите отдельный дескриптор файла для регистрации

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

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

$log = fopen('log.txt', 'w'); 
if (some_condition) { 
    fwrite($log, $text . PHP_EOL); 
} 

Затем, в конце сценария, вы можете хранить файл журнала, где хотите.

Отключить Laravel в журнал запросов

Laravel хранит все ваши запросы, хранящиеся в памяти, и это, вероятно, будет проблемой для вашего приложения. К счастью, вы можете использовать disableQueryLog method, чтобы освободить ценную оперативную память.

DB::connection()->disableQueryLog(); 

Использование сырья запросов при необходимости

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

Если вы знаете свой путь вокруг SQL, вы можете execute raw queries to the database.


Edit:

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

+0

Отличный совет. Я отключил журнал запросов и преобразовал свой файл Maatwebsite Laravel Excel, чтобы использовать ваш предложенный пример fgetcsv. В настоящее время он работает, и моя память вообще не шипит. У меня есть вопрос, хотя, это chunking его с 1 куском за раз (с использованием пакета larvel excel) так же, как fgetcsv 1 за раз, или это все еще будет мешать и слить память с течением времени? – dmotors

+0

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

+0

Получил до 30 тыс. Строк, что является монументальной разницей по сравнению с 7к. У моего работника очереди был 10000 секундный тайм-аут, поэтому я просто подниму его до некоторого большого числа, так как Laravel Forge не похоже, что он позволяет мне не делать таймаута. Я буду рассматривать это рабочее решение. – dmotors

Смежные вопросы