Hello,

I'm currently providing the http://drupal.org/project/apc cache backend project owner a lot of performance and consistency patches. I'm also maintaining the http://drupal.org/project/redis cache backend, and I'm also the maintainer of the http://drupal.org/project/cache_backport module.

There is one thing really bothering me, all along, is that the cache prefix configuration is not unified. I saw that you have this bug opened: #467226: Support for per-bin key prefix.

What I propose is to defined a common cache prefix variable name, and allow a common per-bin cache prefix mecanism all the way, so that every one of those modules would use the same variable for prefixing their cache.

The goal is when an admin configures its cache backends, it doesn't have to configure prefix differently for all of these backends if it uses at least two of them.

What do you think?

I'm not gonna provide any patches here, since it's not about code, but really about find a common variable name and structure for this particular feature, because many cache backends will use it.

@catch For the future, I would really like to see this feature being in core directly in D8 and cache backends totally relieved of this prefix handling. It would be quite easy to do it by proxying the get/set functions via a wrapper factory or using a default implemention on which the backends could rely. This needs some discussion thought.

Comments

pounard’s picture

Status: Active » Needs work

I personnally created the $conf['cache_prefix'] variable for Redis.

If we want all to support a per bin prefix, I propose that all modules uses this convention:
The variable 'cache_prefix' is an array or a string. If a string, we use a common prefix for all cache bins.

Example:

$conf['cache_prefix'] = 'someprefix_';

If an array, each array key is a bin name, and associated value the associated prefix.
A specific meta bin named 'default' tells default behavior.
Keys can be either a string (prefix name) or FALSE (no prefix forced for this bin).
This behavior is the same for all bins, including the default meta bin.

Example:

// Default prefix (all backends) no prefix if not set or set to FALSE:
$conf['cache_prefix']['default'] = 'someprefix_';
// Sample prefix for cache_page bin:
$conf['cache_prefix']['cache_page'] = 'otherprefix_';
// Force no prefix for the 'cache_bootstrap' bin:
$conf['cache_prefix']['cache_bootstrap'] = FALSE;

Are you OK with this proposal?

Peter Bowey’s picture

Title: Unify cache prefix configuration variable name for all backends » Unify cache bin prefix: proposal to unified convention

Refer #1
Seems a rather good convention to me!
Can you enlarge on situations for multi-site bins, and the 'default' (where no prefix is forced for a bin)?

catch’s picture

Sounds good, no objections from me.

pounard’s picture

@catch, nice! I will implement this for Redis and propose a patch for APC. Are you sure my config proposal in #1 fits for Memcache? I'd really like it to be real simple, and maybe create a "official" cache backend documentation file for this that all module would include?

@peter bowey: I'd better say that nothing specified means no prefix at all, I don't care it's dangerous for multisite or not, it's not core related at this point. The goal of making core aware of this is to reach a centralized reliable solution, but right now on D7 it would require API breaking patches. I'm afraid no module can do it on its own without a central API. The way to solve this would be to create a N-th module that would be a dependency for all cache backends, I guess this is not revelant yet until no convention is fixed, and not revelant since most cache backends would not accept to be dependent on this module I guess (more maintenance and design change for all of them at first sight seems a lot to ask).

pounard’s picture

If it can help you, I have this algorithm:

abstract class Redis_Cache_Base implements DrupalCacheInterface {
  /**
   * @var string
   */
  protected $bin;

  /**
   * @var string
   */
  protected $prefix;

  /**
   * Get prefix for bin using the configuration.
   * 
   * @param string $bin
   * 
   * @return string
   *   Can be an empty string, if no prefix set.
   */
  protected static function getPrefixForBin($bin) {
    if (isset($GLOBALS['drupal_test_info']) && !empty($test_info['test_run_id'])) {
      return $test_info['test_run_id'];
    } else {
      $prefixes = variable_get('cache_prefix', '');

      if (is_string($prefixes)) {
        // Variable can be a string, which then considered as a default behavior.
        return $prefixes;
      }

      if (isset($prefixes[$bin])) {
        if (FALSE !== $prefixes[$bin]) {
          // If entry is set and not FALSE, an explicit prefix is set for the bin.
          return $prefixes[$bin];
        } else {
          // If we have an explicit false, it means no prefix whatever is the
          // default configuration.
          return '';
        }
      } else {
        // Key is not set, we can safely rely on default behavior.
        if (isset($prefixes['default']) && FALSE !== $prefixes['default']) {
          return $prefixes['default'];
        } else {
          // When default is not set or an explicit FALSE, this means no prefix.
          return '';
        }
      }
    }
  }

  function __construct($bin) {
    $this->bin = $bin;

    $prefix = self::getPrefixForBin($this->bin);

    if (empty($prefix) && isset($_SERVER['HTTP_HOST'])) {
      // Provide a fallback for multisite. This is on purpose not inside the
      // getPrefixForBin() function in order to decouple the unified prefix
      // variable logic and custom module related security logic, that is not
      // necessary for all backends.
      $this->prefix = $_SERVER['HTTP_HOST'] . '_';
    } else {
      $this->prefix = $prefix;
    }
  }

  protected function getKey($cid) {
    return $this->prefix . $this->bin . ':' . $cid;
  }
}

I'm using it in Redis backend.

EDIT: Added back the multsite security that was present before.
EDIT (bis): Added the variable being a string support (forgot that the first time).

