Bug 237498: Add memcached integration
r=dkl, a=sgreen git-svn-id: svn://10.0.0.236/trunk@265148 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
1768a0c01a
commit
01efd2530d
@ -1 +1 @@
|
|||||||
8833
|
8834
|
||||||
@ -19,23 +19,24 @@ BEGIN {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use Bugzilla::Config;
|
|
||||||
use Bugzilla::Constants;
|
|
||||||
use Bugzilla::Auth;
|
use Bugzilla::Auth;
|
||||||
use Bugzilla::Auth::Persist::Cookie;
|
use Bugzilla::Auth::Persist::Cookie;
|
||||||
use Bugzilla::CGI;
|
use Bugzilla::CGI;
|
||||||
use Bugzilla::Extension;
|
use Bugzilla::Config;
|
||||||
|
use Bugzilla::Constants;
|
||||||
use Bugzilla::DB;
|
use Bugzilla::DB;
|
||||||
|
use Bugzilla::Error;
|
||||||
|
use Bugzilla::Extension;
|
||||||
|
use Bugzilla::Field;
|
||||||
|
use Bugzilla::Flag;
|
||||||
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
||||||
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES);
|
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES);
|
||||||
use Bugzilla::Install::Util qw(init_console include_languages);
|
use Bugzilla::Install::Util qw(init_console include_languages);
|
||||||
|
use Bugzilla::Memcached;
|
||||||
use Bugzilla::Template;
|
use Bugzilla::Template;
|
||||||
use Bugzilla::User;
|
|
||||||
use Bugzilla::Error;
|
|
||||||
use Bugzilla::Util;
|
|
||||||
use Bugzilla::Field;
|
|
||||||
use Bugzilla::Flag;
|
|
||||||
use Bugzilla::Token;
|
use Bugzilla::Token;
|
||||||
|
use Bugzilla::User;
|
||||||
|
use Bugzilla::Util;
|
||||||
|
|
||||||
use File::Basename;
|
use File::Basename;
|
||||||
use File::Spec::Functions;
|
use File::Spec::Functions;
|
||||||
@ -641,6 +642,12 @@ sub process_cache {
|
|||||||
return $_process_cache;
|
return $_process_cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# This is a memcached wrapper, which provides cross-process and cross-system
|
||||||
|
# caching.
|
||||||
|
sub memcached {
|
||||||
|
return $_[0]->process_cache->{memcached} ||= Bugzilla::Memcached->_new();
|
||||||
|
}
|
||||||
|
|
||||||
# Private methods
|
# Private methods
|
||||||
|
|
||||||
# Per-process cleanup. Note that this is a plain subroutine, not a method,
|
# Per-process cleanup. Note that this is a plain subroutine, not a method,
|
||||||
@ -949,17 +956,89 @@ this Bugzilla installation.
|
|||||||
Tells you whether or not a specific feature is enabled. For names
|
Tells you whether or not a specific feature is enabled. For names
|
||||||
of features, see C<OPTIONAL_MODULES> in C<Bugzilla::Install::Requirements>.
|
of features, see C<OPTIONAL_MODULES> in C<Bugzilla::Install::Requirements>.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 B<CACHING>
|
||||||
|
|
||||||
|
Bugzilla has several different caches available which provide different
|
||||||
|
capabilities and lifetimes.
|
||||||
|
|
||||||
|
The keys of all caches are unregulated; use of prefixes is suggested to avoid
|
||||||
|
collisions.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item B<Request Cache>
|
||||||
|
|
||||||
|
The request cache is a hashref which supports caching any perl variable for the
|
||||||
|
duration of the current request. At the end of the current request the contents
|
||||||
|
of this cache are cleared.
|
||||||
|
|
||||||
|
Examples of its use include caching objects to avoid re-fetching the same data
|
||||||
|
from the database, and passing data between otherwise unconnected parts of
|
||||||
|
Bugzilla.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<request_cache>
|
||||||
|
|
||||||
|
Returns a hashref which can be checked and modified to store any perl variable
|
||||||
|
for the duration of the current request.
|
||||||
|
|
||||||
=item C<clear_request_cache>
|
=item C<clear_request_cache>
|
||||||
|
|
||||||
Removes all entries from the C<request_cache>.
|
Removes all entries from the C<request_cache>.
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
=head1 B<Methods in need of POD>
|
=item B<Process Cache>
|
||||||
|
|
||||||
|
The process cache is a hashref which support caching of any perl variable. If
|
||||||
|
Bugzilla is configured to run using Apache mod_perl, the contents of this cache
|
||||||
|
are persisted across requests for the lifetime of the Apache worker process
|
||||||
|
(which varies depending on the SizeLimit configuration in mod_perl.pl).
|
||||||
|
|
||||||
|
If Bugzilla isn't running under mod_perl, the process cache's contents are
|
||||||
|
cleared at the end of the request.
|
||||||
|
|
||||||
|
The process cache is only suitable for items which never change while Bugzilla
|
||||||
|
is running (for example the path where Bugzilla is installed).
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
=item process_cache
|
=item C<process_cache>
|
||||||
|
|
||||||
|
Returns a hashref which can be checked and modified to store any perl variable
|
||||||
|
for the duration of the current process (mod_perl) or request (mod_cgi).
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=item B<Memcached>
|
||||||
|
|
||||||
|
If Memcached is installed and configured, Bugzilla can use it to cache data
|
||||||
|
across requests and between webheads. Unlike the request and process caches,
|
||||||
|
only scalars, hashrefs, and arrayrefs can be stored in Memcached.
|
||||||
|
|
||||||
|
Memcached integration is only required for large installations of Bugzilla -- if
|
||||||
|
you have multiple webheads then configuring Memcached is recommended.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<memcached>
|
||||||
|
|
||||||
|
Returns a C<Bugzilla::Memcached> object. An object is always returned even if
|
||||||
|
Memcached is not available.
|
||||||
|
|
||||||
|
See the documentation for the C<Bugzilla::Memcached> module for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 B<Methods in need of POD>
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
=item init_page
|
=item init_page
|
||||||
|
|
||||||
@ -971,8 +1050,6 @@ Removes all entries from the C<request_cache>.
|
|||||||
|
|
||||||
=item active_custom_fields
|
=item active_custom_fields
|
||||||
|
|
||||||
=item request_cache
|
|
||||||
|
|
||||||
=item has_flags
|
=item has_flags
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|||||||
@ -353,9 +353,9 @@ sub initialize {
|
|||||||
$_[0]->_create_cf_accessors();
|
$_[0]->_create_cf_accessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cache_key {
|
sub object_cache_key {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my $key = $class->SUPER::cache_key(@_)
|
my $key = $class->SUPER::object_cache_key(@_)
|
||||||
|| return;
|
|| return;
|
||||||
return $key . ',' . Bugzilla->user->id;
|
return $key . ',' . Bugzilla->user->id;
|
||||||
}
|
}
|
||||||
@ -4422,7 +4422,7 @@ Ensures the accessors for custom fields are always created.
|
|||||||
|
|
||||||
=item set_op_sys
|
=item set_op_sys
|
||||||
|
|
||||||
=item cache_key
|
=item object_cache_key
|
||||||
|
|
||||||
=item bug_group
|
=item bug_group
|
||||||
|
|
||||||
|
|||||||
32
mozilla/webtools/bugzilla/Bugzilla/Config/Memcached.pm
Normal file
32
mozilla/webtools/bugzilla/Bugzilla/Config/Memcached.pm
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
#
|
||||||
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
||||||
|
package Bugzilla::Config::Memcached;
|
||||||
|
|
||||||
|
use 5.10.1;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
use Bugzilla::Config::Common;
|
||||||
|
|
||||||
|
our $sortkey = 1550;
|
||||||
|
|
||||||
|
sub get_param_list {
|
||||||
|
return (
|
||||||
|
{
|
||||||
|
name => 'memcached_servers',
|
||||||
|
type => 't',
|
||||||
|
default => ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name => 'memcached_namespace',
|
||||||
|
type => 't',
|
||||||
|
default => 'bugzilla:',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
@ -1362,14 +1362,19 @@ sub _bz_real_schema {
|
|||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
return $self->{private_real_schema} if exists $self->{private_real_schema};
|
return $self->{private_real_schema} if exists $self->{private_real_schema};
|
||||||
|
|
||||||
my ($data, $version) = $self->selectrow_array(
|
my $bz_schema;
|
||||||
"SELECT schema_data, version FROM bz_schema");
|
unless ($bz_schema = Bugzilla->memcached->get({ key => 'bz_schema' })) {
|
||||||
|
$bz_schema = $self->selectrow_arrayref(
|
||||||
|
"SELECT schema_data, version FROM bz_schema"
|
||||||
|
);
|
||||||
|
Bugzilla->memcached->set({ key => 'bz_schema', value => $bz_schema });
|
||||||
|
}
|
||||||
|
|
||||||
(die "_bz_real_schema tried to read the bz_schema table but it's empty!")
|
(die "_bz_real_schema tried to read the bz_schema table but it's empty!")
|
||||||
if !$data;
|
if !$bz_schema;
|
||||||
|
|
||||||
$self->{private_real_schema} =
|
$self->{private_real_schema} =
|
||||||
$self->_bz_schema->deserialize_abstract($data, $version);
|
$self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
|
||||||
|
|
||||||
return $self->{private_real_schema};
|
return $self->{private_real_schema};
|
||||||
}
|
}
|
||||||
@ -1411,6 +1416,8 @@ sub _bz_store_real_schema {
|
|||||||
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
|
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
|
||||||
$sth->bind_param(2, $schema_version);
|
$sth->bind_param(2, $schema_version);
|
||||||
$sth->execute();
|
$sth->execute();
|
||||||
|
|
||||||
|
Bugzilla->memcached->clear({ key => 'bz_schema' });
|
||||||
}
|
}
|
||||||
|
|
||||||
# For bz_populate_enum_tables
|
# For bz_populate_enum_tables
|
||||||
|
|||||||
@ -394,6 +394,14 @@ sub OPTIONAL_MODULES {
|
|||||||
version => '0',
|
version => '0',
|
||||||
feature => ['typesniffer'],
|
feature => ['typesniffer'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# memcached
|
||||||
|
{
|
||||||
|
package => 'Cache-Memcached',
|
||||||
|
module => 'Cache::Memcached',
|
||||||
|
version => '0',
|
||||||
|
feature => ['memcached'],
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
|
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
|
||||||
@ -417,6 +425,7 @@ use constant FEATURE_FILES => (
|
|||||||
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
|
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
|
||||||
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
|
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
|
||||||
updates => ['Bugzilla/Update.pm'],
|
updates => ['Bugzilla/Update.pm'],
|
||||||
|
memcached => ['Bugzilla/Memcache.pm'],
|
||||||
);
|
);
|
||||||
|
|
||||||
# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
|
# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
|
||||||
|
|||||||
346
mozilla/webtools/bugzilla/Bugzilla/Memcached.pm
Normal file
346
mozilla/webtools/bugzilla/Bugzilla/Memcached.pm
Normal file
@ -0,0 +1,346 @@
|
|||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
#
|
||||||
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
||||||
|
package Bugzilla::Memcached;
|
||||||
|
|
||||||
|
use 5.10.1;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Bugzilla::Error;
|
||||||
|
use Bugzilla::Util qw(trick_taint);
|
||||||
|
use Scalar::Util qw(blessed);
|
||||||
|
|
||||||
|
sub _new {
|
||||||
|
my $invocant = shift;
|
||||||
|
my $class = ref($invocant) || $invocant;
|
||||||
|
my $self = {};
|
||||||
|
|
||||||
|
# always return an object to simplify calling code when memcached is
|
||||||
|
# disabled.
|
||||||
|
if (Bugzilla->feature('memcached')
|
||||||
|
&& Bugzilla->params->{memcached_servers})
|
||||||
|
{
|
||||||
|
require Cache::Memcached;
|
||||||
|
$self->{memcached} =
|
||||||
|
Cache::Memcached->new({
|
||||||
|
servers => [ split(/[, ]+/, Bugzilla->params->{memcached_servers}) ],
|
||||||
|
namespace => Bugzilla->params->{memcached_namespace} || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return bless($self, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set {
|
||||||
|
my ($self, $args) = @_;
|
||||||
|
return unless $self->{memcached};
|
||||||
|
|
||||||
|
# { key => $key, value => $value }
|
||||||
|
if (exists $args->{key}) {
|
||||||
|
$self->_set($args->{key}, $args->{value});
|
||||||
|
}
|
||||||
|
|
||||||
|
# { table => $table, id => $id, name => $name, data => $data }
|
||||||
|
elsif (exists $args->{table} && exists $args->{id} && exists $args->{name}) {
|
||||||
|
# For caching of Bugzilla::Object, we have to be able to clear the
|
||||||
|
# cached values when given either the object's id or name.
|
||||||
|
my ($table, $id, $name, $data) = @$args{qw(table id name data)};
|
||||||
|
$self->_set("$table.id.$id", $data);
|
||||||
|
if (defined $name) {
|
||||||
|
$self->_set("$table.name_id.$name", $id);
|
||||||
|
$self->_set("$table.id_name.$id", $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
ThrowCodeError('params_required', { function => "Bugzilla::Memcached::set",
|
||||||
|
params => [ 'key', 'table' ] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get {
|
||||||
|
my ($self, $args) = @_;
|
||||||
|
return unless $self->{memcached};
|
||||||
|
|
||||||
|
# { key => $key }
|
||||||
|
if (exists $args->{key}) {
|
||||||
|
return $self->_get($args->{key});
|
||||||
|
}
|
||||||
|
|
||||||
|
# { table => $table, id => $id }
|
||||||
|
elsif (exists $args->{table} && exists $args->{id}) {
|
||||||
|
my ($table, $id) = @$args{qw(table id)};
|
||||||
|
return $self->_get("$table.id.$id");
|
||||||
|
}
|
||||||
|
|
||||||
|
# { table => $table, name => $name }
|
||||||
|
elsif (exists $args->{table} && exists $args->{name}) {
|
||||||
|
my ($table, $name) = @$args{qw(table name)};
|
||||||
|
return unless my $id = $self->_get("$table.name_id.$name");
|
||||||
|
return $self->_get("$table.id.$id");
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
ThrowCodeError('params_required', { function => "Bugzilla::Memcached::get",
|
||||||
|
params => [ 'key', 'table' ] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear {
|
||||||
|
my ($self, $args) = @_;
|
||||||
|
return unless $self->{memcached};
|
||||||
|
|
||||||
|
# { key => $key }
|
||||||
|
if (exists $args->{key}) {
|
||||||
|
$self->_delete($args->{key});
|
||||||
|
}
|
||||||
|
|
||||||
|
# { table => $table, id => $id }
|
||||||
|
elsif (exists $args->{table} && exists $args->{id}) {
|
||||||
|
my ($table, $id) = @$args{qw(table id)};
|
||||||
|
my $name = $self->_get("$table.id_name.$id");
|
||||||
|
$self->_delete("$table.id.$id");
|
||||||
|
$self->_delete("$table.name_id.$name") if defined $name;
|
||||||
|
$self->_delete("$table.id_name.$id");
|
||||||
|
}
|
||||||
|
|
||||||
|
# { table => $table, name => $name }
|
||||||
|
elsif (exists $args->{table} && exists $args->{name}) {
|
||||||
|
my ($table, $name) = @$args{qw(table name)};
|
||||||
|
return unless my $id = $self->_get("$table.name_id.$name");
|
||||||
|
$self->_delete("$table.id.$id");
|
||||||
|
$self->_delete("$table.name_id.$name");
|
||||||
|
$self->_delete("$table.id_name.$id");
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
ThrowCodeError('params_required', { function => "Bugzilla::Memcached::clear",
|
||||||
|
params => [ 'key', 'table' ] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_all {
|
||||||
|
my ($self) = @_;
|
||||||
|
return unless my $memcached = $self->{memcached};
|
||||||
|
if (!$memcached->incr("prefix", 1)) {
|
||||||
|
$memcached->add("prefix", time());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# in order to clear all our keys, we add a prefix to all our keys. when we
|
||||||
|
# need to "clear" all current keys, we increment the prefix.
|
||||||
|
sub _prefix {
|
||||||
|
my ($self) = @_;
|
||||||
|
# we don't want to change prefixes in the middle of a request
|
||||||
|
my $request_cache = Bugzilla->request_cache;
|
||||||
|
if (!$request_cache->{memcached_prefix}) {
|
||||||
|
my $memcached = $self->{memcached};
|
||||||
|
my $prefix = $memcached->get("prefix");
|
||||||
|
if (!$prefix) {
|
||||||
|
$prefix = time();
|
||||||
|
if (!$memcached->add("prefix", $prefix)) {
|
||||||
|
# if this failed, either another process set the prefix, or
|
||||||
|
# memcached is down. assume we lost the race, and get the new
|
||||||
|
# value. if that fails, memcached is down so use a dummy
|
||||||
|
# prefix for this request.
|
||||||
|
$prefix = $memcached->get("prefix") || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$request_cache->{memcached_prefix} = $prefix;
|
||||||
|
}
|
||||||
|
return $request_cache->{memcached_prefix};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _set {
|
||||||
|
my ($self, $key, $value) = @_;
|
||||||
|
if (blessed($value)) {
|
||||||
|
# we don't support blessed objects
|
||||||
|
ThrowCodeError('param_invalid', { function => "Bugzilla::Memcached::set",
|
||||||
|
param => "value" });
|
||||||
|
}
|
||||||
|
return $self->{memcached}->set($self->_prefix . ':' . $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _get {
|
||||||
|
my ($self, $key) = @_;
|
||||||
|
|
||||||
|
my $value = $self->{memcached}->get($self->_prefix . ':' . $key);
|
||||||
|
return unless defined $value;
|
||||||
|
|
||||||
|
# detaint returned values
|
||||||
|
# hashes and arrays are detainted just one level deep
|
||||||
|
if (ref($value) eq 'HASH') {
|
||||||
|
map { defined($_) && trick_taint($_) } values %$value;
|
||||||
|
}
|
||||||
|
elsif (ref($value) eq 'ARRAY') {
|
||||||
|
trick_taint($_) foreach @$value;
|
||||||
|
}
|
||||||
|
elsif (!ref($value)) {
|
||||||
|
trick_taint($value);
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _delete {
|
||||||
|
my ($self, $key) = @_;
|
||||||
|
return $self->{memcached}->delete($self->_prefix . ':' . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
Bugzilla::Memcached - Interface between Bugzilla and Memcached.
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use Bugzilla;
|
||||||
|
|
||||||
|
my $memcached = Bugzilla->memcached;
|
||||||
|
|
||||||
|
# grab data from the cache. there is no need to check if memcached is
|
||||||
|
# available or enabled.
|
||||||
|
my $data = $memcached->get({ key => 'data_key' });
|
||||||
|
if (!defined $data) {
|
||||||
|
# not in cache, generate the data and populate the cache for next time
|
||||||
|
$data = some_long_process();
|
||||||
|
$memcached->set({ key => 'data_key', value => $data });
|
||||||
|
}
|
||||||
|
# do something with $data
|
||||||
|
|
||||||
|
# updating the profiles table directly shouldn't be attempted unless you know
|
||||||
|
# what you're doing. if you do update a table directly, you need to clear that
|
||||||
|
# object from memcached.
|
||||||
|
$dbh->do("UPDATE profiles SET request_count=10 WHERE login_name=?", undef, $login);
|
||||||
|
$memcached->clear({ table => 'profiles', name => $login });
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
If Memcached is installed and configured, Bugzilla can use it to cache data
|
||||||
|
across requests and between webheads. Unlike the request and process caches,
|
||||||
|
only scalars, hashrefs, and arrayrefs can be stored in Memcached.
|
||||||
|
|
||||||
|
Memcached integration is only required for large installations of Bugzilla --
|
||||||
|
if you have multiple webheads then configuring Memcache is recommended.
|
||||||
|
|
||||||
|
L<Bugzilla::Memcached> provides an interface to a Memcached server/servers, with
|
||||||
|
the ability to get, set, or clear entries from the cache.
|
||||||
|
|
||||||
|
The stored value must be an unblessed hashref, unblessed array ref, or a
|
||||||
|
scalar. Currently nested data structures are supported but require manual
|
||||||
|
de-tainting after reading from Memcached (flat data structures are automatically
|
||||||
|
de-tainted).
|
||||||
|
|
||||||
|
All values are stored in the Memcached systems using the prefix configured with
|
||||||
|
the C<memcached_namespace> parameter, as well as an additional prefix managed
|
||||||
|
by this class to allow all values to be cleared when C<checksetup.pl> is
|
||||||
|
executed.
|
||||||
|
|
||||||
|
Do not create an instance of this object directly, instead use
|
||||||
|
L<Bugzilla-E<gt>memcached()|Bugzilla/memcached>.
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
=head2 Setting
|
||||||
|
|
||||||
|
Adds a value to Memcached.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<set({ key =E<gt> $key, value =E<gt> $value })>
|
||||||
|
|
||||||
|
Adds the C<value> using the specific C<key>.
|
||||||
|
|
||||||
|
=item C<set({ table =E<gt> $table, id =E<gt> $id, name =E<gt> $name, data =E<gt> $data })>
|
||||||
|
|
||||||
|
Adds the C<data> using a keys generated from the C<table>, C<id>, and C<name>.
|
||||||
|
All three parameters must be provided, however C<name> can be provided but set
|
||||||
|
to C<undef>.
|
||||||
|
|
||||||
|
This is a convenience method which allows cached data to be later retrieved by
|
||||||
|
specifying the C<table> and either the C<id> or C<name>.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head2 Getting
|
||||||
|
|
||||||
|
Retrieves a value from Memcached. Returns C<undef> if no matching values were
|
||||||
|
found in the cache.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<get({ key =E<gt> $key })>
|
||||||
|
|
||||||
|
Return C<value> with the specified C<key>.
|
||||||
|
|
||||||
|
=item C<get({ table =E<gt> $table, id =E<gt> $id })>
|
||||||
|
|
||||||
|
Return C<value> with the specified C<table> and C<id>.
|
||||||
|
|
||||||
|
=item C<get({ table =E<gt> $table, name =E<gt> $name })>
|
||||||
|
|
||||||
|
Return C<value> with the specified C<table> and C<name>.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head2 Clearing
|
||||||
|
|
||||||
|
Removes the matching value from Memcached.
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item C<clear({ key =E<gt> $key })>
|
||||||
|
|
||||||
|
Removes C<value> with the specified C<key>.
|
||||||
|
|
||||||
|
=item C<clear({ table =E<gt> $table, id =E<gt> $id })>
|
||||||
|
|
||||||
|
Removes C<value> with the specified C<table> and C<id>, as well as the
|
||||||
|
corresponding C<table> and C<name> entry.
|
||||||
|
|
||||||
|
=item C<clear({ table =E<gt> $table, name =E<gt> $name })>
|
||||||
|
|
||||||
|
Removes C<value> with the specified C<table> and C<name>, as well as the
|
||||||
|
corresponding C<table> and C<id> entry.
|
||||||
|
|
||||||
|
=item C<clear_all>
|
||||||
|
|
||||||
|
Removes all values from the cache.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 Bugzilla::Object CACHE
|
||||||
|
|
||||||
|
The main driver for Memcached integration is to allow L<Bugzilla::Object> based
|
||||||
|
objects to be automatically cached in Memcache. This is enabled on a
|
||||||
|
per-package basis by setting the C<USE_MEMCACHED> constant to any true value.
|
||||||
|
|
||||||
|
The current implementation is an opt-in (USE_MEMCACHED is false by default),
|
||||||
|
however this will change to opt-out once further testing has been completed
|
||||||
|
(USE_MEMCACHED will be true by default).
|
||||||
|
|
||||||
|
=head1 DIRECT DATABASE UPDATES
|
||||||
|
|
||||||
|
If an object is cached and the database is updated directly (instead of via
|
||||||
|
C<$object-E<gt>update()>), then it's possible for the data in the cache to be
|
||||||
|
out of sync with the database.
|
||||||
|
|
||||||
|
As an example let's consider an extension which adds a timestamp field
|
||||||
|
C<last_activitiy_ts> to the profiles table and user object which contains the
|
||||||
|
user's last activity. If the extension were to call C<$user-E<gt>update()>,
|
||||||
|
then an audit entry would be created for each change to the C<last_activity_ts>
|
||||||
|
field, which is undesirable.
|
||||||
|
|
||||||
|
To remedy this, the extension updates the table directly. It's critical with
|
||||||
|
Memcached that it then clears the cache:
|
||||||
|
|
||||||
|
$dbh->do("UPDATE profiles SET last_activity_ts=? WHERE userid=?",
|
||||||
|
undef, $timestamp, $user_id);
|
||||||
|
Bugzilla->memcached->clear({ table => 'profiles', id => $user_id });
|
||||||
|
|
||||||
@ -34,6 +34,11 @@ use constant AUDIT_CREATES => 1;
|
|||||||
use constant AUDIT_UPDATES => 1;
|
use constant AUDIT_UPDATES => 1;
|
||||||
use constant AUDIT_REMOVES => 1;
|
use constant AUDIT_REMOVES => 1;
|
||||||
|
|
||||||
|
# When USE_MEMCACHED is true, the class is suitable for serialisation to
|
||||||
|
# Memcached. This will be flipped to true by default once the majority of
|
||||||
|
# Bugzilla Object have been tested with Memcached.
|
||||||
|
use constant USE_MEMCACHED => 0;
|
||||||
|
|
||||||
# This allows the JSON-RPC interface to return Bugzilla::Object instances
|
# This allows the JSON-RPC interface to return Bugzilla::Object instances
|
||||||
# as though they were hashes. In the future, this may be modified to return
|
# as though they were hashes. In the future, this may be modified to return
|
||||||
# less information.
|
# less information.
|
||||||
@ -48,11 +53,41 @@ sub new {
|
|||||||
my $class = ref($invocant) || $invocant;
|
my $class = ref($invocant) || $invocant;
|
||||||
my $param = shift;
|
my $param = shift;
|
||||||
|
|
||||||
my $object = $class->_cache_get($param);
|
my $object = $class->_object_cache_get($param);
|
||||||
return $object if $object;
|
return $object if $object;
|
||||||
|
|
||||||
$object = $class->new_from_hash($class->_load_from_db($param));
|
my ($data, $set_memcached);
|
||||||
$class->_cache_set($param, $object);
|
if (Bugzilla->feature('memcached')
|
||||||
|
&& $class->USE_MEMCACHED
|
||||||
|
&& ref($param) eq 'HASH' && $param->{cache})
|
||||||
|
{
|
||||||
|
if (defined $param->{id}) {
|
||||||
|
$data = Bugzilla->memcached->get({
|
||||||
|
table => $class->DB_TABLE,
|
||||||
|
id => $param->{id},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
elsif (defined $param->{name}) {
|
||||||
|
$data = Bugzilla->memcached->get({
|
||||||
|
table => $class->DB_TABLE,
|
||||||
|
name => $param->{name},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$set_memcached = $data ? 0 : 1;
|
||||||
|
}
|
||||||
|
$data ||= $class->_load_from_db($param);
|
||||||
|
|
||||||
|
if ($data && $set_memcached) {
|
||||||
|
Bugzilla->memcached->set({
|
||||||
|
table => $class->DB_TABLE,
|
||||||
|
id => $data->{$class->ID_FIELD},
|
||||||
|
name => $data->{$class->NAME_FIELD},
|
||||||
|
data => $data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$object = $class->new_from_hash($data);
|
||||||
|
$class->_object_cache_set($param, $object);
|
||||||
|
|
||||||
return $object;
|
return $object;
|
||||||
}
|
}
|
||||||
@ -157,32 +192,32 @@ sub initialize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Provides a mechanism for objects to be cached in the request_cache
|
# Provides a mechanism for objects to be cached in the request_cache
|
||||||
sub _cache_get {
|
sub _object_cache_get {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
my $cache_key = $class->cache_key($param)
|
my $cache_key = $class->object_cache_key($param)
|
||||||
|| return;
|
|| return;
|
||||||
return Bugzilla->request_cache->{$cache_key};
|
return Bugzilla->request_cache->{$cache_key};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _cache_set {
|
sub _object_cache_set {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($param, $object) = @_;
|
my ($param, $object) = @_;
|
||||||
my $cache_key = $class->cache_key($param)
|
my $cache_key = $class->object_cache_key($param)
|
||||||
|| return;
|
|| return;
|
||||||
Bugzilla->request_cache->{$cache_key} = $object;
|
Bugzilla->request_cache->{$cache_key} = $object;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _cache_remove {
|
sub _object_cache_remove {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
$param->{cache} = 1;
|
$param->{cache} = 1;
|
||||||
my $cache_key = $class->cache_key($param)
|
my $cache_key = $class->object_cache_key($param)
|
||||||
|| return;
|
|| return;
|
||||||
delete Bugzilla->request_cache->{$cache_key};
|
delete Bugzilla->request_cache->{$cache_key};
|
||||||
}
|
}
|
||||||
|
|
||||||
sub cache_key {
|
sub object_cache_key {
|
||||||
my $class = shift;
|
my $class = shift;
|
||||||
my ($param) = @_;
|
my ($param) = @_;
|
||||||
if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
|
if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
|
||||||
@ -461,8 +496,9 @@ sub update {
|
|||||||
$self->audit_log(\%changes) if $self->AUDIT_UPDATES;
|
$self->audit_log(\%changes) if $self->AUDIT_UPDATES;
|
||||||
|
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
$self->_cache_remove({ id => $self->id });
|
Bugzilla->memcached->clear({ table => $table, id => $self->id });
|
||||||
$self->_cache_remove({ name => $self->name }) if $self->name;
|
$self->_object_cache_remove({ id => $self->id });
|
||||||
|
$self->_object_cache_remove({ name => $self->name }) if $self->name;
|
||||||
|
|
||||||
if (wantarray) {
|
if (wantarray) {
|
||||||
return (\%changes, $old_self);
|
return (\%changes, $old_self);
|
||||||
@ -481,8 +517,9 @@ sub remove_from_db {
|
|||||||
$self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
|
$self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
|
||||||
$dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
|
$dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
|
||||||
$dbh->bz_commit_transaction();
|
$dbh->bz_commit_transaction();
|
||||||
$self->_cache_remove({ id => $self->id });
|
Bugzilla->memcached->clear({ table => $table, id => $self->id });
|
||||||
$self->_cache_remove({ name => $self->name }) if $self->name;
|
$self->_object_cache_remove({ id => $self->id });
|
||||||
|
$self->_object_cache_remove({ name => $self->name }) if $self->name;
|
||||||
undef $self;
|
undef $self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1399,7 +1436,7 @@ C<0> otherwise.
|
|||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
=item cache_key
|
=item object_cache_key
|
||||||
|
|
||||||
=item check_time
|
=item check_time
|
||||||
|
|
||||||
|
|||||||
@ -206,6 +206,9 @@ Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
|
|||||||
# Final checks
|
# Final checks
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
|
# Clear all keys from Memcached
|
||||||
|
Bugzilla->memcached->clear_all();
|
||||||
|
|
||||||
# Check if the default parameter for urlbase is still set, and if so, give
|
# Check if the default parameter for urlbase is still set, and if so, give
|
||||||
# notification that they should go and visit editparams.cgi
|
# notification that they should go and visit editparams.cgi
|
||||||
if (Bugzilla->params->{'urlbase'} eq '') {
|
if (Bugzilla->params->{'urlbase'} eq '') {
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
[%# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
#
|
||||||
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
# defined by the Mozilla Public License, v. 2.0.
|
||||||
|
#%]
|
||||||
|
[%
|
||||||
|
title = "Memcached"
|
||||||
|
desc = "Set up Memcached integration"
|
||||||
|
%]
|
||||||
|
|
||||||
|
[% param_descs = {
|
||||||
|
memcached_servers =>
|
||||||
|
"If this option is set, $terms.Bugzilla will integrate with Memcached. " _
|
||||||
|
"Specify one of more server, separated by spaces, using hostname:port " _
|
||||||
|
"notation (for example: 127.0.0.1:11211).",
|
||||||
|
|
||||||
|
memcached_namespace =>
|
||||||
|
"Specify a string to prefix to each key on Memcached.",
|
||||||
|
}
|
||||||
|
%]
|
||||||
@ -91,6 +91,7 @@ END
|
|||||||
feature_jsonrpc_faster => 'Make JSON-RPC Faster',
|
feature_jsonrpc_faster => 'Make JSON-RPC Faster',
|
||||||
feature_new_charts => 'New Charts',
|
feature_new_charts => 'New Charts',
|
||||||
feature_old_charts => 'Old Charts',
|
feature_old_charts => 'Old Charts',
|
||||||
|
feature_memcached => 'Memcached Support',
|
||||||
feature_mod_perl => 'mod_perl',
|
feature_mod_perl => 'mod_perl',
|
||||||
feature_moving => 'Move Bugs Between Installations',
|
feature_moving => 'Move Bugs Between Installations',
|
||||||
feature_patch_viewer => 'Patch Viewer',
|
feature_patch_viewer => 'Patch Viewer',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user