-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pimcore Memory Leak on Importing thousands of Objects #3104
Comments
Have u tried |
For example: <?php
namespace AppBundle\Command;
use Pimcore\Model\DataObject\Service;
use Pimcore\Model\DataObject\Test;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppTestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('app:test')
->addArgument('itemsToCreate', InputArgument::REQUIRED);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$itemsToCreate = $input->getArgument('itemsToCreate');
$progress = new ProgressBar($output, $itemsToCreate);
$progress->setFormat(
'%current%/%max% [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%) %memory:6s%: %message%'
);
for ($i = 0; $i < $itemsToCreate; $i++) {
$progress->setMessage('Create Item with Number '.$i);
$item = new Test();
$item->setBlub(uniqid());
$item->setBlub2(uniqid());
$item->setBlubLocalized(uniqid());
$item->setKey($i.'-'.uniqid());
$item->setParent(Service::createFolderByPath('/blub-'.$itemsToCreate.'/'.$i % 100));
$item->save();
if ($i % 10 === 0) {
\Pimcore::collectGarbage();
}
$progress->advance();
}
}
} |
Yes, several times, here are the test results, but only with 1k items, as waiting another 20minutes is quite boring :P With garbage collect:
Collect Garbage actually consumes 3.33mb of memory as well (Pimcore::collectGarbage memory) command used: <?php
namespace AppBundle\Command;
use Pimcore\Db;
use Pimcore\Model\DataObject\Service;
use Pimcore\Model\DataObject\Test;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppTestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('app:test')
->addArgument('itemsToCreate', InputArgument::REQUIRED);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$itemsToCreate = $input->getArgument('itemsToCreate');
$progress = new ProgressBar($output, $itemsToCreate);
$progress->setFormat(
'%current%/%max% [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%) %memory:6s%: %message%'
);
for ($i = 0; $i < $itemsToCreate; $i++) {
$progress->setMessage('Create Item with Number '.$i);
$item = new Test();
$item->setBlub(uniqid());
$item->setBlub2(uniqid());
$item->setBlubLocalized(uniqid());
$item->setKey($i.'-'.uniqid());
$item->setParent(Service::createFolderByPath('/blub-'.$itemsToCreate.'/'.$i % 100));
$item->save();
if ($i % 100 === 0) {
\Pimcore::collectGarbage();
}
$progress->advance();
}
}
} |
I also have to say that it runs on DEV Environment, on an PROD it doesn't consume as much, but increases in a same rate. |
@dvesh3 can you try to reproduce the problem? Thanks! |
@brusch I ended up disabling the profiler (injecting it into my command) and unsetting the sql logger for my SyncCommand to lower the ram usage
|
made some more tests: The biggest improvement is disabling the SQL Logger Tests with profiler disabled and no SQL Logger
Tests with profiler disabled and no SQL Logger and Versions disabled
Tests with profiler disabled and no SQL Logger and Versions disabled and Cache disabled
command used: <?php
namespace AppBundle\Command;
use Pimcore\Cache;
use Pimcore\Db;
use Pimcore\Model\DataObject\Service;
use Pimcore\Model\DataObject\Test;
use Pimcore\Model\Version;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppTestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('app:test')
->addArgument('itemsToCreate', InputArgument::REQUIRED);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getContainer()->get('profiler')->disable();
Db::getConnection()->getConfiguration()->setSQLLogger(null);
// Version::disable(); //Second run
// Cache::disable(); //Third run
$itemsToCreate = $input->getArgument('itemsToCreate');
$progress = new ProgressBar($output, $itemsToCreate);
$progress->setFormat(
'%current%/%max% [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%) %memory:6s%: %message%'
);
for ($i = 0; $i < $itemsToCreate; $i++) {
$progress->setMessage('Create Item with Number '.$i);
$item = new Test();
$item->setBlub(uniqid());
$item->setBlub2(uniqid());
$item->setBlubLocalized(uniqid());
$item->setKey($i.'-'.uniqid());
$item->setParent(Service::createFolderByPath('/blub-'.$itemsToCreate.'/'.$i % 100));
$item->save();
$progress->advance();
}
}
} |
Some tests with ENV=prod
Tests with profiler disabled and no SQL Logger and Versions disabled and Cache disabled
|
I used @dpfaffenbauer Import command to import 20k objects in DEV mode <?php
namespace AppBundle\Command;
use Pimcore\Model\DataObject\Service;
use Pimcore\Model\DataObject\Test;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class AppTestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('app:test')
->addArgument('itemsToCreate', InputArgument::REQUIRED);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$itemsToCreate = $input->getArgument('itemsToCreate');
$progress = new ProgressBar($output, $itemsToCreate);
$progress->setFormat(
'%current%/%max% [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%) %memory:6s%: %message%'
);
for ($i = 0; $i < $itemsToCreate; $i++) {
$progress->setMessage('Create Item with Number '.$i);
$item = new Test();
$item->setBlub(uniqid());
$item->setBlub2(uniqid());
$item->setBlubLocalized(uniqid());
$item->setKey($i.'-'.uniqid());
$item->setParent(Service::createFolderByPath('/blub-'.$itemsToCreate.'/'.$i % 100));
$item->save();
if ($i % 10 === 0) {
\Pimcore::collectGarbage();
}
$progress->advance();
}
}
} without tweaking any configuration and using
after disabling Symfony doctrine "logging" and "profiling" doctrine:
dbal:
default_connection: default
connections:
default:
........
logging: false
profiling: false
@brusch I think we can introduce these configurations in System settings. your suggestion? |
Thats a huge improvement there. Another one would be to see where those 260MB of ram go. I mean, the operation is not too heavy to loose a lot of ram there. |
@dpfaffenbauer if you want to keep on using the dev env (e.g. for developing) you can run your commands with the '--no-debug' flag. This should actually disable the sql-logger Since logging and profiling is still enabled for dev in 142067d |
@AlternateIf Yeah I know, but thanks. @brusch @dvesh3 I am currently preparing a test with Doctrine ORM as a comparison. I am importing 100000 datasets with 3 entities (to sort of simulate Pimcore a bit) and it only takes 3 mins and 18mb of RAM to do that without any increase at all. https://blackfire.io/profiles/69f22bfd-2427-4204-baa7-ba4f823cd91f/graph Importing a million sets would only take about 20 mins. And that is with blackfire agent running. Without it would only take about 10 mins. |
@brusch One solution would be to calculate change-sets and only persist actual changes from the object, like doctrine ORM does. |
@brusch disabling Monolog doesn't help. @dpfaffenbauer i think this will require huge efforts as changeset calculation works on managed entities. |
@dvesh3 thanks! should we try to find out by profiling the scripts above (Xdebug / Blackfire) ? |
@dvesh3 Sure, but the results would be amazing. I think that Pimcore's DataObject could work pretty well with Doctrine. But probably only ORM.NEXT and not 2.5. Anyways, I bought a Blackfire subscription to get more insights. Here is the graph with 1k items (same command): https://blackfire.io/profiles/08af9edc-72b9-4129-a824-e783a427045f/graph Pimcore executes 24 216 queries inserting 1000 items and thats one of the memory problems. |
Following changes improved memory usage a lot (like 50%): https://github.com/pimcore/pimcore/blob/master/models/DataObject/Concrete/Dao.php#L223 change to if ($isUpdate) {
$this->db->update('object_store_' . $this->model->getClassId(), $data, ['oo_id' => $this->model->getId()]);
}
else {
$this->db->insert('object_store_' . $this->model->getClassId(), $data);
} https://github.com/pimcore/pimcore/blob/master/models/DataObject/Concrete/Dao.php#L326 change to $this->db->update('object_query_' . $this->model->getClassId(), $data, ['oo_id' => $this->model->getId()]); It seems like https://github.com/pimcore/pimcore/blob/master/lib/Cache/Core/CoreHandler.php#L747 change to protected function addClearedTags($tags)
{
if (!is_array($tags)) {
$tags = [$tags];
}
foreach ($tags as $tag) {
if (!in_array($tag, $this->clearedTags)) {
$this->clearedTags[] = $tag;
}
}
return $this;
} array_unique takes a lot of memory with large arrays as well. In general it seems that the current Cache implementation with MySQL is super slow. CoreShop uses the Pimcore Cache for all the Doctrine stuff, and that is very slow. Getting data directly from DB and hydrate it again is way faster than the Cache implementation. Anyways, these 3 optimization helped me to come from 200mb of memory down to 86mb. Which is a good improvement. I am not using the Test Command here, I am using a more complex DataObject (Product from CoreShop) with some custom Editables and Object Indices. |
5K Import with TestCommand
@dpfaffenbauer i don't think |
@dvesh3 hmmm.... odd, it made it better for me :D, did you try that with the simple class or a more complex one with many relations? |
@dpfaffenbauer yeah, even i was not sure so cleared cache and tried it multiple times with same TestCommand but didn't get better result. |
@dvesh3 Can you rerun your tests with and without my changes but warmup the cache before running blackfire. In your test with my changes, the kernel was reinitialized and that took about 40 mb of ram. So these comparisons aren't really valuable. |
For what it's worth, we are experiencing the same performance issues but not with any import. When we clear the cache (without warm-up) and reload the website memory goes up to 2.4GB and CPU goes up to 200% after which the site gets killed by our server. In the log i see lots of deadlocks on the cache table. edit |
@dpfaffenbauer Ok so I reran the same script after cache warmup for 5k import, there is a slight difference with your changes. I'm not sure what made your result better with these changes.
|
ok, so my changes do affect performance ;) I guess the reason it helps a lot for me is that I use very complex objects with a lot of relations and persisting one of those Objects actually executes a whole lot of things. |
any updates on this? |
We're still on it ... |
yesterday i had some problems with symfony and long running console tasks on production: if fingers_crossed monolog handler is active (and on production it is usually) you have to clear the buffer after each iteration. see this: symfony/monolog-bundle#118 and here is a good code repo with examples: https://github.com/LongRunning/LongRunning |
@brusch just tested your changes, didn't improve anything at all, in fact the opposite happened, it now needs 5mb more memory. Unfortunately I can't share the blackfire results from that test. I will try to find some time for my public test. |
@dpfaffenbauer 😲ok, that's surprising. |
Yep, but that should be a one time thing since I've put the cleanup tasks into a dedicated service, but that's not a leak - so not really relevant, isn't it? |
Maybe, not sure. But overall memory usage increased by around that much. I will of course do some more tests, also on a production server, cause those are more reliable than the ones in my dev env. |
@brusch it worked for me!!..almost 50% improvement in memory utilization however time increased by 20% so that's trade-off we should expect. running 5k import script with latest changes brings following results:
|
@dpfaffenbauer can you please try again with prod env? and share your findings. Thanks! |
In my tests there is definitely an improvement, although |
Not the biggest topic but why calls the runtime cache the container on each single call? Wouldn't it be better to hold the instance internally as soon as the final instance is created? It's no memory leak but all these container calls sum up to 3 percent of the total runtime in the blackfire report I looked at. |
@markus-moser Good point, done, see: 48c0bc3 |
But I am quite surprised to be honest, cause the container already holds an instance and just gets it when it already has been created.... But anyhow, every optimization is good :) |
@brusch i don't feel good about latest optimization changes: 78.9mb for 5k import https://blackfire.io/profiles/08af69f9-f824-40fb-9c01-6568e1d65145/graph |
@dvesh3 I see, you're right, let me check that. Thanks! |
@dvesh3 fixed |
Maybe that's the answer to our question? |
I'm closing that for now, it really seems that the remaining issues are related to doctrine/dbal#3047 |
Based on complexity and data-amount of an Object Class, memory increases heavily on Importing/updating thousands of items. I have an import with around 20k Products, with a lot of informations like classifications, images, relations, etc. Around every 10th product, memory increases about 2MB. So, 20k products means a memory consumption of around:
100mb base usage (large import file needs to be kept in memory + symfony container)
+
every 10th item consumes 2 mb=
more than 4gb of ram usage.To make this easier test-able, I created a new Pimcore installation (latest stable release, 5.2.3). Created a very simple class:
And ran imports with different amounts (used PHP7.1):
So I guess you can imagine the memory consumption for 20k or more items.
Command used for importing data:
Wrapping import in a nested transactions brings following results. A bit better but not much.
Command used for nested transactions
TL;DR
Pimcore uses a lot of memory on persisting a lot of items. I am not sure why and how to optimize. But importing thousands of items in one import is not possible at the moment.
The text was updated successfully, but these errors were encountered: