[WIP] Second level cache #580

Closed
wants to merge 71 commits into
from

Conversation

Projects
None yet
Owner

FabioBatSilva commented Feb 14, 2013

Hi guys. :)

After a look into some implementations I end up with the following solution for the second level cache..

There is lot of work todo before merge it, but i'd like to get your thoughts before i go any further on this approach.
I hope my drafts are good enough to explain the idea :

Cache strategies

* READ_ONLY (DEFAULT)   : ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks.
* NONSTRICT_READ_WRITE  : Nonstrict Read Write Cache doesn’t employ any locks but can do reads, inserts , updates and deletes.
* READ_WRITE            : Read Write cache employs locks the entity before update/delete.

classes / interfaces

  • Region :
    Defines a contract for accessing a entity/collection data cache. (Doesn’t employ any locks)
  • ConcurrentRegion :
    Defines contract for concurrently managed data region. (Locks the data before update/delete.)
  • CacheKey / EntityCacheKey / CollectionCacheKey/ QueryCacheKey:
    Defines entity / collection key to be stored in the cache region.
  • EntityHidrator / CollectionHidrator
    Build cache entries and rebuild entities/colection from cache
  • CacheFactory
    Factory from second level cache components

Collection Caching

The most common use case is to cache entities. But we can also cache relationships.
A “collection cache” caches the primary keys of entities that are members of a collection (OneToMany/ManyToMany).
and each element will be cached into its region.

Only identifiers will be cached for collection. When a collection is read from the second level cache it will create proxies based on the cached identifiers, if the application needs to access an element, Doctrine will go to the cache to load the element data.

Query Cache

The query cache does not cache the state of the actual entities in the result set;
it caches only identifier values for an individual query.
So the query cache should always be used in conjunction with the second-level cache.

Query Cache validation

UpdateTimestampsCacheRegion (hibernate approach) :
  • The timestamp cache region keeps track of the last update for each table. (updated for each table modification)
  • A single timestamps region it's utilized by all query cache instances.
  • In most hibernate cache implementations is recommend do not configured cache timeout at all.
  • When a query is loaded from cache, the timestamp region is checked for all tables in the query.
  • If the timestamp of the last update on a table is greater than the time the query results were cached,
    Then the entry is removed and the query goes straight to the database.

OPERATIONS

INSERT :

*************************************************************************************************
UnitOfWork#commit
    Connection#beginTransaction
    Persister#executeInserts
    Connection#commit
    CachedPersister#afterTransactionComplete
        -> Region#put
*************************************************************************************************
                    | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |
-------------------------------------------------------------------------------------------------
pre-insert          |                       |                           |                       |
-------------------------------------------------------------------------------------------------
on-insert           |                       |                           |                       |
-------------------------------------------------------------------------------------------------
after-transaction   | add item to the cache | add item to the cache     | add item to the cache |
-------------------------------------------------------------------------------------------------

UPDATE :

*************************************************************************************************
UnitOfWork#commit
    Connection#beginTransaction
    CachedPersister#update
        -> Region#lock
        -> execute
    Connection#commit
    CachedPersister#afterTransactionComplete
        -> Region#put
        -> Region#unlock
*************************************************************************************************
                    | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |
-------------------------------------------------------------------------------------------------
pre-update          |                       |                           | lock item             |
-------------------------------------------------------------------------------------------------
on-update           | throws exception      |                           |                       |
-------------------------------------------------------------------------------------------------
after-transaction   |                       |  update item cache        | remove item cache     |
-------------------------------------------------------------------------------------------------

DELETE :

*************************************************************************************************
UnitOfWork#commit
    Connection#beginTransaction
    CachedPersister#delete
        -> Region#lock
        -> execute
    Connection#commit
    CachedPersister#afterTransactionComplete
        -> Region#evict
*************************************************************************************************
                    | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |
-------------------------------------------------------------------------------------------------
pre-remove          |                       |                           |                       |
-------------------------------------------------------------------------------------------------
on-remove           |                       |                           | lock item             |
-------------------------------------------------------------------------------------------------
after-transaction   | remove item cache     |  remove item cache        | remove item cache     |
-------------------------------------------------------------------------------------------------

USAGE :

<?php

/**
 * @Entity
 * @Cache("NONSTRICT_READ_WRITE")
 */
class State
{
    /**
     * @Id
     * @GeneratedValue
     * @Column(type="integer")
     */
    protected $id;
    /**
     * @Column
     */
    protected $name;
    /**
     * @Cache()
     * @ManyToOne(targetEntity="Country")
     * @JoinColumn(name="country_id", referencedColumnName="id")
     */
    protected $country;
    /**
     * @Cache()
     * @OneToMany(targetEntity="City", mappedBy="state")
     */
    protected $cities;
}
<?php

$em->persist(new State($name, $country));
$em->flush();                                // Put into cache

$em->clear();                                // Clear entity manager

$state   = $em->find('Entity\State', 1);     // Retreive item from cache
$country = $state->getCountry();             // Retreive item from cache
$cities  = $state->getCities();              // Load from database and put into cache

$state->setName("New Name");
$em->persist($state);
$em->flush();                                // Update item cache

$em->clear();                                // Clear entity manager

$em->find('Entity\State', 1)->getCities();   // Retreive from cache


$em->getCache()->containsEntity('Entity\State', $state->getId())  // Check if the cache exists
$em->getCache()->evictEntity('Entity\State', $state->getId());    // Remove an entity from cache
$em->getCache()->evictEntityRegion('Entity\State');               // Remove all entities from cache

