Geheugengebruik langlopende php scripts

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • mithras
  • Registratie: Maart 2003
  • Niet online
Ik wil een job queue implementeren met behulp van Beanstalkd in php. Het idee is vrij simpel, maar ik kom er niet achter waarom met mijn opstelling het geheugengebruik bij elke job toeneemt. Ik zou graag wat advies willen :)

Een job is simpel:
PHP:
1
2
3
4
5
interface Job
{
    public function __invoke(); // For execution
    public function getId();  // For Pheanstalk compatibility
}


Ik haal de job uit Beanstalkd via Pheanstalk, waarin ik de data van de job en de parameters als Json haal:

PHP:
1
2
3
4
5
6
7
8
$pheanstalk = new Pheanstalk('0.0.0.0');

$data   = json_decode($pheanstalk->reserve()->getData());
$name   = $data->name;
$params = $data->params;

$job = new $name($params);
$job();


De reserve() van Beanstalkd is blocking, dus er wordt net zo lang gewacht tot er response is, oftwel totdat er een job in de queue verschijnt. Ik wil dit proces inzetten om continue jobs af te werken. Met de CLI apps van Zend Framework 2 kan je eenvoudig controllers dispatchen, dus ik dacht aan dit:

app.php worker reserve --watch-tube=my-tube --ignore-tube=default

En dan dit:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class WorkerController extends ActionController
}
  public function reserveAction ()
  {
    while (true) {
      $job = $this->pheanstalk->reserve();
      $job = $this->instantiateJob($job);
      
      $this->executeJob($job);
    }
  }

  // Andere methods instantiateJob() executeJob()
}
Wat hier gebeurt is eigenlijk niet veel meer dan de outline van hierboven. Om te testen heb ik een job aangemaakt, SimpleJob:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace SlmTesting\Job;

use SlmQueue\Job\Job;

class SimpleJob implements Job
{
    protected $id;
    
    public function getId ()
    {
        return $this->id;
    }
    
    public function setId ($id) 
    {
        $this->id = $id;
    }
    
    public function __invoke ()
    {
        $foo = 12.3;
    }
}


Deze voeg ik 10.000 keer toe aan Beanstalkd en ga vervolgens ze uitvoeren. Het geheugengebruik (memory_get_usage) neemt toe:

0:     57372
1000:  549812
2000:  1075444
3000:  1539636
4000:  2126708
5000:  2599092
6000:  3055092
7000:  3511092
8000:  4229236
9000:  4718004
10000: 5174004


Wat ik heb geprobeerd en wat niet werkt:
  1. Vóór while(true) een gc_enable() call doen
  2. In de loop (net als output, zo om de 1000 keer) een gc_collect_cycles() call doen
  3. Na execution de job expliciet unsetten unset($job)
  4. Na execution de $job naar null toewijzen
Dit zijn jobs die ook behoorlijk geheugenintensieve taken kunnen uitvoeren, dus elke taak moet wel weer "schoon" beginnen. Liefst houd ik de loop ook in php en niet via sh loopen en in php één job uitvoeren.

Acties:
  • 0 Henk 'm!

  • mithras
  • Registratie: Maart 2003
  • Niet online
Niemand?

Ik ben trouwens extreem gaan versimpelen. Zojuist dit uitgevoerd, waarbij het geheugen niet lineair toeneemt:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

$mem = memory_get_usage();
$n   = 10000;

for ($i = 0; $i <= $n; $i++) {
    $job = new Job;
    $job();
    
    if ($i % ($n/10) === 0) {
        echo memory_get_usage() - $mem . "\n";
    }
}

class Job
{
    public function __invoke ()
    {
        $foo = 12.3;
    }
}


Dus ik ga nu wel stapje voor stapje dit uitbreiden naar bovenstaande use case en kijken waar er echt stront aan de knikker ontstaat :p

Acties:
  • 0 Henk 'm!

  • xzaz
  • Registratie: Augustus 2005
  • Laatst online: 11-09 12:49
Misschien even een stapje terug nemen. PHP en langlopende scripts is bij mij altijd een no no. PHP is gemaakt voor run -> parse -> en weer weg.
Wat is de reden voor het gebruik van PHP?

Schiet tussen de palen en je scoort!