pounard’s picture

Here is the documentation I wrote. Please I need a review, any improvement would be nice. I'd really like to see such documentation end up being shared to all backends, so it could be externalized in some drupal.org handbook page and be improved across time for everyone.

Prefixing site cache entries (avoiding sites name collision)
------------------------------------------------------------

If you need to differenciate multiple sites using the same Redis instance and
database, you will need to specify a prefix for your site cache entries.

Cache prefix configuration attemps to use a unified variable accross contrib
backends that support this feature. This variable name is 'cache_prefix'.

This variable is polymorphic, the simplest version is to provide a raw string
that will be the default prefix for all cache bins:

  $conf['cache_prefix'] = 'mysite_';

Alternatively, to provide the same functionnality, you can provide the variable
as an array:

  $conf['cache_prefix']['default'] = 'mysite_';

This allows you to provide different prefix depending on the bin name. Common
usage is that each key inside the 'cache_prefix' array is a bin name, the value
the associated prefix. If the value is explicitely FALSE, then no prefix is
used for this bin.

The 'default' meta bin name is provided to define the default prefix for non
specified bins. It behaves like the other names, which means that an explicit
FALSE will order the backend not to provide any prefix for any non specified
bin.

Here is a complex sample:

  // Default behavior for all bins, prefix is 'mysite_'.
  $conf['cache_prefix']['default'] = 'mysite_';

  // Set no prefix explicitely for 'cache' and 'cache_bootstrap' bins.
  $conf['cache_prefix']['cache'] = FALSE;
  $conf['cache_prefix']['cache_bootstrap'] = FALSE;

  // Set another prefix for 'cache_menu' bin.
  $conf['cache_prefix']['cache_menu'] = 'menumysite_';

Note that if you don't specify the default behavior, the Redis module will
attempt to use the HTTP_HOST variable in order to provide a multisite safe
default behavior. Notice that this is not failsafe, in such environment you
are strongly advised to set at least an explicit default prefix.

Note that this last note is Redis only specific, because per default Redis
server will not namespace data, thus sharing an instance for multiple sites
will create conflicts. This is not true for every backends.
mikeytown2’s picture

Using the HTTP_HOST variable isn't ideal. I would use something that uses conf_path() for the default prefix.

function settings_get_pathname() {
  static $path;
  if (isset($path)) {
    return $path;
  }
  $path = conf_path();
  if (is_link($path)) {
    $path = readlink($path);
  }
  $path = substr($path, strrpos($path, '/')+1);
  return $path;
}

If using D7, use drupal_static() instead of static.

mikeytown2’s picture

In terms of use cases I would like to add in multiple prefixes.

$conf['cache_prefix'] = example.com;
$conf['cache_prefix']['cache_page'] = $_COOKIE;

so the prefix would be $_COOKIE, but I would like it to be 'example.com' . ':' . $_COOKIE.

Some alts

$conf['cache_prefix']['#global'] = 'example.com';
$conf['cache_prefix']['#global'] .= ':' . 'sub-directory';
$conf['cache_prefix']['cache_page'] = $_COOKIE['key_a'];
$conf['cache_prefix']['cache_page'] .= ':' . $_GET['key_b'];
$conf['cache_prefix']['cache_bootstrap'] = FALSE; // Ignores the global.
unset($conf['cache_prefix']['cache']); // Use the global.
$conf['cache_prefix']['#global'][] = 'example.com';
$conf['cache_prefix']['#global'][] = 'sub-directory';
$conf['cache_prefix']['cache_page'][] = $_COOKIE['key_a'];
$conf['cache_prefix']['cache_page'][] = $_GET['key_b'];
$conf['cache_prefix']['cache_bootstrap'] = FALSE; // Ignores the global.
unset($conf['cache_prefix']['cache']); // Use the global.

cache_page = 'example.com' . ':' . 'sub-directory' . ':' . $_COOKIE['key_a'] . ':' . $_GET['key_b']
cache_bootstrap = ''
cache = 'example.com' . ':' . 'sub-directory'

One last use case is

$conf['cache_prefix']['#global'][] = 'example.com';
$conf['cache_prefix']['cache_menu']['#global'] = FALSE;
$conf['cache_prefix']['cache_menu'][] = 'mine';

cache_menu= 'mine'.

I would vote for an array of arrays with the #global special key as that provides the most flexibly and is fairly easy to maintain a global prefix with cache prefixes as well (a use case I currently have).

jason.fisher’s picture

Old conversation, but I wanted to add that my preferred standard is to use the database name as the prefix. The cache is built from and is unique to that database. If you use Domains, you might want a different operation, but in that case, the segregation should be happening within the cache bin keys.

mikeytown2’s picture

Using the database name as the default sounds like a really good default.
$databases['default']['default']['database']

pounard’s picture

One note thought: database system is designed to be able to run multiple sites in the same database using table prefixes.

mikeytown2’s picture

Think this will work. prefix is defined by default in settings.php

$databases['default']['default']['database'] . ':' . $databases['default']['default']['prefix'];
pounard’s picture

Yes, you're right.

pounard’s picture

Issue summary: View changes

Added note about future.

geek-merlin’s picture

Note: I had live situations where there are same-named databases on different db hosts. So in that case safer:
scheme://user@host/database#prefix
Note: no PW.

japerry’s picture

Status: Needs work » Closed (outdated)

Closing as Drupal 7 is no longer supported.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.