Skip to content

Loading…

DDC-1402: Huge performance leak in SingleTablePersister #2025

Closed
doctrinebot opened this Issue · 3 comments

2 participants

@doctrinebot

Jira issue originally created by user sb_demarque:

This code :

/****
 * @Entity
 * @InheritanceType("SINGLE_TABLE")
 * @DiscriminatorColumn(name="discr", type="string")
 * @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
 */
class Person
{
    /*** @Id @Column(type="integer") **/ private $id;
}

/****
 * @Entity
 */
class Employee extends Person
{
    /*** @Column(type="integer") **/ private $a;
    /*** @Column(type="integer") **/ private $b;
    /*** @Column(type="integer") **/ private $c;
    /*** @Column(type="integer") **/ private $d;
    /*** @Column(type="integer") **/ private $e;
    /*** @Column(type="integer") **/ private $f;
    /*** @Column(type="integer") **/ private $g;
    /*** @Column(type="integer") **/ private $h;
}

foreach (range(0, 20) as $i) {
    $time = microtime(true);
    foreach (range(1, 100) as $j) {
        $id = ($i * 100) <ins> $j;
        $entityManager->find('Person', $id);
    }
    printf("%4d ==> %f\n", $id, microtime(true) - $time);
}

Will output:

 100 ==> 0.461275
 200 ==> 1.128404
 300 ==> 1.823122
 400 ==> 2.521054
 500 ==> 3.232034
 600 ==> 3.950081
 700 ==> 4.648849
 800 ==> 5.380236
 900 ==> 6.080108
1000 ==> 6.807214
1100 ==> 7.519942
1200 ==> 8.238971
1300 ==> 8.951686
1400 ==> 9.648996
1500 ==> 10.370053
1600 ==> 11.069523
1700 ==> 11.791530
1800 ==> 12.481427
1900 ==> 13.190570
2000 ==> 13.902810
2100 ==> 14.671100

With the first and last SELECT queries as:

SELECT t0.id AS id1, discr, t0.a AS a2, t0.b AS b3, t0.c AS c4, t0.d AS d5, t0.e AS e6, t0.f AS f7, t0.g AS g8, t0.h AS h9 FROM Person t0 WHERE t0.id = 1 AND t0.discr IN ('person', 'employee')

...

SELECT t0.id AS id1, discr, t0.a AS a16794, t0.b AS b16795, t0.c AS c16796, t0.d AS d16797, t0.e AS e16798, t0.f AS f16799, t0.g AS g16800, t0.h AS h16801 FROM Person t0 WHERE t0.id = 2100 AND t0.discr IN ('person', 'employee')

Notes:

** Last 100 SELECT queries take more than 14 seconds to execute! (table is empty*)

  • Filed as a bug because a real life scenario caused major performance leak and memory usage when attempting to do 6000 calls to find().

Analysis:

Calls to SingleTablePersister::_getSelectColumnListSQL()* do not use *BasicEntityPersister::$_selectColumnListSql

This generates a lot of calls to _getSelectColumnSQL() (See last SELECT query with alias counter up to 16800)

This generates a lot of calls to addFieldResult()

And this fills up pretty quickly ResultSetMapping::$declaringClasses

To finally put a huge burden on SimpleObjectHydrator::_prepare()* where it iterates on $this->_rsm->declaringClasses and calls *getClassMetadata() for each one!

And all that, including _prepare(), is executed for each SELECT, even though none of them find any result (the table is empty).

I fixed my project using the following patch:

diff --git a/doctrine-orm/Doctrine/ORM/Persisters/SingleTablePersister.php b/doctrine-orm/Doctrine/ORM/Persisters/SingleTablePersister.php
index f910a8e..78b27cb 100644
--- a/doctrine-orm/Doctrine/ORM/Persisters/SingleTablePersister.php
</ins><ins></ins> b/doctrine-orm/Doctrine/ORM/Persisters/SingleTablePersister.php
@@ -41,6 <ins>41,15 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
     /*** {@inheritdoc} **/
     protected function _getSelectColumnListSQL()
     {
</ins>        /*** @see BasicEntityPersister::_getSelectColumnListSQL() **/
<ins>        if ($this->_selectColumnListSql !== null) {
</ins>            return $this->_selectColumnListSql;
<ins>        }
</ins>
<ins>        #####
</ins>        #####
<ins>        #####
</ins>
         $columnList = parent::_getSelectColumnListSQL();

         // Append discriminator column
@@ -74,7 <ins>83,13 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
             }
         }

-        return $columnList;
</ins>        #####
<ins>        #####
</ins>        #####
<ins>
</ins>        /*** @see BasicEntityPersister::_getSelectColumnListSQL() **/
<ins>        $this->_selectColumnListSql = $columnList;
</ins>        return $this->_selectColumnListSql;
     }

     /*** {@inheritdoc} **/

I do not know if this patch is safe for everybody.

But, well, you can easily reproduce the problem and analyze the phenomenon using a profiler on the sample code provided.

Thanks for this great piece of software. I hope this will help you find and fix the bug.

@doctrinebot

Comment created by @beberlei:

good catch, thank you very much!

fixed and merged back into 2.1.x

@doctrinebot

Issue was closed with resolution "Fixed"

@beberlei beberlei was assigned by doctrinebot
@doctrinebot doctrinebot added this to the 2.1.3 milestone
@doctrinebot doctrinebot closed this
@doctrinebot doctrinebot added the Bug label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.