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