Cache slam defense using a semaphore to prevent dogpile effect.

Related tags

Caching php caching
Overview

metaphore

PHP cache slam defense using a semaphore to prevent dogpile effect (aka clobbering updates, stampending herd or Slashdot effect).

Problem: too many requests hit your website at the same time while it tries to regenerate same content slamming your database, eg. when cache expired.

Solution: first request generates new content while all the subsequent requests get (stale) content from cache until it's refreshed by the first request.

Read http://www.sobstel.org/blog/preventing-dogpile-effect/ for more details.

Scrutinizer Code Quality Build Status

Installation

In composer.json file:

"require": {
  "sobstel/metaphore": "1.2.*"
}

or just composer require sobstel/metaphore

Usage

use Metaphore\Cache;
use Metaphore\Store\MemcachedStore;

// initialize $memcached object (new Memcached())

$cache = new Cache(new MemcachedStore($memcached));
$cache->cache('key', function() {
    // generate content
}, 30);

Public API (methods)

  • __construct(ValueStoreInterface $valueStore, LockManager $lockManager = null)

  • cache($key, callable $callable, [$ttl, [$onNoStaleCacheCallable]]) - returns result

  • delete($key)

  • getValue($key) - returns Value object

  • setResult($key, $result, Ttl $ttl) - sets result (without anti-dogpile-effect mechanism)

  • onNoStaleCache($callable)

  • getValueStore()

  • getLockManager()

Value store vs lock store

Cache values and locks can be handled by different stores.

$valueStore = new Metaphore\MemcachedStore($memcached);

$lockStore = new Your\Custom\MySQLLockStore($connection);
$lockManager = new Metaphore\LockManager($lockStore);

$cache = new Metaphore\Cache($valueStore, $lockManager);

By default - if no 2nd argument passed to Cache constructor - value store is used as a lock store.

Sample use case might be to have custom MySQL GET_LOCK/RELEASE_LOCK for locks and still use in-built Memcached store for storing values.

Time-to-live

You can pass simple integer value...

$cache->cache('key', callback, 30); // cache for 30 secs

.. or use more advanced Metaphore\TTl object, which gives you control over grace period and lock ttl.

// $ttl, $grace_ttl, $lock_ttl
$ttl = new Ttl(30, 60, 15);

$cache->cache('key', callback, $ttl);
  • $ttl - regular cache time (in seconds)
  • $grace_ttl - grace period, how long to allow to serve stale content while new one is being generated (in seconds), similar to HTTP's stale-while-revalidate, default is 60s
  • $lock_ttl - lock time, how long to prevent other request(s) from generating same content, default is 5s

Ttl value is added to current timestamp (time() + $ttl).

No stale cache

In rare situations, when cache gets expired and there's no stale (generated earlier) content available, all requests will start generating new content.

You can add listener to catch this:

$cache->onNoStaleCache(function (NoStaleCacheEvent $event) {
    Logger::log(sprintf('no stale cache detected for key %s', $event->getKey()));
});

You can also affect value that is returned:

$cache->onNoStaleCache(function (NoStaleCacheEvent $event) {
    $event->setResult('new custom result');
});

Tests

Run all tests: phpunit.

If no memcached or/and redis installed: phpunit --exclude-group=notisolated or phpunit --exclude-group=memcached,redis.

Comments
  • Revise logic behind determining ttl for lock

    Revise logic behind determining ttl for lock

    Currently it's a half of grace_ttl. More a guess, and not logical really. Don't have a better idea for now though.

    It's important for cases when first request fails for some reason. New content is not generated while lock is not released.

        public function getLockTtl()
        {
            if (!isset($this->lockTtl)) {
                // educated guess (remove lock early enough so if anything goes wrong
                // with first process, another one can pick up)
                // SMELL: a bit problematic, why $grace_ttl/2 ???
                $this->lockTtl = max(1, (int)($this->getGraceTtl()/2));
            }
    
            return $this->lockTtl;
        }
    
    opened by sobstel 3
  • Dead lock issue

    Dead lock issue

    In case of fatal error, server restart... we can have a situation of dead lock issue.

    Why not putting a ttl in the lock data and in case cache is not regenerating until this ttl, another thread is allow to get the lock and generate the cache.

    opened by lchenay 2
  • Add FilePhpStore Class

    Add FilePhpStore Class

    Save the file in php with: deny access from web-browser.

    It is based on the FileStore class.

    use Metaphore\Cache;
    use Metaphore\Store\FilePhpStore;
    
    $cache = new Cache(new FilePhpStore('data/')); 
    
    $cache->cache('key', function() {
    		//code
    	}, 60);
    

    folder result:

    | + - data/
      | + - 3c6e0b8a9c15224a8228b9a98ca1531d.php
    
    opened by dziul 1
  • Ttl issue when it's timestamp (ttl > 30days)

    Ttl issue when it's timestamp (ttl > 30days)

    Hi, i'm using your nice library for our platform and i notice an issue :

    When we set a ttl > 30days, your class Ttl convert in unix timestamp the value, then at the moment of save with setResult you add again time(). Obviously it will not work if the value is already a unix timestamp

    $obj->cache('aa', function({ return 'aaa'}, (time() + 24*60*60*365))) => ttl generated : 2979400318 instead of 1505468441

    @edit : my mistake i just realize your library handle this and i should not set the timestamp but only seconds :)

    opened by kruggs 1
  • fix getting value from non existing key from redis

    fix getting value from non existing key from redis

    Metaphore relies on 'false' type to mark result value as non existing

    Redis will return NULL when key does not exist http://redis.io/commands/GET

    That's why there is a need to cast null to false when getting value from redis

    opened by peengle 0
  • pcntl_fork

    pcntl_fork

    Have you considered implementing pcntl_fork for the callback functions? That might avoid freezing the callback request to the client that currently is "unstaling" the result.

    wontfix 
    opened by larcho 1
  • Consider PSR-6 compatibility

    Consider PSR-6 compatibility

    Standard cache interface (will be PSR-6) has got similar concepts to metaphore:

    • CacheItemInterface resembles Value object
    • CacheItemPoolInterface indicates cache storage mechanism, like Memcache

    PSR-6 is not finalized (official) yet, but the approach seems to be discussed and decided already. Due to similarities I though it might be beneficial to be forward-compatible, heck even be the first implementation.

    Up to discussion.

    wontfix 
    opened by adambro 4
Releases(v1.2.6)
Owner
Przemek Sobstel
⛰🏃🇦🇷⚽⌨️🧉🐕
Przemek Sobstel
The place to keep your cache.

Stash - A PHP Caching Library Stash makes it easy to speed up your code by caching the results of expensive functions or code. Certain actions, like d

Tedious Developments 944 Jan 4, 2023
PHP cache library, with adapters for e.g. Memcached, Redis, Couchbase, APC(u), SQL and additional capabilities (e.g. transactions, stampede protection) built on top.

Donate/Support: Documentation: https://www.scrapbook.cash - API reference: https://docs.scrapbook.cash Table of contents Installation & usage Adapters

Matthias Mullie 295 Nov 28, 2022
:zap: Simple Cache Abstraction Layer for PHP

⚡ Simple Cache Class This is a simple Cache Abstraction Layer for PHP >= 7.0 that provides a simple interaction with your cache-server. You can define

Lars Moelleken 27 Dec 8, 2022
Doctrine Cache component

Doctrine Cache Cache component extracted from the Doctrine Common project. Documentation This library is deprecated and will no longer receive bug fix

Doctrine 7.6k Jan 3, 2023
LRU Cache implementation in PHP

PHP LRU Cache implementation Intro WTF is a LRU Cache? LRU stands for Least Recently Used. It's a type of cache that usually has a fixed capacity and

Rogério Vicente 61 Jun 23, 2022
Simple cache abstraction layer implementing PSR-16

sabre/cache This repository is a simple abstraction layer for key-value caches. It implements PSR-16. If you need a super-simple way to support PSR-16

sabre.io 48 Sep 9, 2022
PSR-6 cache implementation adapting a given PSR-16 instance

PSR-6 cache implementation adapting PSR-16 This package provides a PSR-6 cache instance when you only have a PSR-16 cache at hand. As PSR-6 is more fe

null 1 Oct 15, 2021
More Than Just a Cache: Redis Data Structures

More Than Just a Cache: Redis Data Structures Redis is a popular key-value store, commonly used as a cache or message broker service. However, it can

Andy Snell 2 Oct 16, 2021
Simple cache

Simple cache

Róbert Kelčák 3 Dec 17, 2022
Elephant - a highly performant PHP Cache Driver for Kirby 3

?? Kirby3 PHP Cache-Driver Elephant - a highly performant PHP Cache Driver for Kirby 3 Commerical Usage Support open source! This plugin is free but i

Bruno Meilick 11 Apr 6, 2022
An improved helper for working with cache

Laravel Cache Installation To get the latest version of Laravel Cache, simply require the project using Composer: $ composer require dragon-code/larav

The Dragon Code 64 Sep 23, 2022
Zend Framework cache backend for MongoDB

Zend_Cache_Backend_Mongo Author: Anton Stöckl About Zend_Cache_Backend_Mongo is a Zend Framework Cache Backend for MongoDB. It supports tags and autoc

Anton Stöckl 12 Feb 19, 2020
PHP local cache

__ ____ _________ ______/ /_ ___ / __ \/ ___/ __ `/ ___/ __ \/ _ \ / /_/ / /__/ /_/ / /__/ / / / __/ / ._

Jayden Lie 48 Sep 9, 2022
A fast, lock-free, shared memory user data cache for PHP

Yac is a shared and lockless memory user data cache for PHP.

Xinchen Hui 815 Dec 18, 2022
A simple cache library. Implements different adapters that you can use and change easily by a manager or similar.

Desarolla2 Cache A simple cache library, implementing the PSR-16 standard using immutable objects. Caching is typically used throughout an applicatito

Daniel González 129 Nov 20, 2022
PHP Cache Duration

PHP Cache Duration Introduction A readable and fluent way to generate PHP cache time. Built and written by Ajimoti Ibukun Quick Samples Instead of thi

null 27 Nov 8, 2022
Twig cache extension

Twig cache extension This extension moved to the Twig organization. Check out the repository over there instead: https://github.com/twigphp/twig-cache

Alexander 394 Nov 20, 2022
This is a Symfony bundle that lets you you integrate your PSR-6 compliant cache service with the framework

PSR-6 Cache bundle This is a Symfony bundle that lets you you integrate your PSR-6 compliant cache service with the framework. It lets you cache your

null 43 Oct 7, 2021