Acties:
  • 0 Henk 'm!

  • Freeaqingme
  • Registratie: April 2006
  • Laatst online: 12:39
Welke versie van php gebruik je? Hoewel de php core group zelf ook zegt dat je beter geen langlopende scripts kan hebben met php, is de performance (en garbage collection) wel flink beterd met php 5.3, en met php5.4 nog meer.

No trees were harmed in creating this message. However, a large number of electrons were terribly inconvenienced.


Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
Ik zie namespaces, en 5.4.0 gebruik je enkel in productie als je masochistisch ingesteld bent, dus het zal wel 5.3 zijn. ;)

Test eens wat er gebeurt als je voor de gc_collect_cycles() calls $this->pheanstalk unset. Als je dan ziet dat al je geheugen in de pheanstalk objecten gaat zitten, zou je kunnen overwegen om elke x iteraties pheanstalk opnieuw aan te maken.

{signature}


Acties:
  • 0 Henk 'm!

  • mithras
  • Registratie: Maart 2003
  • Niet online
Oplossing is gevonden (denk ik) :X Maar even eerst reacties afhandelen :)
xzaz schreef op zaterdag 07 april 2012 @ 16:42:
Misschien even een stapje terug nemen. PHP en langlopende scripts is bij mij altijd een no no. PHP is gemaakt voor run -> parse -> en weer weg.
Wat is de reden voor het gebruik van PHP?
Allereerst omdat ik het als een uitdaging zie. Tegenwoordig is er zoveel meer mogelijk met php dan vroeger, het lijkt me leuk als dit ook mogelijk is. Daarnaast is in ons team de kennis van php zeer groot, van sh achtige dingen een stuk minder. We zullen moeten google'n naar oplossingen en als er iets misgaat, kan geen van ons puur op ervaring ingrijpen.
Freeaqingme schreef op zaterdag 07 april 2012 @ 16:50:
Welke versie van php gebruik je? Hoewel de php core group zelf ook zegt dat je beter geen langlopende scripts kan hebben met php, is de performance (en garbage collection) wel flink beterd met php 5.3, en met php5.4 nog meer.
php 5.3.2 inderdaad :) Omdat gc_collect() etc bestaat (dat is ook sinds 5.3.0) dacht ik hier het wel mee te kunnen. Php 5.2 was uitgesloten, sowieso.
Voutloos schreef op zaterdag 07 april 2012 @ 17:10:
Ik zie namespaces, en 5.4.0 gebruik je enkel in productie als je masochistisch ingesteld bent, dus het zal wel 5.3 zijn. ;)

Test eens wat er gebeurt als je voor de gc_collect_cycles() calls $this->pheanstalk unset. Als je dan ziet dat al je geheugen in de pheanstalk objecten gaat zitten, zou je kunnen overwegen om elke x iteraties pheanstalk opnieuw aan te maken.
Nou, het ligt gewoon aan de implementatie van dependency injection :P Het laden van de job ging door:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected function loadJob (Pheanstalk_Job $job)
{
    $data   = Json::decode($job->getData());
    $params = array('id' => $job->getId());
    if (isset($data->params)) {
        $params += (array) $data->params;
    }

    $job = $this->getLocator()->get($data->name, $params);

    if ($job instanceof LocatorAware) {
        $job->setLocator($this->getLocator());
    }

    if ($job instanceof Producer) {
        $job->setPheanstalk($this->pheanstalk);
    }

    return $job;
}
Maar het "leuke" van Zend\Di\Di is dat de instance standaard geshared is. Dus wat je ook doet in je controller met unsetten, de instance blijft hangen in de DI. Omdat an sich DI wel handig is voor het instantiaten van de jobs (gezien de automatische parameter setting) ga ik nu maar even kijken hoe ik die shared instances kan uitzetten cq kan verwijderen uit de DI instanceManager.

Toch nog een gevalletje PEBKAC |:(

/edit:
Opgelost door Zend\Di nu volledig te omzeilen, parameters setten wordt nu afgedwongen via de interface:
PHP:
1
2
3
4
5
6
interface Job
{
    public function __invoke();
    public function setParams (array $params);
    public function getId();
}

[ Voor 4% gewijzigd door mithras op 07-04-2012 22:59 ]

Pagina: 1