Тайм-аут загрузки файла Laravel - добавление фрагмента в updateOrCreate?

Я пытаюсь загрузить пользовательский CSV-файл и передать данные в базу данных, но из-за размера CSV/строк время ожидания истекает. Данные необходимо проверить, чтобы увидеть, есть ли они уже в базе данных, и обновить или создать.

Я применил фрагмент к CSV для чтения данных, но не знаю, можно ли добавить фрагмент в раздел загрузки в базу данных?

Вот моя функция

public function import(Request $request) {

    if($request->file('imported-file')) {

        $path = $request->file('imported-file')->getRealPath();

        $data = Excel::filter('chunk')->load($path)->chunk(200, function($results) {

            foreach($results as $row) {

                if(!empty($row['postcode'])) {

                    $url = "https://maps.googleapis.com/maps/api/geocode/xml?address=".urlencode($row['postcode'])."&region=uk&key=";
                    $tmp = file_get_contents($url);
                    $xml = simplexml_load_string($tmp);

                    if((string)$xml->status == 'OK' && isset($xml->result[0])) {

                        $lat = 0;
                        $lng = 0;

                        if(isset($xml->result[0]->geometry->location->lat)) {
                            $lat = (string)$xml->result[0]->geometry->location->lat;
                        }
                        if(isset($xml->result[0]->geometry->location->lng)) {
                            $lng = (string)$xml->result[0]->geometry->location->lng;
                        }

                    }

                    Import::updateOrCreate(
                        [
                            'sitecode' => $row['sitecode']
                        ],
                        [
                            'sitecode' => $row['sitecode'],
                            'sitename' => $row['sitename'],
                            'address_1' => $row['address_1'],
                            'address_2' => $row['address_2'],
                            'address_town' => $row['address_town'],
                            'address_postcode' => $row['postcode'],
                            'charity' => $row['charity'],
                            'latitude' => $lat,
                            'longitude' => $lng,
                            'approved' => 1
                        ]
                    );

                } else {

                    // Postcode not valid!!!

                }


            } // endforeach

            Session::flash('sucess', 'Import was sucessful.');
            return redirect()->route('locations');

        });

    } else {

        Session::flash('error', 'Please select a file to upload!');
        return back();

    }

}

person CIB    schedule 27.11.2017    source источник
comment
set_time_limit может помочь php.net/manual/en/function.set-time -limit.php   -  person Raymond Nijland    schedule 27.11.2017
comment
@RaymondNijland, все равно не имеет значения.   -  person CIB    schedule 27.11.2017


Ответы (1)


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

Если вы используете установку Nginx/PHP-FPM, вы должны просмотреть Nginx, PHP и файл конфигурации PHP-FPM.

PHP-конфигурация

Начнем с PHP. Откройте файл /etc/php/<phpversion>/fpm/php.ini и найдите max_execution_time. Вы должны найти что-то вроде

max_execution_time = 30

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

max_execution_time = 300

на 5 минут.

Затем давайте рассмотрим конфигурацию PHP-FPM. Откройте конфигурацию вашего пула, например /etc/php/<phpversion>/fpm/pool.d/www.conf, и найдите request_terminate_timeout. В моей конфигурации он отключен:

; Default Value: 0
;request_terminate_timeout = 0

Значение по умолчанию — 0 (отключено), но если оно у вас включено, вы должны увеличить число, например.

request_terminate_timeout = 400

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

Конфигурация Nginx

Наконец, посмотрите на конфигурацию Nginx в /etc/nginx/sites-available/yoursite.conf. Там вы должны найти раздел, который настраивает связь между Nginx и PHP-FPM. Там вы найдете fastcgi_read_timeout, это максимальное время, в течение которого Nginx будет ждать, пока PHP-FPM вернет некоторые данные:

location ~ \.php$ {
    # ...
    fastcgi_read_timeout 300;
    # ...
}

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

Подводить итоги

Если вы считаете, что обработка может занять до 30 минут, используйте такие цифры:

  • in /etc/php/<phpversion>/fpm/php.ini:

    max_execution_time = 1800        ; 1800 secs = 30 minutes
    
  • in /etc/php/<phpversion>/fpm/pool.d/www.conf:

    request_terminate_timeout = 0    ; no timeout, or greater than 1800
    
  • in /etc/nginx/sites-available/yoursite.conf:

    fastcgi_read_timeout = 2000;     # 2000 secs, or at least greater than the previous twos
    

С этой комбинацией max_execution_time будет управлять среди других, и вы будете знать, что ваш процесс имеет 30 минут времени выполнения, потому что тайм-ауты PHP-FPM и Nginx должны происходить после PHP.

Не забывайте о клиентской стороне

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

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

Обычно вы можете изменить это значение. С помощью dropzone вы можете установить тайм-аут на 2100 секунд с помощью

var myDropzone = new Dropzone("#uploader", {
    // ...
    timeout: "2100",
    // ...
});

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

Долгосрочные задачи: правильный путь

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

  1. не выполняйте обработку CSV сразу после загрузки
  2. вместо этого загрузите файл, поместите его в очередь и уведомите клиента, чтобы он вернулся позже.
  3. выполнять обработку CSV в фоновом режиме с помощью работника очереди

Ознакомьтесь с документацией Laravel по очередям (https://laravel.com/docs/5.5/queues).

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

Я надеюсь, что это может помочь :)

person Marco Pallante    schedule 28.11.2017
comment
Привет, спасибо, что нашли время написать длинный ответ. Я последовал вашему совету и добавил задание/очередь. Я загружаю CSV, затем отправляю задание, обрабатываю csv и загружаю оттуда в базу данных. Я запускаю php artisan queue:work и все в порядке! Ну, на моем макбуке это так. Я загрузил репозиторий на свой imac с точно такой же настройкой, но когда я запускаю рабочую команду, я просто получаю это снова и снова, как бесконечный цикл? [2017-11-29 11:48:21] Обработка: App\Jobs\ProcessCSV [2017-11-29 11:48:22] Обработка: App\Jobs\ProcessCSV Есть идеи? - person CIB; 29.11.2017
comment
Вероятно, вы столкнулись с истечением срока действия задания и повторными испытаниями (laravel.com/docs/ 5.5/queues#job-expirations-and-timeouts). В конфигурации очередей (config/queue.php) есть параметр retry_after, который имеет значение по умолчанию 90 секунд. По истечении этого времени, если задание не было завершено, рабочий запустит его снова. Также убедитесь, что у вас есть рабочий --timeout достаточно большой (или игнорируйте параметры, я думаю, что по умолчанию без тайм-аута). - person Marco Pallante; 30.11.2017