$em->getCache()->containsCollection('Entity\State', 'cities', $state->getId());   // Check if the cache exists        
$em->getCache()->evictCollection('Entity\State', 'cities', $state->getId());      // Remove an entity collection from cache
$em->getCache()->evictCollectionRegion('Entity\State', 'cities');                 // Remove all collections from cache

TODO :

  • Handle many to many collection
  • Handle inheritance
  • Remove/add colection items on update
  • Improve region tests
  • Improve cached persisters coverage
  • Implement xml / yml / php drivers
  • Implement transaction region (Improve cache drivers)
  • Implement query cache (need more tests)
  • Read and write region when using query cache
  • Update documentation
  • Implement cache cache hits and misses log
  • .... ????

Hello,

thank you for positing this Pull Request. I have automatically opened an issue on our Jira Bug Tracker for you with the details of this Pull-Request. See the Link:

http://doctrine-project.org/jira/browse/DDC-2295

@beberlei beberlei commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region.php
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Cache;
+
+/**
+ * Defines a contract for accessing a particular named region.
+ *
+ * @since 2.5
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+interface Region extends \Countable
@beberlei

beberlei Feb 14, 2013

Owner

Why countable? Most caches cannot know the number of entries.

@beberlei beberlei commented on the diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region.php
+
+ /**
+ * Remove an item from the cache.
+ *
+ * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item.
+ *
+ * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region.
+ */
+ public function evict(CacheKey $key);
+
+ /**
+ * Remove all contents of this particular cache region.
+ *
+ * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
+ */
+ public function evictAll();
@beberlei

beberlei Feb 14, 2013

Owner

Any idea how to implement this with all caches? Using prefix increments?

@FabioBatSilva

FabioBatSilva Feb 14, 2013

Owner

My Idea here it's to support different cache regions,
DefaultRegion it's the simplest, not good for concurrent use cases but allow usage of all common-cache drivers. we may also have MemcachedRegion or ApcRegion which can use more advanced operations suported by each driver.

