1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Cache; 10 11use strict; 12use warnings; 13 14our @ObjectDependencies = ( 15 'Kernel::Config', 16 'Kernel::System::Log', 17); 18 19=head1 NAME 20 21Kernel::System::Cache - Key/value based data cache for OTRS 22 23=head1 DESCRIPTION 24 25This is a simple data cache. It can store key/value data both 26in memory and in a configured cache backend for persistent caching. 27 28This can be controlled via the config settings C<Cache::InMemory> and 29C<Cache::InBackend>. The backend can also be selected with the config setting 30C<Cache::Module> and defaults to file system based storage for permanent caching. 31 32=head1 CACHING STRATEGY 33 34Caching works based on C<CacheType>s and C<CacheKey>s. 35 36=head2 CACHE TYPES 37 38For file based caching, 39a C<CacheType> groups all contained entries in a top level directory like 40C<var/tmp/CacheFileStorable/MyCacheType>. This means also that all entries of a specific 41C<CacheType> can be deleted with one function call, L</CleanUp()>. 42 43Typically, every backend module like L<Kernel::System::Valid> has its own CacheType that is stored in C<$Self> 44for consistent use. There could be exceptions when modules have much cached data that needs to be cleaned up 45together. In this case additional C<CacheType>s could be used, but this should be avoided. 46 47=head2 CACHE KEYS 48 49A C<CacheKey> is used to identify a single cached entry within a C<CacheType>. The specified cache key will be 50internally hashed to a file name that is used to fetch/store that particular cache entry, 51like C<var/tmp/CacheFileStorable/Valid/2/1/217727036cc9b1804f7c0f4f7777ef86>. 52 53It is important that all variables that lead to the output of different results of a function 54must be part of the C<CacheKey> if the entire function result is to be stored in a separate cache entry. 55For example, L<Kernel::System::State/StateGet()> allows fetching of C<State>s by C<Name> or by C<ID>. 56So there are different cache keys for both cases: 57 58 my $CacheKey; 59 if ( $Param{Name} ) { 60 $CacheKey = 'StateGet::Name::' . $Param{Name}; 61 } 62 else { 63 $CacheKey = 'StateGet::ID::' . $Param{ID}; 64 } 65 66Please avoid the creation of too many different cache keys, as this can be a burden for storage 67and performance of the system. Sometimes it can be helpful to implement a function like the one presented above 68in another way: C<StateGet()> could call the cached C<StateList()> internally and fetch the requested entry from 69its result. This depends on the amount of data, of course. 70 71=head2 CACHING A BACKEND MODULE 72 73=over 4 74 75=item Define a C<CacheType> and a C<CacheTTL>. 76 77Every module should have its own C<CacheType> which typically resembles the module name. 78The C<CacheTTL> defines how long a cache is valid. This depends on the data, but a value of 30 days is 79considered a good default choice. 80 81=item Add caching to methods fetching data. 82 83All functions that list and fetch entities can potentially get caches. 84 85=item Implement cache cleanup. 86 87All functions that add, modify or delete entries need to make sure that the cache stays consistent. 88All of these operations typically need to cleanup list method caches, while only modify and delete 89affect individual cache entries that need to be deleted. 90 91Whenever possible, avoid calling L</CleanUp()> for an entire cache type, but instead delete individual 92cache entries with L</Delete()> to keep as much cached data as possible. 93 94It is recommendable to implement a C<_CacheCleanup()> method in the module that centralizes cache cleanup. 95 96=item Extend module tests. 97 98Please also extend the module tests to work on non-cached and cached values 99(e. g. calling a method more than one time) to ensure consistency of both cached and non-cached data, 100and proper cleanup on deleting entities. 101 102=back 103 104=head1 PUBLIC INTERFACE 105 106=head2 new() 107 108Don't use the constructor directly, use the ObjectManager instead: 109 110 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 111 112=cut 113 114sub new { 115 my ( $Type, %Param ) = @_; 116 117 # allocate new hash for object 118 my $Self = {}; 119 bless( $Self, $Type ); 120 121 # 0=off; 1=set+get_cache; 2=+delete+get_request; 122 $Self->{Debug} = $Param{Debug} || 0; 123 124 # cache backend 125 my $CacheModule = $Kernel::OM->Get('Kernel::Config')->Get('Cache::Module') 126 || 'Kernel::System::Cache::FileStorable'; 127 128 # Store backend in $Self for fastest access. 129 $Self->{CacheObject} = $Kernel::OM->Get($CacheModule); 130 $Self->{CacheInMemory} = $Kernel::OM->Get('Kernel::Config')->Get('Cache::InMemory') // 1; 131 $Self->{CacheInBackend} = $Kernel::OM->Get('Kernel::Config')->Get('Cache::InBackend') // 1; 132 133 return $Self; 134} 135 136=head2 Configure() 137 138change cache configuration settings at runtime. You can use this to disable the cache in 139environments where it is not desired, such as in long running scripts. 140 141please, to turn CacheInMemory off in persistent environments. 142 143 $CacheObject->Configure( 144 CacheInMemory => 1, # optional 145 CacheInBackend => 1, # optional 146 ); 147 148=cut 149 150sub Configure { 151 my ( $Self, %Param ) = @_; 152 153 SETTING: 154 for my $Setting (qw(CacheInMemory CacheInBackend)) { 155 next SETTING if !exists $Param{$Setting}; 156 $Self->{$Setting} = $Param{$Setting} ? 1 : 0; 157 } 158 159 return; 160} 161 162=head2 Set() 163 164store a value in the cache. 165 166 $CacheObject->Set( 167 Type => 'ObjectName', # only [a-zA-Z0-9_] chars usable 168 Key => 'SomeKey', 169 Value => 'Some Value', 170 TTL => 60 * 60 * 24 * 20, # seconds, this means 20 days 171 ); 172 173The Type here refers to the group of entries that should be cached and cleaned up together, 174usually this will represent the OTRS object that is supposed to be cached, like 'Ticket'. 175 176The Key identifies the entry (together with the type) for retrieval and deletion of this value. 177 178The C<TTL> controls when the cache will expire. Please note that the in-memory cache is not persistent 179and thus has no C<TTL>/expiry mechanism. 180 181Please note that if you store complex data, you have to make sure that the data is not modified 182in other parts of the code as the in-memory cache only refers to it. Otherwise also the cache would 183contain the modifications. If you cannot avoid this, you can disable the in-memory cache for this 184value: 185 186 $CacheObject->Set( 187 Type => 'ObjectName', 188 Key => 'SomeKey', 189 Value => { ... complex data ... }, 190 191 TTL => 60 * 60 * 24 * 1, # optional, default 20 days 192 CacheInMemory => 0, # optional, defaults to 1 193 CacheInBackend => 1, # optional, defaults to 1 194 ); 195 196=cut 197 198sub Set { 199 my ( $Self, %Param ) = @_; 200 201 for my $Needed (qw(Type Key Value)) { 202 if ( !defined $Param{$Needed} ) { 203 $Kernel::OM->Get('Kernel::System::Log')->Log( 204 Priority => 'error', 205 Message => "Need $Needed!", 206 ); 207 return; 208 } 209 } 210 211 # set default TTL to 20 days 212 $Param{TTL} //= 60 * 60 * 24 * 20; 213 214 # Enforce cache type restriction to make sure it works properly on all file systems. 215 if ( $Param{Type} !~ m{ \A [a-zA-Z0-9_]+ \z}smx ) { 216 $Kernel::OM->Get('Kernel::System::Log')->Log( 217 Priority => 'error', 218 Message => 219 "Cache Type '$Param{Type}' contains invalid characters, use [a-zA-Z0-9_] only!", 220 ); 221 return; 222 } 223 224 # debug 225 if ( $Self->{Debug} > 0 ) { 226 $Kernel::OM->Get('Kernel::System::Log')->Log( 227 Priority => 'notice', 228 Message => "Set Key:$Param{Key} TTL:$Param{TTL}!", 229 ); 230 } 231 232 # Set in-memory cache. 233 if ( $Self->{CacheInMemory} && ( $Param{CacheInMemory} // 1 ) ) { 234 $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} } = $Param{Value}; 235 } 236 237 # If in-memory caching is not active, make sure the in-memory 238 # cache is not in an inconsistent state. 239 else { 240 delete $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} }; 241 } 242 243 # Set persistent cache. 244 if ( $Self->{CacheInBackend} && ( $Param{CacheInBackend} // 1 ) ) { 245 return $Self->{CacheObject}->Set(%Param); 246 } 247 248 # If persistent caching is not active, make sure the persistent 249 # cache is not in an inconsistent state. 250 else { 251 return $Self->{CacheObject}->Delete(%Param); 252 } 253 254 return 1; 255} 256 257=head2 Get() 258 259fetch a value from the cache. 260 261 my $Value = $CacheObject->Get( 262 Type => 'ObjectName', # only [a-zA-Z0-9_] chars usable 263 Key => 'SomeKey', 264 ); 265 266Please note that if you store complex data, you have to make sure that the data is not modified 267in other parts of the code as the in-memory cache only refers to it. Otherwise also the cache would 268contain the modifications. If you cannot avoid this, you can disable the in-memory cache for this 269value: 270 271 my $Value = $CacheObject->Get( 272 Type => 'ObjectName', 273 Key => 'SomeKey', 274 275 CacheInMemory => 0, # optional, defaults to 1 276 CacheInBackend => 1, # optional, defaults to 1 277 ); 278 279 280=cut 281 282sub Get { 283 my ( $Self, %Param ) = @_; 284 285 for my $Needed (qw(Type Key)) { 286 if ( !$Param{$Needed} ) { 287 $Kernel::OM->Get('Kernel::System::Log')->Log( 288 Priority => 'error', 289 Message => "Need $Needed!", 290 ); 291 return; 292 } 293 } 294 295 # check in-memory cache 296 if ( $Self->{CacheInMemory} && ( $Param{CacheInMemory} // 1 ) ) { 297 if ( exists $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} } ) { 298 return $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} }; 299 } 300 } 301 302 return if ( !$Self->{CacheInBackend} || !( $Param{CacheInBackend} // 1 ) ); 303 304 # check persistent cache 305 my $Value = $Self->{CacheObject}->Get(%Param); 306 307 # set in-memory cache 308 if ( defined $Value ) { 309 if ( $Self->{CacheInMemory} && ( $Param{CacheInMemory} // 1 ) ) { 310 $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} } = $Value; 311 } 312 } 313 314 return $Value; 315} 316 317=head2 Delete() 318 319deletes a single value from the cache. 320 321 $CacheObject->Delete( 322 Type => 'ObjectName', # only [a-zA-Z0-9_] chars usable 323 Key => 'SomeKey', 324 ); 325 326Please note that despite the cache configuration, Delete and CleanUp will always 327be executed both in memory and in the backend to avoid inconsistent cache states. 328 329=cut 330 331sub Delete { 332 my ( $Self, %Param ) = @_; 333 334 for my $Needed (qw(Type Key)) { 335 if ( !$Param{$Needed} ) { 336 $Kernel::OM->Get('Kernel::System::Log')->Log( 337 Priority => 'error', 338 Message => "Need $Needed!", 339 ); 340 return; 341 } 342 } 343 344 # Delete and cleanup operations should also be done if the cache is disabled 345 # to avoid inconsistent states. 346 347 # delete from in-memory cache 348 delete $Self->{Cache}->{ $Param{Type} }->{ $Param{Key} }; 349 350 # delete from persistent cache 351 return $Self->{CacheObject}->Delete(%Param); 352} 353 354=head2 CleanUp() 355 356delete parts of the cache or the full cache data. 357 358To delete the whole cache: 359 360 $CacheObject->CleanUp(); 361 362To delete the data of only one cache type: 363 364 $CacheObject->CleanUp( 365 Type => 'ObjectName', # only [a-zA-Z0-9_] chars usable 366 ); 367 368To delete all data except of some types: 369 370 $CacheObject->CleanUp( 371 KeepTypes => ['Object1', 'Object2'], 372 ); 373 374To delete only expired cache data: 375 376 $CacheObject->CleanUp( 377 Expired => 1, # optional, defaults to 0 378 ); 379 380Type/KeepTypes and Expired can be combined to only delete expired data of a single type 381or of all types except the types to keep. 382 383Please note that despite the cache configuration, Delete and CleanUp will always 384be executed both in memory and in the backend to avoid inconsistent cache states. 385 386=cut 387 388sub CleanUp { 389 my ( $Self, %Param ) = @_; 390 391 # cleanup in-memory cache 392 # We don't have TTL/expiry information here, so just always delete to be sure. 393 if ( $Param{Type} ) { 394 delete $Self->{Cache}->{ $Param{Type} }; 395 } 396 elsif ( $Param{KeepTypes} ) { 397 my %KeepTypeLookup; 398 @KeepTypeLookup{ @{ $Param{KeepTypes} } } = undef; 399 TYPE: 400 for my $Type ( sort keys %{ $Self->{Cache} || {} } ) { 401 next TYPE if exists $KeepTypeLookup{$Type}; 402 delete $Self->{Cache}->{$Type}; 403 } 404 } 405 else { 406 delete $Self->{Cache}; 407 } 408 409 # cleanup persistent cache 410 return $Self->{CacheObject}->CleanUp(%Param); 411} 412 4131; 414 415=head1 TERMS AND CONDITIONS 416 417This software is part of the OTRS project (L<https://otrs.org/>). 418 419This software comes with ABSOLUTELY NO WARRANTY. For details, see 420the enclosed file COPYING for license information (GPL). If you 421did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 422 423=cut 424