summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla.pm108
-rw-r--r--Bugzilla/Bug.pm4
-rw-r--r--Bugzilla/Config/Memcached.pm32
-rw-r--r--Bugzilla/DB.pm17
-rw-r--r--Bugzilla/Install/Requirements.pm9
-rw-r--r--Bugzilla/Memcached.pm346
-rw-r--r--Bugzilla/Object.pm67
-rwxr-xr-xchecksetup.pl3
-rw-r--r--template/en/default/admin/params/memcached.html.tmpl22
-rw-r--r--template/en/default/setup/strings.txt.pl1
10 files changed, 577 insertions, 32 deletions
diff --git a/Bugzilla.pm b/Bugzilla.pm
index bcb77218e..82a5e9490 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -35,24 +35,25 @@ BEGIN {
}
}
-use Bugzilla::Config;
-use Bugzilla::Constants;
use Bugzilla::Auth;
use Bugzilla::Auth::Persist::Cookie;
use Bugzilla::CGI;
-use Bugzilla::Extension;
+use Bugzilla::Config;
+use Bugzilla::Constants;
use Bugzilla::DB;
+use Bugzilla::Error;
+use Bugzilla::Extension;
+use Bugzilla::Field;
+use Bugzilla::Flag;
use Bugzilla::Hook;
use Bugzilla::Install::Localconfig qw(read_localconfig);
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES);
use Bugzilla::Install::Util qw(init_console include_languages);
+use Bugzilla::Memcached;
use Bugzilla::Template;
+use Bugzilla::Token;
use Bugzilla::User;
-use Bugzilla::Error;
use Bugzilla::Util;
-use Bugzilla::Field;
-use Bugzilla::Flag;
-use Bugzilla::Token;
use File::Basename;
use File::Spec::Functions;
@@ -659,6 +660,12 @@ sub 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
# Per-process cleanup. Note that this is a plain subroutine, not a method,
@@ -792,10 +799,10 @@ not an arrayref.
=item C<user>
-C<undef> if there is no currently logged in user or if the login code has not
-yet been run. If an sudo session is in progress, the C<Bugzilla::User>
-corresponding to the person who is being impersonated. If no session is in
-progress, the current C<Bugzilla::User>.
+Default C<Bugzilla::User> object if there is no currently logged in user or
+if the login code has not yet been run. If an sudo session is in progress,
+the C<Bugzilla::User> corresponding to the person who is being impersonated.
+If no session is in progress, the current C<Bugzilla::User>.
=item C<set_user>
@@ -968,3 +975,82 @@ Tells you whether or not a specific feature is enabled. For names
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>
+
+Removes all entries from the C<request_cache>.
+
+=back
+
+=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
+
+=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
+
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index e0b1b603f..1ba34f0a0 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -370,9 +370,9 @@ sub initialize {
$_[0]->_create_cf_accessors();
}
-sub cache_key {
+sub object_cache_key {
my $class = shift;
- my $key = $class->SUPER::cache_key(@_)
+ my $key = $class->SUPER::object_cache_key(@_)
|| return;
return $key . ',' . Bugzilla->user->id;
}
diff --git a/Bugzilla/Config/Memcached.pm b/Bugzilla/Config/Memcached.pm
new file mode 100644
index 000000000..08d8ce0e7
--- /dev/null
+++ b/Bugzilla/Config/Memcached.pm
@@ -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;
diff --git a/Bugzilla/DB.pm b/Bugzilla/DB.pm
index 4877471c7..61cd3eab8 100644
--- a/Bugzilla/DB.pm
+++ b/Bugzilla/DB.pm
@@ -1383,14 +1383,19 @@ sub _bz_real_schema {
my ($self) = @_;
return $self->{private_real_schema} if exists $self->{private_real_schema};
- my ($data, $version) = $self->selectrow_array(
- "SELECT SQL_CACHE schema_data, version FROM bz_schema");
+ my $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!")
- if !$data;
+ if !$bz_schema;
- $self->{private_real_schema} =
- $self->_bz_schema->deserialize_abstract($data, $version);
+ $self->{private_real_schema} =
+ $self->_bz_schema->deserialize_abstract($bz_schema->[0], $bz_schema->[1]);
return $self->{private_real_schema};
}
@@ -1432,6 +1437,8 @@ sub _bz_store_real_schema {
$sth->bind_param(1, $store_me, $self->BLOB_TYPE);
$sth->bind_param(2, $schema_version);
$sth->execute();
+
+ Bugzilla->memcached->clear({ key => 'bz_schema' });
}
# For bz_populate_enum_tables
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index d52b576df..cea5a4a34 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -372,6 +372,14 @@ sub OPTIONAL_MODULES {
version => '0.96',
feature => ['mod_perl'],
},
+
+ # memcached
+ {
+ package => 'Cache-Memcached',
+ module => 'Cache::Memcached',
+ version => '0',
+ feature => ['memcached'],
+ },
);
my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
@@ -394,6 +402,7 @@ use constant FEATURE_FILES => (
'Bugzilla/JobQueue/*', 'jobqueue.pl'],
patch_viewer => ['Bugzilla/Attachment/PatchReader.pm'],
updates => ['Bugzilla/Update.pm'],
+ memcached => ['Bugzilla/Memcache.pm'],
);
# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
diff --git a/Bugzilla/Memcached.pm b/Bugzilla/Memcached.pm
new file mode 100644
index 000000000..b1b10311b
--- /dev/null
+++ b/Bugzilla/Memcached.pm
@@ -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 });
+
diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm
index 4525fa78a..43d2c07ac 100644
--- a/Bugzilla/Object.pm
+++ b/Bugzilla/Object.pm
@@ -47,6 +47,11 @@ use constant AUDIT_CREATES => 1;
use constant AUDIT_UPDATES => 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
# as though they were hashes. In the future, this may be modified to return
# less information.
@@ -61,11 +66,41 @@ sub new {
my $class = ref($invocant) || $invocant;
my $param = shift;
- my $object = $class->_cache_get($param);
+ my $object = $class->_object_cache_get($param);
return $object if $object;
- $object = $class->new_from_hash($class->_load_from_db($param));
- $class->_cache_set($param, $object);
+ my ($data, $set_memcached);
+ 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;
}
@@ -172,32 +207,32 @@ sub initialize {
# Provides a mechanism for objects to be cached in the request_cahce
-sub _cache_get {
+sub _object_cache_get {
my $class = shift;
my ($param) = @_;
- my $cache_key = $class->cache_key($param)
+ my $cache_key = $class->object_cache_key($param)
|| return;
return Bugzilla->request_cache->{$cache_key};
}
-sub _cache_set {
+sub _object_cache_set {
my $class = shift;
my ($param, $object) = @_;
- my $cache_key = $class->cache_key($param)
+ my $cache_key = $class->object_cache_key($param)
|| return;
Bugzilla->request_cache->{$cache_key} = $object;
}
-sub _cache_remove {
+sub _object_cache_remove {
my $class = shift;
my ($param, $object) = @_;
$param->{cache} = 1;
- my $cache_key = $class->cache_key($param)
+ my $cache_key = $class->object_cache_key($param)
|| return;
delete Bugzilla->request_cache->{$cache_key};
}
-sub cache_key {
+sub object_cache_key {
my $class = shift;
my ($param) = @_;
if (ref($param) && $param->{cache} && ($param->{id} || $param->{name})) {
@@ -476,8 +511,10 @@ sub update {
$self->audit_log(\%changes) if $self->AUDIT_UPDATES;
$dbh->bz_commit_transaction();
- $self->_cache_remove({ id => $self->id });
- $self->_cache_remove({ name => $self->name }) if $self->name;
+ Bugzilla->memcached->clear({ table => $table, id => $self->id })
+ if $self->USE_MEMCACHED && @values;
+ $self->_object_cache_remove({ id => $self->id });
+ $self->_object_cache_remove({ name => $self->name }) if $self->name;
if (wantarray) {
return (\%changes, $old_self);
@@ -496,8 +533,10 @@ sub remove_from_db {
$self->audit_log(AUDIT_REMOVE) if $self->AUDIT_REMOVES;
$dbh->do("DELETE FROM $table WHERE $id_field = ?", undef, $self->id);
$dbh->bz_commit_transaction();
- $self->_cache_remove({ id => $self->id });
- $self->_cache_remove({ name => $self->name }) if $self->name;
+ Bugzilla->memcached->clear({ table => $table, id => $self->id })
+ if $self->USE_MEMCACHED;
+ $self->_object_cache_remove({ id => $self->id });
+ $self->_object_cache_remove({ name => $self->name }) if $self->name;
undef $self;
}
diff --git a/checksetup.pl b/checksetup.pl
index 6d9230dd9..4f37ea350 100755
--- a/checksetup.pl
+++ b/checksetup.pl
@@ -236,6 +236,9 @@ Bugzilla::Hook::process('install_before_final_checks', { silent => $silent });
# 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
# notification that they should go and visit editparams.cgi
if (Bugzilla->params->{'urlbase'} eq '') {
diff --git a/template/en/default/admin/params/memcached.html.tmpl b/template/en/default/admin/params/memcached.html.tmpl
new file mode 100644
index 000000000..eef39860a
--- /dev/null
+++ b/template/en/default/admin/params/memcached.html.tmpl
@@ -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.",
+ }
+%]
diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl
index 837c95d18..103a2a3f5 100644
--- a/template/en/default/setup/strings.txt.pl
+++ b/template/en/default/setup/strings.txt.pl
@@ -102,6 +102,7 @@ END
feature_jsonrpc_faster => 'Make JSON-RPC Faster',
feature_new_charts => 'New Charts',
feature_old_charts => 'Old Charts',
+ feature_memcached => 'Memcached Support',
feature_mod_perl => 'mod_perl',
feature_moving => 'Move Bugs Between Installations',
feature_patch_viewer => 'Patch Viewer',