Skip to content

Commit

Permalink
Memcached cache module added
Browse files Browse the repository at this point in the history
Author-Change-Id: IB#1046574
  • Loading branch information
pboguslawski committed May 2, 2023
1 parent 1b9b156 commit 6bca153
Show file tree
Hide file tree
Showing 3 changed files with 256 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 6.5.2 ????-??-??
- 2023-05-02 Added memcached cache module.

# 6.5.1 2023-03-09
- 2023-02-28 Added options tickets-created-before-date and tickets-created-before-days to console command Admin::Article::StorageSwitch.
- 2023-02-28 Fixed encoding of postmaster filter name in AdminPostMasterFilter.
Expand Down
19 changes: 19 additions & 0 deletions Kernel/Config/Files/XML/Framework.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8435,6 +8435,25 @@ via the Preferences button after logging in.
</Item>
</Value>
</Setting>
<Setting Name="Cache::Module::Memcached###Servers" Required="0" Valid="0">
<Description Translatable="1">Defines the memcached servers.</Description>
<Navigation>Core::Cache</Navigation>
<Value>
<Array>
<Item>127.0.0.1:11211</Item>
</Array>
</Value>
</Setting>
<Setting Name="Cache::Module::Memcached###Parameters" Required="0" Valid="0">
<Description Translatable="1">Defines all the (additional) memcached parameters.</Description>
<Navigation>Core::Cache</Navigation>
<Value>
<Hash>
<Item Key="max_size">16777216</Item>
<Item Key="utf8">1</Item>
</Hash>
</Value>
</Setting>
<Setting Name="AutoComplete::Agent###Default" Required="0" Valid="1">
<Description Translatable="1">Defines the config options for the autocompletion feature.</Description>
<Navigation>Frontend::Agent</Navigation>
Expand Down
234 changes: 234 additions & 0 deletions Kernel/System/Cache/Memcached.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# --
# Kernel/System/Cache/Memcached.pm - Memcached module for OTRS cache
# Copyright (C) 2014-2021 Informatyka Boguslawski sp. z o.o. sp.k., http://www.ib.pl/
# Based on:
# http://code.google.com/p/memcached/wiki/NewProgrammingTricks#Namespacing
# FileStorable.pm by OTRS AG, http://otrs.com/
# Memcached.pm by c.a.p.e. IT GmbH, http://www.cape-it.de
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --

package Kernel::System::Cache::Memcached;

use strict;
use warnings;

use Cache::Memcached::Fast;
use Digest::MD5 qw();
use Time::HiRes qw();

our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Encode',
'Kernel::System::Log',
);

sub new {
my ( $Type, %Param ) = @_;

# Allocate new hash for object.
my $Self = {};
bless( $Self, $Type );

# Get config object.
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');

# Get memcached connection parameters and open connection to cache.
$Self->{Config} = $ConfigObject->Get('Cache::Module::Memcached');
if ($Self->{Config} && $Self->{Config}->{Servers} && $Self->{Config}->{Parameters}) {
my $InitParams = {
servers => $Self->{Config}->{Servers},
%{ $Self->{Config}->{Parameters} },
};

$Self->{MemcachedObject} = Cache::Memcached::Fast->new($InitParams);

if (!$Self->{MemcachedObject}) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unable to initialize memcached connector: $!",
);
}
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Memcached enabled but no valid Cache::Module::Memcached configuration found!',
);
}

return $Self;
}

sub Set {
my ( $Self, %Param ) = @_;

# Check needed params.
for my $Needed (qw(Type Key Value TTL)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}

return if !$Self->{MemcachedObject};

# Get memcached key name for given Type and Key.
my $MemcachedKeyName = $Self->_getMemcachedKeyName(
%Param,
InitNamespaceOnError => 1,
);
return if !$MemcachedKeyName;

# Problems occured when using absolute TTLs /time()+$Param{TTL}/ and
# relative TTLs greater than memcached threshold /$Param{TTL}>2592000/
# so we'll limit TTLs to 2592000 (30 days) here. Probably bug in
# Cache::Memcached::Fast. For TTL value details see memcached proto spec
# https://github.com/memcached/memcached/blob/master/doc/protocol.txt
if ( $Param{TTL} > 2592000 ) {
$Param{TTL} = 2592000;
}

return $Self->{MemcachedObject}->set(
$MemcachedKeyName,
$Param{Value},
$Param{TTL}
);
}

sub Get {
my ( $Self, %Param ) = @_;

# Check needed params.
for my $Needed (qw(Type Key)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}

return if !$Self->{MemcachedObject};

# Get memcached key name for given Type and Key; do not initialize
# namespace in case of namespace reading error (namespace init after
# simple communication errors would cause same effect as CleanUp
# on this object data type).
my $MemcachedKeyName = $Self->_getMemcachedKeyName(
%Param,
InitNamespaceOnError => 0,
);
return if !$MemcachedKeyName;

return $Self->{MemcachedObject}->get($MemcachedKeyName);
}

sub Delete {
my ( $Self, %Param ) = @_;

# Check needed params.
for my $Needed (qw(Type Key)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}

return if ( !$Self->{MemcachedObject} );

# Get memcached key name for given Type and Key.
my $MemcachedKeyName = $Self->_getMemcachedKeyName(
%Param,
InitNamespaceOnError => 1,
);
return if !$MemcachedKeyName;

return $Self->{MemcachedObject}->delete($MemcachedKeyName);
}

sub CleanUp {
my ( $Self, %Param ) = @_;

return if !$Self->{MemcachedObject};

# Memcached expires data automatically.
return 1 if $Param{Expired};

if ( $Param{Type} ) {
# Invalidate namespace in cache by incrementing it; memcached will
# take care of removing invalidated keys (LRU). In case of incrementing
# error try to create new namespace.
if (!$Self->{MemcachedObject}->incr('Namespace:' . $Param{Type}, 1)) {
my $Miliseconds = int(Time::HiRes::gettimeofday() * 1000);
if (!$Self->{MemcachedObject}->add('Namespace:' . $Param{Type}, $Miliseconds)) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Error deleting objects of type $Param{Type} in memcached!",
);
}
}

return 1;
}
else {
# Flush all the cache if no type specified.
return $Self->{MemcachedObject}->flush_all();
}
}

=item _getMemcachedKeyName()
Use MD5 digest of Type::Namespace::Key for memcached key (memcached key max
length is 250). Namespace for given cache object type is taken from cache.
New namespace is created if namespace is not found. For this algo idea see
http://code.google.com/p/memcached/wiki/NewProgrammingTricks#Namespacing
We use miliseconds not microseconds because overflow in the "incr" memcached
command wraps around the 64 bit mark.
Returns mamcached key name if determined or nothing on error:
my $PreparedKey = $Self->_getMemcachedKeyName(
Type => 'CacheObjectTypeName',
Key => 'KeyName',
InitNamespaceOnError => 0, # set 1 to initialize namespace if cannot be read
);
=cut

sub _getMemcachedKeyName {
my ( $Self, %Param ) = @_;

# Try to find namespace for given key object type.
my $MemcachedNamespace = $Self->{MemcachedObject}->get('Namespace:' . $Param{Type});

# If namespace not found - if allowed, create new one using miliseconds since the epoch.
if (!$MemcachedNamespace && $Param{InitNamespaceOnError}) {
my $Miliseconds = int(Time::HiRes::gettimeofday() * 1000);

if ($Self->{MemcachedObject}->add('Namespace:' . $Param{Type}, $Miliseconds)) {
# Get namespace from cache in case it was updated meanwhile.
$MemcachedNamespace = $Self->{MemcachedObject}->get('Namespace:' . $Param{Type});
}
}

# Return nothing if namespace cannot be found.
return if !$MemcachedNamespace;

my $MemcachedKeyName = $Param{Type} . ':' . $MemcachedNamespace . ':' . $Param{Key};
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$MemcachedKeyName );
$MemcachedKeyName = Digest::MD5::md5_hex($MemcachedKeyName);
return $MemcachedKeyName;
}

1;

0 comments on commit 6bca153

Please sign in to comment.