propelorm / Propel2

Propel2 is an open-source high-performance Object-Relational Mapping (ORM) for modern PHP
http://propelorm.org/
MIT License
1.26k stars 393 forks source link

Skip init of Propel tables in order to boost performance #1909

Closed profuel closed 1 year ago

profuel commented 1 year ago

When database table map is initialized, originally we create all table classes. Regular request uses 5-10 tables, so creation of classes is not required.

With this change, only mapping from the qualified table name to a php class name is stored in the database, and the table map class creation happens only on-demand.

mringler commented 1 year ago

Looks like what you are doing is to replace the array holding the qualified names of the table classes by an associative array, which maps the common table name to the qualified name. So this:

[
  0 => '\\Propel\\Tests\\Bookstore\\Book'
]

becomes this:

[
  'book' => '\\Propel\\Tests\\Bookstore\\Book'
]

And later on, you want to use the common name from the key to avoid having to access the static property of the class:

    public function registerTableMapClass(string $tableMapClass): void
    {
        $tableName = $tableMapClass::TABLE_NAME; // <----- this line you want to avoid
        $this->tables[$tableName] = $tableMapClass;

        $tablePhpName = $tableMapClass::TABLE_PHP_NAME; // <----- btw. looks like you just removed this
        $this->addTableByPhpName($tablePhpName, $tableMapClass);
    }

From what I see, this is the only change that might impact runtime, depending on the overhead of accessing the static property.

So far, did I figure it out correctly, is that what you want to do?

If so, can you give me a rough idea of what amount of time can be saved with this?

profuel commented 1 year ago

Thanks for review @mringler.

Yes, you've got the idea right!

On my local running project, with 320 tables, performance win is 5-50ms. And this is a constant win for every call. So with 10 calls a second, you get 1.2-12 hours CPU time win a day.

Main goal here is to avoid any Propel database mappings, when no DB operations are expected during the call processing.

mringler commented 1 year ago

Ah, nice, sounds good, sorry it took me a bit to get back to you!

In your changes, you removed the possibility to get tables by php name (i.e. 'Bookstore' instead of 'bookstore_schemas.bookstore'), but I don't think we can just get rid of it. I'm pretty sure that when that works, tests will also run through.

To fix this, the loadDatabase.php file would have to also include the php names, which does not work with a simple key-value structure, but is still easy to do. Two possibilities spring to mind:

First is to just write tuples to the file, which include name, php name and class, i.e.

[
  0 => ['bookstore_schemas.book', 'Book', '\\Propel\\Tests\\Bookstore\\Book']
]

and then build the two table maps (name to table class and php name to table class) from that, which should be easy enough.

Alternatively, we could just write the two maps into the file as they are stored internally inside the DatabaseMap class, i.e. something like

$serviceContainer->initDatabaseMaps([
  'default' => [
    'tables' => [
      'bookstore_schemas.book' => '\\Propel\\Tests\\Bookstore\\Book'
    ],
    'tablesByPhpName' => [
      'Book' => '\\Propel\\Tests\\Bookstore\\Book'
    ]
  ]
])

The advantage here is that we don't have to build a second structure, but just insert the maps into the DatabaseMap, and we might be able to use existing code to generate the content of the loadDatabase.php, since we basically just write a dump to that file.

Does that make sense? Let me know what you think about it. Do you favor one of those options, or even a whole other approach?

profuel commented 1 year ago

No problem with review time at all, thanks Moritz!

Getting table by PHP name works anyway, if you check the function \Propel\Runtime\Map\DatabaseMap::getTableByPhpName

Though I agree, building full tables map make more sense, mentioned above function will return from the first IF much faster.

mringler commented 1 year ago

Hey Andrey, my plan was to ask if you can implement the changes we talked about, and I just wanted to have a quick look at how it would work. So I played around with it a bit, and it worked almost immediately. Now I fell like I grabbed it from you, but at the same time, asking you to do it again seems wrong, too. So I just created a new MR #1910. It'd be great if you have a look at it, maybe do a review, and maybe even check if does make a difference with your large database.

profuel commented 1 year ago

I will happily close this one, once I test your solution. thanks @mringler !

dereuromark commented 1 year ago

Alternative PR is open and ready to be merged.