@beberlei beberlei and 2 others commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(CacheKey $key)
+ {
+ return $this->cache->fetch($this->entryKey($key)) ?: null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function put(CacheKey $key, array $value)
+ {
+ $entriesKey = $this->entriesMapKey();
@beberlei

beberlei Feb 14, 2013

Owner

this is a no go, a key with all the entries will cause serious cache contention and lost updates in case of high concurrency.

@FabioBatSilva

FabioBatSilva Feb 14, 2013

Owner

I think we can iterate all entries and delete using the prefix, but it might be too slow..

@stof

stof Feb 14, 2013

Member

but Doctrine Cache does not give a way to get all entries. It does not know what are the available cached values (and underlying drivers may not allow to do it either)

@beberlei beberlei commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/UnitOfWork.php
@@ -361,6 +373,10 @@ public function commit($entity = null)
$this->em->close();
$conn->rollback();
+ foreach ($this->cachedPersisters as $persister) {
+ $persister->afterTransactionComplete(false);
@beberlei

beberlei Feb 14, 2013

Owner

true/false => violation of single responsibility principle for that method. Can you make two methods out of it, composed out of smaller methods that do the job?

Owner

beberlei commented Feb 14, 2013

So far it looks awesome specficially that it doesn't affect the code path of the current implementation of UnitOfWork much. It would be really awesome if we could keep it that way. With regard to the changes in the persisters we might need to employ a strategy pattern to get this working better. Think of the getEntityPersister() method detecting which code strategy to inject into the persister. I want to avoid cluttering the persisters even more, they already decide on so many different configurations.

Regarding the implementation I made comments on the cache, the mapEntityKeys() implementation is not good the way it is.

How will locking of items work? Can you explain? It seems to be a critical thing and i don't see how it can be implemented. Java has some pretty powerful caches.

Otherwise keep up the good work, and try not affecting the current code paths too much ;)

Owner

beberlei commented Feb 14, 2013

@FabioBatSilva just thinking - is it possible to refactor the persisters from inheritance into composition?

The inherited code is either SQL Building, or update/insert related. By splitting the persisters API up in more single responsibilities it might be easier to introduce caching in a clean way. Currently just for by id queries its rather simple, i suppose the persisters require much more changes for the other caches.

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+
+ /**
+ * @param \Doctrine\ORM\EntityManager $em
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->em = $em;
+ $this->uow = $this->em->getUnitOfWork();
+ }
+
+ /**
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ *
+ * @return \Doctrine\ORM\Cache\RegionAccess
+ */
+ public function getEntityCacheRegionAcess(ClassMetadata $metadata)
@stof

stof Feb 14, 2013

Member

The typehint is wrong. It should either be the ClassMetadata interface from Common or ClassMetadataInfo (which implements all the logic in the ORM and is the parent of ClassMetadata for BC reasons)

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+ */
+ private $uow;
+
+ /**
+ * @param \Doctrine\ORM\EntityManager $em
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->em = $em;
+ $this->uow = $this->em->getUnitOfWork();
+ }
+
+ /**
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ *
+ * @return \Doctrine\ORM\Cache\RegionAccess
@stof

stof Feb 14, 2013

Member

|null

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+ * @return \Doctrine\ORM\Cache\RegionAccess
+ */
+ public function getCollectionCacheRegionAcess(ClassMetadata $metadata, $association)
+ {
+ $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
+
+ if ( ! $persister->hasCache()) {
+ return null;
+ }
+
+ return $persister->getCacheRegionAcess();
+ }
+
+ /**
+ * Determine whether the cache contains data for the given entity "instance".
+ * <p/>
@stof

stof Feb 14, 2013

Member

<p/> should be removed

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+ $persister = $this->uow->getEntityPersister($metadata->rootEntityName);
+ $key = $this->buildEntityCacheKey($metadata, $identifier);
+
+ if ( ! $persister->hasCache()) {
+ return false;
+ }
+
+ return $persister->getCacheRegionAcess()->getRegion()->contains($key);
+ }
+
+ /**
+ * Evicts the entity data for a particular entity "instance".
+ *
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param mixed $identifier The entity identifier.
+ */
@stof

stof Feb 14, 2013

Member

missing return value

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+ /**
+ * Determine whether the cache contains data for the given collection.
+ *
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param string $association The field name that represents the association.
+ * @param mixed $ownerIdentifier The identifier of the owning entity.
+ *
+ * @return boolean true if the underlying cache contains corresponding data; false otherwise.
+ */
+ public function containsCollection(ClassMetadata $metadata, $association, array $ownerIdentifier)
+ {
+ $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association));
+ $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier);
+
+ if ( ! $persister->hasCache()) {
+ return;
@stof

stof Feb 14, 2013

Member

should return a boolean

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache.php
+
+ /**
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param array $identifier The entity identifier.
+ *
+ * @return \Doctrine\ORM\Cache\EntityCacheKey
+ */
+ public function buildEntityCacheKey(ClassMetadata $metadata, array $identifier)
+ {
+ return new EntityCacheKey($identifier, $metadata->rootEntityName);
+ }
+
+ /**
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param string $association The field name that represents the association.
+ * @param mixed $ownerIdentifier The identifier of the owning entity.
@stof

stof Feb 14, 2013

Member

mixed typehinted as array ?

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/CollectionEntryStructure.php
+ * @var \Doctrine\ORM\EntityManager
+ */
+ private $em;
+
+ /**
+ * @var \Doctrine\ORM\UnitOfWork
+ */
+ private $uow;
+
+ /**
+ * @param \Doctrine\ORM\EntityManager $em The entity manager.
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->em = $em;
+ $this->uow = $em->getUnitOfWork();
@stof

stof Feb 14, 2013

Member

extra spaces

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region.php
+
+ /**
+ * Determine whether this region contains data for the given key.
+ *
+ * @param \Doctrine\ORM\Cache\CacheKey $key The cache key
+ *
+ * @return boolean <tt>TRUE</tt> if the underlying cache contains corresponding data; <tt>FALSE</tt> otherwise.
+ */
+ public function contains(CacheKey $key);
+
+ /**
+ * Get an item from the cache.
+ *
+ * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved.
+ *
+ * @return array The cached object or data <tt>NULL</tt>
@stof

stof Feb 14, 2013

Member

array The cached object ? Really ?

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region.php
+
+ /**
+ * Remove all contents of this particular cache region.
+ *
+ * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
+ */
+ public function evictAll();
+
+ /**
+ * Get the contents of this region as an array.
+ *
+ * @return array The cached data.
+ *
+ * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region.
+ */
+ public function toArray();
@stof

stof Feb 14, 2013

Member

Are you sure all caches are able to give you all entries ?

@stof

stof Feb 14, 2013

Member

btw, do you really need this function ? It is currently not used at all

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/Region/DefaultRegion.php
+
+ if (isset($properties['lifetime']) && $properties['lifetime'] > 0) {
+ $this->lifetime = (integer) $properties['lifetime'];
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return \Doctrine\Common\Cache\AccessProvider
@stof

stof Feb 14, 2013

Member

Wrong. This is returning a Cache, not an AccessProvider (which does not exist).

Thus, is there really a reason to expose the dependency of the class ?

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php
+namespace Doctrine\ORM\Cache;
+
+/**
+ * Defines contract for regions which hold transactionally-managed data.
+ *
+ * @since 2.5
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+interface TransactionalRegionAccess extends RegionAccess
+{
+ /**
+ * We are going to attempt to update/delete the keyed object.
+ *
+ * @param \Doctrine\ORM\Cache\CacheKey $identifier The key of the item to lock.
+ *
+ * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item; or null.
@stof

stof Feb 14, 2013

Member

then it should be @return Lock|null for proper documentation

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -190,6 +189,21 @@ class ClassMetadataInfo implements ClassMetadata
const TO_MANY = 12;
/**
+ * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
+ */
+ const CACHE_USAGE_READ_ONLY = 1;
+
+ /**
+ * Nonstrict Read Write Cache doesn’t employ any locks but can do reads, inserts and deletes.
+ */
+ const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
+
+ /**
+ * Read Write cache employs locks the entity before insert/update/delete.
@stof

stof Feb 14, 2013

Member

there is an issue in the sentence

@stof stof and 1 other commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -975,6 +994,50 @@ public function getReflectionClass()
}
/**
+ * @param array $cache
+ *
+ * @return void
+ */
+ public function enableCache(array $cache)
+ {
+ if ( ! isset($cache['usage'])) {
+ $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
+ }
+
+ if ( ! isset($cache['region'])) {
+ $cache['region'] = str_replace('\\', '', $this->rootEntityName);
@stof

stof Feb 14, 2013

Member

is it important to have different regions for different entities ? If yes, there is an issue here as it can cause conflicts in some cases: Model\User\Info and Model\UserInfo produce the same region for instance (even if such case is probably not a common)

@FabioBatSilva

FabioBatSilva Feb 14, 2013

Owner

It matters, both entities are going to be stored on the same region.. i'll change it..

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
+require_once __DIR__.'/../EntityListeners.php';
+require_once __DIR__.'/../Cache.php';
@stof

stof Feb 14, 2013

Member

you should add the newline at the end to avoid such 2 lines diff next time

@stof stof and 1 other commented on an outdated diff Feb 14, 2013

...ctrine/ORM/Persisters/AbstractCollectionPersister.php
+ * @var array
+ */
+ protected $queuedCache = array();
+
+ /**
+ * @var boolean
+ */
+ protected $hasCache = false;
+
+ /**
+ * @var boolean
+ */
+ protected $isTransactionalRegionAccess = false;
+
+ /**
+ * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\TransactionalRegionAccess
@stof

stof Feb 14, 2013

Member

@var \Doctrine\ORM\Cache\RegionAccess is enough as TransactionalRegionAccess extends it

@FabioBatSilva

FabioBatSilva Feb 14, 2013

Owner

Sure ? we are accessing methods that are not in RegionAccess

@stof

stof Feb 14, 2013

Member

It does not change the fact that TransactionalRegionAccess extends RegionAccess so it is already matched byt he left part of the OR.

and if you access methods in the TransactionalRegionAccess, you have to enclose it in an instanceof check anyway, otherwise it means you require a TransactionalRegionAccess in all cases (in which case this phpdoc is also wrong)

@stof stof commented on an outdated diff Feb 14, 2013

...ctrine/ORM/Persisters/AbstractCollectionPersister.php
$this->uow = $em->getUnitOfWork();
$this->conn = $em->getConnection();
$this->platform = $this->conn->getDatabasePlatform();
$this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
+ $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled();
+
+ if ($this->hasCache) {
+ $sourceClass = $em->getClassMetadata($association['sourceEntity']);
+ $this->cacheRegionAccess = $em->getConfiguration()
+ ->getSecondLevelCacheAccessProvider()
+ ->buildCollectioRegionAccessStrategy($sourceClass, $association['fieldName']);
+
+ $this->cacheEntryStructure = new CollectionEntryStructure($em);
+ $this->isTransactionalRegionAccess = ($this->cacheRegionAccess instanceof TransactionalRegionAccess);
@stof

stof Feb 14, 2013

Member

This property is never used

@stof stof commented on an outdated diff Feb 14, 2013

lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -731,8 +838,42 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint
$hydrator = $this->em->newHydrator($this->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT);
$entities = $hydrator->hydrateAll($stmt, $this->rsm, $hints);
+ $entity = $entities ? $entities[0] : null;
@stof

stof Feb 14, 2013

Member

you don't need this variable here. You could keep it directly in the return statement (as previously)

@stof stof commented on an outdated diff Feb 14, 2013

...Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Doctrine\Tests\ORM\Cache;
+
+use Doctrine\ORM\Cache\Region;
+use Doctrine\Common\Cache\ArrayCache;
+use Doctrine\ORM\Cache\EntityCacheKey;
+use Doctrine\ORM\Cache\Region\DefaultRegion;
+use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess;
+
+require_once __DIR__ . '/../../TestInit.php';
@stof

stof Feb 14, 2013

Member

you don't need this as it is bootstrap file already (update your phpunit config file by comparing it to the dist one if needed to be sure you are not using an outdated one)

@stof

stof Feb 14, 2013

Member

and this is true for all testcases

@stof stof commented on an outdated diff Feb 14, 2013

tests/Doctrine/Tests/OrmFunctionalTestCase.php
@@ -413,6 +442,19 @@ protected function _getEntityManager($config = null, $eventManager = null) {
return \Doctrine\ORM\EntityManager::create($conn, $config);
}
+
+ /**
+ * @param \Doctrine\Common\Cache\Cache $cache
+ */
+ protected function enableSecondLevelCache(Cache $cache = null)
+ {
+ $cache = $cache ?: self::getSharedSecondLevelCacheDriverImpl();
+ $provider = new \Doctrine\ORM\Cache\CacheAccessProvider($cache);
@stof

stof Feb 14, 2013

Member

you should add a use statement IMO

Owner

beberlei commented Feb 14, 2013

@stof sorry to bark in, but we should focus code reviews on the functional level for now, this PR will take another 1-2 months to get stable and will be very tricky. It would be much easier to follow in that period if the comments were related to the implementation only so long.

Owner

FabioBatSilva commented Feb 14, 2013

@beberlei

How will locking of items work? Can you explain? It seems to be a critical thing and i don't see how it can be implemented. Java has some pretty powerful caches.

If i got it corectly Hibernate suport 2 types of concurrently managed data.

read/write

  • It basically lock the item while try to update the cache
  • UPDATE
    • lockItem :
      • Stop any other transactions reading or writing this item from/to the cache. (assuming that no other transaction holds a simultaneous lock).
      • Send them straight to the database instead.
    • afterUpdate
      • Update item cache.
    • unlockItem
      • Release the lock on the item. Other transactions may now read / write the cache

Transactional -> (This one we CAN NOT implement.)

  • Provides support for fully transactional cache providers based on JTA.
Owner

FabioBatSilva commented Feb 14, 2013

The inherited code is either SQL Building, or update/insert related. By splitting the persisters API up in more single responsibilities it might be easier to introduce caching in a clean way. Currently just for by id queries its rather simple, i suppose the persisters require much more changes for the other caches.

Seems good..
While i was looking into hibernate implementation i saw an interesting approach to handle these responsibilities.
They use commands to encapsulate each persister operation,
So persister are acessible into commands and are used basicly for SQL Building, operation executions are inside commands like InsertCommand/ UpdateCommand.

austinh commented Mar 20, 2013

Does anyone know when this second level cache would be released? I was looking to implement something like this myself, especially because just result caching collections in 2.3 causes duplication in cache (too much memory)/outdated objects(inconvenient for users), and this second level cache implemntation seems to solve all those problems. Not sure if I should wait for upgrade (depends on how long before 2.5)

Owner

Ocramius commented Mar 20, 2013

@austinh you can help yourself by sending pull requests to fabio's branch.

@Ocramius is there a plan to integrate this in to Doctrine?

Coverage Status

Coverage remained the same when pulling fc655ce342f03cb26a947578b2e32f6489616154 on FabioBatSilva:second-level-cache into 8e1111c on doctrine:master.

Member

stof commented Jun 28, 2013

@FabioBatSilva is it possible to avoid having a comment from @coveralls on each PR ? It would be spammy (github introduced the status API to avoid spamming in comment to report CI results)

@Baachi Baachi commented on an outdated diff Jun 28, 2013

lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php
+ */
+ public function clearStats()
+ {
+ $this->cachePutCountMap = array();
+ $this->cacheHitCountMap = array();
+ $this->cacheMissCountMap = array();
+ }
+
+ /**
+ * Get the total number of put in cache.
+ *
+ * @return integer
+ */
+ public function getPutCount()
+ {
+ $total = 0;
@Baachi

Baachi Jun 28, 2013

This is better:

return array_sum($this->cachePutCountMap);
Owner

beberlei commented Jun 30, 2013

@stof found the button to disable the spam

Owner

FabioBatSilva commented Jun 30, 2013

\o/
Thanks @beberlei

Owner

guilhermeblanco commented Jul 1, 2013

@FabioBatSilva How long you still think it'll be required to finish this?
I know that all drivers are quite quick... I wonder about hash generation and performance impact on this in overall execution.
About cache drivers, we briefly spoke about taking advantage of 2i (secondary indexes) whenever possible to reduce I/O but I don't know if that's what you meant here. Anyway, I'll be back to work in 2 days, we can schedule a meeting and talk about what's left to be done and maybe we can make a joint force to finish this sooner.
Also, I wonder if you got rid of that idea about tables to timestamp already...

Finally, just commenting that Punta Cana (Dominican Republic) is f*ckin' awesome. =D

Cheers,

Owner

FabioBatSilva commented Jul 1, 2013

@guilhermeblanco, I total agree that would be awesome take advantage of secondary indexes,
we can use that to load the whole result instead reading each entities after retreive the query.

About the query validation, for now I took the easy way using only lifetime to validadte the query.

When you get back to office we can discuse our final enforces to get this done.
But I think it already cover most of the cases,
and except for cache driver and hash improvement it is almost ready to go.

Finally.. Please, turn off your laptop and go get drunk.. =)

Cheers

@cordoval cordoval commented on the diff Jul 2, 2013

lib/Doctrine/ORM/AbstractQuery.php
+ if ($this->cacheable && $this->isCacheEnabled()) {
+ return $this->executeUsingQueryCache($parameters, $hydrationMode);
+ }
+
+ return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
+ }
+
+ /**
+ * Execute query ignoring second level cache.
+ *
+ * @param ArrayCollection|array|null $parameters
+ * @param integer|null $hydrationMode
+ *
+ * @return mixed
+ */
+ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
@cordoval

cordoval Jul 2, 2013

Contributor

i don't know why uncle Bob says not to pass null but in PHP I always see passing nulls as crazy

@beberlei beberlei commented on an outdated diff Jul 5, 2013

lib/Doctrine/ORM/UnitOfWork.php
*
* @throws ORMInvalidArgumentException
* @throws ORMException
*
* @return void
*/
- private function computeAssociationChanges($assoc, $value)
+ private function computeAssociationChanges($entity, $assoc, $value)
@beberlei

beberlei Jul 5, 2013

Owner

you dont use the $entity in the method as far as i can see? why is it passed?

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
@@ -580,7 +564,7 @@ public function delete($entity)
}
if (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])) {
- $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type'];
@beberlei

beberlei Jul 30, 2013

Owner

this looks wrong

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/Persisters/CachedEntityPersister.php
+
+use Doctrine\Common\Util\ClassUtils;
+use Doctrine\Common\Collections\Criteria;
+
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Doctrine\ORM\PersistentCollection;
+use Doctrine\ORM\EntityManagerInterface;
+
+use Doctrine\ORM\Cache\QueryCacheKey;
+use Doctrine\ORM\Cache;
+
+/**
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ * @since 2.5
+ */
+class CachedEntityPersister implements CachedPersister, EntityPersister
@beberlei

beberlei Jul 30, 2013

Owner

Can you move that to Doctrine\ORM\Cache\Persiters\EntityCachePersister instead?

@beberlei beberlei commented on an outdated diff Jul 30, 2013

...Doctrine/ORM/Persisters/CachedCollectionPersister.php
+
+namespace Doctrine\ORM\Persisters;
+
+use Doctrine\ORM\Cache\EntityCacheKey;
+use Doctrine\ORM\Cache\CollectionCacheKey;
+use Doctrine\ORM\Cache\ConcurrentRegionAccess;
+
+use Doctrine\Common\Util\ClassUtils;
+use Doctrine\ORM\PersistentCollection;
+use Doctrine\ORM\EntityManagerInterface;
+
+/**
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ * @since 2.5
+ */
+class CachedCollectionPersister implements CachedPersister, CollectionPersister
@beberlei

beberlei Jul 30, 2013

Owner

Can you move that to Doctrine\ORM\Cache\Persiters\CollectionCachePersister instead?

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/Persisters/CachedPersister.php
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+namespace Doctrine\ORM\Persisters;
+
+/**
+ * Interface for persister that support second level cache.
+ *
+ * @since 2.5
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+interface CachedPersister
@beberlei

beberlei Jul 30, 2013

Owner

Can you move that to Doctrine\ORM\Cache\Persisters\CachePersister instead?

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/UnitOfWork.php
@@ -942,6 +983,8 @@ private function executeInserts($class)
$persister->addInsert($entity);
+ $inserted = true;
@beberlei

beberlei Jul 30, 2013

Owner

is this still needed? Doesnt seem so

@beberlei

beberlei Jul 30, 2013

Owner

ah just seeing it is used. sorry

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/UnitOfWork.php
@@ -930,6 +970,7 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
*/
private function executeInserts($class)
{
+ $inserted = false;
@beberlei

beberlei Jul 30, 2013

Owner

is this still needed? Doesnt seem so

@beberlei beberlei commented on the diff Jul 30, 2013

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -578,6 +592,11 @@ class ClassMetadataInfo implements ClassMetadata
public $versionField;
/**
+ * @var array
+ */
+ public $cache;
@beberlei

beberlei Jul 30, 2013

Owner

you need to add that to __sleep and __wakeup

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/Persisters/CollectionPersister.php
+
+/**
+ * Collection persister interface
+ * Define the behavior that should be implemented by all collection persisters.
+ *
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ * @since 2.5
+ */
+interface CollectionPersister
+{
+ /**
+ * The class name of the source entity.
+ *
+ * @return \Doctrine\ORM\Mapping\ClassMetadata
+ */
+ public function getSourceEntityMetadata();
@beberlei

beberlei Jul 30, 2013

Owner

why is this necessary? I don't see this method used in the public space and yet for the normal collection persisters it now requires 1 persister per collection field, not per collcetion type as before. That increases the memory by quite a bit, unnecessarily. Can you move back to the more stateless approach? Maybe if necessary move these methods to the CollectionCachePersister if you need them inside the caching strategies.

Owner

beberlei commented Jul 30, 2013

Todos, I will add more if necessary:

  • the original collection persister could still be based only on type, not on source-target mapping, reducing the amount of necessary persisters in the non-cache case
  • in AbstractQuery i think there are too many new instance variables, this needs to be refactored somehow
  • can you extract foreach ($this->cachedPersisters .... in a new method, and make it run over collectionPersisters and persisters if $hasCache=true? This might trigger afterTransactionComplete() on a bit more persisters than necessary, but it avoids having the additional state and removes alot of code from the UnitOfWork.

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/UnitOfWork.php
@@ -38,6 +38,16 @@
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Event\ListenersInvoker;
+use Doctrine\ORM\Persisters\CachedPersister;
@beberlei

beberlei Jul 30, 2013

Owner

I would like to go back to using new Persisters\CachedPersister etc instead of importing 10 classes.

@beberlei beberlei commented on an outdated diff Jul 30, 2013

lib/Doctrine/ORM/UnitOfWork.php
@@ -378,6 +416,10 @@ public function commit($entity = null)
$this->dispatchPostFlushEvent();
+ foreach ($this->cachedPersisters as $persister) {
@beberlei

beberlei Jul 30, 2013

Owner

can you extract this in a new method, and make it run over collectionPersisters and persisters if $hasCache=true? This might trigger afterTransactionComplete() on a bit more persisters than necessary, but it avoids having the additional state and removes alot of code from the UnitOfWork.

@deeky666 deeky666 commented on the diff Aug 22, 2013

lib/Doctrine/ORM/AbstractQuery.php
- $this->_em = $em;
- $this->parameters = new ArrayCollection();
+ $this->_em = $em;
+ $this->parameters = new ArrayCollection();
+ $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger();
+ }
+
+ /**
+ *
+ * Enable/disable second level query (result) caching for this query.
+ *
+ * @param boolean $cacheable
+ *
+ * @return \Doctrine\ORM\AbstractQuery This query instance.
+ */
+ public function setCacheable($cacheable)
@deeky666

deeky666 Aug 22, 2013

Member

Wouldn't it be cleaner to have two methods like enableCache() and disableCache() instead of setCacheable()? I guess that's a little easier to read/understand in the first place and you don't need a boolean type cast inside the method.

@deeky666 deeky666 commented on the diff Aug 22, 2013

lib/Doctrine/ORM/AbstractQuery.php
@@ -759,6 +895,23 @@ public function iterate($parameters = null, $hydrationMode = null)
*/
public function execute($parameters = null, $hydrationMode = null)
{
+ if ($this->cacheable && $this->isCacheEnabled()) {
@deeky666

deeky666 Aug 22, 2013

Member

You don't need to check for $this->cacheable as it already is checked for in $this->isCacheEnabled().

@deeky666 deeky666 commented on the diff Aug 22, 2013

lib/Doctrine/ORM/Cache.php
+ * May read items from the cache, and add items to the cache.
+ */
+ const MODE_NORMAL = 3;
+
+ /**
+ * The session will never read items from the cache,
+ * but will refresh items to the cache as it reads them from the database.
+ */
+ const MODE_REFRESH = 4;
+
+ /**
+ * Construct
+ *
+ * @param \Doctrine\ORMEntityManagerInterface $em
+ */
+ public function __construct(EntityManagerInterface $em);
@deeky666

deeky666 Aug 22, 2013

Member

Is it reasonable to have the constructor as part of the contract? IIRC I have read somewhere that this isn't even working as expected concerning the signature enforcement...

@Majkl578

Majkl578 Aug 22, 2013

Member

It's definitely bad for a library like Doctrine. User-land implementations may (and will) need to use constructor to pass their own dependencies. See e.g. here: https://github.com/Kdyby/DoctrineCache/blob/master/src/Kdyby/DoctrineCache/Cache.php#L50-59

@fprochazka

fprochazka Aug 22, 2013

Contributor

This should be change to injectEntityManager or something.

@deeky666 deeky666 commented on the diff Aug 22, 2013

lib/Doctrine/ORM/Cache.php
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM;
+
+use Doctrine\ORM\EntityManagerInterface;
+
+/**
+ * Provides an API for querying/managing the second level cache regions.
+ *
+ * @since 2.5
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+interface Cache
@deeky666

deeky666 Aug 22, 2013

Member

This interface smells a little like SRP violation. You have nearly exactly the same methods in here for caching entities, collection and queries. It seems like this interface could be split up into three interfaces EntityCache, CollectionCache and QueryCache. Or maybe there is some better option, at least this interface seems to have too many responsibilities IMO.

@cordoval

cordoval Aug 22, 2013

Contributor

SRP?

@deeky666

deeky666 Aug 22, 2013

Member

Single Responsibility Principle

@deeky666 deeky666 commented on an outdated diff Aug 22, 2013

lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php
+ */
+
+namespace Doctrine\ORM\Cache\Access;
+
+use Doctrine\ORM\Cache\CacheException;
+use Doctrine\ORM\Cache\CacheEntry;
+use Doctrine\ORM\Cache\CacheKey;
+use Doctrine\ORM\Cache\Lock;
+
+/**
+ * Specific read-only region access strategy
+ *
+ * @since 2.5
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+class ReadOnlyRegionAccess extends NonStrictReadWriteRegionAccessStrategy
@deeky666

deeky666 Aug 22, 2013

Member

Shouldn't the class rather be called ReadOnlyRegionAccessStrategy for consistency?

FabioBatSilva and others added some commits Feb 13, 2013

@FabioBatSilva FabioBatSilva Second cache level POC 8fe90d1
@FabioBatSilva FabioBatSilva First round of refactory 52d4b88
@FabioBatSilva FabioBatSilva Fix factory method name 55146a3
@FabioBatSilva FabioBatSilva refactoring region access tests 0731254
@FabioBatSilva FabioBatSilva basically support for many to many collection 9466a44
@FabioBatSilva FabioBatSilva Refactoring tests d532fdf
@FabioBatSilva FabioBatSilva store cascade collections 3468ed7
@FabioBatSilva FabioBatSilva refactoring collections store b062340
@FabioBatSilva FabioBatSilva handle one to many collections storage 2904ecf
@FabioBatSilva FabioBatSilva tests put entities on persist 0ee0833
@FabioBatSilva FabioBatSilva test one to many orphan removal da0398a
@FabioBatSilva FabioBatSilva drop support for Region#toArray and Region#count 3097ea7
@FabioBatSilva FabioBatSilva Configurable cache interface ea84b4d
@FabioBatSilva FabioBatSilva test DefaultCache af77be3
@FabioBatSilva FabioBatSilva drop cache key api from cache interface 85e23d6
@FabioBatSilva FabioBatSilva refactoring default cache test 29ef4e7
@FabioBatSilva FabioBatSilva Basic interaction between query and second level cache 43c0d44
fabios add cache entry and configurable region lifetime 84f670c
fabios test flush fail 784fe63
fabios test select using parameters and missing entity cache cb3aa8d
fabios improve query test coverage 06fc8d3
fabios reload collection when entity cache is missing 014c2b7
fabios single table inheritance mapping 894e3c8
fabios Basic inheritance support 961c773
fabios fix test tear down 4d058a4
fabios test collection using inheritance 0ed6845
fabios check collection cache only if the owner entity cache its enabled f97eca0
fabios remove lock mode parameter db4efdf
fabios fix cache configuration exception message 4f7d4ff
fabios basic cache loggin/stats ffc624d
fabios fix cache log and improve hit/miss/put coverage c8ed9be
fabios query cache log b8b76d7
fabios move query cache API to AbstractQuery b71623f
fabios test fetch-join one-to-many 3b34145
fabios put entity loaded from query c163601
fabios handling fetch-joins b61becc
fabios drop suport for query x-to-many f571349
fabios Transaction region access strategy 29caac4
fabios test basic concurrent 3348d6c
fabios suport cached collection in a query cache 77d8b7a
fabios cache query limit 6209a51
fabios test query life-time and query region 81d2e90
fabios check if cache is enabled into uow 9ff0e21
fabios Performance test and improvements f6a5efe
fabios Cache join table inheritance 96fe2df
fabios test query cache exceptions fd2bee9
fabios Configurable query cache validation strategy 4105a80
fabios Implement xml/yml/php drivers fef2ac7
fabios Refactoring collection cache 92a6a7b
fabios Refactoring cache to use persister decorator cb916c4
fabios reduce QueryCache dependence 43e3b0d
fabios Improve one-to-many collection coverage 4ff7a64
fabios Test collection lock 9771f05
fabios Query cache mode 907820e
fabios Basic documentation for second level cache 7396e56
fabios Support entity repository 453fa0a
@FabioBatSilva FabioBatSilva Run test suite using second-level-cache b8f368e
fabios Improve code coverage 59ff676
@FabioBatSilva FabioBatSilva association type based collections persisters 73fb0f7
@FabioBatSilva FabioBatSilva Move cached persisters 6caaf2c
@FabioBatSilva FabioBatSilva Improve code coverage 376a50a
@FabioBatSilva FabioBatSilva Fix second-level-cache build a447775
@FabioBatSilva FabioBatSilva add travis build filter whitelist 4dbbdbf
fabios Refactoring region access strategies 2e23555
fabios Drop RegionAccessStrategy in favor of CachedPersister f757894
fabios Coverage improvement f99c04a
fabios Update DefaultRegion to use namespaces 814b794
fabios Small performance improvements a1d5f96
fabios Fix doc layout 4fe697a
fabios Change CacheFactory interface c7267f0
fabios FileLockRegion and some docs update 0cc72cd
Owner

FabioBatSilva commented Oct 1, 2013

Wow !!

71 Commits, 141 Files Changed..

I'm going to rebase and reopen this PR..

FabioBatSilva referenced this pull request Oct 1, 2013

Merged

Second level cache #808

Member

Majkl578 commented Oct 1, 2013

I don't get why you didn't just force-push... :)

FabioBatSilva deleted the FabioBatSilva:second-level-cache branch Dec 20, 2013

Very nice work!!
Anyone has an estimation when this will be released?

Owner

guilhermeblanco commented Apr 7, 2014

@skafandri this already got into 2.5 branch (master) as of #808

Awesome! Many thanks!

Hi, awesome work.

I'm using ZF2 + doctrine currently. Could you please tell me how to enable this feature using configuration files? I use module.config.php to configure my entitymanager.

Followed the documentation at http://doctrine-orm.readthedocs.org/en/latest/reference/second-level-cache.html but it does not have an example of how to pass the setting as a configuration option.

I've tried putting it in the module.config.php in the doctrine=>configuration=>orm_default section as second_level_cache_enabled, but that does not work.

Thanks.

Cheers.

@jarrettj I think you have to wait for this pr to be merged.
doctrine/DoctrineORMModule#295

@jarrettj in your entity manager config add:

 second_level_cache:
                    enabled: true
                    region_cache_driver: memcached
                    regions:
                        region_name:
                            lifetime: 3600
                            type: default

@skafandri have you actually got it to work in zend framework 2 + doctrine? I tried adding your options to module.config.php:

        'configuration' => array(
            'orm_default' => array(
                'generate_proxies'  => true,
                'proxy_dir'         => __DIR__ . '/../src/Application/Proxy',
                'proxy_namespace'   => 'Application\Proxy',
                'filters'           => array(),
                'string_functions'   => array(
                    'REGEXP'  => 'Application\DoctrineFunction\Regexp'
                ),
                'second_level_cache' => array(
                        'enabled' => true,
                        'region_cache_driver' => 'memcached',
                        'regions' => array(
                                        'region_name' => '',
                                        'lifetime' => 3600,
                                        'type' => 'default'
                            )
                ),
            )
        ),

Sorry for being dumb! No luck. My errors:

Fatal error: Uncaught exception 'Zend\Stdlib\Exception\BadMethodCallException' with message 'The option "second_level_cache" does not have a matching "setSecondLevelCache" setter method which must be defined' in /var/www/litigation/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php on line 1093
( ! ) Zend\Stdlib\Exception\BadMethodCallException: The option "second_level_cache" does not have a matching "setSecondLevelCache" setter method which must be defined in /var/www/litigation/vendor/zendframework/zendframework/library/Zend/Stdlib/AbstractOptions.php on line 110

Thanks. Cheers.

@jarrettj He is talking about entity manager config. If you want to use it with zf2 module you have definitely wait for doctrine/DoctrineORMModule#295.

Cool @FabianKoestring thanks! Then I'll patiently wait. :)

Well, the waiting is over @bakura10 thanks for merging 2nd level cache into doctrine-orm-module. Added the following to module.config.php:

                'second_level_cache' => array(
                    'enabled'               => true,
                    'default_lifetime'      => 7200,
                    'default_lock_lifetime' => 500,

And updated my composer.json:

        "doctrine/doctrine-orm-module": "master",
        "doctrine/orm": "2.5.*@dev",

Hope that helps anyone else using ZF2 + doctrine.

How would you set the file lock region directory to use the READ_WRITE mode?

Member

bakura10 commented Jan 26, 2015

Setting the cache usage is done in your entity with annotations. Setting the second level cache is a two steps process : you mark your entity as cacheable with an optional region, and you configure the region through arrays

@bakura10 All my entities have the Cache("READ_WRITE") annotation. But that does not work. It throws an error saying:

Fatal error: Uncaught exception 'LogicException' with message 'If you what to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). ' in /var/www/litigation/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php on line 225

The configuration documentation does not have any details on how to set this file lock region directory.

If I use Cache("NONSTRICT_READ_WRITE") annotation, it works. But it does not cache relationships. Even when adding the Cache("NONSTRICT_READ_WRITE") annotation to associations and their target entity classes.

Thought if I tried Cache("READ_WRITE") instead, the entity association would maybe work.

Member

bakura10 commented Jan 26, 2015

Looks like an option is missing. I'll have a look at this after lunch

Cool man :)

bakura10 referenced this pull request in doctrine/DoctrineORMModule Jan 26, 2015

Merged

Allow to set file lock for SLC #378

Member

bakura10 commented Jan 26, 2015

@jarrettj , addressed by doctrine/DoctrineORMModule#378

Thanks for reporting!

Only happy to help dude! Will have a look later.

File lock directory option works! :) Associations still don't work though. :(

I'll have a closer look tomorrow though. Thanks for the help so far though guys.

All jesus, all working! Just did a restart of memcache. This is Freakin Awesome! :)

Member

bakura10 commented Jan 26, 2015

Good to know! :) Second Level Cache is indeed an awesome feature!

Owner

guilhermeblanco commented Jan 26, 2015

@bakura10 @jarrettj SLC will leverage its power once all cache drivers implement multi get/save support. =D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment