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:45local.INFOParsing Started
17.31 mb
[2019-12-26 10:56:45local.INFOAdd content to crawler
17.36 mb
[2019-12-26 10:56:46local.INFOContent added
17.37 mb
[2019-12-26 10:56:46local.INFOLinks found
17.44 mb
[2019-12-26 10:56:46local.INFOFound 484 links
17.45 mb
[2019-12-26 10:56:46local.INFOUnique 0 links
17.71 mb
[2019-12-26 10:56:46local.INFOLinks saved
17.67 mb
[2019-12-26 10:56:46local.INFOCrawler cleaned
17.65 mb
[2019-12-26 10:56:46local.INFOBegin recursive
17.71 mb
...
[
2019-12-26 11:03:08local.INFOParsing Started
37.4 mb
[2019-12-26 11:03:08local.INFOAdd content to crawler
37.42 mb
[2019-12-26 11:03:10local.INFOContent added
37.42 mb
[2019-12-26 11:03:10local.INFOLinks found
37.5 mb
[2019-12-26 11:03:10local.INFOFound 486 links
37.49 mb
[2019-12-26 11:03:10local.INFOUnique 0 links
37.76 mb
[2019-12-26 11:03:10local.INFOLinks saved
37.72 mb
[2019-12-26 11:03:10local.INFOCrawler cleaned
37.7 mb
[2019-12-26 11:03:10local.INFOBegin recursive
37.69 mb
PHP
// memory_get_usage(false) + gc_collect_cycles();
[2019-12-26 11:05:09local.INFOParsing Started
17.14 mb
[2019-12-26 11:05:09local.INFOAdd content to crawler
17.16 mb
[2019-12-26 11:05:11local.INFOContent added
17.16 mb
[2019-12-26 11:05:11local.INFOLinks found
17.24 mb
[2019-12-26 11:05:11local.INFOFound 487 links
17.24 mb
[2019-12-26 11:05:11local.INFOUnique 0 links
17.51 mb
[2019-12-26 11:05:11local.INFOLinks saved
17.47 mb
[2019-12-26 11:05:11local.INFOCrawler cleaned
17.45 mb
[2019-12-26 11:05:11local.INFOBegin recursive
17.51 mb

...

[
2019-12-26 11:12:31local.INFOParsing Started
38.64 mb
[2019-12-26 11:12:31local.INFOAdd content to crawler
38.66 mb
[2019-12-26 11:12:34local.INFOContent added
38.66 mb
[2019-12-26 11:12:34local.INFOLinks found
38.74 mb
[2019-12-26 11:12:34local.INFOFound 480 links
38.74 mb
[2019-12-26 11:12:34local.INFOUnique 0 links
39 mb
[2019-12-26 11:12:34local.INFOLinks saved
38.9 mb
[2019-12-26 11:12:34local.INFOCrawler cleaned
38.88 mb
[2019-12-26 11:12:34local.INFOBegin 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:59local.INFOParsing Started
17.52 mb
this 592
manager591link 1156log 594
[2019-12-29 13:10:59local.INFOAdd content to crawler
17.54 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOContent added
17.54 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOLinks found
17.63 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOFound 498 links
17.62 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOUnique 3 links
17.92 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOLinks saved
17.86 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOCrawler cleaned
17.84 mb
this 592
manager591link 1156log 594
[2019-12-29 13:11:01local.INFOBegin recursive
17.9 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:05local.INFOParsing Started
17.9 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:05local.INFOAdd content to crawler
17.92 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:06local.INFOContent added
17.93 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:06local.INFOLinks found
18.01 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:06local.INFOFound 499 links
18.01 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:07local.INFOUnique 7 links
18.3 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:07local.INFOLinks saved
18.24 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:07local.INFOCrawler cleaned
18.22 mb
this 592
manager591link 1359log 594
[2019-12-29 13:11:07local.INFOBegin recursive
18.28 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:12local.INFOParsing Started
18.28 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:12local.INFOAdd content to crawler
18.3 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOContent added
18.31 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOLinks found
18.41 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOFound 498 links
18.4 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOUnique 5 links
18.69 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOLinks saved
18.63 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOCrawler cleaned
18.62 mb
this 592
manager591link 1597log 594
[2019-12-29 13:11:13local.INFOBegin recursive
18.68 mb
this 592
manager591link 1835log 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)

Не в сети

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