Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#1 25.12.2019 13:31:33

TrueKanonir
Откуда: Ташкент
Сообщений: 221

Утекает память при парсинге html документа

Добрый день.

Имеется 10к html документов. В них обычные таблицы которые я через symfony dom crawler обхожу в цикле, и забираю нужную инфу.

Сам метод

PHP
    // Выше в коде
    
set_time_limit(0);

    
/**
     * Init crawler
     *
     * @return \Illuminate\Http\Response
     * @throws \Exception
     */
    
public function init()
    {
        
Log::info("Parsing Started \n" . $this->convert(memory_get_usage(true)));
        
// If no pending links in db
        
if(is_null($this->link)) {
            return 
Response::make('Success', 200);
        }

        if(
$this->link->exists) {
            
$this->setDocument($this->link->document);
        }

        try {
            
Log::info("Add content to crawler \n" . $this->convert(memory_get_usage(true)));
            
$this->crawler->addHtmlContent($this->loadPage());
            
Log::info("Content added \n" . $this->convert(memory_get_usage(true)));
            
$this->searchLinks();
            
Log::info("Links found \n" . $this->convert(memory_get_usage(true)));

            
$this->storeParsedLinks();
            
Log::info("Links saved \n" . $this->convert(memory_get_usage(true)));

            
$this->clear();
            
Log::info("Crawler cleaned \n" . $this->convert(memory_get_usage(true)));

            
$this->writeToLog('Parsing finished');

        } catch (\
Exception $e) {
            
$this->writeToLog('ERROR: ' . $e->getMessage());

            
$this->link->setParsed();
        }

        
$this->link = $this->link->getFirstPending();
        
Log::info("Begin recursive \n" . $this->convert(memory_get_usage(true)));

        
$this->init();
    }

И вот где мне кажется память утекает судя по логам.
Пришлось этот метод, и метод получения ссылок переписать на чистые php функции для работы с массивами (array_unique(), array_diff(), array_filter()). Так стало улетать по 2мб памяти.
До этого все операции с массивами происходили методами коллекции,и память утекала на 40мб

PHP
    /**
     * Store parsed links to db
     *
     * @return void
     */
    
private function storeParsedLinks(): void
    
{
        
$links = $this->getLinks();

        
Log::info("Found " . count($links) . " links \n" . $this->convert(memory_get_usage(true)));

        
$dbLinks = $this->link->getMissed($links);

        
$newLinks = array_diff($links, $dbLinks);

        
Log::info("Unique " . count($newLinks) . " links \n" . $this->convert(memory_get_usage(true)));

        foreach (
$newLinks as $link) {
            
$this->link->store($link);
        }
    }

А вот как память утекает

[2019-12-25 15:01:49] local.INFO: Parsing Started
20 mb
[2019-12-25 15:01:49] local.INFO: Add content to crawler
20 mb
[2019-12-25 15:01:51] local.INFO: Content added
20 mb
[2019-12-25 15:01:51] local.INFO: Links found
20 mb
[2019-12-25 15:01:51] local.INFO: Found 480 links
20 mb
[2019-12-25 15:01:51] local.INFO: Unique 0 links
20 mb
[2019-12-25 15:01:51] local.INFO: Links saved
20 mb
[2019-12-25 15:01:51] local.INFO: Crawler cleaned
20 mb
[2019-12-25 15:01:51] local.INFO: Begin recursive
20 mb
[2019-12-25 15:01:55] local.INFO: Parsing Started
20 mb
[2019-12-25 15:01:55] local.INFO: Add content to crawler
20 mb
[2019-12-25 15:01:56] local.INFO: Content added
20 mb
[2019-12-25 15:01:56] local.INFO: Links found
20 mb
[2019-12-25 15:01:56] local.INFO: Found 482 links
20 mb
[2019-12-25 15:01:56] local.INFO: Unique 0 links
22 mb
[2019-12-25 15:01:56] local.INFO: Links saved
22 mb
[2019-12-25 15:01:56] local.INFO: Crawler cleaned
22 mb
[2019-12-25 15:01:56] local.INFO: Begin recursive
22 mb
[2019-12-25 15:02:02] local.INFO: Parsing Started
22 mb
[2019-12-25 15:02:02] local.INFO: Add content to crawler
22 mb
[2019-12-25 15:02:04] local.INFO: Content added
22 mb
[2019-12-25 15:02:04] local.INFO: Links found
22 mb
[2019-12-25 15:02:04] local.INFO: Found 488 links
22 mb
[2019-12-25 15:02:04] local.INFO: Unique 3 links
22 mb
[2019-12-25 15:02:04] local.INFO: Links saved
22 mb
[2019-12-25 15:02:04] local.INFO: Crawler cleaned
22 mb
[2019-12-25 15:02:04] local.INFO: Begin recursive
22 mb
[2019-12-25 15:02:08] local.INFO: Parsing Started
22 mb
[2019-12-25 15:02:08] local.INFO: Add content to crawler
22 mb
[2019-12-25 15:02:09] local.INFO: Content added
22 mb
[2019-12-25 15:02:09] local.INFO: Links found
22 mb
[2019-12-25 15:02:09] local.INFO: Found 488 links
22 mb
[2019-12-25 15:02:09] local.INFO: Unique 0 links
22 mb
[2019-12-25 15:02:09] local.INFO: Links saved
22 mb
[2019-12-25 15:02:09] local.INFO: Crawler cleaned
22 mb
[2019-12-25 15:02:09] local.INFO: Begin recursive
22 mb

...
// Спустя какое то время память с 20мб до 64 доходит
[2019-12-25 15:15:53] local.INFO: Parsing Started
62 mb
[2019-12-25 15:15:53] local.INFO: Add content to crawler
62 mb
[2019-12-25 15:16:00] local.INFO: Content added
62 mb
[2019-12-25 15:16:00] local.INFO: Links found
62 mb
[2019-12-25 15:16:00] local.INFO: Found 480 links
62 mb
[2019-12-25 15:16:00] local.INFO: Unique 0 links
64 mb
[2019-12-25 15:16:00] local.INFO: Links saved
64 mb
[2019-12-25 15:16:00] local.INFO: Crawler cleaned
64 mb
[2019-12-25 15:16:00] local.INFO: Begin recursive
64 mb
[2019-12-25 15:16:04] local.INFO: Parsing Started
64 mb
[2019-12-25 15:16:04] local.INFO: Add content to crawler
64 mb
[2019-12-25 15:16:06] local.INFO: Content added
64 mb
[2019-12-25 15:16:06] local.INFO: Links found
64 mb
[2019-12-25 15:16:06] local.INFO: Found 481 links
64 mb
[2019-12-25 15:16:06] local.INFO: Unique 0 links
64 mb
[2019-12-25 15:16:06] local.INFO: Links saved
64 mb
[2019-12-25 15:16:06] local.INFO: Crawler cleaned
64 mb
[2019-12-25 15:16:06] local.INFO: Begin recursive
64 mb

Не могу понять почему чистильщик не очищает память?

Изменено TrueKanonir (25.12.2019 13:46:26)

Не в сети

#2 25.12.2019 21:32:36

Re: Утекает память при парсинге html документа

Не могу понять почему чистильщик не очищает память?

А какой лимит по памяти у процесса (memory_limit)? Может ему и не нужно, т.к. памяти еще хватает.

Попробуй gc_collect_cycles(); - это вызовет GC принудительно.

Точнее сказать нельзя, потому что не известно, что там в getMissed().

$this->convert(memory_get_usage(true)));

Учти, что:

real_usage
Set this to TRUE to get total memory allocated from system, including unused pages. If not set or FALSE only the used memory is reported.

Так что статистика может быть нерепрезентативна. Сравни с memory_get_usage(false).

Не в сети

