How to boost performance Doctrine2 with Symfony2. How to configure metadata caching? How to optimize DQL queries for caching. How to properly setup transaction demarcation with EntityManager. How to deal with EntityManager and Listeners with Symfony2 container.
5. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
plus things that can go wrong while working with Symfony2
107 queries executed in 17.25 seconds
6. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
While working with databases,
the most important strategy is
to avoid querying a database.
13. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Metadata
What can be effectively cached?
Query Result
14. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Parsing model metadata from different sources with
each request can have a serious impact on your
application’s performance. It is a good practice to
cache it.
15. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Each time you write a DQL query or use the
QueryBuilder to create one, Doctrine has to
transform that query to SQL. This is an unnecessary
waste of available resources.
16. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Usually, there is no special reason to hit a database
for rarely modified data. Configure Result Cache
strategy to make sure that your database is not
swamped with such queries.
17. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Be aware!
Make sure you properly create your queries.
18. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
# Fetch expired subscriptions (non-cachable)
SELECT s
FROM ASBillingBundle:Subscription s
WHERE s.validTo < ?
[1397855248]
!
# Fetch expired subscriptions (cachable)
SELECT s
FROM ASBillingBundle:Subscription s
WHERE s.validTo < CURRENT_TIMESTAMP()
You can cache both DQL and Native SQL queries.
28. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Setting caching driver for Query Result Cache is not
sufficient for enabling the strategy. You need to
explicitly enable it for each and every query you’re
interested in.
30. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
With great power comes great responsibility.
Voltaire (1694-1778)
31. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Be aware that high traffic websites can generate
races while accessing cached data. This situation is
called cache slam. When implementing own cache
driver, please refer to your backend’s documentation
regarding races avoidance.
32. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
If anything goes wrong, you can manually clear the
cache without restarting/flushing the storage.
php app/console doctrine:cache:clear-metadata
php app/console doctrine:cache:clear-query
php app/console doctrine:cache:clear-result
34. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
You can begin your journey with code optimization
while using Doctrine2 with properly set up domain
models.
Please be aware that the following guidelines are for
use with Doctrine2, not necessarily with database
design in general.
36. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
If you really need bidirectional associations, try to
use lazy fetching strategy, but in most cases
bidirectional relations are not necessary and should
be avoided.
37. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Be aware that lazy fetching strategy creates
dynamic proxy objects (class is generated on the
fly).
Fortunately, Symfony2 handles proxy generation
with cache warmup task.
38. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
For aggregations (with Collections) you can use
EXTRA LAZY strategy which will not load the whole
collection on first access, as it is done in LAZY
strategy.
39. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Dropping such relations does not mean that you
can’t use JOIN with your DQL / Native SQL queries.
40. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
namespace ASBillingBundleEntity;
!
class Subscription
{
/**
* @ORMManyToOne(targetEntity="User")
* @ORMJoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
!
!
!
class User
{
// ...
}
41. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
SELECT u
FROM ASBillingBundle:User u
JOIN ASBillingBundle:Subscription s WITH s.user = u
WHERE s.validTo <= CURRENT_DATE()
43. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
1. Do not configure cascade operations on every
association you have in your domain.
2. Unless required (e.g. additional file removal), use
DBMS cascades instead of ORM ones.
44. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Remember that you do not setup ORM cascades on
target entites but on the triggering ones. Opposite
to DBMS configuration.
45. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
namespace ASBillingBundleEntity;
!
class Subscription
{
/**
* @ORMOneToOne(targetEntity="User", cascade={"remove"})
* @ORMJoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
This is wrong:
46. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
namespace ASBillingBundleEntity;
!
class Subscription
{
/**
* @ORMManyToOne(targetEntity="User")
* @ORMJoinColumn(
* name="user_id",
* referencedColumnName="id",
* onDelete="CASCADE"
* )
*/
protected $user;
}
This is correct:
48. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Be smart about listening to lifecycle events like
persist, update, or remove. Know the difference
between Listener and Subscriber.
Do not identify Doctrine2 event subscribers with
Symfony2 subscribers, which are more dynamic!
49. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Avoid requiring EntityManager in your event
listeners (it will just not work).
50. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
namespace ASBillingBundleListener;
!
use SymfonyBridgeDoctrineRegistryInterface;
!
abstract class LifecycleListener
{
protected $doctrine;
protected $manager;
!
public function __construct(
RegistryInterface $doctrine,
$manager = 'default'
) {
$this->doctrine = $doctrine;
$this->manager = $manager;
}
!
protected function getEntityManager()
{
return $this->doctrine->getEntityManager($this->manager);
}
}
52. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Fetch
Modes
Return only data you are in need of.
Hydration
Modes
Partial
Objects
53. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
When dealing with one-to-one and many-to-one
relations, you can switch between EAGER and LAZY
fetching modes right in your Query object.
<?php
!
$query = $em->createQuery(
"SELECT s FROM ASBillingBundle:Subscription s"
)
->setFetchMode(
"ASBillingBundle:Subscription",
"user",
DoctrineORMMappingClassMetadata::FETCH_EAGER
);
54. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
If not necessary, do not fetch a whole object (with
relations) to just get its „validTo” field value.
<?php
!
$expirationDate = $em->createQuery(
"SELECT s.validTo " .
"FROM ASBillingBundle:Subscription s " .
"WHERE s.user = ?1"
)
->setParameter(1, $user)
->getSingleScalarResult();
55. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Unless you can deal with scalar values or arrays, you
may need to wrap data in your model’s class. Use
Partial Objects for this purpose.
<?php
!
$reference = $em->createQuery(
"SELECT PARTIAL s.{plan,validTo} " .
"FROM ASBillingBundle:Subscription s " .
"WHERE s.id = ?1"
)->setParameter(1, $id)->getResult();
!
$reference->getPartialReference("ASBillingBundle:Subscription", $id);
57. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
If you have to process data for large result sets,
think about using DQL UPDATE/DELETE queries or
Iterable Result.
When using Iterable Result always remember to
detach all existing and not required anymore objects
from Doctrine!
58. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
$batch = 50;
$i = 0;
!
$iterableResult = $em->createQuery(
"SELECT s FROM ASBillingBundle:Subscription s"
)->iterate();
!
foreach ($iterableResult as $row) {
$subscription = $row[0];
$subscription->renew();
if (($i % $batch) === 0) {
$em->flush(); // Execute queued updates
$em->clear(); // Detach objects and GC them
}
++$i;
}
!
$em->flush(); // Execute remainig updates
$em->clear(); // Detach remaining objects and GC them
59. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
$iterableResult = $em->createQuery(
"SELECT s FROM ASBillingBundle:Subscription s " .
"WHERE s.validTo = CURRENT_DATE() AND s.trialing = TRUE"
)->iterate();
!
foreach ($iterableResult as $row) {
$mailer->notifyTrialExpiration($row[0]);
$em->detach($row[0]); // Detach and GC immediately
}
61. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
Proper transaction boundary definition can have
positive impact on your application’s performance.
Doctrine2 takes care of proper implicit demarcation
for UnitOfWork–handled objects and queue all
operations until EntityManager#flush() is invoked.
62. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
However, Doctrine2 encourages developers to take
over and control transaction demarcation tasks
themselves.
Please remember to flush the EntityManager prior to
transaction commit. In case of any exceptions,
always close the EntityManager.
63. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
$em->getConnection()->beginTransaction();
!
try {
$user = new User;
$subscription = new Subscription;
$subscription->setUser($user);
!
// ... do some work
!
$em->persist($user);
$em->persist($subscription);
$em->flush();
$em->getConnection()->commit();
} catch (Exception $e) {
$em->getConnection()->rollback();
$em->close();
!
// ... do something about the exception
}
64. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
$em->transactional(function($em) {
$user = new User;
$subscription = new Subscription;
$subscription->setUser($user);
!
// ... do some work
!
$em->persist($user);
$em->persist($subscription);
});
Using EntityManager#transactional() will automatically flush the
manager prior to commit.
65. Effective Doctrine2: Performance Tips for Symfony2 Developers marcin.chwedziak
<?php
!
$em->getConnection()->transactional(function($em) {
$user = new User;
$subscription = new Subscription;
$subscription->setUser($user);
!
// ... do some work
!
$em->persist($user);
$em->persist($subscription);
$em->flush();
});
Warning! This will not close EntityManager on exception.