{"action":"created","issue":{"url":"https://api.github.com/repos/doctrine/doctrine2/issues/580","labels_url":"https://api.github.com/repos/doctrine/doctrine2/issues/580/labels{/name}","comments_url":"https://api.github.com/repos/doctrine/doctrine2/issues/580/comments","events_url":"https://api.github.com/repos/doctrine/doctrine2/issues/580/events","html_url":"https://github.com/doctrine/doctrine2/pull/580","id":10980201,"number":580,"title":"[WIP] Second level cache","user":{"login":"FabioBatSilva","id":588172,"avatar_url":"https://avatars.githubusercontent.com/u/588172?v=3","gravatar_id":"","url":"https://api.github.com/users/FabioBatSilva","html_url":"https://github.com/FabioBatSilva","followers_url":"https://api.github.com/users/FabioBatSilva/followers","following_url":"https://api.github.com/users/FabioBatSilva/following{/other_user}","gists_url":"https://api.github.com/users/FabioBatSilva/gists{/gist_id}","starred_url":"https://api.github.com/users/FabioBatSilva/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/FabioBatSilva/subscriptions","organizations_url":"https://api.github.com/users/FabioBatSilva/orgs","repos_url":"https://api.github.com/users/FabioBatSilva/repos","events_url":"https://api.github.com/users/FabioBatSilva/events{/privacy}","received_events_url":"https://api.github.com/users/FabioBatSilva/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":34,"created_at":"2013-02-14T01:01:02Z","updated_at":"2015-01-26T13:38:45Z","closed_at":"2013-10-01T15:52:08Z","pull_request":{"url":"https://api.github.com/repos/doctrine/doctrine2/pulls/580","html_url":"https://github.com/doctrine/doctrine2/pull/580","diff_url":"https://github.com/doctrine/doctrine2/pull/580.diff","patch_url":"https://github.com/doctrine/doctrine2/pull/580.patch"},"body":"Hi guys. :)\r\n\r\n\r\nAfter a look into some implementations I end up with the following solution for the second level cache..\r\n\r\nThere is lot of work todo before merge it, but i'd like to get your thoughts before i go any further on this approach.\r\nI hope my drafts are good enough to explain the idea :\r\n\r\n### Cache strategies\r\n\r\n\t* READ_ONLY (DEFAULT)   : ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks.\r\n\t* NONSTRICT_READ_WRITE  : Nonstrict Read Write Cache doesn’t employ any locks but can do reads, inserts , updates and deletes.\r\n\t* READ_WRITE            : Read Write cache employs locks the entity before update/delete.\r\n\r\n\r\n### classes / interfaces\r\n\r\n* **Region** : \r\n    Defines a contract for accessing a entity/collection data cache. (Doesn’t employ any locks)\r\n\r\n\r\n* **ConcurrentRegion** :\r\n\tDefines contract for concurrently managed data region. (Locks the data before update/delete.)\r\n\r\n\r\n* **CacheKey / EntityCacheKey / CollectionCacheKey/ QueryCacheKey**:\r\n\tDefines entity / collection key to be stored in the cache region.\r\n\r\n\r\n* **EntityHidrator / CollectionHidrator**\r\n\tBuild cache entries and rebuild entities/colection from cache\r\n\r\n* **CacheFactory**\r\n\tFactory from second level cache components\r\n\r\n\r\n### Collection Caching\r\n\r\nThe most common use case is to cache entities. But we can also cache relationships. \r\nA “collection cache” caches the primary keys of entities that are members of a collection (OneToMany/ManyToMany). \r\nand each element will be cached into its region.\r\n\r\nOnly 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.\r\n\r\n\r\n### Query Cache\r\n\r\nThe query cache does not cache the state of the actual entities in the result set; \r\nit caches only identifier values for an individual query.\r\nSo the query cache should always be used in conjunction with the second-level cache.\r\n\r\n#### Query Cache validation\r\n\r\n> ##### UpdateTimestampsCacheRegion (hibernate approach) : \r\n\r\n> * The timestamp cache region keeps track of the last update for each table. (updated for each table modification)\r\n> * A single timestamps region it's utilized by all query cache instances.\r\n> * In most hibernate cache implementations is recommend do not configured cache timeout at all.\r\n> * When a query is loaded from cache, the timestamp region is checked for all tables in the query. \r\n> * If the timestamp of the last update on a table is greater than the time the query results were cached,\r\n  Then the entry is removed and the query goes straight to the database.\r\n\r\n### OPERATIONS\r\n\r\n\r\n#### INSERT :\r\n\r\n    *************************************************************************************************\r\n    UnitOfWork#commit\r\n        Connection#beginTransaction\r\n        Persister#executeInserts\r\n        Connection#commit\r\n        CachedPersister#afterTransactionComplete\r\n            -> Region#put\r\n    *************************************************************************************************\r\n                        | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |\r\n    -------------------------------------------------------------------------------------------------\r\n    pre-insert          |                       |                           |                       |\r\n    -------------------------------------------------------------------------------------------------\r\n    on-insert           |                       |                           |                       |\r\n    -------------------------------------------------------------------------------------------------\r\n    after-transaction   | add item to the cache | add item to the cache     | add item to the cache |\r\n    -------------------------------------------------------------------------------------------------\r\n\r\n\r\n#### UPDATE :\r\n\r\n    *************************************************************************************************\r\n    UnitOfWork#commit\r\n        Connection#beginTransaction\r\n        CachedPersister#update\r\n            -> Region#lock\r\n            -> execute\r\n        Connection#commit\r\n        CachedPersister#afterTransactionComplete\r\n            -> Region#put\r\n            -> Region#unlock\r\n    *************************************************************************************************\r\n                        | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |\r\n    -------------------------------------------------------------------------------------------------\r\n    pre-update          |                       |                           | lock item             |\r\n    -------------------------------------------------------------------------------------------------\r\n    on-update           | throws exception      |                           |                       |\r\n    -------------------------------------------------------------------------------------------------\r\n    after-transaction   |                       |  update item cache        | remove item cache     |\r\n    -------------------------------------------------------------------------------------------------\r\n\r\n\r\n#### DELETE : \r\n\r\n    *************************************************************************************************\r\n    UnitOfWork#commit\r\n        Connection#beginTransaction\r\n        CachedPersister#delete\r\n            -> Region#lock\r\n            -> execute\r\n        Connection#commit\r\n        CachedPersister#afterTransactionComplete\r\n            -> Region#evict\r\n    *************************************************************************************************\r\n                        | READ-ONLY             | NONSTRICT-READ-WRITE      | READ-WRITE            |\r\n    -------------------------------------------------------------------------------------------------\r\n    pre-remove          |                       |                           |                       |\r\n    -------------------------------------------------------------------------------------------------\r\n    on-remove           |                       |                           | lock item             |\r\n    -------------------------------------------------------------------------------------------------\r\n    after-transaction   | remove item cache     |  remove item cache        | remove item cache     |\r\n    -------------------------------------------------------------------------------------------------\r\n\r\n\r\n#### USAGE : \r\n```php\r\n<?php\r\n\r\n/**\r\n * @Entity\r\n * @Cache(\"NONSTRICT_READ_WRITE\")\r\n */\r\nclass State\r\n{\r\n    /**\r\n     * @Id\r\n     * @GeneratedValue\r\n     * @Column(type=\"integer\")\r\n     */\r\n    protected $id;\r\n    /**\r\n     * @Column\r\n     */\r\n    protected $name;\r\n    /**\r\n     * @Cache()\r\n     * @ManyToOne(targetEntity=\"Country\")\r\n     * @JoinColumn(name=\"country_id\", referencedColumnName=\"id\")\r\n     */\r\n    protected $country;\r\n    /**\r\n     * @Cache()\r\n     * @OneToMany(targetEntity=\"City\", mappedBy=\"state\")\r\n     */\r\n    protected $cities;\r\n}\r\n```\r\n\r\n```php\r\n<?php\r\n\r\n$em->persist(new State($name, $country));\r\n$em->flush();\t\t\t\t\t\t\t     // Put into cache\r\n\r\n$em->clear();\t\t\t\t\t\t\t\t // Clear entity manager\r\n\r\n$state \t = $em->find('Entity\\State', 1);\t // Retreive item from cache\r\n$country = $state->getCountry();\t\t\t // Retreive item from cache\r\n$cities  = $state->getCities();\t\t\t\t // Load from database and put into cache\r\n\r\n$state->setName(\"New Name\");\r\n$em->persist($state);\r\n$em->flush();\t\t\t\t\t\t\t     // Update item cache\r\n\r\n$em->clear();\t\t\t\t\t\t\t\t // Clear entity manager\r\n\r\n$em->find('Entity\\State', 1)->getCities();   // Retreive from cache\r\n\r\n\r\n$em->getCache()->containsEntity('Entity\\State', $state->getId())  // Check if the cache exists\r\n$em->getCache()->evictEntity('Entity\\State', $state->getId());\t  // Remove an entity from cache\r\n$em->getCache()->evictEntityRegion('Entity\\State');\t\t\t      // Remove all entities from cache\r\n\r\n$em->getCache()->containsCollection('Entity\\State', 'cities', $state->getId());   // Check if the cache exists        \r\n$em->getCache()->evictCollection('Entity\\State', 'cities', $state->getId()); \t  // Remove an entity collection from cache\r\n$em->getCache()->evictCollectionRegion('Entity\\State', 'cities');\t\t\t      // Remove all collections from cache\r\n\r\n```\r\n\r\n#### TODO :\r\n* [x] Handle many to many collection\r\n* [x] Handle inheritance\r\n* [x] Remove/add colection items on update\r\n* [x] Improve region tests\r\n* [x] Improve cached persisters coverage\r\n* [x] Implement xml / yml / php drivers\r\n* [x] Implement transaction region _(Improve cache drivers)_\r\n* [x] Implement query cache *__(need more tests)__*\r\n* [x] Read and write region when using query cache\r\n* [x] Update documentation\r\n* [x] Implement cache cache hits and misses log\r\n* [ ] .... ????"},"comment":{"url":"https://api.github.com/repos/doctrine/doctrine2/issues/comments/71461451","html_url":"https://github.com/doctrine/doctrine2/pull/580#issuecomment-71461451","issue_url":"https://api.github.com/repos/doctrine/doctrine2/issues/580","id":71461451,"user":{"login":"bakura10","id":1198915,"avatar_url":"https://avatars.githubusercontent.com/u/1198915?v=3","gravatar_id":"","url":"https://api.github.com/users/bakura10","html_url":"https://github.com/bakura10","followers_url":"https://api.github.com/users/bakura10/followers","following_url":"https://api.github.com/users/bakura10/following{/other_user}","gists_url":"https://api.github.com/users/bakura10/gists{/gist_id}","starred_url":"https://api.github.com/users/bakura10/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bakura10/subscriptions","organizations_url":"https://api.github.com/users/bakura10/orgs","repos_url":"https://api.github.com/users/bakura10/repos","events_url":"https://api.github.com/users/bakura10/events{/privacy}","received_events_url":"https://api.github.com/users/bakura10/received_events","type":"User","site_admin":false},"created_at":"2015-01-26T13:38:45Z","updated_at":"2015-01-26T13:38:45Z","body":"@jarrettj , addressed by https://github.com/doctrine/DoctrineORMModule/pull/378\r\n\r\nThanks for reporting!"}}