#3 26.12.2019 09:23:55

TrueKanonir
Откуда: Ташкент
Сообщений: 221

Re: Утекает память при парсинге html документа

  1. А какой лимит по памяти у процесса (memory_limit)?
memory_limit = 1536M

Когда использовались коллекции, и память до 1.56 гб доходила. Процесс падал с 500 ошибкой и месагой *out of mamory limit…* ну это и понятно

  1. Попробуй gc_collect_cycles();

Ниже второй код с его использованием. разницы нет.

  1. Сравни с memory_get_usage(false).
PHP
// memory_get_usage(false)
[2019-12-26 10:56:45] local.INFO: Parsing Started
17.31 mb
[2019-12-26 10:56:45] local.INFO: Add content to crawler
17.36 mb
[2019-12-26 10:56:46] local.INFO: Content added
17.37 mb
[2019-12-26 10:56:46] local.INFO: Links found
17.44 mb
[2019-12-26 10:56:46] local.INFO: Found 484 links
17.45 mb
[2019-12-26 10:56:46] local.INFO: Unique 0 links
17.71 mb
[2019-12-26 10:56:46] local.INFO: Links saved
17.67 mb
[2019-12-26 10:56:46] local.INFO: Crawler cleaned
17.65 mb
[2019-12-26 10:56:46] local.INFO: Begin recursive
17.71 mb
...
[
2019-12-26 11:03:08] local.INFO: Parsing Started
37.4 mb
[2019-12-26 11:03:08] local.INFO: Add content to crawler
37.42 mb
[2019-12-26 11:03:10] local.INFO: Content added
37.42 mb
[2019-12-26 11:03:10] local.INFO: Links found
37.5 mb
[2019-12-26 11:03:10] local.INFO: Found 486 links
37.49 mb
[2019-12-26 11:03:10] local.INFO: Unique 0 links
37.76 mb
[2019-12-26 11:03:10] local.INFO: Links saved
37.72 mb
[2019-12-26 11:03:10] local.INFO: Crawler cleaned
37.7 mb
[2019-12-26 11:03:10] local.INFO: Begin recursive
37.69 mb
PHP
// memory_get_usage(false) + gc_collect_cycles();
[2019-12-26 11:05:09] local.INFO: Parsing Started
17.14 mb
[2019-12-26 11:05:09] local.INFO: Add content to crawler
17.16 mb
[2019-12-26 11:05:11] local.INFO: Content added
17.16 mb
[2019-12-26 11:05:11] local.INFO: Links found
17.24 mb
[2019-12-26 11:05:11] local.INFO: Found 487 links
17.24 mb
[2019-12-26 11:05:11] local.INFO: Unique 0 links
17.51 mb
[2019-12-26 11:05:11] local.INFO: Links saved
17.47 mb
[2019-12-26 11:05:11] local.INFO: Crawler cleaned
17.45 mb
[2019-12-26 11:05:11] local.INFO: Begin recursive
17.51 mb

...

[
2019-12-26 11:12:31] local.INFO: Parsing Started
38.64 mb
[2019-12-26 11:12:31] local.INFO: Add content to crawler
38.66 mb
[2019-12-26 11:12:34] local.INFO: Content added
38.66 mb
[2019-12-26 11:12:34] local.INFO: Links found
38.74 mb
[2019-12-26 11:12:34] local.INFO: Found 480 links
38.74 mb
[2019-12-26 11:12:34] local.INFO: Unique 0 links
39 mb
[2019-12-26 11:12:34] local.INFO: Links saved
38.9 mb
[2019-12-26 11:12:34] local.INFO: Crawler cleaned
38.88 mb
[2019-12-26 11:12:34] local.INFO: Begin recursive
38.93 mb
  1. Точнее сказать нельзя, потому что не известно, что там в getMissed()

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

PHP
    /**
     * Check if link already exists
     *
     * @param array $links
     * @return mixed
     */
    
