php-fig/fig-standards — pull request #63 by dlsniper (2012-12-01 19:34:00+00:00)
Title: Yet another cache proposal

## Problem to be solved

Unify the caching interface across libraries and framework in a simple yet
extensible way.

## Current implementations

Doctrine 2 has a stable implementation going on.

Zend Framework 2 has something as well.

Stash provides a good standalone library.

Symfony2 has none and I haven't checked the other frameworks / libraries.

## Inspiration and credits

This is based on the work of @Evert from here:
https://github.com/evert/fig-standards/blob/master/proposed/objectcache.md

It is also taken feedback from here Symfony2 comunity here:
https://github.com/symfony/symfony/pull/3211

It has been started here as a proposal for a way to implement cache:
https://github.com/symfony/symfony/pull/5902

Also @c-datculescu has been very helpful in identifying possible use cases and
problems as well as bouncing back ideas or helping out with thoughts on the
various issues.

## Proposed solution

This solution addresses the basic needs for a driver library since most of the
time caching is done using key/value systems that do not support advanced
features like tags, namespaces, transactions or locking.

Having this fact into consideration the libraries should be kept as simple as
possible and don't reinvent the wheel while help for advanced use cases or
problems should be pointed out.

## Lack of certain features, see tagging, namespace or locking

### Tagging and namespaces

In regards to the lack of tags/namespace support from the proposed solution,
I give you the following case, that originated in a discussion with a friend
of mine.

\- What if the driver doesn't support tagging?

\- Simple, it's the proxy job to know what the driver supports and in this case
emulate support for the missing feature.

\- Ok, but how would you handle say 5 tags / item with a length of say 10
characters and about 15.000 items in stored?

\- Well we'd need implement something that gets the maximum permitted storage
size for the given driver, then create some special, unique entries where
the information is stored.

\- Great but now you have another problem. Concurrency/traffic and time spent
in the proxy emulating this feature. In a highly concurrent environment, what
whould happen to the tag pool and changes you want to perform on it?

\- Without locking it would introduce a problem as one client could update same
tag as another but with different results. One should then check in the tag
pool if the desired result is there then redo the operation if it failed.

So you can see where this is leading.

Same goes for namespaces.

### Locking

Locking is something that could be done easily but I currently don't see many
cases where it would be useful. Locking would be useful in highly concurrent
environment but in that case it would mean that you would rely on users, for
example, to generate the cache, which would mean that you have one user unhappy
because he's performing a potentially heavy operation. Which is not good by
design.

For situations where a locking would be required, think about the implications
about resources implied, from waiting users to running PHP processes that wait
for the initial process to finish processing the cache update or to the
detection of when that process finished/crashed.

### Reason for skipping the metioned features from the current proposal

While the common approach with caching is:

```php
$exists = false;
$cacheItem = $driver->get($key, $exists);

if (!$exists) {
// make heavy operation
$cacheItem = $resultFromOperation;
$driver->save($cacheItem);
}
```

A better approach would be to have multiple caching layers and just go from
one to another if there's a problem retrieving the data from one layer until
a cronjob makes it available again.

```php
$exists = false;
$cacheItem = $apcDriver->get($key, $exists);

if (!$exists) {
$cacheItem = $memcachedDriver->get($key, $exists);

if (!$exists) {
$cacheItem = $dbCache->get($key, $exists);

if (!$exists) {
// make heavy operation
$cacheItem = $resultFromOperation;
$apcDriver->save($cacheItem);
}
}
}
```

Also, in most cases, APC driver should be a good one. But if you need a
distributed solution, Memcached, Redis or even MongoDB (thought it would
not be a recommendation to use it for caching) make out great distributed
systems which can be clustered and have redundancy and so on. Solutions
like having a cluster and in front of it a HAProxy server to balance out
requests one servers are down are available in such situations.

For lower level websites that just need a simple solution, the concurrency
should not be an issue in terms of having less caching levels as well as
even saving the object from the user request.

The ideal solution would have a cronjob that's able to populate the cache
before the items in it expire.

It is true that we can definitely provide emulation for all of the features,
having the library providing them would mean an additional level of abstraction
that will stand between the user and desired functionality and this is what
enterprise users don't agree with, for good reasons.

It is my opinion that we also need to teach the users how to think better
applications rather that just provide them tools that can magically do
more that they are suppose to do.

This way, people will be aware of the limitations and instead of having
a library/framework doing the workaround they would either create a better
logic in the application or help improve the tools/applications that are
suppose to be handling the issue.

If the above train of thoughts wasn't enough to satisfy the needs of the
community both current and future, then I've also added some 'advanced'
interfaces that are designed to tackle the missing functionality from the
initial proposal and you can find it here:
https://github.com/dlsniper/fig-standards/blob/extended-cache-proposal/proposed/extended-cache.md

Best regards.