public function getMissed(array $links)
    {
        return 
$this->whereIn('link', $links)->pluck('link')->all();
    }

Не в сети

#4 26.12.2019 09:27:58

TrueKanonir
Откуда: Ташкент
Сообщений: 221

Re: Утекает память при парсинге html документа

Незнаю как теперь быть, так как файлов еще очень много (помимо этих 10к). Эти все файлы за 0 лет работы скопились.
Хотел что бы какой нибудь из продаванов нажал пару кнопок, скрипт в очередь встал, и забыть про него.
Но теперь блин придется по ходу следить, или использовать шедулер что бы заного процесс запускать когда память сядит…

Не в сети

#5 26.12.2019 11:29:01

Re: Утекает память при парсинге html документа

Ниже второй код с его использованием. разницы нет.

Значит что-то держит память, ссылки не дохлые.

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

Видимо, здесь и утекает. В Laravel ногу сломишь, пока поймешь, где, что и кто не очистил (я еще в L3 находил утечки, даже Тейлору писал, а L3 был на порядок проще того, что сейчас), поэтому попробуй переписать этот кусок в виде сырого запроса, благо он у тебя простой. Если это не поможет - тогда попробуй и вовсе его закомментировать и заменить на чтение данных из файла (просто для тестов) через json_decode(file_get_contents()) или require() (если данные в виде PHP-кода - var_export()). Если действительно утечка здесь, то после замены память будет в норме, сколько бы итераций не прошло.

Эти все файлы за 0 лет работы скопились.

Много файлов за 0 лет работы скопилось? roll

Но теперь блин придется по ходу следить, или использовать шедулер что бы заного процесс запускать когда память сядит…

А чего ты хотел, Laravel оптимизированным по памяти никак не назовешь, с его кучей прокладок и фасадов. Он хорош в отдельно взятой области - обработке запросов от клиента. В этом плане никакие утечки не страшны, потому что в типичном запросе GC может вообще не вызываться, запрос быстро отработал и всю память собрали принудительно. А у тебя, видимо, тысячи объектов, и ты при этом хочешь плюшки Laravel в виде коллекций и ORM... Не, так не бывает.

Не в сети

#6 27.12.2019 16:51:41

TrueKanonir
Откуда: Ташкент
Сообщений: 221

Re: Утекает память при парсинге html документа

  1. Видимо, здесь и утекает. В Laravel ногу сломишь, пока поймешь, где, что и кто не очистил (я еще в L3 находил утечки, даже Тейлору писал, а L3 был на порядок проще того, что сейчас), поэтому попробуй переписать этот кусок в виде сырого запроса, благо он у тебя простой. Если это не поможет — тогда попробуй и вовсе его закомментировать и заменить на чтение данных из файла (просто для тестов) через json_decode(file_get_contents()) или require() (если данные в виде PHP-кода — var_export()). Если действительно утечка здесь, то после замены память будет в норме, сколько бы итераций не прошло

Завтра проверю.

  1. Много файлов за 0 лет работы скопилось?

Ыть, очепятолся. За 10 лет))
Эти все файлы были раньше базой в лотусе (таможенная софтина для учета экспортеров / импортеров). По сути, тот же ексель, только в своем формате. Ну, и кто то решил, что удобно будет экспортировать все в html таблицы…

  1. А чего ты хотел, Laravel оптимизированным по памяти никак не назовешь, с его кучей прокладок и фасадов. Он хорош в отдельно взятой области — обработке запросов от клиента. В этом плане никакие утечки не страшны, потому что в типичном запросе GC может вообще не вызываться, запрос быстро отработал и всю память собрали принудительно. А у тебя, видимо, тысячи объектов, и ты при этом хочешь плюшки Laravel в виде коллекций и ORM… Не, так не бывает

Хотел все и сразу)

Крайняк, если не решу проблему, то на питоне все сделаю
Proger_XP, спасибо за помощ!

Не в сети

#7 28.12.2019 19:45:46

Re: Утекает память при парсинге html документа

Крайняк, если не решу проблему, то на питоне все сделаю

На PHP вполне нормально реализуются долгоживущие парсеры. У самого PHP утечек нет, у меня бывали и бывают процессы, которые стартуют с ОС и работают, пока не перезагрузишь. Это именно проблема (или "нецелевое использование" - как угодно) Laravel. В твоем случае достаточно переписать пару мест на "более голый" PHP (степень "голости" зависит от тебя). Python - это слишком радикальное решение, ИМХО.

В конце концов, "whereIn()->pluck()" это тот же PDO + array_column(), 2-3 строчки максимум.

Не в сети

#8 31.12.2019 16:34:38

TrueKanonir
Откуда: Ташкент
Сообщений: 221

Re: Утекает память при парсинге html документа

  1. На PHP вполне нормально реализуются долгоживущие парсеры. У самого PHP утечек нет, у меня бывали и бывают процессы, которые стартуют с ОС и работают, пока не перезагрузишь. Это именно проблема (или «нецелевое использование» — как угодно) Laravel. В твоем случае достаточно переписать пару мест на «более голый» PHP (степень «голости» зависит от тебя). Python — это слишком радикальное решение, ИМХО.

Дня 2 назад опять занялся этой задачей. И в итоге вроде нашел в чем проблема.

PHP
[2019-12-29 13:10:59] local.INFO: Parsing Started
17.52 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:10:59] local.INFO: Add content to crawler
17.54 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Content added
17.54 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Links found
17.63 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Found 498 links
17.62 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Unique 3 links
17.92 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Links saved
17.86 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Crawler cleaned
17.84 mb
this 592
| manager591| link 1156| log 594
[2019-12-29 13:11:01] local.INFO: Begin recursive
17.9 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:05] local.INFO: Parsing Started
17.9 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:05] local.INFO: Add content to crawler
17.92 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:06] local.INFO: Content added
17.93 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:06] local.INFO: Links found
18.01 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:06] local.INFO: Found 499 links
18.01 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:07] local.INFO: Unique 7 links
18.3 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:07] local.INFO: Links saved
18.24 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:07] local.INFO: Crawler cleaned
18.22 mb
this 592
| manager591| link 1359| log 594
[2019-12-29 13:11:07] local.INFO: Begin recursive
18.28 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:12] local.INFO: Parsing Started
18.28 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:12] local.INFO: Add content to crawler
18.3 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Content added
18.31 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Links found
18.41 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Found 498 links
18.4 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Unique 5 links
18.69 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Links saved
18.63 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Crawler cleaned
18.62 mb
this 592
| manager591| link 1597| log 594
[2019-12-29 13:11:13] local.INFO: Begin recursive
18.68 mb
this 592
| manager591| link 1835| log 594

Если смотреть на id после памяти, то объект ссылки пересоздается (судя по id). Ну это и понятно, так как идут запросы вида

PHP
    // Модель Link.php
    /**
     * Get first pending link
     *
     * @return mixed
     */
    
public function getFirstPending()
    {
        return 
$this->oldest()
            ->
where([
                
'state' => self::PENDING
            
])
            ->
first();
    }

    
/**
     * Set parsed state
     *
     * @return mixed
     */
    
public function setParsed()
    {
        return 
$this->update([
            
'state' => self::PARSED
        
]);
    }

    
/**
     * Set failed state
     *
     * @return mixed
     */
    
public function setFailed()
    {
        return 
$this->update([
            
'state' => self::FAILED
        
]);
    }

Ну а там внутри (в самих методах модели) newEloquentBuilder() которые и порождают новые объекты, которые скорее всего не умирают после отработки, а висят в памяти.

  1. В конце концов, «whereIn()->pluck()» это тот же PDO + array_column(), 2-3 строчки максимум.

После праздников перепишу все на сырые запросы

Изменено TrueKanonir (31.12.2019 16:36:51)

Не в сети

Подвал раздела