line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Redis::CappedCollection; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Redis::CappedCollection - Provides fixed size (determined by 'maxmemory' |
6
|
|
|
|
|
|
|
Redis server setting) collections with FIFO data removal. |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
This documentation refers to C version 1.10 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=cut |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
#-- Pragmas -------------------------------------------------------------------- |
15
|
|
|
|
|
|
|
|
16
|
104
|
|
|
104
|
|
9345692
|
use 5.010; |
|
104
|
|
|
|
|
276
|
|
17
|
104
|
|
|
104
|
|
539
|
use strict; |
|
104
|
|
|
|
|
123
|
|
|
104
|
|
|
|
|
1747
|
|
18
|
104
|
|
|
104
|
|
355
|
use warnings; |
|
104
|
|
|
|
|
149
|
|
|
104
|
|
|
|
|
2318
|
|
19
|
104
|
|
|
104
|
|
17143
|
use bytes; |
|
104
|
|
|
|
|
348
|
|
|
104
|
|
|
|
|
419
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# ENVIRONMENT ------------------------------------------------------------------ |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $VERSION = '1.10'; |
24
|
|
|
|
|
|
|
|
25
|
104
|
|
|
|
|
4907
|
use Exporter qw( |
26
|
|
|
|
|
|
|
import |
27
|
104
|
|
|
104
|
|
3551
|
); |
|
104
|
|
|
|
|
137
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
30
|
|
|
|
|
|
|
$DATA_VERSION |
31
|
|
|
|
|
|
|
$DEFAULT_CONNECTION_TIMEOUT |
32
|
|
|
|
|
|
|
$DEFAULT_OPERATION_TIMEOUT |
33
|
|
|
|
|
|
|
$DEFAULT_SERVER |
34
|
|
|
|
|
|
|
$DEFAULT_PORT |
35
|
|
|
|
|
|
|
$NAMESPACE |
36
|
|
|
|
|
|
|
$MIN_MEMORY_RESERVE |
37
|
|
|
|
|
|
|
$MAX_MEMORY_RESERVE |
38
|
|
|
|
|
|
|
$DEFAULT_CLEANUP_ITEMS |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$E_NO_ERROR |
41
|
|
|
|
|
|
|
$E_MISMATCH_ARG |
42
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE |
43
|
|
|
|
|
|
|
$E_NETWORK |
44
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT |
45
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY |
46
|
|
|
|
|
|
|
$E_COLLECTION_DELETED |
47
|
|
|
|
|
|
|
$E_REDIS |
48
|
|
|
|
|
|
|
$E_DATA_ID_EXISTS |
49
|
|
|
|
|
|
|
$E_OLDER_THAN_ALLOWED |
50
|
|
|
|
|
|
|
$E_NONEXISTENT_DATA_ID |
51
|
|
|
|
|
|
|
$E_INCOMP_DATA_VERSION |
52
|
|
|
|
|
|
|
$E_REDIS_DID_NOT_RETURN_DATA |
53
|
|
|
|
|
|
|
$E_UNKNOWN_ERROR |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
#-- load the modules ----------------------------------------------------------- |
57
|
|
|
|
|
|
|
|
58
|
104
|
|
|
104
|
|
332
|
use Carp; |
|
104
|
|
|
|
|
121
|
|
|
104
|
|
|
|
|
4181
|
|
59
|
104
|
|
|
104
|
|
35765
|
use Const::Fast; |
|
104
|
|
|
|
|
175028
|
|
|
104
|
|
|
|
|
482
|
|
60
|
104
|
|
|
|
|
4760
|
use Digest::SHA1 qw( |
61
|
|
|
|
|
|
|
sha1_hex |
62
|
104
|
|
|
104
|
|
44497
|
); |
|
104
|
|
|
|
|
47413
|
|
63
|
104
|
|
|
|
|
7180
|
use List::Util qw( |
64
|
|
|
|
|
|
|
min |
65
|
104
|
|
|
104
|
|
550
|
); |
|
104
|
|
|
|
|
125
|
|
66
|
104
|
|
|
104
|
|
1965
|
use Mouse; |
|
104
|
|
|
|
|
40180
|
|
|
104
|
|
|
|
|
738
|
|
67
|
104
|
|
|
104
|
|
35488
|
use Mouse::Util::TypeConstraints; |
|
104
|
|
|
|
|
134
|
|
|
104
|
|
|
|
|
604
|
|
68
|
104
|
|
|
|
|
6511
|
use Params::Util qw( |
69
|
|
|
|
|
|
|
_ARRAY |
70
|
|
|
|
|
|
|
_ARRAY0 |
71
|
|
|
|
|
|
|
_HASH0 |
72
|
|
|
|
|
|
|
_CLASSISA |
73
|
|
|
|
|
|
|
_INSTANCE |
74
|
|
|
|
|
|
|
_NONNEGINT |
75
|
|
|
|
|
|
|
_NUMBER |
76
|
|
|
|
|
|
|
_STRING |
77
|
104
|
|
|
104
|
|
31693
|
); |
|
104
|
|
|
|
|
96403
|
|
78
|
104
|
|
|
104
|
|
54884
|
use Redis '1.976'; |
|
104
|
|
|
|
|
1698504
|
|
|
104
|
|
|
|
|
3184
|
|
79
|
104
|
|
|
|
|
5208
|
use Redis::CappedCollection::Util qw( |
80
|
|
|
|
|
|
|
format_message |
81
|
104
|
|
|
104
|
|
32475
|
); |
|
104
|
|
|
|
|
174
|
|
82
|
104
|
|
|
104
|
|
706
|
use Time::HiRes (); |
|
104
|
|
|
|
|
129
|
|
|
104
|
|
|
|
|
1281
|
|
83
|
104
|
|
|
104
|
|
299
|
use Try::Tiny; |
|
104
|
|
|
|
|
93
|
|
|
104
|
|
|
|
|
929455
|
|
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
class_type 'Redis'; |
86
|
|
|
|
|
|
|
class_type 'Test::RedisServer'; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
#-- declarations --------------------------------------------------------------- |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=head1 SYNOPSIS |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
use 5.010; |
93
|
|
|
|
|
|
|
use strict; |
94
|
|
|
|
|
|
|
use warnings; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
#-- Common |
97
|
|
|
|
|
|
|
use Redis::CappedCollection qw( |
98
|
|
|
|
|
|
|
$DEFAULT_SERVER |
99
|
|
|
|
|
|
|
$DEFAULT_PORT |
100
|
|
|
|
|
|
|
); |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
my $server = $DEFAULT_SERVER.':'.$DEFAULT_PORT; |
103
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( redis => { server => $server } ); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Insert new data into collection |
106
|
|
|
|
|
|
|
my $list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' ); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# Change the element of the list with the ID $list_id |
109
|
|
|
|
|
|
|
$updated = $coll->update( $list_id, $data_id, 'New data' ); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# Get data from a list with the ID $list_id |
112
|
|
|
|
|
|
|
@data = $coll->receive( $list_id ); |
113
|
|
|
|
|
|
|
# or to obtain the data ordered from the oldest to the newest |
114
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
115
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
A brief example of the C usage is provided in |
119
|
|
|
|
|
|
|
L"An Example"> section. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
The data structures used by C on Redis server |
122
|
|
|
|
|
|
|
are explained in L"CappedCollection data structure"> section. |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head1 ABSTRACT |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Redis::CappedCollection module provides fixed sized collections that have |
127
|
|
|
|
|
|
|
a auto-FIFO age-out feature. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
The collection consists of multiple lists containing data items ordered |
130
|
|
|
|
|
|
|
by time. Each list must have an unique ID within the collection and each |
131
|
|
|
|
|
|
|
data item has unique ID within its list. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Automatic data removal (when size limit is reached) may remove the oldest |
134
|
|
|
|
|
|
|
item from any list. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
Collection size is determined by 'maxmemory' Redis server setting. |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=head1 DESCRIPTION |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
Main features of the package are: |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=over 3 |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=item * |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
Support creation of capped collection, status monitoring, |
147
|
|
|
|
|
|
|
updating the data set, obtaining consistent data from the collection, |
148
|
|
|
|
|
|
|
automatic data removal, error reporting. |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=item * |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Simple API for inserting and retrieving data and for managing collection. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=back |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Capped collections are fixed-size collections that have an auto-FIFO |
157
|
|
|
|
|
|
|
age-out feature based on the time of the inserted data. When collection |
158
|
|
|
|
|
|
|
size reaches memory limit, the oldest data elements are removed automatically |
159
|
|
|
|
|
|
|
to provide space for the new elements. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
The lists in capped collection store their data items ordered by item time. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
To insert a new data item into the capped collection, provide list ID, data ID, |
164
|
|
|
|
|
|
|
data and optional data time (current time is used if not specified). |
165
|
|
|
|
|
|
|
If there is a list with the given ID, the data is inserted into the existing list, |
166
|
|
|
|
|
|
|
otherwise the new list is created automatically. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
You may update the existing data in the collection, providing list ID, data ID and |
169
|
|
|
|
|
|
|
optional data time. If no time is specified, the updated data will keep |
170
|
|
|
|
|
|
|
its existing time. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Once the space is fully utilized, newly added data will replace |
173
|
|
|
|
|
|
|
the oldest data in the collection. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Limits are specified when the collection is created. |
176
|
|
|
|
|
|
|
Collection size is determined by 'maxmemory' redis server settings. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
The package includes the utilities to dump and restore the collection: |
179
|
|
|
|
|
|
|
F, F . |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=head2 EXPORT |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
None by default. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Additional constants are available for import, which can be used |
186
|
|
|
|
|
|
|
to define some type of parameters. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
These are the defaults: |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head3 $DEFAULT_SERVER |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
Default Redis local server: C<'localhost'>. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=cut |
195
|
|
|
|
|
|
|
const our $DEFAULT_SERVER => 'localhost'; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head3 $DEFAULT_PORT |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
Default Redis server port: 6379. |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=cut |
202
|
|
|
|
|
|
|
const our $DEFAULT_PORT => 6379; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head3 $DEFAULT_CONNECTION_TIMEOUT |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Default socket timeout for connection, number of seconds: 0.1 . |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=cut |
209
|
|
|
|
|
|
|
const our $DEFAULT_CONNECTION_TIMEOUT => 0.1; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=head3 $DEFAULT_OPERATION_TIMEOUT |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
Default socket timeout for read and write operations, number of seconds: 1. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=cut |
216
|
|
|
|
|
|
|
const our $DEFAULT_OPERATION_TIMEOUT => 1; |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head3 $NAMESPACE |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Namespace name used keys on the Redis server: C<'C'>. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
const our $NAMESPACE => 'C'; |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=head3 $MIN_MEMORY_RESERVE, $MAX_MEMORY_RESERVE |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Minimum and maximum memory reserve limits based on 'maxmemory' |
228
|
|
|
|
|
|
|
configuration of the Redis server. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Not used when C<'maxmemory'> = 0 (not set in the F). |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
The following values are used by default: |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
$MIN_MEMORY_RESERVE = 0.05; # 5% |
235
|
|
|
|
|
|
|
$MAX_MEMORY_RESERVE = 0.5; # 50% |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=cut |
238
|
|
|
|
|
|
|
const our $MIN_MEMORY_RESERVE => 0.05; # 5% memory reserve coefficient |
239
|
|
|
|
|
|
|
const our $MAX_MEMORY_RESERVE => 0.5; # 50% memory reserve coefficient |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head3 $DEFAULT_CLEANUP_ITEMS |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
Number of additional elements to delete from collection during cleanup procedure when collection size |
244
|
|
|
|
|
|
|
exceeds 'maxmemory'. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
Default 100 elements. 0 means no minimal cleanup required, |
247
|
|
|
|
|
|
|
so memory cleanup will be performed only to free up sufficient amount of memory. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
const our $DEFAULT_CLEANUP_ITEMS => 100; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head3 $DATA_VERSION |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Current data structure version - 3. |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=cut |
257
|
|
|
|
|
|
|
const our $DATA_VERSION => 3; # incremented for each incompatible data structure change |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=over |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=item Error codes |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
More details about error codes are provided in L section. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=back |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
Possible error codes: |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=cut |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=over 3 |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=item C<$E_NO_ERROR> |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
-1000 - No error |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=cut |
278
|
|
|
|
|
|
|
const our $E_NO_ERROR => -1000; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=item C<$E_MISMATCH_ARG> |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
-1001 - Invalid argument. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Thrown by methods when there is a missing required argument or argument value is invalid. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=cut |
287
|
|
|
|
|
|
|
const our $E_MISMATCH_ARG => -1001; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item C<$E_DATA_TOO_LARGE> |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
-1002 - Data is too large. |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=cut |
294
|
|
|
|
|
|
|
const our $E_DATA_TOO_LARGE => -1002; |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=item C<$E_NETWORK> |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
-1003 - Error in connection to Redis server. |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=cut |
301
|
|
|
|
|
|
|
const our $E_NETWORK => -1003; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=item C<$E_MAXMEMORY_LIMIT> |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
-1004 - Command not allowed when used memory > 'maxmemory'. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
This means that the command is not allowed when used memory > C |
308
|
|
|
|
|
|
|
in the F file. |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=cut |
311
|
|
|
|
|
|
|
const our $E_MAXMEMORY_LIMIT => -1004; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item C<$E_MAXMEMORY_POLICY> |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
-1005 - Redis server have incompatible C setting. |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
Thrown when Redis server have incompatible C setting in F. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
=cut |
320
|
|
|
|
|
|
|
const our $E_MAXMEMORY_POLICY => -1005; |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=item C<$E_COLLECTION_DELETED> |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
-1006 - Collection elements was removed prior to use. |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
This means that the system part of the collection was removed prior to use. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=cut |
329
|
|
|
|
|
|
|
const our $E_COLLECTION_DELETED => -1006; |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=item C<$E_REDIS> |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
-1007 - Redis error message. |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
This means that other Redis error message detected. |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=cut |
338
|
|
|
|
|
|
|
const our $E_REDIS => -1007; |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=item C<$E_DATA_ID_EXISTS> |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
-1008 - Attempt to add data with an existing ID |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
This means that you are trying to insert data with an ID that is already in |
345
|
|
|
|
|
|
|
the data list. |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=cut |
348
|
|
|
|
|
|
|
const our $E_DATA_ID_EXISTS => -1008; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=item C<$E_OLDER_THAN_ALLOWED> |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
-1009 - Attempt to add outdated data |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
This means that you are trying to insert the data with the time older than |
355
|
|
|
|
|
|
|
the time of the oldest element currently stored in collection. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=cut |
358
|
|
|
|
|
|
|
const our $E_OLDER_THAN_ALLOWED => -1009; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
=item C<$E_NONEXISTENT_DATA_ID> |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
-1010 - Attempt to access the elements missing in the collection. |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
This means that you are trying to update data which does not exist. |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=cut |
367
|
|
|
|
|
|
|
const our $E_NONEXISTENT_DATA_ID => -1010; |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=item C<$E_INCOMP_DATA_VERSION> |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
-1011 - Attempt to access the collection with incompatible data structure, created |
372
|
|
|
|
|
|
|
by an older or newer version of this module. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=cut |
375
|
|
|
|
|
|
|
const our $E_INCOMP_DATA_VERSION => -1011; |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=item C<$E_REDIS_DID_NOT_RETURN_DATA> |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
-1012 - The Redis server did not return data. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
Check the settings in the file F. |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=cut |
384
|
|
|
|
|
|
|
const our $E_REDIS_DID_NOT_RETURN_DATA => -1012; |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=item C<$E_UNKNOWN_ERROR> |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
-1013 - Unknown error. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
Possibly you should modify the constructor parameters for more intense automatic memory release. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=back |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=cut |
395
|
|
|
|
|
|
|
const our $E_UNKNOWN_ERROR => -1013; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
our $DEBUG = 0; # > 0 for 'used_memory > maxmemory' waiting |
398
|
|
|
|
|
|
|
# 1 - use 'confess' instead 'croak' for errors |
399
|
|
|
|
|
|
|
# 2 - lua-script durations logging, use 'croak' for errors |
400
|
|
|
|
|
|
|
# 3 - lua-script durations logging, use 'confess' for errors |
401
|
|
|
|
|
|
|
our $WAIT_USED_MEMORY = 0; # No attempt to protect against used_memory > maxmemory |
402
|
|
|
|
|
|
|
#our $WAIT_USED_MEMORY = 1; |
403
|
|
|
|
|
|
|
our $_MAX_WORKING_CYCLES = 10_000_000; # for _long_term_operation method |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
our %ERROR = ( |
406
|
|
|
|
|
|
|
$E_NO_ERROR => 'No error', |
407
|
|
|
|
|
|
|
$E_MISMATCH_ARG => 'Invalid argument', |
408
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE => 'Data is too large', |
409
|
|
|
|
|
|
|
$E_NETWORK => 'Error in connection to Redis server', |
410
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT => "Command not allowed when used memory > 'maxmemory'", |
411
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY => "Redis server have incompatible 'maxmemory-policy' setting. Use 'noeviction' only.", |
412
|
|
|
|
|
|
|
$E_COLLECTION_DELETED => 'Collection elements was removed prior to use', |
413
|
|
|
|
|
|
|
$E_REDIS => 'Redis error message', |
414
|
|
|
|
|
|
|
$E_DATA_ID_EXISTS => 'Attempt to add data to an existing ID', |
415
|
|
|
|
|
|
|
$E_OLDER_THAN_ALLOWED => 'Attempt to add data over outdated', |
416
|
|
|
|
|
|
|
$E_NONEXISTENT_DATA_ID => 'Non-existent data id', |
417
|
|
|
|
|
|
|
$E_INCOMP_DATA_VERSION => 'Incompatible data version', |
418
|
|
|
|
|
|
|
$E_REDIS_DID_NOT_RETURN_DATA => 'The Redis server did not return data', |
419
|
|
|
|
|
|
|
$E_UNKNOWN_ERROR => 'Unknown error', |
420
|
|
|
|
|
|
|
); |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
const our $REDIS_ERROR_CODE => 'ERR'; |
423
|
|
|
|
|
|
|
const our $REDIS_MEMORY_ERROR_CODE => 'OOM'; |
424
|
|
|
|
|
|
|
const our $REDIS_MEMORY_ERROR_MSG => "$REDIS_MEMORY_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }."; |
425
|
|
|
|
|
|
|
const our $MAX_DATASIZE => 512*1024*1024; # A String value can be at max 512 Megabytes in length. |
426
|
|
|
|
|
|
|
const my $MAX_REMOVE_RETRIES => 2; # the number of remove retries when memory limit is near |
427
|
|
|
|
|
|
|
const my $USED_MEMORY_POLICY => 'noeviction'; |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
const my $SCRIPT_DEBUG_LEVEL => 2; |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# status field names |
432
|
|
|
|
|
|
|
const my $_LISTS => 'lists'; |
433
|
|
|
|
|
|
|
const my $_ITEMS => 'items'; |
434
|
|
|
|
|
|
|
const my $_OLDER_ALLOWED => 'older_allowed'; |
435
|
|
|
|
|
|
|
const my $_CLEANUP_BYTES => 'cleanup_bytes'; |
436
|
|
|
|
|
|
|
const my $_CLEANUP_ITEMS => 'cleanup_items'; |
437
|
|
|
|
|
|
|
const my $_MAX_LIST_ITEMS => 'max_list_items'; |
438
|
|
|
|
|
|
|
const my $_MEMORY_RESERVE => 'memory_reserve'; |
439
|
|
|
|
|
|
|
const my $_DATA_VERSION => 'data_version'; |
440
|
|
|
|
|
|
|
const my $_LAST_REMOVED_TIME => 'last_removed_time'; |
441
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_BYTES => 'last_cleanup_bytes'; |
442
|
|
|
|
|
|
|
# information fields |
443
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_ITEMS => 'last_cleanup_items'; |
444
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_MAXMEMORY => 'last_cleanup_maxmemory'; |
445
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_USED_MEMORY => 'last_cleanup_used_memory'; |
446
|
|
|
|
|
|
|
const my $_LAST_CLEANUP_BYTES_MUST_BE_DELETED => 'last_bytes_must_be_deleted'; |
447
|
|
|
|
|
|
|
const my $_INSERTS_SINCE_CLEANING => 'inserts_since_cleaning'; |
448
|
|
|
|
|
|
|
const my $_UPDATES_SINCE_CLEANING => 'updates_since_cleaning'; |
449
|
|
|
|
|
|
|
const my $_MAX_LAST_CLEANUP_VALUES => 10; |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
my $_lua_namespace = "local NAMESPACE = '".$NAMESPACE."'"; |
452
|
|
|
|
|
|
|
my $_lua_queue_key = "local QUEUE_KEY = NAMESPACE..':Q:'..coll_name"; |
453
|
|
|
|
|
|
|
my $_lua_status_key = "local STATUS_KEY = NAMESPACE..':S:'..coll_name"; |
454
|
|
|
|
|
|
|
my $_lua_data_keys = "local DATA_KEYS = NAMESPACE..':D:'..coll_name"; |
455
|
|
|
|
|
|
|
my $_lua_time_keys = "local TIME_KEYS = NAMESPACE..':T:'..coll_name"; |
456
|
|
|
|
|
|
|
my $_lua_data_key = "local DATA_KEY = DATA_KEYS..':'..list_id"; |
457
|
|
|
|
|
|
|
my $_lua_time_key = "local TIME_KEY = TIME_KEYS..':'..list_id"; |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
my %lua_script_body; |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
my $_lua_first_step = <<"END_FIRST_STEP"; |
462
|
|
|
|
|
|
|
collectgarbage( 'stop' ) |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
__START_STEP__ |
465
|
|
|
|
|
|
|
END_FIRST_STEP |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
#--- log_work_function --------------------------------------------------------- |
468
|
|
|
|
|
|
|
my $_lua_log_work_function = <<"END_LOG_WORK_FUNCTION"; |
469
|
|
|
|
|
|
|
local _SCRIPT_NAME |
470
|
|
|
|
|
|
|
local _DEBUG_LEVEL = tonumber( ARGV[1] ) |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
local _log_work = function ( log_str, script_name ) |
473
|
|
|
|
|
|
|
if script_name ~= nil then |
474
|
|
|
|
|
|
|
_SCRIPT_NAME = script_name |
475
|
|
|
|
|
|
|
end |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
if _DEBUG_LEVEL >= $SCRIPT_DEBUG_LEVEL then |
478
|
|
|
|
|
|
|
if script_name ~= nil then |
479
|
|
|
|
|
|
|
local args_str = cjson.encode( ARGV ) |
480
|
|
|
|
|
|
|
redis.log( redis.LOG_NOTICE, 'lua-script '..log_str..': '..script_name..' ('..args_str..')' ) |
481
|
|
|
|
|
|
|
else |
482
|
|
|
|
|
|
|
redis.log( redis.LOG_NOTICE, 'lua-script '..log_str..': '.._SCRIPT_NAME ) |
483
|
|
|
|
|
|
|
end |
484
|
|
|
|
|
|
|
end |
485
|
|
|
|
|
|
|
end |
486
|
|
|
|
|
|
|
END_LOG_WORK_FUNCTION |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
#--- reduce_list_items --------------------------------------------------------- |
489
|
|
|
|
|
|
|
my $_lua_reduce_list_items = <<"END_REDUCE_LIST_ITEMS"; |
490
|
|
|
|
|
|
|
local reduce_list_items = function ( list_id ) |
491
|
|
|
|
|
|
|
local max_list_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_MAX_LIST_ITEMS' ) ) |
492
|
|
|
|
|
|
|
if max_list_items ~= nil and max_list_items > 0 and redis.call( 'EXISTS', DATA_KEY ) == 1 then |
493
|
|
|
|
|
|
|
local list_items = redis.call( 'HLEN', DATA_KEY ) |
494
|
|
|
|
|
|
|
local older_allowed = tonumber( redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ) |
495
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) ) |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
while list_items >= max_list_items do |
498
|
|
|
|
|
|
|
local removed_data_time, removed_data_id |
499
|
|
|
|
|
|
|
list_items = list_items - 1 |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
if list_items == 0 then |
502
|
|
|
|
|
|
|
removed_data_time = tonumber( redis.call( 'ZSCORE', QUEUE_KEY, list_id ) ) |
503
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, list_id ) |
504
|
|
|
|
|
|
|
redis.call( 'DEL', DATA_KEY ) |
505
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 ) |
506
|
|
|
|
|
|
|
else |
507
|
|
|
|
|
|
|
removed_data_id, removed_data_time = unpack( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' ) ) |
508
|
|
|
|
|
|
|
removed_data_id = tonumber( removed_data_id ) |
509
|
|
|
|
|
|
|
removed_data_time = tonumber( removed_data_time ) |
510
|
|
|
|
|
|
|
redis.call( 'ZREM', TIME_KEY, removed_data_id ) |
511
|
|
|
|
|
|
|
redis.call( 'HDEL', DATA_KEY, removed_data_id ) |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
local lowest_data_id, lowest_data_time = unpack( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' ) ) |
514
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, lowest_data_time, lowest_data_id ) |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
if redis.call( 'HLEN', DATA_KEY ) == 1 then |
517
|
|
|
|
|
|
|
redis.call( 'DEL', TIME_KEY ) |
518
|
|
|
|
|
|
|
end |
519
|
|
|
|
|
|
|
end |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -1 ) |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
if older_allowed == 0 and ( last_removed_time == 0 or removed_data_time < last_removed_time ) then |
524
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', removed_data_time ) |
525
|
|
|
|
|
|
|
last_removed_time = removed_data_time |
526
|
|
|
|
|
|
|
end |
527
|
|
|
|
|
|
|
end |
528
|
|
|
|
|
|
|
end |
529
|
|
|
|
|
|
|
end |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
reduce_list_items( list_id ) |
532
|
|
|
|
|
|
|
END_REDUCE_LIST_ITEMS |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
#--- clean_data ---------------------------------------------------------------- |
535
|
|
|
|
|
|
|
my $_lua_clean_data = <<"END_CLEAN_DATA"; |
536
|
|
|
|
|
|
|
-- remove the control structures of the collection |
537
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then |
538
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', QUEUE_KEY ) |
539
|
|
|
|
|
|
|
end |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
-- each element of the list are deleted separately, as total number of items may be too large to send commands 'DEL' |
542
|
|
|
|
|
|
|
$_lua_data_keys |
543
|
|
|
|
|
|
|
$_lua_time_keys |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
local arr = redis.call( 'KEYS', DATA_KEYS..':*' ) |
546
|
|
|
|
|
|
|
if #arr > 0 then |
547
|
|
|
|
|
|
|
|
548
|
|
|
|
|
|
|
-- remove structures store data lists |
549
|
|
|
|
|
|
|
for i = 1, #arr do |
550
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', arr[i] ) |
551
|
|
|
|
|
|
|
end |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
-- remove structures store time lists |
554
|
|
|
|
|
|
|
arr = redis.call( 'KEYS', TIME_KEYS..':*' ) |
555
|
|
|
|
|
|
|
for i = 1, #arr do |
556
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', arr[i] ) |
557
|
|
|
|
|
|
|
end |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
end |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
__FINISH_STEP__ |
562
|
|
|
|
|
|
|
return ret |
563
|
|
|
|
|
|
|
END_CLEAN_DATA |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
#--- cleaning ------------------------------------------------------------------ |
566
|
|
|
|
|
|
|
my $_lua_cleaning = <<"END_CLEANING"; |
567
|
|
|
|
|
|
|
local REDIS_USED_MEMORY = 0 |
568
|
|
|
|
|
|
|
local REDIS_MAXMEMORY = 0 |
569
|
|
|
|
|
|
|
local ROLLBACK = {} |
570
|
|
|
|
|
|
|
local TOTAL_BYTES_DELETED = 0 |
571
|
|
|
|
|
|
|
local LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0 |
572
|
|
|
|
|
|
|
local LAST_CLEANUP_ITEMS = 0 |
573
|
|
|
|
|
|
|
local LAST_OPERATION = '' |
574
|
|
|
|
|
|
|
local INSERTS_SINCE_CLEANING = 0 |
575
|
|
|
|
|
|
|
local UPDATES_SINCE_CLEANING = 0 |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
local _DEBUG, _DEBUG_ID, _FUNC_NAME |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
local table_merge = function ( t1, t2 ) |
580
|
|
|
|
|
|
|
for key, val in pairs( t2 ) do |
581
|
|
|
|
|
|
|
t1[ key ] = val |
582
|
|
|
|
|
|
|
end |
583
|
|
|
|
|
|
|
end |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
local _debug_log = function ( values ) |
586
|
|
|
|
|
|
|
table_merge( values, { |
587
|
|
|
|
|
|
|
_DEBUG_ID = _DEBUG_ID, |
588
|
|
|
|
|
|
|
_FUNC_NAME = _FUNC_NAME, |
589
|
|
|
|
|
|
|
REDIS_USED_MEMORY = REDIS_USED_MEMORY, |
590
|
|
|
|
|
|
|
list_id = list_id, |
591
|
|
|
|
|
|
|
data_id = data_id, |
592
|
|
|
|
|
|
|
data_len = #data, |
593
|
|
|
|
|
|
|
ROLLBACK = ROLLBACK |
594
|
|
|
|
|
|
|
} ) |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
redis.log( redis.LOG_NOTICE, _FUNC_NAME..': '..cjson.encode( values ) ) |
597
|
|
|
|
|
|
|
end |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
local _setup = function ( argv_idx, func_name, extra_data_len ) |
600
|
|
|
|
|
|
|
LAST_OPERATION = func_name |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
REDIS_MAXMEMORY = tonumber( redis.call( 'CONFIG', 'GET', 'maxmemory' )[2] ) |
603
|
|
|
|
|
|
|
local memory_reserve_coefficient = 1 + tonumber( redis.call( 'HGET', STATUS_KEY, '$_MEMORY_RESERVE' ) ) |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
local redis_used_memory = string.match( |
606
|
|
|
|
|
|
|
redis.call( 'INFO', 'memory' ), |
607
|
|
|
|
|
|
|
'used_memory:(%d+)' |
608
|
|
|
|
|
|
|
) |
609
|
|
|
|
|
|
|
REDIS_USED_MEMORY = tonumber( redis_used_memory ) |
610
|
|
|
|
|
|
|
LAST_CLEANUP_BYTES_MUST_BE_DELETED = REDIS_USED_MEMORY + extra_data_len - math.floor( REDIS_MAXMEMORY / memory_reserve_coefficient ) |
611
|
|
|
|
|
|
|
if LAST_CLEANUP_BYTES_MUST_BE_DELETED < 0 or REDIS_MAXMEMORY == 0 then |
612
|
|
|
|
|
|
|
LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0 |
613
|
|
|
|
|
|
|
end |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING' ) ) |
616
|
|
|
|
|
|
|
if INSERTS_SINCE_CLEANING == nil then |
617
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = 0 |
618
|
|
|
|
|
|
|
end |
619
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING' ) ) |
620
|
|
|
|
|
|
|
if UPDATES_SINCE_CLEANING == nil then |
621
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = 0 |
622
|
|
|
|
|
|
|
end |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
_FUNC_NAME = func_name |
625
|
|
|
|
|
|
|
_DEBUG_ID = tonumber( ARGV[ argv_idx ] ) |
626
|
|
|
|
|
|
|
if _DEBUG_ID ~= 0 then |
627
|
|
|
|
|
|
|
_DEBUG = true |
628
|
|
|
|
|
|
|
_debug_log( { |
629
|
|
|
|
|
|
|
_STEP = '_setup', |
630
|
|
|
|
|
|
|
maxmemory = REDIS_MAXMEMORY, |
631
|
|
|
|
|
|
|
redis_used_memory = REDIS_USED_MEMORY, |
632
|
|
|
|
|
|
|
bytes_must_be_deleted = LAST_CLEANUP_BYTES_MUST_BE_DELETED |
633
|
|
|
|
|
|
|
} ) |
634
|
|
|
|
|
|
|
else |
635
|
|
|
|
|
|
|
_DEBUG = false |
636
|
|
|
|
|
|
|
end |
637
|
|
|
|
|
|
|
end |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
local renew_last_cleanup_values = function () |
640
|
|
|
|
|
|
|
if LAST_CLEANUP_ITEMS > 0 then |
641
|
|
|
|
|
|
|
local last_cleanup_bytes_values = cjson.decode( redis.call( 'HGET', STATUS_KEY, '$_LAST_CLEANUP_BYTES' ) ) |
642
|
|
|
|
|
|
|
local last_cleanup_items_values = cjson.decode( redis.call( 'HGET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS' ) ) |
643
|
|
|
|
|
|
|
if #last_cleanup_bytes_values >= 10 then |
644
|
|
|
|
|
|
|
table.remove ( last_cleanup_bytes_values, 1 ) |
645
|
|
|
|
|
|
|
table.remove ( last_cleanup_items_values, 1 ) |
646
|
|
|
|
|
|
|
end |
647
|
|
|
|
|
|
|
table.insert( last_cleanup_bytes_values, TOTAL_BYTES_DELETED ) |
648
|
|
|
|
|
|
|
table.insert( last_cleanup_items_values, LAST_CLEANUP_ITEMS ) |
649
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', cjson.encode( last_cleanup_bytes_values ) ) |
650
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS', cjson.encode( last_cleanup_items_values ) ) |
651
|
|
|
|
|
|
|
end |
652
|
|
|
|
|
|
|
end |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
local cleaning_error = function ( error_msg ) |
655
|
|
|
|
|
|
|
if _DEBUG then |
656
|
|
|
|
|
|
|
_debug_log( { |
657
|
|
|
|
|
|
|
_STEP = 'cleaning_error', |
658
|
|
|
|
|
|
|
error_msg = error_msg |
659
|
|
|
|
|
|
|
} ) |
660
|
|
|
|
|
|
|
end |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
for _, rollback_command in ipairs( ROLLBACK ) do |
663
|
|
|
|
|
|
|
redis.call( unpack( rollback_command ) ) |
664
|
|
|
|
|
|
|
end |
665
|
|
|
|
|
|
|
-- Level 2 points the error to where the function that called error was called |
666
|
|
|
|
|
|
|
renew_last_cleanup_values() |
667
|
|
|
|
|
|
|
__FINISH_STEP__ |
668
|
|
|
|
|
|
|
error( error_msg, 2 ) |
669
|
|
|
|
|
|
|
end |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
-- deleting old data to make room for new data |
672
|
|
|
|
|
|
|
local cleaning = function ( list_id, data_id, is_cleaning_needed ) |
673
|
|
|
|
|
|
|
local coll_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_ITEMS' ) ) |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
if coll_items == 0 then |
676
|
|
|
|
|
|
|
return |
677
|
|
|
|
|
|
|
end |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
local cleanup_bytes = tonumber( redis.call( 'HGET', STATUS_KEY, '$_CLEANUP_BYTES' ) ) |
680
|
|
|
|
|
|
|
local cleanup_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_CLEANUP_ITEMS' ) ) |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
if not ( is_cleaning_needed or LAST_CLEANUP_BYTES_MUST_BE_DELETED > 0 ) then |
683
|
|
|
|
|
|
|
return |
684
|
|
|
|
|
|
|
end |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
if _DEBUG then |
687
|
|
|
|
|
|
|
_debug_log( { |
688
|
|
|
|
|
|
|
_STEP = 'Before cleanings', |
689
|
|
|
|
|
|
|
coll_items = coll_items, |
690
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
691
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
692
|
|
|
|
|
|
|
} ) |
693
|
|
|
|
|
|
|
end |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
local items_deleted = 0 |
696
|
|
|
|
|
|
|
local bytes_deleted = 0 |
697
|
|
|
|
|
|
|
local lists_deleted = 0 |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
repeat |
700
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 0 then |
701
|
|
|
|
|
|
|
-- Level 2 points the error to where the function that called error was called |
702
|
|
|
|
|
|
|
renew_last_cleanup_values() |
703
|
|
|
|
|
|
|
__FINISH_STEP__ |
704
|
|
|
|
|
|
|
error( 'Queue key does not exist', 2 ) |
705
|
|
|
|
|
|
|
end |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
-- continue to work with the to_delete (requiring removal) data and for them using the prefix 'to_delete_' |
708
|
|
|
|
|
|
|
local to_delete_list_id, last_removed_time = unpack( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' ) ) |
709
|
|
|
|
|
|
|
last_removed_time = tonumber( last_removed_time ) |
710
|
|
|
|
|
|
|
-- key data structures |
711
|
|
|
|
|
|
|
local to_delete_data_key = DATA_KEYS..':'..to_delete_list_id |
712
|
|
|
|
|
|
|
local to_delete_time_key = TIME_KEYS..':'..to_delete_list_id |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
-- looking for the oldest data |
715
|
|
|
|
|
|
|
local to_delete_data_id |
716
|
|
|
|
|
|
|
local to_delete_data |
717
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', to_delete_data_key ) |
718
|
|
|
|
|
|
|
-- #FIXME: to_delete_data -> to_delete_data_len |
719
|
|
|
|
|
|
|
-- HSTRLEN key field |
720
|
|
|
|
|
|
|
-- Available since 3.2.0. |
721
|
|
|
|
|
|
|
if items == 1 then |
722
|
|
|
|
|
|
|
to_delete_data_id, to_delete_data = unpack( redis.call( 'HGETALL', to_delete_data_key ) ) |
723
|
|
|
|
|
|
|
else |
724
|
|
|
|
|
|
|
to_delete_data_id = unpack( redis.call( 'ZRANGE', to_delete_time_key, 0, 0 ) ) |
725
|
|
|
|
|
|
|
to_delete_data = redis.call( 'HGET', to_delete_data_key, to_delete_data_id ) |
726
|
|
|
|
|
|
|
end |
727
|
|
|
|
|
|
|
local to_delete_data_len = #to_delete_data |
728
|
|
|
|
|
|
|
to_delete_data = nil -- free memory |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
if _DEBUG then |
731
|
|
|
|
|
|
|
_debug_log( { |
732
|
|
|
|
|
|
|
_STEP = 'Before real cleaning', |
733
|
|
|
|
|
|
|
items = items, |
734
|
|
|
|
|
|
|
to_delete_list_id = to_delete_list_id, |
735
|
|
|
|
|
|
|
to_delete_data_id = to_delete_data_id, |
736
|
|
|
|
|
|
|
to_delete_data_len = to_delete_data_len |
737
|
|
|
|
|
|
|
} ) |
738
|
|
|
|
|
|
|
end |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
if to_delete_list_id == list_id and to_delete_data_id == data_id then |
741
|
|
|
|
|
|
|
if items_deleted == 0 then |
742
|
|
|
|
|
|
|
-- Its first attempt to clean the conflicting data, for which the primary operation executed. |
743
|
|
|
|
|
|
|
-- In this case, we are roll back operations that have been made before, and immediately return an error, |
744
|
|
|
|
|
|
|
-- shifting the handling of such errors on the client. |
745
|
|
|
|
|
|
|
cleaning_error( "$REDIS_MEMORY_ERROR_MSG" ) |
746
|
|
|
|
|
|
|
end |
747
|
|
|
|
|
|
|
break |
748
|
|
|
|
|
|
|
end |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
if _DEBUG then |
751
|
|
|
|
|
|
|
_debug_log( { |
752
|
|
|
|
|
|
|
_STEP = 'Why it is cleared?', |
753
|
|
|
|
|
|
|
coll_items = coll_items, |
754
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
755
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
756
|
|
|
|
|
|
|
items_deleted = items_deleted, |
757
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
758
|
|
|
|
|
|
|
} ) |
759
|
|
|
|
|
|
|
end |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
-- actually remove the oldest item |
762
|
|
|
|
|
|
|
redis.call( 'HDEL', to_delete_data_key, to_delete_data_id ) |
763
|
|
|
|
|
|
|
items = items - 1 |
764
|
|
|
|
|
|
|
coll_items = coll_items - 1 |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time ) |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
if items > 0 then |
769
|
|
|
|
|
|
|
-- If the list has more data |
770
|
|
|
|
|
|
|
redis.call( 'ZREM', to_delete_time_key, to_delete_data_id ) |
771
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', to_delete_time_key, 0, 0, 'WITHSCORES' )[2] ) |
772
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, to_delete_list_id ) |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
if items == 1 then |
775
|
|
|
|
|
|
|
redis.call( 'DEL', to_delete_time_key ) |
776
|
|
|
|
|
|
|
end |
777
|
|
|
|
|
|
|
else |
778
|
|
|
|
|
|
|
-- If the list does not have data |
779
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
780
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, to_delete_list_id ) |
781
|
|
|
|
|
|
|
lists_deleted = lists_deleted + 1 |
782
|
|
|
|
|
|
|
end |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
-- amount of data collection decreased |
785
|
|
|
|
|
|
|
items_deleted = items_deleted + 1 |
786
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted + to_delete_data_len |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
if _DEBUG then |
789
|
|
|
|
|
|
|
_debug_log( { |
790
|
|
|
|
|
|
|
_STEP = 'After real cleaning', |
791
|
|
|
|
|
|
|
to_delete_data_key = to_delete_data_key, |
792
|
|
|
|
|
|
|
to_delete_data_id = to_delete_data_id, |
793
|
|
|
|
|
|
|
items = items, |
794
|
|
|
|
|
|
|
items_deleted = items_deleted, |
795
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
796
|
|
|
|
|
|
|
} ) |
797
|
|
|
|
|
|
|
end |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
until |
800
|
|
|
|
|
|
|
coll_items <= 0 |
801
|
|
|
|
|
|
|
or ( |
802
|
|
|
|
|
|
|
items_deleted >= cleanup_items |
803
|
|
|
|
|
|
|
and bytes_deleted >= cleanup_bytes |
804
|
|
|
|
|
|
|
) |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
if items_deleted > 0 then |
807
|
|
|
|
|
|
|
-- reduce the number of items in the collection |
808
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -items_deleted ) |
809
|
|
|
|
|
|
|
end |
810
|
|
|
|
|
|
|
if lists_deleted > 0 then |
811
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
812
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -lists_deleted ) |
813
|
|
|
|
|
|
|
end |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
if _DEBUG then |
816
|
|
|
|
|
|
|
_debug_log( { |
817
|
|
|
|
|
|
|
_STEP = 'Cleaning finished', |
818
|
|
|
|
|
|
|
items_deleted = items_deleted, |
819
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted, |
820
|
|
|
|
|
|
|
lists_deleted = lists_deleted, |
821
|
|
|
|
|
|
|
cleanup_bytes = cleanup_bytes, |
822
|
|
|
|
|
|
|
cleanup_items = cleanup_items, |
823
|
|
|
|
|
|
|
coll_items = coll_items, |
824
|
|
|
|
|
|
|
} ) |
825
|
|
|
|
|
|
|
end |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
if bytes_deleted > 0 then |
828
|
|
|
|
|
|
|
if TOTAL_BYTES_DELETED == 0 then -- first cleaning |
829
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = 0 |
830
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING ) |
831
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = 0 |
832
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING ) |
833
|
|
|
|
|
|
|
end |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
TOTAL_BYTES_DELETED = TOTAL_BYTES_DELETED + bytes_deleted |
836
|
|
|
|
|
|
|
LAST_CLEANUP_ITEMS = LAST_CLEANUP_ITEMS + items_deleted |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
-- information values |
839
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_MAXMEMORY', REDIS_MAXMEMORY ) |
840
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_USED_MEMORY', REDIS_USED_MEMORY ) |
841
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES_MUST_BE_DELETED', LAST_CLEANUP_BYTES_MUST_BE_DELETED ) |
842
|
|
|
|
|
|
|
end |
843
|
|
|
|
|
|
|
end |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
local call_with_error_control = function ( list_id, data_id, ... ) |
846
|
|
|
|
|
|
|
local retries = $MAX_REMOVE_RETRIES |
847
|
|
|
|
|
|
|
local ret |
848
|
|
|
|
|
|
|
local error_msg = '' |
849
|
|
|
|
|
|
|
repeat |
850
|
|
|
|
|
|
|
ret = redis.pcall( ... ) |
851
|
|
|
|
|
|
|
if type( ret ) == 'table' and ret.err ~= nil then |
852
|
|
|
|
|
|
|
error_msg = "$REDIS_MEMORY_ERROR_MSG - " .. ret.err .. " (call = " .. cjson.encode( { ... } ) .. ")" |
853
|
|
|
|
|
|
|
if _DEBUG then |
854
|
|
|
|
|
|
|
_debug_log( { |
855
|
|
|
|
|
|
|
_STEP = 'call_with_error_control', |
856
|
|
|
|
|
|
|
error_msg = error_msg, |
857
|
|
|
|
|
|
|
retries = retries |
858
|
|
|
|
|
|
|
} ) |
859
|
|
|
|
|
|
|
end |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
cleaning( list_id, data_id, true ) |
862
|
|
|
|
|
|
|
else |
863
|
|
|
|
|
|
|
break |
864
|
|
|
|
|
|
|
end |
865
|
|
|
|
|
|
|
retries = retries - 1 |
866
|
|
|
|
|
|
|
until retries == 0 |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
if retries == 0 then |
869
|
|
|
|
|
|
|
-- Operation returned an error related to insufficient memory. |
870
|
|
|
|
|
|
|
-- Start cleaning process and then re-try operation. |
871
|
|
|
|
|
|
|
-- Repeat the cycle of operation + memory cleaning a couple of times and return an error / fail, |
872
|
|
|
|
|
|
|
-- if it still did not work. |
873
|
|
|
|
|
|
|
cleaning_error( error_msg ) |
874
|
|
|
|
|
|
|
end |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
return ret |
877
|
|
|
|
|
|
|
end |
878
|
|
|
|
|
|
|
END_CLEANING |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
#--- pre_upsert ---------------------------------------------------------------- |
881
|
|
|
|
|
|
|
my $_lua_pre_upsert = <<"END_PRE_UPSERT"; |
882
|
|
|
|
|
|
|
$_lua_first_step |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
885
|
|
|
|
|
|
|
local list_id = ARGV[3] |
886
|
|
|
|
|
|
|
local data_id = ARGV[4] |
887
|
|
|
|
|
|
|
local data = ARGV[5] |
888
|
|
|
|
|
|
|
local data_time = tonumber( ARGV[6] ) |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
-- key data storage structures |
891
|
|
|
|
|
|
|
$_lua_namespace |
892
|
|
|
|
|
|
|
$_lua_queue_key |
893
|
|
|
|
|
|
|
$_lua_status_key |
894
|
|
|
|
|
|
|
$_lua_data_keys |
895
|
|
|
|
|
|
|
$_lua_time_keys |
896
|
|
|
|
|
|
|
$_lua_data_key |
897
|
|
|
|
|
|
|
$_lua_time_key |
898
|
|
|
|
|
|
|
$_lua_cleaning |
899
|
|
|
|
|
|
|
END_PRE_UPSERT |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
my $_lua_insert_body = <<"END_INSERT_BODY"; |
902
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
903
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
904
|
|
|
|
|
|
|
__FINISH_STEP__ |
905
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0, 0, 0 } |
906
|
|
|
|
|
|
|
end |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
$_lua_reduce_list_items |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
-- verification of the existence of old data with new data identifier |
911
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
912
|
|
|
|
|
|
|
__FINISH_STEP__ |
913
|
|
|
|
|
|
|
return { $E_DATA_ID_EXISTS, 0, 0, 0 } |
914
|
|
|
|
|
|
|
end |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
-- Validating the time of new data, if required |
917
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) ) |
918
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
if tonumber( redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ) == 0 then |
920
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then |
921
|
|
|
|
|
|
|
if data_time < last_removed_time then |
922
|
|
|
|
|
|
|
__FINISH_STEP__ |
923
|
|
|
|
|
|
|
return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 } |
924
|
|
|
|
|
|
|
end |
925
|
|
|
|
|
|
|
end |
926
|
|
|
|
|
|
|
end |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
-- deleting obsolete data, if it is necessary |
929
|
|
|
|
|
|
|
local data_len = #data |
930
|
|
|
|
|
|
|
_setup( 7, 'insert', data_len ) -- 7 -> is the index of ARGV[7] |
931
|
|
|
|
|
|
|
cleaning( list_id, data_id, false ) |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
-- add data to the list |
934
|
|
|
|
|
|
|
-- Remember that the list and the collection can be automatically deleted after the "crowding out" old data |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
-- the existing data |
937
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
938
|
|
|
|
|
|
|
local existing_id, existing_time |
939
|
|
|
|
|
|
|
if items == 1 then |
940
|
|
|
|
|
|
|
existing_id = unpack( redis.call( 'HGETALL', DATA_KEY ) ) |
941
|
|
|
|
|
|
|
existing_time = tonumber( redis.call( 'ZSCORE', QUEUE_KEY, list_id ) ) |
942
|
|
|
|
|
|
|
end |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
-- actually add data to the list |
945
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data ) |
946
|
|
|
|
|
|
|
data = nil -- free memory |
947
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'HDEL', DATA_KEY, data_id } ) |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
if redis.call( 'HLEN', DATA_KEY ) == 1 then -- list recreated after cleaning |
950
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', 1 ) |
951
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'HINCRBY', STATUS_KEY, '$_LISTS', -1 } ) |
952
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', QUEUE_KEY, data_time, list_id ) |
953
|
|
|
|
|
|
|
else |
954
|
|
|
|
|
|
|
if items == 1 then |
955
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, existing_time, existing_id ) |
956
|
|
|
|
|
|
|
table.insert( ROLLBACK, 1, { 'ZREM', TIME_KEY, existing_id } ) |
957
|
|
|
|
|
|
|
end |
958
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, data_time, data_id ) |
959
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] |
960
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
961
|
|
|
|
|
|
|
end |
962
|
|
|
|
|
|
|
|
963
|
|
|
|
|
|
|
-- reflect the addition of new data |
964
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', 1 ) |
965
|
|
|
|
|
|
|
if data_time < last_removed_time then |
966
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 ) |
967
|
|
|
|
|
|
|
end |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
renew_last_cleanup_values() |
970
|
|
|
|
|
|
|
-- redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', LAST_CLEANUP_BYTES ) |
971
|
|
|
|
|
|
|
INSERTS_SINCE_CLEANING = INSERTS_SINCE_CLEANING + 1 |
972
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING ) |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
__FINISH_STEP__ |
975
|
|
|
|
|
|
|
return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED } |
976
|
|
|
|
|
|
|
END_INSERT_BODY |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
#--- insert -------------------------------------------------------------------- |
979
|
|
|
|
|
|
|
$lua_script_body{insert} = <<"END_INSERT"; |
980
|
|
|
|
|
|
|
-- adding data to a list of collections |
981
|
|
|
|
|
|
|
$_lua_pre_upsert |
982
|
|
|
|
|
|
|
$_lua_insert_body |
983
|
|
|
|
|
|
|
END_INSERT |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
#--- update_body --------------------------------------------------------------- |
986
|
|
|
|
|
|
|
my $_lua_update_body = <<"END_UPDATE_BODY"; |
987
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
988
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
989
|
|
|
|
|
|
|
__FINISH_STEP__ |
990
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0, 0, 0 } |
991
|
|
|
|
|
|
|
end |
992
|
|
|
|
|
|
|
|
993
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
994
|
|
|
|
|
|
|
__FINISH_STEP__ |
995
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
996
|
|
|
|
|
|
|
end |
997
|
|
|
|
|
|
|
local extra_data_len |
998
|
|
|
|
|
|
|
local data_len = #data |
999
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
1000
|
|
|
|
|
|
|
-- #FIXME: existed_data -> existed_data_len |
1001
|
|
|
|
|
|
|
-- HSTRLEN key field |
1002
|
|
|
|
|
|
|
-- Available since 3.2.0. |
1003
|
|
|
|
|
|
|
local existed_data = redis.call( 'HGET', DATA_KEY, data_id ) |
1004
|
|
|
|
|
|
|
extra_data_len = data_len - #existed_data |
1005
|
|
|
|
|
|
|
existed_data = nil -- free memory |
1006
|
|
|
|
|
|
|
else |
1007
|
|
|
|
|
|
|
__FINISH_STEP__ |
1008
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
1009
|
|
|
|
|
|
|
end |
1010
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) ) |
1012
|
|
|
|
|
|
|
if tonumber( redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ) == 0 then |
1013
|
|
|
|
|
|
|
if data_time ~= 0 and data_time < last_removed_time then |
1014
|
|
|
|
|
|
|
__FINISH_STEP__ |
1015
|
|
|
|
|
|
|
return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 } |
1016
|
|
|
|
|
|
|
end |
1017
|
|
|
|
|
|
|
end |
1018
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
-- deleting obsolete data, if it can be necessary |
1020
|
|
|
|
|
|
|
_setup( 7, 'update', extra_data_len ) -- 7 is the index of ARGV[7] |
1021
|
|
|
|
|
|
|
cleaning( list_id, data_id, false ) |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
-- data change |
1024
|
|
|
|
|
|
|
-- Remember that the list and the collection can be automatically deleted after the "crowding out" old data |
1025
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 0 then |
1026
|
|
|
|
|
|
|
__FINISH_STEP__ |
1027
|
|
|
|
|
|
|
return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 } |
1028
|
|
|
|
|
|
|
end |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
-- data to be changed were not removed |
1031
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
-- actually change |
1033
|
|
|
|
|
|
|
call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data ) |
1034
|
|
|
|
|
|
|
data = nil -- free memory |
1035
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
if data_time ~= 0 then |
1037
|
|
|
|
|
|
|
if redis.call( 'HLEN', DATA_KEY ) == 1 then |
1038
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, data_time, list_id ) |
1039
|
|
|
|
|
|
|
else |
1040
|
|
|
|
|
|
|
redis.call( 'ZADD', TIME_KEY, data_time, data_id ) |
1041
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] ) |
1042
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
1043
|
|
|
|
|
|
|
end |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
if data_time < last_removed_time then |
1046
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 ) |
1047
|
|
|
|
|
|
|
end |
1048
|
|
|
|
|
|
|
end |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
renew_last_cleanup_values() |
1051
|
|
|
|
|
|
|
UPDATES_SINCE_CLEANING = UPDATES_SINCE_CLEANING + 1 |
1052
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING ) |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
__FINISH_STEP__ |
1055
|
|
|
|
|
|
|
return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED } |
1056
|
|
|
|
|
|
|
END_UPDATE_BODY |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
#--- update -------------------------------------------------------------------- |
1059
|
|
|
|
|
|
|
$lua_script_body{update} = <<"END_UPDATE"; |
1060
|
|
|
|
|
|
|
-- update the data in the list of collections |
1061
|
|
|
|
|
|
|
$_lua_pre_upsert |
1062
|
|
|
|
|
|
|
$_lua_update_body |
1063
|
|
|
|
|
|
|
END_UPDATE |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
#--- upsert -------------------------------------------------------------------- |
1066
|
|
|
|
|
|
|
$lua_script_body{upsert} = <<"END_UPSERT"; |
1067
|
|
|
|
|
|
|
-- update or insert the data in the list of collections |
1068
|
|
|
|
|
|
|
$_lua_pre_upsert |
1069
|
|
|
|
|
|
|
local start_time = tonumber( ARGV[8] ) |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
-- verification of the existence of old data with new data identifier |
1072
|
|
|
|
|
|
|
if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then |
1073
|
|
|
|
|
|
|
if data_time == -1 then |
1074
|
|
|
|
|
|
|
data_time = 0 |
1075
|
|
|
|
|
|
|
end |
1076
|
|
|
|
|
|
|
-- Run update now |
1077
|
|
|
|
|
|
|
$_lua_update_body |
1078
|
|
|
|
|
|
|
else |
1079
|
|
|
|
|
|
|
if data_time == -1 then |
1080
|
|
|
|
|
|
|
data_time = start_time |
1081
|
|
|
|
|
|
|
end |
1082
|
|
|
|
|
|
|
-- Run insert now |
1083
|
|
|
|
|
|
|
$_lua_insert_body |
1084
|
|
|
|
|
|
|
end |
1085
|
|
|
|
|
|
|
END_UPSERT |
1086
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
#--- receive ------------------------------------------------------------------- |
1088
|
|
|
|
|
|
|
$lua_script_body{receive} = <<"END_RECEIVE"; |
1089
|
|
|
|
|
|
|
-- returns the data from the list |
1090
|
|
|
|
|
|
|
$_lua_first_step |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1093
|
|
|
|
|
|
|
local list_id = ARGV[3] |
1094
|
|
|
|
|
|
|
local mode = ARGV[4] |
1095
|
|
|
|
|
|
|
local data_id = ARGV[5] |
1096
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
-- key data storage structures |
1098
|
|
|
|
|
|
|
$_lua_namespace |
1099
|
|
|
|
|
|
|
$_lua_status_key |
1100
|
|
|
|
|
|
|
$_lua_data_keys |
1101
|
|
|
|
|
|
|
$_lua_data_key |
1102
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1104
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1105
|
|
|
|
|
|
|
-- sort of a mistake |
1106
|
|
|
|
|
|
|
__FINISH_STEP__ |
1107
|
|
|
|
|
|
|
return nil |
1108
|
|
|
|
|
|
|
end |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
local ret |
1111
|
|
|
|
|
|
|
if mode == 'val' then |
1112
|
|
|
|
|
|
|
-- returns the specified element of the data list |
1113
|
|
|
|
|
|
|
ret = redis.call( 'HGET', DATA_KEY, data_id ) |
1114
|
|
|
|
|
|
|
elseif mode == 'len' then |
1115
|
|
|
|
|
|
|
-- returns the length of the data list |
1116
|
|
|
|
|
|
|
ret = redis.call( 'HLEN', DATA_KEY ) |
1117
|
|
|
|
|
|
|
elseif mode == 'vals' then |
1118
|
|
|
|
|
|
|
-- returns all the data from the list |
1119
|
|
|
|
|
|
|
ret = redis.call( 'HVALS', DATA_KEY ) |
1120
|
|
|
|
|
|
|
elseif mode == 'all' then |
1121
|
|
|
|
|
|
|
-- returns all data IDs and data values of the data list |
1122
|
|
|
|
|
|
|
ret = redis.call( 'HGETALL', DATA_KEY ) |
1123
|
|
|
|
|
|
|
else |
1124
|
|
|
|
|
|
|
-- sort of a mistake |
1125
|
|
|
|
|
|
|
ret = nil |
1126
|
|
|
|
|
|
|
end |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
__FINISH_STEP__ |
1129
|
|
|
|
|
|
|
return ret |
1130
|
|
|
|
|
|
|
END_RECEIVE |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
#--- pop_oldest ---------------------------------------------------------------- |
1133
|
|
|
|
|
|
|
$lua_script_body{pop_oldest} = <<"END_POP_OLDEST"; |
1134
|
|
|
|
|
|
|
-- retrieve the oldest data stored in the collection |
1135
|
|
|
|
|
|
|
$_lua_first_step |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
-- key data storage structures |
1140
|
|
|
|
|
|
|
$_lua_namespace |
1141
|
|
|
|
|
|
|
$_lua_queue_key |
1142
|
|
|
|
|
|
|
$_lua_status_key |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1145
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1146
|
|
|
|
|
|
|
-- sort of a mistake |
1147
|
|
|
|
|
|
|
__FINISH_STEP__ |
1148
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, nil, nil, nil } |
1149
|
|
|
|
|
|
|
end |
1150
|
|
|
|
|
|
|
if redis.call( 'EXISTS', QUEUE_KEY ) == 0 then |
1151
|
|
|
|
|
|
|
__FINISH_STEP__ |
1152
|
|
|
|
|
|
|
return { $E_NO_ERROR, false, nil, nil } |
1153
|
|
|
|
|
|
|
end |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1156
|
|
|
|
|
|
|
local list_exist = 0 |
1157
|
|
|
|
|
|
|
local list_id = false |
1158
|
|
|
|
|
|
|
local data = false |
1159
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
-- identifier of the list with the oldest data |
1161
|
|
|
|
|
|
|
list_id = unpack( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0 ) ) |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
-- key data storage structures |
1164
|
|
|
|
|
|
|
$_lua_data_keys |
1165
|
|
|
|
|
|
|
$_lua_time_keys |
1166
|
|
|
|
|
|
|
$_lua_data_key |
1167
|
|
|
|
|
|
|
$_lua_time_key |
1168
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1170
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1171
|
|
|
|
|
|
|
__FINISH_STEP__ |
1172
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, nil, nil, nil } |
1173
|
|
|
|
|
|
|
end |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
-- Features the oldest data |
1176
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
1177
|
|
|
|
|
|
|
local data_id |
1178
|
|
|
|
|
|
|
if items == 1 then |
1179
|
|
|
|
|
|
|
data_id = unpack( redis.call( 'HGETALL', DATA_KEY ) ) |
1180
|
|
|
|
|
|
|
else |
1181
|
|
|
|
|
|
|
data_id = unpack( redis.call( 'ZRANGE', TIME_KEY, 0, 0 ) ) |
1182
|
|
|
|
|
|
|
end |
1183
|
|
|
|
|
|
|
local last_removed_time = tonumber( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] ) |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
-- get data |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
-- actually get data |
1188
|
|
|
|
|
|
|
data = redis.call( 'HGET', DATA_KEY, data_id ) |
1189
|
|
|
|
|
|
|
|
1190
|
|
|
|
|
|
|
-- delete the data from the list |
1191
|
|
|
|
|
|
|
redis.call( 'HDEL', DATA_KEY, data_id ) |
1192
|
|
|
|
|
|
|
items = items - 1 |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
-- obtain information about the data that has become the oldest |
1195
|
|
|
|
|
|
|
local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] ) |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
if items > 0 then |
1198
|
|
|
|
|
|
|
-- If the list has more data |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
-- delete the information about the time of the data |
1201
|
|
|
|
|
|
|
redis.call( 'ZREM', TIME_KEY, data_id ) |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id ) |
1204
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
if items == 1 then |
1206
|
|
|
|
|
|
|
-- delete the list data structure 'zset' |
1207
|
|
|
|
|
|
|
redis.call( 'DEL', TIME_KEY ) |
1208
|
|
|
|
|
|
|
end |
1209
|
|
|
|
|
|
|
else |
1210
|
|
|
|
|
|
|
-- if the list is no more data |
1211
|
|
|
|
|
|
|
-- delete the list data structure 'zset' |
1212
|
|
|
|
|
|
|
redis.call( 'DEL', TIME_KEY ) |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
1215
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 ) |
1216
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
1217
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, list_id ) |
1218
|
|
|
|
|
|
|
end |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -1 ) |
1221
|
|
|
|
|
|
|
redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time ) |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
__FINISH_STEP__ |
1224
|
|
|
|
|
|
|
return { $E_NO_ERROR, true, list_id, data } |
1225
|
|
|
|
|
|
|
END_POP_OLDEST |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
#--- collection_info ----------------------------------------------------------- |
1228
|
|
|
|
|
|
|
$lua_script_body{collection_info} = <<"END_COLLECTION_INFO"; |
1229
|
|
|
|
|
|
|
-- to obtain information on the status of the collection |
1230
|
|
|
|
|
|
|
$_lua_first_step |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1233
|
|
|
|
|
|
|
|
1234
|
|
|
|
|
|
|
-- key data storage structures |
1235
|
|
|
|
|
|
|
$_lua_namespace |
1236
|
|
|
|
|
|
|
$_lua_queue_key |
1237
|
|
|
|
|
|
|
$_lua_status_key |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
-- determine whether there is a collection |
1240
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1241
|
|
|
|
|
|
|
__FINISH_STEP__ |
1242
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false, false, false, false, false, false, false, false } |
1243
|
|
|
|
|
|
|
end |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] |
1246
|
|
|
|
|
|
|
local lists, items, older_allowed, cleanup_bytes, cleanup_items, max_list_items, memory_reserve, data_version, last_removed_time = unpack( redis.call( 'HMGET', STATUS_KEY, |
1247
|
|
|
|
|
|
|
'$_LISTS', |
1248
|
|
|
|
|
|
|
'$_ITEMS', |
1249
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', |
1250
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', |
1251
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', |
1252
|
|
|
|
|
|
|
'$_MAX_LIST_ITEMS', |
1253
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', |
1254
|
|
|
|
|
|
|
'$_DATA_VERSION', |
1255
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME' |
1256
|
|
|
|
|
|
|
) ) |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
if type( data_version ) ~= 'string' then data_version = '0' end |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
__FINISH_STEP__ |
1261
|
|
|
|
|
|
|
return { |
1262
|
|
|
|
|
|
|
$E_NO_ERROR, |
1263
|
|
|
|
|
|
|
lists, |
1264
|
|
|
|
|
|
|
items, |
1265
|
|
|
|
|
|
|
older_allowed, |
1266
|
|
|
|
|
|
|
cleanup_bytes, |
1267
|
|
|
|
|
|
|
cleanup_items, |
1268
|
|
|
|
|
|
|
max_list_items, |
1269
|
|
|
|
|
|
|
memory_reserve, |
1270
|
|
|
|
|
|
|
data_version, |
1271
|
|
|
|
|
|
|
last_removed_time, |
1272
|
|
|
|
|
|
|
oldest_item_time |
1273
|
|
|
|
|
|
|
} |
1274
|
|
|
|
|
|
|
END_COLLECTION_INFO |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
#--- oldest_time --------------------------------------------------------------- |
1277
|
|
|
|
|
|
|
$lua_script_body{oldest_time} = <<"END_OLDEST_TIME"; |
1278
|
|
|
|
|
|
|
-- to obtain time corresponding to the oldest data in the collection |
1279
|
|
|
|
|
|
|
$_lua_first_step |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
-- key data storage structures |
1284
|
|
|
|
|
|
|
$_lua_namespace |
1285
|
|
|
|
|
|
|
$_lua_queue_key |
1286
|
|
|
|
|
|
|
$_lua_status_key |
1287
|
|
|
|
|
|
|
|
1288
|
|
|
|
|
|
|
-- determine whe, falther there is a collection |
1289
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1290
|
|
|
|
|
|
|
__FINISH_STEP__ |
1291
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false } |
1292
|
|
|
|
|
|
|
end |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] |
1295
|
|
|
|
|
|
|
__FINISH_STEP__ |
1296
|
|
|
|
|
|
|
return { $E_NO_ERROR, oldest_item_time } |
1297
|
|
|
|
|
|
|
END_OLDEST_TIME |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
#--- list_info ----------------------------------------------------------------- |
1300
|
|
|
|
|
|
|
$lua_script_body{list_info} = <<"END_LIST_INFO"; |
1301
|
|
|
|
|
|
|
-- to obtain information on the status of the data list |
1302
|
|
|
|
|
|
|
$_lua_first_step |
1303
|
|
|
|
|
|
|
|
1304
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1305
|
|
|
|
|
|
|
local list_id = ARGV[3] |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
-- key data storage structures |
1308
|
|
|
|
|
|
|
$_lua_namespace |
1309
|
|
|
|
|
|
|
$_lua_queue_key |
1310
|
|
|
|
|
|
|
$_lua_status_key |
1311
|
|
|
|
|
|
|
$_lua_data_keys |
1312
|
|
|
|
|
|
|
$_lua_data_key |
1313
|
|
|
|
|
|
|
|
1314
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1315
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1316
|
|
|
|
|
|
|
__FINISH_STEP__ |
1317
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, false, nil } |
1318
|
|
|
|
|
|
|
end |
1319
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1320
|
|
|
|
|
|
|
__FINISH_STEP__ |
1321
|
|
|
|
|
|
|
return { $E_NO_ERROR, false, nil } |
1322
|
|
|
|
|
|
|
end |
1323
|
|
|
|
|
|
|
|
1324
|
|
|
|
|
|
|
-- the length of the data list |
1325
|
|
|
|
|
|
|
local items = redis.call( 'HLEN', DATA_KEY ) |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
-- the second data |
1328
|
|
|
|
|
|
|
local oldest_item_time = redis.call( 'ZSCORE', QUEUE_KEY, list_id ) |
1329
|
|
|
|
|
|
|
|
1330
|
|
|
|
|
|
|
__FINISH_STEP__ |
1331
|
|
|
|
|
|
|
return { $E_NO_ERROR, items, oldest_item_time } |
1332
|
|
|
|
|
|
|
END_LIST_INFO |
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
#--- drop_collection ----------------------------------------------------------- |
1335
|
|
|
|
|
|
|
$lua_script_body{drop_collection} = <<"END_DROP_COLLECTION"; |
1336
|
|
|
|
|
|
|
-- to remove the entire collection |
1337
|
|
|
|
|
|
|
$_lua_first_step |
1338
|
|
|
|
|
|
|
|
1339
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1340
|
|
|
|
|
|
|
|
1341
|
|
|
|
|
|
|
-- key data storage structures |
1342
|
|
|
|
|
|
|
$_lua_namespace |
1343
|
|
|
|
|
|
|
$_lua_queue_key |
1344
|
|
|
|
|
|
|
$_lua_status_key |
1345
|
|
|
|
|
|
|
|
1346
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1347
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1348
|
|
|
|
|
|
|
|
1349
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 1 then |
1350
|
|
|
|
|
|
|
ret = ret + redis.call( 'DEL', STATUS_KEY ) |
1351
|
|
|
|
|
|
|
end |
1352
|
|
|
|
|
|
|
|
1353
|
|
|
|
|
|
|
$_lua_clean_data |
1354
|
|
|
|
|
|
|
END_DROP_COLLECTION |
1355
|
|
|
|
|
|
|
|
1356
|
|
|
|
|
|
|
#--- drop_collection ----------------------------------------------------------- |
1357
|
|
|
|
|
|
|
$lua_script_body{clear_collection} = <<"END_CLEAR_COLLECTION"; |
1358
|
|
|
|
|
|
|
-- to remove the entire collection data |
1359
|
|
|
|
|
|
|
$_lua_first_step |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
-- key data storage structures |
1364
|
|
|
|
|
|
|
$_lua_namespace |
1365
|
|
|
|
|
|
|
$_lua_queue_key |
1366
|
|
|
|
|
|
|
$_lua_status_key |
1367
|
|
|
|
|
|
|
|
1368
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1369
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
redis.call( 'HMSET', STATUS_KEY, |
1372
|
|
|
|
|
|
|
'$_LISTS', 0, |
1373
|
|
|
|
|
|
|
'$_ITEMS', 0, |
1374
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME', 0 |
1375
|
|
|
|
|
|
|
) |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
$_lua_clean_data |
1378
|
|
|
|
|
|
|
END_CLEAR_COLLECTION |
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
#--- drop_list ----------------------------------------------------------------- |
1381
|
|
|
|
|
|
|
$lua_script_body{drop_list} = <<"END_DROP_LIST"; |
1382
|
|
|
|
|
|
|
-- to remove the data_list |
1383
|
|
|
|
|
|
|
$_lua_first_step |
1384
|
|
|
|
|
|
|
|
1385
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1386
|
|
|
|
|
|
|
local list_id = ARGV[3] |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
-- key data storage structures |
1389
|
|
|
|
|
|
|
$_lua_namespace |
1390
|
|
|
|
|
|
|
$_lua_queue_key |
1391
|
|
|
|
|
|
|
$_lua_status_key |
1392
|
|
|
|
|
|
|
$_lua_data_keys |
1393
|
|
|
|
|
|
|
$_lua_data_key |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
-- determine whether there is a list of data and a collection |
1396
|
|
|
|
|
|
|
if redis.call( 'EXISTS', STATUS_KEY ) == 0 then |
1397
|
|
|
|
|
|
|
__FINISH_STEP__ |
1398
|
|
|
|
|
|
|
return { $E_COLLECTION_DELETED, 0 } |
1399
|
|
|
|
|
|
|
end |
1400
|
|
|
|
|
|
|
if redis.call( 'EXISTS', DATA_KEY ) == 0 then |
1401
|
|
|
|
|
|
|
__FINISH_STEP__ |
1402
|
|
|
|
|
|
|
return { $E_NO_ERROR, 0 } |
1403
|
|
|
|
|
|
|
end |
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
-- initialize the data returned from the script |
1406
|
|
|
|
|
|
|
local ret = 0 -- the number of deleted items |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
-- key data storage structures |
1409
|
|
|
|
|
|
|
$_lua_time_keys |
1410
|
|
|
|
|
|
|
$_lua_time_key |
1411
|
|
|
|
|
|
|
|
1412
|
|
|
|
|
|
|
-- determine the size of the data in the list and delete the list structure |
1413
|
|
|
|
|
|
|
local bytes_deleted = 0 |
1414
|
|
|
|
|
|
|
local vals = redis.call( 'HVALS', DATA_KEY ) |
1415
|
|
|
|
|
|
|
local list_items = #vals |
1416
|
|
|
|
|
|
|
for i = 1, list_items do |
1417
|
|
|
|
|
|
|
bytes_deleted = bytes_deleted + #vals[ i ] |
1418
|
|
|
|
|
|
|
end |
1419
|
|
|
|
|
|
|
redis.call( 'DEL', DATA_KEY, TIME_KEY ) |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
-- reduce the number of items in the collection |
1422
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -list_items ) |
1423
|
|
|
|
|
|
|
-- reduce the number of lists stored in a collection |
1424
|
|
|
|
|
|
|
redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 ) |
1425
|
|
|
|
|
|
|
-- remove the name of the list from the queue collection |
1426
|
|
|
|
|
|
|
redis.call( 'ZREM', QUEUE_KEY, list_id ) |
1427
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
__FINISH_STEP__ |
1429
|
|
|
|
|
|
|
return { $E_NO_ERROR, 1 } |
1430
|
|
|
|
|
|
|
END_DROP_LIST |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
#--- verify_collection --------------------------------------------------------- |
1433
|
|
|
|
|
|
|
$lua_script_body{verify_collection} = <<"END_VERIFY_COLLECTION"; |
1434
|
|
|
|
|
|
|
-- creation of the collection and characterization of the collection by accessing existing collection |
1435
|
|
|
|
|
|
|
$_lua_first_step |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1438
|
|
|
|
|
|
|
local older_allowed = ARGV[3] |
1439
|
|
|
|
|
|
|
local cleanup_bytes = ARGV[4] |
1440
|
|
|
|
|
|
|
local cleanup_items = ARGV[5] |
1441
|
|
|
|
|
|
|
local max_list_items = ARGV[6] |
1442
|
|
|
|
|
|
|
local memory_reserve = ARGV[7] |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
local data_version = '$DATA_VERSION' |
1445
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
-- key data storage structures |
1447
|
|
|
|
|
|
|
$_lua_namespace |
1448
|
|
|
|
|
|
|
$_lua_status_key |
1449
|
|
|
|
|
|
|
|
1450
|
|
|
|
|
|
|
-- determine whether there is a collection |
1451
|
|
|
|
|
|
|
local status_exist = redis.call( 'EXISTS', STATUS_KEY ) |
1452
|
|
|
|
|
|
|
|
1453
|
|
|
|
|
|
|
if status_exist == 1 then |
1454
|
|
|
|
|
|
|
-- if there is a collection |
1455
|
|
|
|
|
|
|
older_allowed, cleanup_bytes, cleanup_items, max_list_items, memory_reserve, data_version = unpack( redis.call( 'HMGET', STATUS_KEY, |
1456
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', |
1457
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', |
1458
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', |
1459
|
|
|
|
|
|
|
'$_MAX_LIST_ITEMS', |
1460
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', |
1461
|
|
|
|
|
|
|
'$_DATA_VERSION' |
1462
|
|
|
|
|
|
|
) ) |
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
if type( data_version ) ~= 'string' then data_version = '0' end |
1465
|
|
|
|
|
|
|
else |
1466
|
|
|
|
|
|
|
-- if you want to create a new collection |
1467
|
|
|
|
|
|
|
redis.call( 'HMSET', STATUS_KEY, |
1468
|
|
|
|
|
|
|
'$_LISTS', 0, |
1469
|
|
|
|
|
|
|
'$_ITEMS', 0, |
1470
|
|
|
|
|
|
|
'$_OLDER_ALLOWED', older_allowed, |
1471
|
|
|
|
|
|
|
'$_CLEANUP_BYTES', cleanup_bytes, |
1472
|
|
|
|
|
|
|
'$_CLEANUP_ITEMS', cleanup_items, |
1473
|
|
|
|
|
|
|
'$_MAX_LIST_ITEMS', max_list_items, |
1474
|
|
|
|
|
|
|
'$_MEMORY_RESERVE', memory_reserve, |
1475
|
|
|
|
|
|
|
'$_DATA_VERSION', data_version, |
1476
|
|
|
|
|
|
|
'$_LAST_REMOVED_TIME', 0, |
1477
|
|
|
|
|
|
|
'$_LAST_CLEANUP_BYTES', '[0]', |
1478
|
|
|
|
|
|
|
'$_LAST_CLEANUP_ITEMS', '[0]' |
1479
|
|
|
|
|
|
|
) |
1480
|
|
|
|
|
|
|
end |
1481
|
|
|
|
|
|
|
|
1482
|
|
|
|
|
|
|
__FINISH_STEP__ |
1483
|
|
|
|
|
|
|
return { |
1484
|
|
|
|
|
|
|
status_exist, |
1485
|
|
|
|
|
|
|
older_allowed, |
1486
|
|
|
|
|
|
|
cleanup_bytes, |
1487
|
|
|
|
|
|
|
cleanup_items, |
1488
|
|
|
|
|
|
|
memory_reserve, |
1489
|
|
|
|
|
|
|
data_version |
1490
|
|
|
|
|
|
|
} |
1491
|
|
|
|
|
|
|
END_VERIFY_COLLECTION |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
#--- long_term_operation ------------------------------------------------------- |
1494
|
|
|
|
|
|
|
$lua_script_body{_long_term_operation} = <<"END_LONG_TERM_OPERATION"; |
1495
|
|
|
|
|
|
|
$_lua_first_step |
1496
|
|
|
|
|
|
|
|
1497
|
|
|
|
|
|
|
local coll_name = ARGV[2] |
1498
|
|
|
|
|
|
|
local return_as_insert = tonumber( ARGV[3] ) |
1499
|
|
|
|
|
|
|
local max_working_cycles = tonumber( ARGV[4] ) |
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
local STATUS_KEY = 'C:S:'..coll_name |
1502
|
|
|
|
|
|
|
local DATA_VERSION_KEY = '$_DATA_VERSION' |
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
local LIST = 'Test_list' |
1505
|
|
|
|
|
|
|
local DATA = 'Data' |
1506
|
|
|
|
|
|
|
|
1507
|
|
|
|
|
|
|
redis.call( 'DEL', LIST ) |
1508
|
|
|
|
|
|
|
|
1509
|
|
|
|
|
|
|
local ret |
1510
|
|
|
|
|
|
|
local i = 1 |
1511
|
|
|
|
|
|
|
while i < max_working_cycles do |
1512
|
|
|
|
|
|
|
-- simple active actions |
1513
|
|
|
|
|
|
|
local data_version = redis.call( 'HGET', STATUS_KEY, DATA_VERSION_KEY ) |
1514
|
|
|
|
|
|
|
ret = redis.call( 'HSET', LIST, i, DATA ) |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
i = i + 1 |
1517
|
|
|
|
|
|
|
end |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
if return_as_insert == 1 then |
1520
|
|
|
|
|
|
|
__FINISH_STEP__ |
1521
|
|
|
|
|
|
|
return { $E_NO_ERROR, 0, 0, 0 } |
1522
|
|
|
|
|
|
|
else |
1523
|
|
|
|
|
|
|
__FINISH_STEP__ |
1524
|
|
|
|
|
|
|
return { $E_NO_ERROR, ret, '_long_term_operation' } |
1525
|
|
|
|
|
|
|
end |
1526
|
|
|
|
|
|
|
END_LONG_TERM_OPERATION |
1527
|
|
|
|
|
|
|
|
1528
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonNegInt', |
1529
|
|
|
|
|
|
|
as 'Int', |
1530
|
|
|
|
|
|
|
where { $_ >= 0 }, |
1531
|
|
|
|
|
|
|
message { format_message( '%s is not a non-negative integer!', $_ ) } |
1532
|
|
|
|
|
|
|
; |
1533
|
|
|
|
|
|
|
|
1534
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonNegNum', |
1535
|
|
|
|
|
|
|
as 'Num', |
1536
|
|
|
|
|
|
|
where { $_ >= 0 }, |
1537
|
|
|
|
|
|
|
message { format_message( '%s is not a non-negative number!', $_ ) } |
1538
|
|
|
|
|
|
|
; |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
subtype __PACKAGE__.'::NonEmptNameStr', |
1541
|
|
|
|
|
|
|
as 'Str', |
1542
|
|
|
|
|
|
|
where { $_ ne '' && $_ !~ /:/ }, |
1543
|
|
|
|
|
|
|
message { format_message( '%s is not a non-empty string!', $_ ) } |
1544
|
|
|
|
|
|
|
; |
1545
|
|
|
|
|
|
|
|
1546
|
|
|
|
|
|
|
subtype __PACKAGE__.'::DataStr', |
1547
|
|
|
|
|
|
|
as 'Str', |
1548
|
|
|
|
|
|
|
where { bytes::length( $_ ) <= $MAX_DATASIZE }, |
1549
|
|
|
|
|
|
|
message { format_message( "'%s' is not a valid data string!", $_ ) } |
1550
|
|
|
|
|
|
|
; |
1551
|
|
|
|
|
|
|
|
1552
|
|
|
|
|
|
|
#-- constructor ---------------------------------------------------------------- |
1553
|
|
|
|
|
|
|
|
1554
|
|
|
|
|
|
|
=head2 CONSTRUCTOR |
1555
|
|
|
|
|
|
|
|
1556
|
|
|
|
|
|
|
=head3 create |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
create( redis => $server, name => $name, ... ) |
1559
|
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
Create a new collection on the Redis server and return an C |
1561
|
|
|
|
|
|
|
object to access it. Must be called as a class method only. |
1562
|
|
|
|
|
|
|
|
1563
|
|
|
|
|
|
|
The C creates and returns a C object that is configured |
1564
|
|
|
|
|
|
|
to work with the default settings if the corresponding arguments were not given. |
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
C argument can be either an existing object of L class |
1567
|
|
|
|
|
|
|
(which is then used for all communication with Redis server) or a hash reference used to create a |
1568
|
|
|
|
|
|
|
new internal Redis object. See documentation of L module for details. |
1569
|
|
|
|
|
|
|
|
1570
|
|
|
|
|
|
|
C takes arguments in key-value pairs. |
1571
|
|
|
|
|
|
|
|
1572
|
|
|
|
|
|
|
This example illustrates a C call with all the valid arguments: |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( |
1575
|
|
|
|
|
|
|
redis => { server => "$server:$port" }, # Redis object |
1576
|
|
|
|
|
|
|
# or hash reference to parameters to create a new Redis object. |
1577
|
|
|
|
|
|
|
name => 'Some name', # Redis::CappedCollection collection name. |
1578
|
|
|
|
|
|
|
cleanup_bytes => 50_000, # The minimum size, in bytes, |
1579
|
|
|
|
|
|
|
# of the data to be released when performing memory cleanup. |
1580
|
|
|
|
|
|
|
cleanup_items => 1_000, # The minimum number of the collection |
1581
|
|
|
|
|
|
|
# elements to be realesed when performing memory cleanup. |
1582
|
|
|
|
|
|
|
max_list_items => 0, # Maximum list items limit |
1583
|
|
|
|
|
|
|
max_datasize => 1_000_000, # Maximum size, in bytes, of the data. |
1584
|
|
|
|
|
|
|
# Default 512MB. |
1585
|
|
|
|
|
|
|
older_allowed => 0, # Allow adding an element to collection that's older |
1586
|
|
|
|
|
|
|
# than the last element removed from collection. |
1587
|
|
|
|
|
|
|
# Default 0. |
1588
|
|
|
|
|
|
|
check_maxmemory => 1, # Controls if collection should try to find out maximum |
1589
|
|
|
|
|
|
|
# available memory from Redis. |
1590
|
|
|
|
|
|
|
# In some cases Redis implementation forbids such request, |
1591
|
|
|
|
|
|
|
# but setting 'check_maxmemory' to false can be used |
1592
|
|
|
|
|
|
|
# as a workaround. |
1593
|
|
|
|
|
|
|
memory_reserve => 0.05, # Reserve coefficient of 'maxmemory'. |
1594
|
|
|
|
|
|
|
# Not used when 'maxmemory' == 0 (it is not set in the redis.conf). |
1595
|
|
|
|
|
|
|
# When you add or modify the data trying to ensure |
1596
|
|
|
|
|
|
|
# reserve of free memory for metadata and bookkeeping. |
1597
|
|
|
|
|
|
|
reconnect_on_error => 1, # Controls ability to force re-connection with Redis on error. |
1598
|
|
|
|
|
|
|
# Boolean argument - default is true and conservative_reconnect is true. |
1599
|
|
|
|
|
|
|
connection_timeout => $DEFAULT_CONNECTION_TIMEOUT, # Socket timeout for connection, |
1600
|
|
|
|
|
|
|
# number of seconds (can be fractional). |
1601
|
|
|
|
|
|
|
# NOTE: Changes external socket configuration. |
1602
|
|
|
|
|
|
|
operation_timeout => $DEFAULT_OPERATION_TIMEOUT, # Socket timeout for read and write operations, |
1603
|
|
|
|
|
|
|
# number of seconds (can be fractional). |
1604
|
|
|
|
|
|
|
# NOTE: Changes external socket configuration. |
1605
|
|
|
|
|
|
|
); |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
The C and C arguments are required. |
1608
|
|
|
|
|
|
|
Do not use the symbol C<':'> in C. |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
The following examples illustrate other uses of the C method: |
1611
|
|
|
|
|
|
|
|
1612
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
1613
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection->create( redis => $redis, name => 'Next collection' ); |
1614
|
|
|
|
|
|
|
my $next_coll = Redis::CappedCollection->create( redis => $coll, name => 'Some name' ); |
1615
|
|
|
|
|
|
|
|
1616
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid or the collection with |
1617
|
|
|
|
|
|
|
same name already exists. |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
=cut |
1620
|
|
|
|
|
|
|
sub create { |
1621
|
0
|
0
|
|
0
|
1
|
|
my $class = _CLASSISA( shift, __PACKAGE__ ) or _croak( 'Must be called as a class method only' ); |
1622
|
0
|
|
|
|
|
|
return $class->new( @_, _create_from_naked_new => 0 ); |
1623
|
|
|
|
|
|
|
} |
1624
|
|
|
|
|
|
|
|
1625
|
|
|
|
|
|
|
sub BUILD { |
1626
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1627
|
|
|
|
|
|
|
|
1628
|
0
|
|
|
|
|
|
my $redis = $self->redis; |
1629
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $redis, 'Redis' ) ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
# have to look into the Redis object ... |
1631
|
0
|
|
|
|
|
|
$self->_server( $redis->{server} ); |
1632
|
0
|
|
|
|
|
|
$self->_redis( $redis ); |
1633
|
|
|
|
|
|
|
} elsif ( _INSTANCE( $redis, 'Test::RedisServer' ) ) { |
1634
|
|
|
|
|
|
|
# to test only |
1635
|
|
|
|
|
|
|
# have to look into the Test::RedisServer object ... |
1636
|
0
|
|
|
|
|
|
my $conf = $redis->conf; |
1637
|
0
|
0
|
|
|
|
|
$conf->{server} = '127.0.0.1:'.$conf->{port} unless exists $conf->{server}; |
1638
|
0
|
|
|
|
|
|
$self->_server( $conf->{server} ); |
1639
|
0
|
0
|
|
|
|
|
my @password = $conf->{requirepass} ? ( password => $conf->{requirepass} ) : (); |
1640
|
|
|
|
|
|
|
$self->_redis( Redis->new( |
1641
|
|
|
|
|
|
|
server => $conf->{server}, |
1642
|
0
|
|
|
|
|
|
@password, |
1643
|
|
|
|
|
|
|
) |
1644
|
|
|
|
|
|
|
); |
1645
|
|
|
|
|
|
|
} elsif ( _INSTANCE( $redis, __PACKAGE__ ) ) { |
1646
|
0
|
|
|
|
|
|
$self->_server( $redis->_server ); |
1647
|
0
|
|
|
|
|
|
$self->_redis( $self->_redis ); |
1648
|
|
|
|
|
|
|
} else { # $redis is hash ref |
1649
|
0
|
|
0
|
|
|
|
$self->_server( $redis->{server} // "$DEFAULT_SERVER:$DEFAULT_PORT" ); |
1650
|
|
|
|
|
|
|
|
1651
|
0
|
|
|
|
|
|
$self->_redis_setup( $redis ); |
1652
|
|
|
|
|
|
|
|
1653
|
0
|
|
|
|
|
|
$self->_redis( $self->_redis_constructor( $redis ) ); |
1654
|
0
|
|
|
|
|
|
$self->_use_external_connection( 0 ); |
1655
|
|
|
|
|
|
|
} |
1656
|
|
|
|
|
|
|
|
1657
|
0
|
|
|
|
|
|
$self->_connection_timeout_trigger( $self->connection_timeout ); |
1658
|
0
|
|
|
|
|
|
$self->_operation_timeout_trigger( $self->operation_timeout ); |
1659
|
|
|
|
|
|
|
$self->_redis->connect if |
1660
|
|
|
|
|
|
|
exists( $self->_redis->{no_auto_connect_on_new} ) |
1661
|
|
|
|
|
|
|
&& $self->_redis->{no_auto_connect_on_new} |
1662
|
|
|
|
|
|
|
&& !$self->_redis->{sock} |
1663
|
0
|
0
|
0
|
|
|
|
; |
|
|
|
0
|
|
|
|
|
1664
|
|
|
|
|
|
|
|
1665
|
0
|
0
|
|
|
|
|
if ( $self->_create_from_naked_new ) { |
1666
|
0
|
|
|
|
|
|
warn 'Redis::CappedCollection->new() is deprecated and will be removed in future. Please use either create() or open() instead.'; |
1667
|
|
|
|
|
|
|
} else { |
1668
|
0
|
0
|
0
|
|
|
|
_croak( format_message( "Collection '%s' already exists", $self->name ) ) |
1669
|
|
|
|
|
|
|
if !$self->_create_from_open && $self->collection_exists( name => $self->name ); |
1670
|
|
|
|
|
|
|
} |
1671
|
|
|
|
|
|
|
|
1672
|
0
|
|
|
|
|
|
my $maxmemory; |
1673
|
0
|
0
|
|
|
|
|
if ( $self->_check_maxmemory ) { |
1674
|
0
|
|
|
|
|
|
( undef, $maxmemory ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory' ); |
1675
|
0
|
0
|
|
|
|
|
defined( _NONNEGINT( $maxmemory ) ) |
1676
|
|
|
|
|
|
|
or $self->_throw( $E_NETWORK ); |
1677
|
|
|
|
|
|
|
} else { |
1678
|
|
|
|
|
|
|
# 0 means all system memory |
1679
|
0
|
|
|
|
|
|
$maxmemory = 0; |
1680
|
|
|
|
|
|
|
} |
1681
|
|
|
|
|
|
|
|
1682
|
0
|
|
|
|
|
|
my ( $major, $minor ) = $self->_redis->info->{redis_version} =~ /^(\d+)\.(\d+)/; |
1683
|
0
|
0
|
0
|
|
|
|
if ( $major < 2 || ( $major == 2 && $minor < 8 ) ) { |
|
|
|
0
|
|
|
|
|
1684
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS ); |
1685
|
0
|
|
|
|
|
|
_croak( "Need a Redis server version 2.8 or higher" ); |
1686
|
|
|
|
|
|
|
} |
1687
|
|
|
|
|
|
|
|
1688
|
0
|
0
|
|
|
|
|
$self->_throw( $E_MAXMEMORY_POLICY ) |
1689
|
|
|
|
|
|
|
unless $self->_maxmemory_policy_ok; |
1690
|
|
|
|
|
|
|
|
1691
|
0
|
|
|
|
|
|
$self->_maxmemory( $maxmemory ); |
1692
|
0
|
0
|
|
|
|
|
$self->max_datasize( min $self->_maxmemory, $self->max_datasize ) |
1693
|
|
|
|
|
|
|
if $self->_maxmemory; |
1694
|
|
|
|
|
|
|
|
1695
|
0
|
|
|
|
|
|
$self->_queue_key( $NAMESPACE.':Q:'.$self->name ); |
1696
|
0
|
|
|
|
|
|
$self->_status_key( _make_status_key( $self->name ) ); |
1697
|
0
|
|
|
|
|
|
$self->_data_keys( _make_data_key( $self->name ) ); |
1698
|
0
|
|
|
|
|
|
$self->_time_keys( $NAMESPACE.':T:'.$self->name ); |
1699
|
|
|
|
|
|
|
|
1700
|
0
|
0
|
|
|
|
|
$self->_verify_collection unless $self->_create_from_open; |
1701
|
|
|
|
|
|
|
|
1702
|
0
|
|
|
|
|
|
return; |
1703
|
|
|
|
|
|
|
} |
1704
|
|
|
|
|
|
|
|
1705
|
|
|
|
|
|
|
#-- public attributes ---------------------------------------------------------- |
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
=head3 open |
1708
|
|
|
|
|
|
|
|
1709
|
|
|
|
|
|
|
open( redis => $server, name => $name, ... ) |
1710
|
|
|
|
|
|
|
|
1711
|
|
|
|
|
|
|
Example: |
1712
|
|
|
|
|
|
|
|
1713
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
1714
|
|
|
|
|
|
|
my $coll = Redis::CappedCollection::open( redis => $redis, name => 'Some name' ); |
1715
|
|
|
|
|
|
|
|
1716
|
|
|
|
|
|
|
Create a C object to work with an existing collection |
1717
|
|
|
|
|
|
|
(created by L). It must be called as a class method only. |
1718
|
|
|
|
|
|
|
|
1719
|
|
|
|
|
|
|
C takes optional arguments. These arguments are in key-value pairs. |
1720
|
|
|
|
|
|
|
Arguments description is the same as for L method. |
1721
|
|
|
|
|
|
|
|
1722
|
|
|
|
|
|
|
=over 3 |
1723
|
|
|
|
|
|
|
|
1724
|
|
|
|
|
|
|
=item I |
1725
|
|
|
|
|
|
|
|
1726
|
|
|
|
|
|
|
=item I |
1727
|
|
|
|
|
|
|
|
1728
|
|
|
|
|
|
|
=item I |
1729
|
|
|
|
|
|
|
|
1730
|
|
|
|
|
|
|
=item I |
1731
|
|
|
|
|
|
|
|
1732
|
|
|
|
|
|
|
=item I |
1733
|
|
|
|
|
|
|
|
1734
|
|
|
|
|
|
|
=item I |
1735
|
|
|
|
|
|
|
|
1736
|
|
|
|
|
|
|
=item I |
1737
|
|
|
|
|
|
|
|
1738
|
|
|
|
|
|
|
=back |
1739
|
|
|
|
|
|
|
|
1740
|
|
|
|
|
|
|
The C and C arguments are mandatory. |
1741
|
|
|
|
|
|
|
|
1742
|
|
|
|
|
|
|
The C creates and returns a C object that is configured |
1743
|
|
|
|
|
|
|
to work with the default settings if the corresponding arguments are not given. |
1744
|
|
|
|
|
|
|
|
1745
|
|
|
|
|
|
|
If C argument is not a L object, a new connection to Redis is established using |
1746
|
|
|
|
|
|
|
passed hash reference to create a new L object. |
1747
|
|
|
|
|
|
|
|
1748
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
1749
|
|
|
|
|
|
|
|
1750
|
|
|
|
|
|
|
=cut |
1751
|
|
|
|
|
|
|
my @_asked_parameters = qw( |
1752
|
|
|
|
|
|
|
redis |
1753
|
|
|
|
|
|
|
name |
1754
|
|
|
|
|
|
|
max_datasize |
1755
|
|
|
|
|
|
|
check_maxmemory |
1756
|
|
|
|
|
|
|
reconnect_on_error |
1757
|
|
|
|
|
|
|
connection_timeout |
1758
|
|
|
|
|
|
|
operation_timeout |
1759
|
|
|
|
|
|
|
); |
1760
|
|
|
|
|
|
|
my @_status_parameters = qw( |
1761
|
|
|
|
|
|
|
older_allowed |
1762
|
|
|
|
|
|
|
cleanup_bytes |
1763
|
|
|
|
|
|
|
cleanup_items |
1764
|
|
|
|
|
|
|
max_list_items |
1765
|
|
|
|
|
|
|
memory_reserve |
1766
|
|
|
|
|
|
|
); |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
sub open { |
1769
|
0
|
0
|
|
0
|
1
|
|
my $class = _CLASSISA( shift, __PACKAGE__ ) or _croak( 'Must be called as a class method only' ); |
1770
|
|
|
|
|
|
|
|
1771
|
0
|
|
|
|
|
|
my %params = @_; |
1772
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%params, \@_asked_parameters ); |
1773
|
|
|
|
|
|
|
|
1774
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless exists $params{redis}; |
1775
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless exists $params{name}; |
1776
|
|
|
|
|
|
|
|
1777
|
0
|
|
|
|
|
|
my $use_external_connection = ref( $params{redis} ) ne 'HASH'; |
1778
|
0
|
|
|
|
|
|
my $redis = $params{redis} = _get_redis( $params{redis} ); |
1779
|
0
|
|
|
|
|
|
my $name = $params{name}; |
1780
|
0
|
0
|
|
|
|
|
if ( collection_exists( redis => $redis, name => $name ) ) { |
1781
|
0
|
|
|
|
|
|
my $info = collection_info( redis => $redis, name => $name ); |
1782
|
0
|
0
|
|
|
|
|
$info->{data_version} == $DATA_VERSION or _croak( $ERROR{ $E_INCOMP_DATA_VERSION } ); |
1783
|
0
|
|
|
|
|
|
$params{ $_ } = $info->{ $_ } foreach @_status_parameters; |
1784
|
0
|
|
|
|
|
|
my $collection = $class->new( %params, |
1785
|
|
|
|
|
|
|
_create_from_naked_new => 0, |
1786
|
|
|
|
|
|
|
_create_from_open => 1, |
1787
|
|
|
|
|
|
|
_use_external_connection => $use_external_connection, |
1788
|
|
|
|
|
|
|
); |
1789
|
0
|
0
|
|
|
|
|
unless ( $use_external_connection ) { |
1790
|
0
|
|
|
|
|
|
$collection->connection_timeout( $redis->{cnx_timeout} ); |
1791
|
0
|
|
|
|
|
|
$collection->operation_timeout( $redis->{read_timeout} ); |
1792
|
|
|
|
|
|
|
} |
1793
|
0
|
|
|
|
|
|
return $collection; |
1794
|
|
|
|
|
|
|
} else { |
1795
|
0
|
|
|
|
|
|
_croak( format_message( "Collection '%s' does not exist", $name ) ); |
1796
|
|
|
|
|
|
|
} |
1797
|
|
|
|
|
|
|
|
1798
|
0
|
|
|
|
|
|
return; |
1799
|
|
|
|
|
|
|
} |
1800
|
|
|
|
|
|
|
|
1801
|
|
|
|
|
|
|
=head2 METHODS |
1802
|
|
|
|
|
|
|
|
1803
|
|
|
|
|
|
|
An exception is thrown (C) if any method argument is not valid or |
1804
|
|
|
|
|
|
|
if a required argument is missing. |
1805
|
|
|
|
|
|
|
|
1806
|
|
|
|
|
|
|
ATTENTION: In the L module the synchronous commands throw an |
1807
|
|
|
|
|
|
|
exception on receipt of an error reply, or return a non-error reply directly. |
1808
|
|
|
|
|
|
|
|
1809
|
|
|
|
|
|
|
=cut |
1810
|
|
|
|
|
|
|
|
1811
|
|
|
|
|
|
|
=head3 name |
1812
|
|
|
|
|
|
|
|
1813
|
|
|
|
|
|
|
Get collection C attribute (collection ID). |
1814
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
1815
|
|
|
|
|
|
|
The C attribute value is used in the L. |
1816
|
|
|
|
|
|
|
|
1817
|
|
|
|
|
|
|
=cut |
1818
|
|
|
|
|
|
|
has name => ( |
1819
|
|
|
|
|
|
|
is => 'ro', |
1820
|
|
|
|
|
|
|
clearer => '_clear_name', |
1821
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonEmptNameStr', |
1822
|
|
|
|
|
|
|
required => 1, |
1823
|
|
|
|
|
|
|
); |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
=head3 redis |
1826
|
|
|
|
|
|
|
|
1827
|
|
|
|
|
|
|
Existing L object or a hash reference with parameters to create a new one. |
1828
|
|
|
|
|
|
|
|
1829
|
|
|
|
|
|
|
=cut |
1830
|
|
|
|
|
|
|
has redis => ( |
1831
|
|
|
|
|
|
|
is => 'ro', |
1832
|
|
|
|
|
|
|
isa => 'Redis|Test::RedisServer|HashRef', |
1833
|
|
|
|
|
|
|
required => 1, |
1834
|
|
|
|
|
|
|
); |
1835
|
|
|
|
|
|
|
|
1836
|
|
|
|
|
|
|
=head3 reconnect_on_error |
1837
|
|
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
Controls ability to force re-connection with Redis on error. |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
=cut |
1841
|
|
|
|
|
|
|
has reconnect_on_error => ( |
1842
|
|
|
|
|
|
|
is => 'rw', |
1843
|
|
|
|
|
|
|
isa => 'Bool', |
1844
|
|
|
|
|
|
|
default => 0, |
1845
|
|
|
|
|
|
|
); |
1846
|
|
|
|
|
|
|
|
1847
|
|
|
|
|
|
|
=head3 connection_timeout |
1848
|
|
|
|
|
|
|
|
1849
|
|
|
|
|
|
|
Controls socket timeout for Redis server connection, number of seconds (can be fractional). |
1850
|
|
|
|
|
|
|
|
1851
|
|
|
|
|
|
|
NOTE: Changes external socket configuration. |
1852
|
|
|
|
|
|
|
|
1853
|
|
|
|
|
|
|
=cut |
1854
|
|
|
|
|
|
|
has connection_timeout => ( |
1855
|
|
|
|
|
|
|
is => 'rw', |
1856
|
|
|
|
|
|
|
isa => 'Maybe['.__PACKAGE__.'::NonNegNum]', |
1857
|
|
|
|
|
|
|
default => undef, |
1858
|
|
|
|
|
|
|
trigger => \&_connection_timeout_trigger, |
1859
|
|
|
|
|
|
|
); |
1860
|
|
|
|
|
|
|
|
1861
|
|
|
|
|
|
|
sub _connection_timeout_trigger { |
1862
|
0
|
|
|
0
|
|
|
my ( $self, $timeout, $old_timeout ) = @_; |
1863
|
|
|
|
|
|
|
|
1864
|
0
|
0
|
0
|
|
|
|
return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) ); |
|
|
|
0
|
|
|
|
|
1865
|
|
|
|
|
|
|
|
1866
|
0
|
0
|
|
|
|
|
if ( my $redis = $self->_redis ) { |
1867
|
0
|
0
|
|
|
|
|
my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or _croak( 'Bad socket object' ); |
1868
|
|
|
|
|
|
|
# IO::Socket provides a way to set a timeout on the socket, |
1869
|
|
|
|
|
|
|
# but the timeout will be used only for connection, |
1870
|
|
|
|
|
|
|
# not for reading / writing operations. |
1871
|
0
|
|
|
|
|
|
$socket->timeout( $redis->{cnx_timeout} = $timeout ); |
1872
|
|
|
|
|
|
|
} |
1873
|
|
|
|
|
|
|
|
1874
|
0
|
|
|
|
|
|
return; |
1875
|
|
|
|
|
|
|
} |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
=head3 operation_timeout |
1878
|
|
|
|
|
|
|
|
1879
|
|
|
|
|
|
|
Controls socket timeout for Redis server read and write operations, number of seconds (can be fractional). |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
NOTE: Changes external socket configuration. |
1882
|
|
|
|
|
|
|
|
1883
|
|
|
|
|
|
|
=cut |
1884
|
|
|
|
|
|
|
has operation_timeout => ( |
1885
|
|
|
|
|
|
|
is => 'rw', |
1886
|
|
|
|
|
|
|
isa => 'Maybe['.__PACKAGE__.'::NonNegNum]', |
1887
|
|
|
|
|
|
|
default => undef, |
1888
|
|
|
|
|
|
|
trigger => \&_operation_timeout_trigger, |
1889
|
|
|
|
|
|
|
); |
1890
|
|
|
|
|
|
|
|
1891
|
|
|
|
|
|
|
sub _operation_timeout_trigger { |
1892
|
0
|
|
|
0
|
|
|
my ( $self, $timeout, $old_timeout ) = @_; |
1893
|
|
|
|
|
|
|
|
1894
|
0
|
0
|
0
|
|
|
|
return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) ); |
|
|
|
0
|
|
|
|
|
1895
|
|
|
|
|
|
|
|
1896
|
0
|
0
|
|
|
|
|
if ( my $redis = $self->_redis ) { |
1897
|
0
|
0
|
|
|
|
|
my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or _croak( 'Bad socket object' ); |
1898
|
|
|
|
|
|
|
# IO::Socket::Timeout provides a way to set a timeout |
1899
|
|
|
|
|
|
|
# on read / write operations on an IO::Socket instance, |
1900
|
|
|
|
|
|
|
# or any IO::Socket::* modules, like IO::Socket::INET. |
1901
|
0
|
0
|
|
|
|
|
if ( defined $timeout ) { |
1902
|
0
|
|
|
|
|
|
$redis->{write_timeout} = $redis->{read_timeout} = $timeout; |
1903
|
0
|
|
|
|
|
|
$redis->_maybe_enable_timeouts( $socket ); |
1904
|
0
|
|
|
|
|
|
$socket->enable_timeout; |
1905
|
|
|
|
|
|
|
} else { |
1906
|
0
|
|
|
|
|
|
$redis->{write_timeout} = $redis->{read_timeout} = 0; |
1907
|
0
|
|
|
|
|
|
$redis->_maybe_enable_timeouts( $socket ); |
1908
|
0
|
|
|
|
|
|
$socket->disable_timeout; |
1909
|
|
|
|
|
|
|
} |
1910
|
|
|
|
|
|
|
} |
1911
|
|
|
|
|
|
|
|
1912
|
0
|
|
|
|
|
|
return; |
1913
|
|
|
|
|
|
|
} |
1914
|
|
|
|
|
|
|
|
1915
|
|
|
|
|
|
|
=head3 cleanup_bytes |
1916
|
|
|
|
|
|
|
|
1917
|
|
|
|
|
|
|
Accessor for C attribute - The minimum size, in bytes, |
1918
|
|
|
|
|
|
|
of the data to be released when performing memory cleanup. |
1919
|
|
|
|
|
|
|
Default 0. |
1920
|
|
|
|
|
|
|
|
1921
|
|
|
|
|
|
|
The C attribute is designed to reduce the release of memory |
1922
|
|
|
|
|
|
|
operations with frequent data changes. |
1923
|
|
|
|
|
|
|
|
1924
|
|
|
|
|
|
|
The C attribute value can be provided to L. |
1925
|
|
|
|
|
|
|
The method returns and sets the current value of the attribute. |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
The C value must be less than or equal to C<'maxmemory'>. Otherwise |
1928
|
|
|
|
|
|
|
an error exception is thrown (C). |
1929
|
|
|
|
|
|
|
|
1930
|
|
|
|
|
|
|
=cut |
1931
|
|
|
|
|
|
|
has cleanup_bytes => ( |
1932
|
|
|
|
|
|
|
is => 'rw', |
1933
|
|
|
|
|
|
|
writer => '_set_cleanup_bytes', |
1934
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1935
|
|
|
|
|
|
|
default => 0, |
1936
|
|
|
|
|
|
|
trigger => sub { |
1937
|
|
|
|
|
|
|
my $self = shift; |
1938
|
|
|
|
|
|
|
!$self->_maxmemory || ( $self->cleanup_bytes <= $self->maxmemory || $self->_throw( $E_MISMATCH_ARG, 'cleanup_bytes' ) ); |
1939
|
|
|
|
|
|
|
}, |
1940
|
|
|
|
|
|
|
); |
1941
|
|
|
|
|
|
|
|
1942
|
|
|
|
|
|
|
=head3 cleanup_items |
1943
|
|
|
|
|
|
|
|
1944
|
|
|
|
|
|
|
The minimum number of the collection elements to be realesed |
1945
|
|
|
|
|
|
|
when performing memory cleanup. Default 100. |
1946
|
|
|
|
|
|
|
|
1947
|
|
|
|
|
|
|
The C attribute is designed to reduce number of times collection cleanup takes place. |
1948
|
|
|
|
|
|
|
Setting value too high may result in unwanted delays during operations with Redis. |
1949
|
|
|
|
|
|
|
|
1950
|
|
|
|
|
|
|
The C attribute value can be used in the L. |
1951
|
|
|
|
|
|
|
The method returns and sets the current value of the attribute. |
1952
|
|
|
|
|
|
|
|
1953
|
|
|
|
|
|
|
=cut |
1954
|
|
|
|
|
|
|
has cleanup_items => ( |
1955
|
|
|
|
|
|
|
is => 'rw', |
1956
|
|
|
|
|
|
|
writer => '_set_cleanup_items', |
1957
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1958
|
|
|
|
|
|
|
default => 100, |
1959
|
|
|
|
|
|
|
); |
1960
|
|
|
|
|
|
|
|
1961
|
|
|
|
|
|
|
=head3 max_list_items |
1962
|
|
|
|
|
|
|
|
1963
|
|
|
|
|
|
|
Maximum list items limit. |
1964
|
|
|
|
|
|
|
|
1965
|
|
|
|
|
|
|
Default 0 means that number of list items not limited. |
1966
|
|
|
|
|
|
|
|
1967
|
|
|
|
|
|
|
The C attribute value can be used in the L. |
1968
|
|
|
|
|
|
|
The method returns and sets the current value of the attribute. |
1969
|
|
|
|
|
|
|
|
1970
|
|
|
|
|
|
|
=cut |
1971
|
|
|
|
|
|
|
has max_list_items => ( |
1972
|
|
|
|
|
|
|
is => 'rw', |
1973
|
|
|
|
|
|
|
writer => '_set_max_list_items', |
1974
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1975
|
|
|
|
|
|
|
default => 0, |
1976
|
|
|
|
|
|
|
); |
1977
|
|
|
|
|
|
|
|
1978
|
|
|
|
|
|
|
=head3 max_datasize |
1979
|
|
|
|
|
|
|
|
1980
|
|
|
|
|
|
|
Accessor for the C attribute. |
1981
|
|
|
|
|
|
|
|
1982
|
|
|
|
|
|
|
The method returns the current value of the attribute if called without arguments. |
1983
|
|
|
|
|
|
|
|
1984
|
|
|
|
|
|
|
Non-negative integer value can be used to specify a new value to |
1985
|
|
|
|
|
|
|
the maximum size of the data introduced into the collection |
1986
|
|
|
|
|
|
|
(methods L and L). |
1987
|
|
|
|
|
|
|
|
1988
|
|
|
|
|
|
|
The C attribute value is used in the L |
1989
|
|
|
|
|
|
|
and operations data entry on the Redis server. |
1990
|
|
|
|
|
|
|
|
1991
|
|
|
|
|
|
|
The L uses the smaller of the values of 512MB and |
1992
|
|
|
|
|
|
|
C<'maxmemory'> limit from a F file. |
1993
|
|
|
|
|
|
|
|
1994
|
|
|
|
|
|
|
=cut |
1995
|
|
|
|
|
|
|
has max_datasize => ( |
1996
|
|
|
|
|
|
|
is => 'rw', |
1997
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
1998
|
|
|
|
|
|
|
default => $MAX_DATASIZE, |
1999
|
|
|
|
|
|
|
lazy => 1, |
2000
|
|
|
|
|
|
|
trigger => sub { |
2001
|
|
|
|
|
|
|
my $self = shift; |
2002
|
|
|
|
|
|
|
$self->max_datasize <= ( $self->_maxmemory ? min( $self->_maxmemory, $MAX_DATASIZE ) : $MAX_DATASIZE ) |
2003
|
|
|
|
|
|
|
|| $self->_throw( $E_MISMATCH_ARG, 'max_datasize' ); |
2004
|
|
|
|
|
|
|
}, |
2005
|
|
|
|
|
|
|
); |
2006
|
|
|
|
|
|
|
|
2007
|
|
|
|
|
|
|
=head3 older_allowed |
2008
|
|
|
|
|
|
|
|
2009
|
|
|
|
|
|
|
Accessor for the C attribute which controls if adding an element |
2010
|
|
|
|
|
|
|
that is older than the last element removed from collection is allowed. |
2011
|
|
|
|
|
|
|
Default is C<0> (not allowed). |
2012
|
|
|
|
|
|
|
|
2013
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
2014
|
|
|
|
|
|
|
The C attribute value is used in the L. |
2015
|
|
|
|
|
|
|
|
2016
|
|
|
|
|
|
|
=cut |
2017
|
|
|
|
|
|
|
has older_allowed => ( |
2018
|
|
|
|
|
|
|
is => 'rw', |
2019
|
|
|
|
|
|
|
isa => 'Bool', |
2020
|
|
|
|
|
|
|
default => 0, |
2021
|
|
|
|
|
|
|
); |
2022
|
|
|
|
|
|
|
|
2023
|
|
|
|
|
|
|
=head3 memory_reserve |
2024
|
|
|
|
|
|
|
|
2025
|
|
|
|
|
|
|
Accessor for the C attribute which specifies the amount of additional |
2026
|
|
|
|
|
|
|
memory reserved for metadata and bookkeeping. |
2027
|
|
|
|
|
|
|
Default C<0.05> (5%) of 'maxmemory'. |
2028
|
|
|
|
|
|
|
Not used when C<'maxmemory'> == 0 (it is not set in the F). |
2029
|
|
|
|
|
|
|
|
2030
|
|
|
|
|
|
|
Valid values must be between C<$MIN_MEMORY_RESERVE> and C<$MAX_MEMORY_RESERVE>. |
2031
|
|
|
|
|
|
|
|
2032
|
|
|
|
|
|
|
The method returns the current value of the attribute. |
2033
|
|
|
|
|
|
|
The C attribute value is used in the L. |
2034
|
|
|
|
|
|
|
|
2035
|
|
|
|
|
|
|
=cut |
2036
|
|
|
|
|
|
|
has memory_reserve => ( |
2037
|
|
|
|
|
|
|
is => 'rw', |
2038
|
|
|
|
|
|
|
writer => '_set_memory_reserve', |
2039
|
|
|
|
|
|
|
isa => 'Num', |
2040
|
|
|
|
|
|
|
default => $MIN_MEMORY_RESERVE, |
2041
|
|
|
|
|
|
|
trigger => sub { |
2042
|
|
|
|
|
|
|
my $self = shift; |
2043
|
|
|
|
|
|
|
my $memory_reserve = $self->memory_reserve; |
2044
|
|
|
|
|
|
|
( _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE ) |
2045
|
|
|
|
|
|
|
|| $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' ); |
2046
|
|
|
|
|
|
|
}, |
2047
|
|
|
|
|
|
|
); |
2048
|
|
|
|
|
|
|
|
2049
|
|
|
|
|
|
|
=head3 last_errorcode |
2050
|
|
|
|
|
|
|
|
2051
|
|
|
|
|
|
|
Get code of the last error. |
2052
|
|
|
|
|
|
|
|
2053
|
|
|
|
|
|
|
See the list of supported error codes in L section. |
2054
|
|
|
|
|
|
|
|
2055
|
|
|
|
|
|
|
=cut |
2056
|
|
|
|
|
|
|
has last_errorcode => ( |
2057
|
|
|
|
|
|
|
reader => 'last_errorcode', |
2058
|
|
|
|
|
|
|
writer => '_set_last_errorcode', |
2059
|
|
|
|
|
|
|
isa => 'Int', |
2060
|
|
|
|
|
|
|
default => $E_NO_ERROR, |
2061
|
|
|
|
|
|
|
); |
2062
|
|
|
|
|
|
|
|
2063
|
|
|
|
|
|
|
#-- public methods ------------------------------------------------------------- |
2064
|
|
|
|
|
|
|
|
2065
|
|
|
|
|
|
|
=head3 insert |
2066
|
|
|
|
|
|
|
|
2067
|
|
|
|
|
|
|
insert( $list_id, $data_id, $data, $data_time ) |
2068
|
|
|
|
|
|
|
|
2069
|
|
|
|
|
|
|
Example: |
2070
|
|
|
|
|
|
|
|
2071
|
|
|
|
|
|
|
$list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' ); |
2072
|
|
|
|
|
|
|
|
2073
|
|
|
|
|
|
|
$list_id = $coll->insert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() ); |
2074
|
|
|
|
|
|
|
|
2075
|
|
|
|
|
|
|
Insert data into the capped collection on the Redis server. |
2076
|
|
|
|
|
|
|
|
2077
|
|
|
|
|
|
|
Arguments: |
2078
|
|
|
|
|
|
|
|
2079
|
|
|
|
|
|
|
=over 3 |
2080
|
|
|
|
|
|
|
|
2081
|
|
|
|
|
|
|
=item C<$list_id> |
2082
|
|
|
|
|
|
|
|
2083
|
|
|
|
|
|
|
Mandatory, non-empty string: list ID. Must not contain C<':'>. |
2084
|
|
|
|
|
|
|
|
2085
|
|
|
|
|
|
|
The data will be inserted into the list with given ID, and the list |
2086
|
|
|
|
|
|
|
is created automatically if it does not exist yet. |
2087
|
|
|
|
|
|
|
|
2088
|
|
|
|
|
|
|
=item C<$data_id> |
2089
|
|
|
|
|
|
|
|
2090
|
|
|
|
|
|
|
Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id> |
2091
|
|
|
|
|
|
|
argument. |
2092
|
|
|
|
|
|
|
|
2093
|
|
|
|
|
|
|
=item C<$data> |
2094
|
|
|
|
|
|
|
|
2095
|
|
|
|
|
|
|
Data value: a string. Data length should not exceed value of L attribute. |
2096
|
|
|
|
|
|
|
|
2097
|
|
|
|
|
|
|
=item C<$data_time> |
2098
|
|
|
|
|
|
|
|
2099
|
|
|
|
|
|
|
Optional data time, a non-negative number. If not specified, the current |
2100
|
|
|
|
|
|
|
value returned by C |
2101
|
|
|
|
|
|
|
returned by L module) are supported to have time |
2102
|
|
|
|
|
|
|
granularity of less than 1 second and stored with 4 decimal places. |
2103
|
|
|
|
|
|
|
|
2104
|
|
|
|
|
|
|
=back |
2105
|
|
|
|
|
|
|
|
2106
|
|
|
|
|
|
|
If collection is set to C and C<$data_time> less than time of the last removed |
2107
|
|
|
|
|
|
|
element (C - see C) then C is set to 0. |
2108
|
|
|
|
|
|
|
The L attribute value is used in the L. |
2109
|
|
|
|
|
|
|
|
2110
|
|
|
|
|
|
|
The method returns the ID of the data list to which the data was inserted (value of |
2111
|
|
|
|
|
|
|
the C<$list_id> argument). |
2112
|
|
|
|
|
|
|
|
2113
|
|
|
|
|
|
|
=cut |
2114
|
|
|
|
|
|
|
sub insert { |
2115
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2116
|
0
|
|
|
|
|
|
my $list_id = shift; |
2117
|
0
|
|
|
|
|
|
my $data_id = shift; |
2118
|
0
|
|
|
|
|
|
my $data = shift; |
2119
|
0
|
|
0
|
|
|
|
my $data_time = shift // time; |
2120
|
|
|
|
|
|
|
|
2121
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2122
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2123
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2124
|
0
|
0
|
|
|
|
|
$list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2125
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2126
|
0
|
0
|
0
|
|
|
|
( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' ); |
2127
|
|
|
|
|
|
|
|
2128
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
2129
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
2130
|
|
|
|
|
|
|
|
2131
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2132
|
|
|
|
|
|
|
|
2133
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2134
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'insert' ), |
2135
|
|
|
|
|
|
|
0, |
2136
|
|
|
|
|
|
|
$DEBUG, |
2137
|
|
|
|
|
|
|
$self->name, |
2138
|
|
|
|
|
|
|
$list_id, |
2139
|
|
|
|
|
|
|
$data_id, |
2140
|
|
|
|
|
|
|
$data, |
2141
|
|
|
|
|
|
|
$data_time, |
2142
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
2143
|
|
|
|
|
|
|
$self->_DEBUG, |
2144
|
|
|
|
|
|
|
); |
2145
|
|
|
|
|
|
|
|
2146
|
0
|
|
|
|
|
|
my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret; |
2147
|
|
|
|
|
|
|
|
2148
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) { |
|
|
|
0
|
|
|
|
|
2149
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2150
|
|
|
|
|
|
|
# Normal result: Nothing to do |
2151
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
2152
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2153
|
|
|
|
|
|
|
} elsif ( |
2154
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
2155
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2156
|
|
|
|
|
|
|
) { |
2157
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2158
|
|
|
|
|
|
|
} else { |
2159
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2160
|
|
|
|
|
|
|
} |
2161
|
|
|
|
|
|
|
} else { |
2162
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2163
|
|
|
|
|
|
|
} |
2164
|
|
|
|
|
|
|
|
2165
|
0
|
0
|
|
|
|
|
return wantarray ? ( $list_id, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : $list_id; |
2166
|
|
|
|
|
|
|
} |
2167
|
|
|
|
|
|
|
|
2168
|
|
|
|
|
|
|
=head3 update |
2169
|
|
|
|
|
|
|
|
2170
|
|
|
|
|
|
|
update( $list_id, $data_id, $data, $new_data_time ) |
2171
|
|
|
|
|
|
|
|
2172
|
|
|
|
|
|
|
Example: |
2173
|
|
|
|
|
|
|
|
2174
|
|
|
|
|
|
|
if ( $coll->update( $list_id, $data_id, 'New data' ) ) { |
2175
|
|
|
|
|
|
|
say "Data updated successfully"; |
2176
|
|
|
|
|
|
|
} else { |
2177
|
|
|
|
|
|
|
say "The data is not updated"; |
2178
|
|
|
|
|
|
|
} |
2179
|
|
|
|
|
|
|
|
2180
|
|
|
|
|
|
|
Updates existing data item. |
2181
|
|
|
|
|
|
|
|
2182
|
|
|
|
|
|
|
Arguments: |
2183
|
|
|
|
|
|
|
|
2184
|
|
|
|
|
|
|
=over 3 |
2185
|
|
|
|
|
|
|
|
2186
|
|
|
|
|
|
|
=item C<$list_id> |
2187
|
|
|
|
|
|
|
|
2188
|
|
|
|
|
|
|
Mandatory, non-empty string: list ID. Must not contain C<':'>. |
2189
|
|
|
|
|
|
|
|
2190
|
|
|
|
|
|
|
=item C<$data_id> |
2191
|
|
|
|
|
|
|
|
2192
|
|
|
|
|
|
|
Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id> |
2193
|
|
|
|
|
|
|
argument. |
2194
|
|
|
|
|
|
|
|
2195
|
|
|
|
|
|
|
=item C<$data> |
2196
|
|
|
|
|
|
|
|
2197
|
|
|
|
|
|
|
New data value: a string. Data length should not exceed value of L attribute. |
2198
|
|
|
|
|
|
|
|
2199
|
|
|
|
|
|
|
=item C<$new_data_time> |
2200
|
|
|
|
|
|
|
|
2201
|
|
|
|
|
|
|
Optional new data time, a non-negative number. If not specified, the existing |
2202
|
|
|
|
|
|
|
data time is preserved. |
2203
|
|
|
|
|
|
|
|
2204
|
|
|
|
|
|
|
=back |
2205
|
|
|
|
|
|
|
|
2206
|
|
|
|
|
|
|
If the collection is set to C and C<$new_data_time> less than time of the last |
2207
|
|
|
|
|
|
|
removed element (C - see L) then C is set to 0. |
2208
|
|
|
|
|
|
|
The L attribute value is used in the L. |
2209
|
|
|
|
|
|
|
|
2210
|
|
|
|
|
|
|
Method returns true if the data is updated or false if the list with the given ID does not exist or |
2211
|
|
|
|
|
|
|
is used an invalid data ID. |
2212
|
|
|
|
|
|
|
|
2213
|
|
|
|
|
|
|
Throws an exception on other errors. |
2214
|
|
|
|
|
|
|
|
2215
|
|
|
|
|
|
|
=cut |
2216
|
|
|
|
|
|
|
sub update { |
2217
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2218
|
0
|
|
|
|
|
|
my $list_id = shift; |
2219
|
0
|
|
|
|
|
|
my $data_id = shift; |
2220
|
0
|
|
|
|
|
|
my $data = shift; |
2221
|
|
|
|
|
|
|
|
2222
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2223
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2224
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2225
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2226
|
|
|
|
|
|
|
|
2227
|
0
|
|
|
|
|
|
my $new_data_time; |
2228
|
0
|
0
|
|
|
|
|
if ( @_ ) { |
2229
|
0
|
|
|
|
|
|
$new_data_time = shift; |
2230
|
0
|
0
|
0
|
|
|
|
( defined( _NUMBER( $new_data_time ) ) && $new_data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'new_data_time' ); |
2231
|
|
|
|
|
|
|
} |
2232
|
|
|
|
|
|
|
|
2233
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
2234
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
2235
|
|
|
|
|
|
|
|
2236
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2237
|
|
|
|
|
|
|
|
2238
|
0
|
|
0
|
|
|
|
my @ret = $self->_call_redis( |
2239
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'update' ), |
2240
|
|
|
|
|
|
|
0, |
2241
|
|
|
|
|
|
|
$DEBUG, |
2242
|
|
|
|
|
|
|
$self->name, |
2243
|
|
|
|
|
|
|
$list_id, |
2244
|
|
|
|
|
|
|
$data_id, |
2245
|
|
|
|
|
|
|
$data, |
2246
|
|
|
|
|
|
|
$new_data_time // 0, |
2247
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
2248
|
|
|
|
|
|
|
$self->_DEBUG, |
2249
|
|
|
|
|
|
|
); |
2250
|
|
|
|
|
|
|
|
2251
|
0
|
|
|
|
|
|
my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret; |
2252
|
|
|
|
|
|
|
|
2253
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) { |
|
|
|
0
|
|
|
|
|
2254
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2255
|
0
|
0
|
|
|
|
|
return wantarray ? ( 1, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : 1; |
2256
|
|
|
|
|
|
|
} elsif ( $error == $E_NONEXISTENT_DATA_ID ) { |
2257
|
0
|
|
|
|
|
|
return 0; |
2258
|
|
|
|
|
|
|
} elsif ( |
2259
|
|
|
|
|
|
|
$error == $E_COLLECTION_DELETED |
2260
|
|
|
|
|
|
|
|| $error == $E_DATA_ID_EXISTS |
2261
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2262
|
|
|
|
|
|
|
) { |
2263
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2264
|
|
|
|
|
|
|
} else { |
2265
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2266
|
|
|
|
|
|
|
} |
2267
|
|
|
|
|
|
|
} else { |
2268
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2269
|
|
|
|
|
|
|
} |
2270
|
|
|
|
|
|
|
|
2271
|
0
|
|
|
|
|
|
return; |
2272
|
|
|
|
|
|
|
} |
2273
|
|
|
|
|
|
|
|
2274
|
|
|
|
|
|
|
=head3 upsert |
2275
|
|
|
|
|
|
|
|
2276
|
|
|
|
|
|
|
upsert( $list_id, $data_id, $data, $data_time ) |
2277
|
|
|
|
|
|
|
|
2278
|
|
|
|
|
|
|
Example: |
2279
|
|
|
|
|
|
|
|
2280
|
|
|
|
|
|
|
$list_id = $coll->upsert( 'Some List_id', 'Some Data_id', 'Some data' ); |
2281
|
|
|
|
|
|
|
|
2282
|
|
|
|
|
|
|
$list_id = $coll->upsert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() ); |
2283
|
|
|
|
|
|
|
|
2284
|
|
|
|
|
|
|
If the list C<$list_id> does not contain data with C<$data_id>, |
2285
|
|
|
|
|
|
|
then it behaves like an L, |
2286
|
|
|
|
|
|
|
otherwise behaves like an L. |
2287
|
|
|
|
|
|
|
|
2288
|
|
|
|
|
|
|
The method returns the ID of the data list to which the data was inserted (value of |
2289
|
|
|
|
|
|
|
the C<$list_id> argument) as the L method. |
2290
|
|
|
|
|
|
|
|
2291
|
|
|
|
|
|
|
=cut |
2292
|
|
|
|
|
|
|
sub upsert { |
2293
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2294
|
0
|
|
|
|
|
|
my $list_id = shift; |
2295
|
0
|
|
|
|
|
|
my $data_id = shift; |
2296
|
0
|
|
|
|
|
|
my $data = shift; |
2297
|
0
|
|
|
|
|
|
my $data_time = shift; |
2298
|
|
|
|
|
|
|
|
2299
|
0
|
|
0
|
|
|
|
$data // $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2300
|
0
|
0
|
0
|
|
|
|
( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' ); |
2301
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2302
|
0
|
0
|
|
|
|
|
$list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2303
|
0
|
0
|
|
|
|
|
defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2304
|
0
|
0
|
0
|
|
|
|
!defined( $data_time ) || ( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' ); |
|
|
|
0
|
|
|
|
|
2305
|
|
|
|
|
|
|
|
2306
|
0
|
|
|
|
|
|
my $data_len = bytes::length( $data ); |
2307
|
0
|
0
|
|
|
|
|
( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE ); |
2308
|
|
|
|
|
|
|
|
2309
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2310
|
|
|
|
|
|
|
|
2311
|
0
|
|
0
|
|
|
|
my @ret = $self->_call_redis( |
2312
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'upsert' ), |
2313
|
|
|
|
|
|
|
0, |
2314
|
|
|
|
|
|
|
$DEBUG, |
2315
|
|
|
|
|
|
|
$self->name, |
2316
|
|
|
|
|
|
|
$list_id, |
2317
|
|
|
|
|
|
|
$data_id, |
2318
|
|
|
|
|
|
|
$data, |
2319
|
|
|
|
|
|
|
$data_time // -1, |
2320
|
|
|
|
|
|
|
# Recommend the inclusion of this option in the case of incomprehensible errors |
2321
|
|
|
|
|
|
|
$self->_DEBUG, |
2322
|
|
|
|
|
|
|
time, |
2323
|
|
|
|
|
|
|
); |
2324
|
|
|
|
|
|
|
|
2325
|
0
|
|
|
|
|
|
my ( $error, $cleanings ) = @ret; |
2326
|
|
|
|
|
|
|
|
2327
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $cleanings ) ) ) { |
|
|
|
0
|
|
|
|
|
2328
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
2329
|
|
|
|
|
|
|
# Normal result: Nothing to do |
2330
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
2331
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2332
|
|
|
|
|
|
|
} elsif ( |
2333
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
2334
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
2335
|
|
|
|
|
|
|
) { |
2336
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
2337
|
|
|
|
|
|
|
} elsif ( $error == $E_NONEXISTENT_DATA_ID ) { |
2338
|
|
|
|
|
|
|
# Nothing to do |
2339
|
|
|
|
|
|
|
} else { |
2340
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
2341
|
|
|
|
|
|
|
} |
2342
|
|
|
|
|
|
|
} else { |
2343
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2344
|
|
|
|
|
|
|
} |
2345
|
|
|
|
|
|
|
|
2346
|
0
|
0
|
|
|
|
|
return wantarray ? ( $list_id, $cleanings ) : $list_id; # as insert |
2347
|
|
|
|
|
|
|
} |
2348
|
|
|
|
|
|
|
|
2349
|
|
|
|
|
|
|
=head3 receive |
2350
|
|
|
|
|
|
|
|
2351
|
|
|
|
|
|
|
receive( $list_id, $data_id ) |
2352
|
|
|
|
|
|
|
|
2353
|
|
|
|
|
|
|
Example: |
2354
|
|
|
|
|
|
|
|
2355
|
|
|
|
|
|
|
my @data = $coll->receive( $list_id ); |
2356
|
|
|
|
|
|
|
say "List '$list_id' has '$_'" foreach @data; |
2357
|
|
|
|
|
|
|
# or |
2358
|
|
|
|
|
|
|
my $list_len = $coll->receive( $list_id ); |
2359
|
|
|
|
|
|
|
say "List '$list_id' has '$list_len' item(s)"; |
2360
|
|
|
|
|
|
|
# or |
2361
|
|
|
|
|
|
|
my $data = $coll->receive( $list_id, $data_id ); |
2362
|
|
|
|
|
|
|
say "List '$list_id' has '$data_id'" if defined $data; |
2363
|
|
|
|
|
|
|
|
2364
|
|
|
|
|
|
|
If the C<$data_id> argument is not specified or is an empty string: |
2365
|
|
|
|
|
|
|
|
2366
|
|
|
|
|
|
|
=over 3 |
2367
|
|
|
|
|
|
|
|
2368
|
|
|
|
|
|
|
=item * |
2369
|
|
|
|
|
|
|
|
2370
|
|
|
|
|
|
|
In a list context, the method returns all the data from the list given by |
2371
|
|
|
|
|
|
|
the C<$list_id> identifier. |
2372
|
|
|
|
|
|
|
|
2373
|
|
|
|
|
|
|
Method returns an empty list if the list with the given ID does not exist. |
2374
|
|
|
|
|
|
|
|
2375
|
|
|
|
|
|
|
=item * |
2376
|
|
|
|
|
|
|
|
2377
|
|
|
|
|
|
|
In a scalar context, the method returns the length of the data list given by |
2378
|
|
|
|
|
|
|
the C<$list_id> identifier. |
2379
|
|
|
|
|
|
|
|
2380
|
|
|
|
|
|
|
=back |
2381
|
|
|
|
|
|
|
|
2382
|
|
|
|
|
|
|
If the C<$data_id> argument is specified: |
2383
|
|
|
|
|
|
|
|
2384
|
|
|
|
|
|
|
=over 3 |
2385
|
|
|
|
|
|
|
|
2386
|
|
|
|
|
|
|
=item * |
2387
|
|
|
|
|
|
|
|
2388
|
|
|
|
|
|
|
The method returns the specified element of the data list. |
2389
|
|
|
|
|
|
|
If the data with C<$data_id> ID does not exist, C is returned. |
2390
|
|
|
|
|
|
|
|
2391
|
|
|
|
|
|
|
=back |
2392
|
|
|
|
|
|
|
|
2393
|
|
|
|
|
|
|
=cut |
2394
|
|
|
|
|
|
|
sub receive { |
2395
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id, $data_id ) = @_; |
2396
|
|
|
|
|
|
|
|
2397
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2398
|
|
|
|
|
|
|
|
2399
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2400
|
|
|
|
|
|
|
|
2401
|
0
|
0
|
|
|
|
|
return unless $self->list_exists( $list_id ); |
2402
|
|
|
|
|
|
|
|
2403
|
0
|
0
|
0
|
|
|
|
if ( defined( $data_id ) && $data_id ne '' ) { |
2404
|
0
|
|
0
|
|
|
|
_STRING( $data_id ) // $self->_throw( $E_MISMATCH_ARG, 'data_id' ); |
2405
|
0
|
|
|
|
|
|
return $self->_call_redis( |
2406
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2407
|
|
|
|
|
|
|
0, |
2408
|
|
|
|
|
|
|
$DEBUG, |
2409
|
|
|
|
|
|
|
$self->name, |
2410
|
|
|
|
|
|
|
$list_id, |
2411
|
|
|
|
|
|
|
'val', |
2412
|
|
|
|
|
|
|
$data_id, |
2413
|
|
|
|
|
|
|
); |
2414
|
|
|
|
|
|
|
} else { |
2415
|
0
|
0
|
|
|
|
|
if ( wantarray ) { |
2416
|
0
|
0
|
|
|
|
|
return $self->_call_redis( |
2417
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2418
|
|
|
|
|
|
|
0, |
2419
|
|
|
|
|
|
|
$DEBUG, |
2420
|
|
|
|
|
|
|
$self->name, |
2421
|
|
|
|
|
|
|
$list_id, |
2422
|
|
|
|
|
|
|
defined( $data_id ) ? 'all' : 'vals', |
2423
|
|
|
|
|
|
|
'', |
2424
|
|
|
|
|
|
|
); |
2425
|
|
|
|
|
|
|
} else { |
2426
|
0
|
|
|
|
|
|
return $self->_call_redis( |
2427
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'receive' ), |
2428
|
|
|
|
|
|
|
0, |
2429
|
|
|
|
|
|
|
$DEBUG, |
2430
|
|
|
|
|
|
|
$self->name, |
2431
|
|
|
|
|
|
|
$list_id, |
2432
|
|
|
|
|
|
|
'len', |
2433
|
|
|
|
|
|
|
'', |
2434
|
|
|
|
|
|
|
); |
2435
|
|
|
|
|
|
|
} |
2436
|
|
|
|
|
|
|
} |
2437
|
|
|
|
|
|
|
|
2438
|
0
|
|
|
|
|
|
return; |
2439
|
|
|
|
|
|
|
} |
2440
|
|
|
|
|
|
|
|
2441
|
|
|
|
|
|
|
=head3 pop_oldest |
2442
|
|
|
|
|
|
|
|
2443
|
|
|
|
|
|
|
The method retrieves the oldest data stored in the collection and removes it from |
2444
|
|
|
|
|
|
|
the collection. |
2445
|
|
|
|
|
|
|
|
2446
|
|
|
|
|
|
|
Returns a list of two elements. |
2447
|
|
|
|
|
|
|
The first element contains the identifier of the list from which the data was retrieved. |
2448
|
|
|
|
|
|
|
The second element contains the extracted data. |
2449
|
|
|
|
|
|
|
|
2450
|
|
|
|
|
|
|
The returned data item is removed from the collection. |
2451
|
|
|
|
|
|
|
|
2452
|
|
|
|
|
|
|
Method returns an empty list if the collection does not contain any data. |
2453
|
|
|
|
|
|
|
|
2454
|
|
|
|
|
|
|
The following examples illustrate uses of the C method: |
2455
|
|
|
|
|
|
|
|
2456
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
2457
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
2458
|
|
|
|
|
|
|
} |
2459
|
|
|
|
|
|
|
|
2460
|
|
|
|
|
|
|
=cut |
2461
|
|
|
|
|
|
|
sub pop_oldest { |
2462
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
2463
|
|
|
|
|
|
|
|
2464
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2465
|
|
|
|
|
|
|
|
2466
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2467
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'pop_oldest' ), |
2468
|
|
|
|
|
|
|
0, |
2469
|
|
|
|
|
|
|
$DEBUG, |
2470
|
|
|
|
|
|
|
$self->name, |
2471
|
|
|
|
|
|
|
); |
2472
|
|
|
|
|
|
|
|
2473
|
0
|
|
|
|
|
|
my ( $error, $queue_exist, $to_delete_id, $to_delete_data ) = @ret; |
2474
|
|
|
|
|
|
|
|
2475
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2476
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2477
|
|
|
|
|
|
|
} else { |
2478
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2479
|
|
|
|
|
|
|
} |
2480
|
|
|
|
|
|
|
|
2481
|
0
|
0
|
|
|
|
|
if ( $queue_exist ) { |
2482
|
0
|
|
|
|
|
|
return( $to_delete_id, $to_delete_data ); |
2483
|
|
|
|
|
|
|
} else { |
2484
|
0
|
|
|
|
|
|
return; |
2485
|
|
|
|
|
|
|
} |
2486
|
|
|
|
|
|
|
} |
2487
|
|
|
|
|
|
|
|
2488
|
|
|
|
|
|
|
=head3 redis_config_ok |
2489
|
|
|
|
|
|
|
|
2490
|
|
|
|
|
|
|
redis_config_ok( redis => $server ) |
2491
|
|
|
|
|
|
|
|
2492
|
|
|
|
|
|
|
Example: |
2493
|
|
|
|
|
|
|
|
2494
|
|
|
|
|
|
|
say 'Redis server config ', $coll->redis_config_ok ? 'OK' : 'NOT OK'; |
2495
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2496
|
|
|
|
|
|
|
say 'Redis server config ', |
2497
|
|
|
|
|
|
|
Redis::CappedCollection::redis_config_ok( redis => $redis ) |
2498
|
|
|
|
|
|
|
? 'OK' |
2499
|
|
|
|
|
|
|
: 'NOT OK' |
2500
|
|
|
|
|
|
|
; |
2501
|
|
|
|
|
|
|
|
2502
|
|
|
|
|
|
|
Check whether there is a Redis server config correct, |
2503
|
|
|
|
|
|
|
now that the 'maxmemory-policy' setting is 'noeviction'. |
2504
|
|
|
|
|
|
|
Returns true if config correct and false otherwise. |
2505
|
|
|
|
|
|
|
|
2506
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2507
|
|
|
|
|
|
|
|
2508
|
|
|
|
|
|
|
If invoked as the object method, C uses the C |
2509
|
|
|
|
|
|
|
attribute from the object as default. |
2510
|
|
|
|
|
|
|
|
2511
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C |
2512
|
|
|
|
|
|
|
argument. |
2513
|
|
|
|
|
|
|
|
2514
|
|
|
|
|
|
|
This argument are in key-value pair as described for L method. |
2515
|
|
|
|
|
|
|
|
2516
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
2517
|
|
|
|
|
|
|
|
2518
|
|
|
|
|
|
|
=cut |
2519
|
|
|
|
|
|
|
sub redis_config_ok { |
2520
|
0
|
|
|
0
|
1
|
|
return _maxmemory_policy_ok( @_ ); |
2521
|
|
|
|
|
|
|
} |
2522
|
|
|
|
|
|
|
|
2523
|
|
|
|
|
|
|
=head3 collection_info |
2524
|
|
|
|
|
|
|
|
2525
|
|
|
|
|
|
|
collection_info( redis => $server, name => $name ) |
2526
|
|
|
|
|
|
|
|
2527
|
|
|
|
|
|
|
Example: |
2528
|
|
|
|
|
|
|
|
2529
|
|
|
|
|
|
|
my $info = $coll->collection_info; |
2530
|
|
|
|
|
|
|
say 'An existing collection uses ', $info->{cleanup_bytes}, " byte of 'cleanup_bytes', ", |
2531
|
|
|
|
|
|
|
$info->{items}, ' items are stored in ', $info->{lists}, ' lists'; |
2532
|
|
|
|
|
|
|
# or |
2533
|
|
|
|
|
|
|
my $info = Redis::CappedCollection::collection_info( |
2534
|
|
|
|
|
|
|
redis => $redis, # or redis => { server => "$server:$port" } |
2535
|
|
|
|
|
|
|
name => 'Collection name', |
2536
|
|
|
|
|
|
|
); |
2537
|
|
|
|
|
|
|
|
2538
|
|
|
|
|
|
|
Get collection information and status. |
2539
|
|
|
|
|
|
|
It can be called as either an existing C object method or a class function. |
2540
|
|
|
|
|
|
|
|
2541
|
|
|
|
|
|
|
C arguments are in key-value pairs. |
2542
|
|
|
|
|
|
|
Arguments description match the arguments description for L method: |
2543
|
|
|
|
|
|
|
|
2544
|
|
|
|
|
|
|
=over 3 |
2545
|
|
|
|
|
|
|
|
2546
|
|
|
|
|
|
|
=item C |
2547
|
|
|
|
|
|
|
|
2548
|
|
|
|
|
|
|
=item C |
2549
|
|
|
|
|
|
|
|
2550
|
|
|
|
|
|
|
=back |
2551
|
|
|
|
|
|
|
|
2552
|
|
|
|
|
|
|
If invoked as the object method, C, arguments are optional and |
2553
|
|
|
|
|
|
|
use corresponding object attributes as defaults. |
2554
|
|
|
|
|
|
|
|
2555
|
|
|
|
|
|
|
If called as a class methods, the arguments are mandatory. |
2556
|
|
|
|
|
|
|
|
2557
|
|
|
|
|
|
|
Returns a reference to a hash with the following elements: |
2558
|
|
|
|
|
|
|
|
2559
|
|
|
|
|
|
|
=over 3 |
2560
|
|
|
|
|
|
|
|
2561
|
|
|
|
|
|
|
=item * |
2562
|
|
|
|
|
|
|
|
2563
|
|
|
|
|
|
|
C - Number of lists in a collection. |
2564
|
|
|
|
|
|
|
|
2565
|
|
|
|
|
|
|
=item * |
2566
|
|
|
|
|
|
|
|
2567
|
|
|
|
|
|
|
C - Number of data items stored in the collection. |
2568
|
|
|
|
|
|
|
|
2569
|
|
|
|
|
|
|
=item * |
2570
|
|
|
|
|
|
|
|
2571
|
|
|
|
|
|
|
C - Time of the oldest data in the collection. |
2572
|
|
|
|
|
|
|
C if the collection does not contain data. |
2573
|
|
|
|
|
|
|
|
2574
|
|
|
|
|
|
|
=item * |
2575
|
|
|
|
|
|
|
|
2576
|
|
|
|
|
|
|
C - True if it is allowed to put data in collection that is older than the last element |
2577
|
|
|
|
|
|
|
removed from collection. |
2578
|
|
|
|
|
|
|
|
2579
|
|
|
|
|
|
|
=item * |
2580
|
|
|
|
|
|
|
|
2581
|
|
|
|
|
|
|
C - Memory reserve coefficient. |
2582
|
|
|
|
|
|
|
|
2583
|
|
|
|
|
|
|
=item * |
2584
|
|
|
|
|
|
|
|
2585
|
|
|
|
|
|
|
C - The minimum size, in bytes, |
2586
|
|
|
|
|
|
|
of the data to be released when performing memory cleanup. |
2587
|
|
|
|
|
|
|
|
2588
|
|
|
|
|
|
|
=item * |
2589
|
|
|
|
|
|
|
|
2590
|
|
|
|
|
|
|
C - The minimum number of the collection elements |
2591
|
|
|
|
|
|
|
to be realesed when performing memory cleanup. |
2592
|
|
|
|
|
|
|
|
2593
|
|
|
|
|
|
|
=item * |
2594
|
|
|
|
|
|
|
|
2595
|
|
|
|
|
|
|
C - Maximum list items limit. |
2596
|
|
|
|
|
|
|
|
2597
|
|
|
|
|
|
|
=item * |
2598
|
|
|
|
|
|
|
|
2599
|
|
|
|
|
|
|
C - Data structure version. |
2600
|
|
|
|
|
|
|
|
2601
|
|
|
|
|
|
|
=item * |
2602
|
|
|
|
|
|
|
|
2603
|
|
|
|
|
|
|
C - time of the last removed element from collection |
2604
|
|
|
|
|
|
|
or 0 if nothing was removed from collection yet. |
2605
|
|
|
|
|
|
|
|
2606
|
|
|
|
|
|
|
=back |
2607
|
|
|
|
|
|
|
|
2608
|
|
|
|
|
|
|
An error will cause the program to throw an exception (C) if an argument is not valid |
2609
|
|
|
|
|
|
|
or the collection does not exist. |
2610
|
|
|
|
|
|
|
|
2611
|
|
|
|
|
|
|
=cut |
2612
|
|
|
|
|
|
|
my @_collection_info_result_keys = qw( |
2613
|
|
|
|
|
|
|
error |
2614
|
|
|
|
|
|
|
lists |
2615
|
|
|
|
|
|
|
items |
2616
|
|
|
|
|
|
|
older_allowed |
2617
|
|
|
|
|
|
|
cleanup_bytes |
2618
|
|
|
|
|
|
|
cleanup_items |
2619
|
|
|
|
|
|
|
max_list_items |
2620
|
|
|
|
|
|
|
memory_reserve |
2621
|
|
|
|
|
|
|
data_version |
2622
|
|
|
|
|
|
|
last_removed_time |
2623
|
|
|
|
|
|
|
oldest_time |
2624
|
|
|
|
|
|
|
); |
2625
|
|
|
|
|
|
|
|
2626
|
|
|
|
|
|
|
sub collection_info { |
2627
|
0
|
|
|
0
|
1
|
|
my $results = {}; |
2628
|
0
|
|
|
|
|
|
my @ret; |
2629
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2630
|
0
|
|
|
|
|
|
my $self = shift; |
2631
|
|
|
|
|
|
|
|
2632
|
0
|
|
|
|
|
|
my %arguments = @_; |
2633
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [] ); |
2634
|
|
|
|
|
|
|
|
2635
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2636
|
|
|
|
|
|
|
|
2637
|
0
|
|
|
|
|
|
@ret = $self->_call_redis( |
2638
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'collection_info' ), |
2639
|
|
|
|
|
|
|
0, |
2640
|
|
|
|
|
|
|
$DEBUG, |
2641
|
|
|
|
|
|
|
$self->name, |
2642
|
|
|
|
|
|
|
); |
2643
|
0
|
|
|
|
|
|
$results = _lists2hash( \@_collection_info_result_keys, \@ret ); |
2644
|
|
|
|
|
|
|
|
2645
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2646
|
|
|
|
|
|
|
|
2647
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2648
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2649
|
|
|
|
|
|
|
} else { |
2650
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2651
|
|
|
|
|
|
|
} |
2652
|
|
|
|
|
|
|
} else { |
2653
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2654
|
|
|
|
|
|
|
|
2655
|
0
|
|
|
|
|
|
my %arguments = @_; |
2656
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
2657
|
|
|
|
|
|
|
|
2658
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2659
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2660
|
|
|
|
|
|
|
|
2661
|
0
|
|
|
|
|
|
my $redis = _get_redis( delete $arguments{redis} ); |
2662
|
0
|
|
|
|
|
|
my $name = delete $arguments{name}; |
2663
|
|
|
|
|
|
|
|
2664
|
0
|
0
|
|
|
|
|
_croak( 'Unknown arguments: ', join( ', ', keys %arguments ) ) if %arguments; |
2665
|
|
|
|
|
|
|
|
2666
|
0
|
|
|
|
|
|
@ret = _call_redis( |
2667
|
|
|
|
|
|
|
$redis, |
2668
|
|
|
|
|
|
|
_lua_script_cmd( $redis, 'collection_info' ), |
2669
|
|
|
|
|
|
|
0, |
2670
|
|
|
|
|
|
|
$DEBUG, |
2671
|
|
|
|
|
|
|
$name, |
2672
|
|
|
|
|
|
|
); |
2673
|
0
|
|
|
|
|
|
$results = _lists2hash( \@_collection_info_result_keys, \@ret ); |
2674
|
|
|
|
|
|
|
|
2675
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2676
|
|
|
|
|
|
|
|
2677
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2678
|
0
|
0
|
|
|
|
|
if ( $error != $E_NO_ERROR ) { |
2679
|
0
|
|
|
|
|
|
_croak( format_message( "Collection '%s' info not received (%s)", $name, $ERROR{ $error } ) ); |
2680
|
|
|
|
|
|
|
} |
2681
|
|
|
|
|
|
|
} else { |
2682
|
0
|
|
|
|
|
|
_unknown_error( @ret ); |
2683
|
|
|
|
|
|
|
} |
2684
|
|
|
|
|
|
|
} |
2685
|
|
|
|
|
|
|
|
2686
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2687
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2688
|
|
|
|
|
|
|
|
2689
|
0
|
|
|
|
|
|
delete $results->{error}; |
2690
|
0
|
|
|
|
|
|
return $results; |
2691
|
|
|
|
|
|
|
} |
2692
|
|
|
|
|
|
|
|
2693
|
|
|
|
|
|
|
=head3 list_info |
2694
|
|
|
|
|
|
|
|
2695
|
|
|
|
|
|
|
list_info( $list_id ) |
2696
|
|
|
|
|
|
|
|
2697
|
|
|
|
|
|
|
Get data list information and status. |
2698
|
|
|
|
|
|
|
|
2699
|
|
|
|
|
|
|
C<$list_id> must be a non-empty string. |
2700
|
|
|
|
|
|
|
|
2701
|
|
|
|
|
|
|
Returns a reference to a hash with the following elements: |
2702
|
|
|
|
|
|
|
|
2703
|
|
|
|
|
|
|
=over 3 |
2704
|
|
|
|
|
|
|
|
2705
|
|
|
|
|
|
|
=item * |
2706
|
|
|
|
|
|
|
|
2707
|
|
|
|
|
|
|
C - Number of data items stored in the data list. |
2708
|
|
|
|
|
|
|
|
2709
|
|
|
|
|
|
|
=item * |
2710
|
|
|
|
|
|
|
|
2711
|
|
|
|
|
|
|
C - The time of the oldest data in the list. |
2712
|
|
|
|
|
|
|
C if the data list does not exist. |
2713
|
|
|
|
|
|
|
|
2714
|
|
|
|
|
|
|
=back |
2715
|
|
|
|
|
|
|
|
2716
|
|
|
|
|
|
|
=cut |
2717
|
|
|
|
|
|
|
my @_list_info_result_keys = qw( |
2718
|
|
|
|
|
|
|
error |
2719
|
|
|
|
|
|
|
items |
2720
|
|
|
|
|
|
|
oldest_time |
2721
|
|
|
|
|
|
|
); |
2722
|
|
|
|
|
|
|
|
2723
|
|
|
|
|
|
|
sub list_info { |
2724
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
2725
|
|
|
|
|
|
|
|
2726
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2727
|
|
|
|
|
|
|
|
2728
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2729
|
|
|
|
|
|
|
|
2730
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2731
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'list_info' ), |
2732
|
|
|
|
|
|
|
0, |
2733
|
|
|
|
|
|
|
$DEBUG, |
2734
|
|
|
|
|
|
|
$self->name, |
2735
|
|
|
|
|
|
|
$list_id, |
2736
|
|
|
|
|
|
|
); |
2737
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_list_info_result_keys, \@ret ); |
2738
|
|
|
|
|
|
|
|
2739
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2740
|
|
|
|
|
|
|
|
2741
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2742
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2743
|
|
|
|
|
|
|
} else { |
2744
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2745
|
|
|
|
|
|
|
} |
2746
|
|
|
|
|
|
|
|
2747
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2748
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2749
|
|
|
|
|
|
|
|
2750
|
0
|
|
|
|
|
|
delete $results->{error}; |
2751
|
0
|
|
|
|
|
|
return $results; |
2752
|
|
|
|
|
|
|
} |
2753
|
|
|
|
|
|
|
|
2754
|
|
|
|
|
|
|
=head3 oldest_time |
2755
|
|
|
|
|
|
|
|
2756
|
|
|
|
|
|
|
my $oldest_time = $coll->oldest_time; |
2757
|
|
|
|
|
|
|
|
2758
|
|
|
|
|
|
|
Get the time of the oldest data in the collection. |
2759
|
|
|
|
|
|
|
Returns C if the collection does not contain data. |
2760
|
|
|
|
|
|
|
|
2761
|
|
|
|
|
|
|
An error exception is thrown (C) if the collection does not exist. |
2762
|
|
|
|
|
|
|
|
2763
|
|
|
|
|
|
|
=cut |
2764
|
|
|
|
|
|
|
my @_oldest_time_result_keys = qw( |
2765
|
|
|
|
|
|
|
error |
2766
|
|
|
|
|
|
|
oldest_time |
2767
|
|
|
|
|
|
|
); |
2768
|
|
|
|
|
|
|
|
2769
|
|
|
|
|
|
|
sub oldest_time { |
2770
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2771
|
|
|
|
|
|
|
|
2772
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2773
|
|
|
|
|
|
|
|
2774
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
2775
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'oldest_time' ), |
2776
|
|
|
|
|
|
|
0, |
2777
|
|
|
|
|
|
|
$DEBUG, |
2778
|
|
|
|
|
|
|
$self->name, |
2779
|
|
|
|
|
|
|
); |
2780
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_oldest_time_result_keys, \@ret ); |
2781
|
|
|
|
|
|
|
|
2782
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
2783
|
|
|
|
|
|
|
|
2784
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
2785
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
2786
|
|
|
|
|
|
|
} else { |
2787
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
2788
|
|
|
|
|
|
|
} |
2789
|
|
|
|
|
|
|
|
2790
|
0
|
|
|
|
|
|
my $oldest_time = $results->{oldest_time}; |
2791
|
0
|
0
|
0
|
|
|
|
!$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) ); |
2792
|
|
|
|
|
|
|
|
2793
|
0
|
|
|
|
|
|
return $oldest_time; |
2794
|
|
|
|
|
|
|
} |
2795
|
|
|
|
|
|
|
|
2796
|
|
|
|
|
|
|
=head3 list_exists |
2797
|
|
|
|
|
|
|
|
2798
|
|
|
|
|
|
|
list_exists( $list_id ) |
2799
|
|
|
|
|
|
|
|
2800
|
|
|
|
|
|
|
Example: |
2801
|
|
|
|
|
|
|
|
2802
|
|
|
|
|
|
|
say "The collection has '$list_id' list" if $coll->list_exists( 'Some_id' ); |
2803
|
|
|
|
|
|
|
|
2804
|
|
|
|
|
|
|
Check whether there is a list in the collection with given |
2805
|
|
|
|
|
|
|
ID C<$list_id>. |
2806
|
|
|
|
|
|
|
|
2807
|
|
|
|
|
|
|
Returns true if the list exists and false otherwise. |
2808
|
|
|
|
|
|
|
|
2809
|
|
|
|
|
|
|
=cut |
2810
|
|
|
|
|
|
|
sub list_exists { |
2811
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
2812
|
|
|
|
|
|
|
|
2813
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
2814
|
|
|
|
|
|
|
|
2815
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2816
|
|
|
|
|
|
|
|
2817
|
0
|
|
|
|
|
|
return $self->_call_redis( 'EXISTS', $self->_data_list_key( $list_id ) ); |
2818
|
|
|
|
|
|
|
} |
2819
|
|
|
|
|
|
|
|
2820
|
|
|
|
|
|
|
=head3 collection_exists |
2821
|
|
|
|
|
|
|
|
2822
|
|
|
|
|
|
|
collection_exists( redis => $server, name => $name ) |
2823
|
|
|
|
|
|
|
|
2824
|
|
|
|
|
|
|
Example: |
2825
|
|
|
|
|
|
|
|
2826
|
|
|
|
|
|
|
say 'The collection ', $coll->name, ' exists' if $coll->collection_exists; |
2827
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2828
|
|
|
|
|
|
|
say "The collection 'Some name' exists" |
2829
|
|
|
|
|
|
|
if Redis::CappedCollection::collection_exists( redis => $redis, name => 'Some name' ); |
2830
|
|
|
|
|
|
|
|
2831
|
|
|
|
|
|
|
Check whether there is a collection with given name. |
2832
|
|
|
|
|
|
|
Returns true if the collection exists and false otherwise. |
2833
|
|
|
|
|
|
|
|
2834
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2835
|
|
|
|
|
|
|
|
2836
|
|
|
|
|
|
|
If invoked as the object method, C uses C and C |
2837
|
|
|
|
|
|
|
attributes from the object as defaults. |
2838
|
|
|
|
|
|
|
|
2839
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
2840
|
|
|
|
|
|
|
arguments. |
2841
|
|
|
|
|
|
|
|
2842
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
2843
|
|
|
|
|
|
|
|
2844
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
2845
|
|
|
|
|
|
|
|
2846
|
|
|
|
|
|
|
=cut |
2847
|
|
|
|
|
|
|
sub collection_exists { |
2848
|
0
|
|
|
0
|
1
|
|
my ( $self, $redis, $name ); |
2849
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2850
|
0
|
|
|
|
|
|
$self = shift; |
2851
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
2852
|
0
|
|
|
|
|
|
$name = $self->name; |
2853
|
|
|
|
|
|
|
} else { |
2854
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2855
|
|
|
|
|
|
|
} |
2856
|
|
|
|
|
|
|
|
2857
|
0
|
|
|
|
|
|
my %arguments = @_; |
2858
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
2859
|
|
|
|
|
|
|
|
2860
|
0
|
0
|
|
|
|
|
unless ( $self ) { |
2861
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2862
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2863
|
|
|
|
|
|
|
} |
2864
|
|
|
|
|
|
|
|
2865
|
0
|
0
|
|
|
|
|
$redis = _get_redis( $arguments{redis} ) unless $self; |
2866
|
0
|
0
|
|
|
|
|
$name = $arguments{name} if exists $arguments{name}; |
2867
|
|
|
|
|
|
|
|
2868
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2869
|
0
|
|
|
|
|
|
return $self->_call_redis( 'EXISTS', _make_status_key( $name ) ); |
2870
|
|
|
|
|
|
|
} else { |
2871
|
0
|
|
|
|
|
|
return _call_redis( $redis, 'EXISTS', _make_status_key( $name ) ); |
2872
|
|
|
|
|
|
|
} |
2873
|
|
|
|
|
|
|
} |
2874
|
|
|
|
|
|
|
|
2875
|
|
|
|
|
|
|
=head3 lists |
2876
|
|
|
|
|
|
|
|
2877
|
|
|
|
|
|
|
lists( $pattern ) |
2878
|
|
|
|
|
|
|
|
2879
|
|
|
|
|
|
|
Example: |
2880
|
|
|
|
|
|
|
|
2881
|
|
|
|
|
|
|
say "The collection has '$_' list" foreach $coll->lists; |
2882
|
|
|
|
|
|
|
|
2883
|
|
|
|
|
|
|
Returns an array of list ID of lists stored in a collection. |
2884
|
|
|
|
|
|
|
Returns all list IDs matching C<$pattern> if C<$pattern> is not empty. |
2885
|
|
|
|
|
|
|
C<$patten> must be a non-empty string. |
2886
|
|
|
|
|
|
|
|
2887
|
|
|
|
|
|
|
Supported glob-style patterns: |
2888
|
|
|
|
|
|
|
|
2889
|
|
|
|
|
|
|
=over 3 |
2890
|
|
|
|
|
|
|
|
2891
|
|
|
|
|
|
|
=item * |
2892
|
|
|
|
|
|
|
|
2893
|
|
|
|
|
|
|
C matches C, C and C |
2894
|
|
|
|
|
|
|
|
2895
|
|
|
|
|
|
|
=item * |
2896
|
|
|
|
|
|
|
|
2897
|
|
|
|
|
|
|
C matches C and C |
2898
|
|
|
|
|
|
|
|
2899
|
|
|
|
|
|
|
=item * |
2900
|
|
|
|
|
|
|
|
2901
|
|
|
|
|
|
|
C matches C and C, but not C |
2902
|
|
|
|
|
|
|
|
2903
|
|
|
|
|
|
|
=back |
2904
|
|
|
|
|
|
|
|
2905
|
|
|
|
|
|
|
Use C<'\'> to escape special characters if you want to match them verbatim. |
2906
|
|
|
|
|
|
|
|
2907
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
2908
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
2909
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
2910
|
|
|
|
|
|
|
Don't use C in your regular application code. |
2911
|
|
|
|
|
|
|
|
2912
|
|
|
|
|
|
|
In addition, it may cause an exception (C) if |
2913
|
|
|
|
|
|
|
the collection contains a very large number of lists |
2914
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
2915
|
|
|
|
|
|
|
|
2916
|
|
|
|
|
|
|
=cut |
2917
|
|
|
|
|
|
|
sub lists { |
2918
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
2919
|
0
|
|
0
|
|
|
|
my $pattern = shift // '*'; |
2920
|
|
|
|
|
|
|
|
2921
|
0
|
|
0
|
|
|
|
_STRING( $pattern ) // $self->_throw( $E_MISMATCH_ARG, 'pattern' ); |
2922
|
|
|
|
|
|
|
|
2923
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
2924
|
|
|
|
|
|
|
|
2925
|
0
|
|
|
|
|
|
my @keys; |
2926
|
|
|
|
|
|
|
try { |
2927
|
0
|
|
|
0
|
|
|
@keys = $self->_call_redis( 'KEYS', $self->_data_list_key( $pattern ) ); |
2928
|
|
|
|
|
|
|
} catch { |
2929
|
0
|
|
|
0
|
|
|
my $error = $_; |
2930
|
0
|
0
|
|
|
|
|
_croak( $error ) unless $self->last_errorcode == $E_REDIS_DID_NOT_RETURN_DATA; |
2931
|
0
|
|
|
|
|
|
}; |
2932
|
|
|
|
|
|
|
|
2933
|
0
|
|
|
|
|
|
return map { ( $_ =~ /:([^:]+)$/ )[0] } @keys; |
|
0
|
|
|
|
|
|
|
2934
|
|
|
|
|
|
|
} |
2935
|
|
|
|
|
|
|
|
2936
|
|
|
|
|
|
|
=head3 resize |
2937
|
|
|
|
|
|
|
|
2938
|
|
|
|
|
|
|
resize( redis => $server, name => $name, ... ) |
2939
|
|
|
|
|
|
|
|
2940
|
|
|
|
|
|
|
Example: |
2941
|
|
|
|
|
|
|
|
2942
|
|
|
|
|
|
|
$coll->resize( cleanup_bytes => 100_000 ); |
2943
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
2944
|
|
|
|
|
|
|
Redis::CappedCollection::resize( redis => $redis, name => 'Some name', older_allowed => 1 ); |
2945
|
|
|
|
|
|
|
|
2946
|
|
|
|
|
|
|
Use the C to change the values of the parameters of the collection. |
2947
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
2948
|
|
|
|
|
|
|
|
2949
|
|
|
|
|
|
|
If invoked as the object method, C uses C and C attributes |
2950
|
|
|
|
|
|
|
from the object as defaults. |
2951
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
2952
|
|
|
|
|
|
|
arguments. |
2953
|
|
|
|
|
|
|
|
2954
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
2955
|
|
|
|
|
|
|
|
2956
|
|
|
|
|
|
|
It is possible to change the following parameters: C, C, |
2957
|
|
|
|
|
|
|
C, C. One or more parameters are required. |
2958
|
|
|
|
|
|
|
|
2959
|
|
|
|
|
|
|
Returns the number of completed changes. |
2960
|
|
|
|
|
|
|
|
2961
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid or the |
2962
|
|
|
|
|
|
|
collection does not exist. |
2963
|
|
|
|
|
|
|
|
2964
|
|
|
|
|
|
|
=cut |
2965
|
|
|
|
|
|
|
sub resize { |
2966
|
0
|
|
|
0
|
1
|
|
my ( $self, $redis, $name ); |
2967
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
2968
|
0
|
|
|
|
|
|
$self = shift; |
2969
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
2970
|
0
|
|
|
|
|
|
$name = $self->name; |
2971
|
|
|
|
|
|
|
} else { |
2972
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
2973
|
|
|
|
|
|
|
} |
2974
|
|
|
|
|
|
|
|
2975
|
0
|
|
|
|
|
|
my %arguments = @_; |
2976
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name', @_status_parameters ] ); |
2977
|
|
|
|
|
|
|
|
2978
|
0
|
0
|
|
|
|
|
unless ( $self ) { |
2979
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
2980
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
2981
|
|
|
|
|
|
|
} |
2982
|
|
|
|
|
|
|
|
2983
|
0
|
0
|
|
|
|
|
$redis = _get_redis( $arguments{redis} ) unless $self; |
2984
|
0
|
0
|
|
|
|
|
$name = $arguments{name} if $arguments{name}; |
2985
|
|
|
|
|
|
|
|
2986
|
0
|
|
|
|
|
|
my $requested_changes = 0; |
2987
|
0
|
|
|
|
|
|
foreach my $parameter ( @_status_parameters ) { |
2988
|
0
|
0
|
|
|
|
|
++$requested_changes if exists $arguments{ $parameter }; |
2989
|
|
|
|
|
|
|
} |
2990
|
0
|
0
|
|
|
|
|
unless ( $requested_changes ) { |
2991
|
0
|
|
|
|
|
|
my $error = 'One or more parameters are required'; |
2992
|
0
|
0
|
|
|
|
|
if ( $self ) { |
2993
|
0
|
|
|
|
|
|
$self->_throw( $E_MISMATCH_ARG, $error ); |
2994
|
|
|
|
|
|
|
} else { |
2995
|
0
|
|
|
|
|
|
_croak( format_message( '%s : %s', $error, $ERROR{ $E_MISMATCH_ARG } ) ); |
2996
|
|
|
|
|
|
|
} |
2997
|
|
|
|
|
|
|
} |
2998
|
|
|
|
|
|
|
|
2999
|
0
|
|
|
|
|
|
my $resized = 0; |
3000
|
0
|
|
|
|
|
|
foreach my $parameter ( @_status_parameters ) { |
3001
|
0
|
0
|
|
|
|
|
if ( exists $arguments{ $parameter } ) { |
3002
|
0
|
0
|
0
|
|
|
|
if ( $parameter eq 'cleanup_bytes' || $parameter eq 'cleanup_items' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3003
|
|
|
|
|
|
|
_croak( "'$parameter' must be nonnegative integer" ) |
3004
|
0
|
0
|
|
|
|
|
unless defined( _NONNEGINT( $arguments{ $parameter } ) ); |
3005
|
|
|
|
|
|
|
} elsif ( $parameter eq 'memory_reserve' ) { |
3006
|
0
|
|
|
|
|
|
my $memory_reserve = $arguments{ $parameter }; |
3007
|
0
|
0
|
0
|
|
|
|
_croak( format_message( "'%s' must have a valid value", $parameter ) ) |
|
|
|
0
|
|
|
|
|
3008
|
|
|
|
|
|
|
unless _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE; |
3009
|
|
|
|
|
|
|
} elsif ( $parameter eq 'older_allowed' ) { |
3010
|
0
|
0
|
|
|
|
|
$arguments{ $parameter } = $arguments{ $parameter } ? 1 :0; |
3011
|
|
|
|
|
|
|
} elsif ( $parameter eq 'max_list_items' ) { |
3012
|
|
|
|
|
|
|
_croak( "'$parameter' must be nonnegative integer" ) |
3013
|
0
|
0
|
|
|
|
|
unless defined( _NONNEGINT( $arguments{ $parameter } ) ); |
3014
|
|
|
|
|
|
|
} |
3015
|
|
|
|
|
|
|
|
3016
|
0
|
|
|
|
|
|
my $ret = 0; |
3017
|
0
|
|
|
|
|
|
my $new_val = $arguments{ $parameter }; |
3018
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3019
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( 'HSET', _make_status_key( $self->name ), $parameter, $new_val ); |
3020
|
|
|
|
|
|
|
} else { |
3021
|
0
|
|
|
|
|
|
$ret = _call_redis( $redis, 'HSET', _make_status_key( $name ), $parameter, $new_val ); |
3022
|
|
|
|
|
|
|
} |
3023
|
|
|
|
|
|
|
|
3024
|
0
|
0
|
|
|
|
|
if ( $ret == 0 ) { # 0 if field already exists in the hash and the value was updated |
3025
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3026
|
0
|
0
|
|
|
|
|
if ( $parameter eq 'cleanup_bytes' ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3027
|
0
|
|
|
|
|
|
$self->_set_cleanup_bytes( $new_val ); |
3028
|
|
|
|
|
|
|
} elsif ( $parameter eq 'cleanup_items' ) { |
3029
|
0
|
|
|
|
|
|
$self->_set_cleanup_items( $new_val ); |
3030
|
|
|
|
|
|
|
} elsif ( $parameter eq 'memory_reserve' ) { |
3031
|
0
|
|
|
|
|
|
$self->_set_memory_reserve( $new_val ); |
3032
|
|
|
|
|
|
|
} elsif ( $parameter eq 'max_list_items' ) { |
3033
|
0
|
|
|
|
|
|
$self->_set_max_list_items( $new_val ); |
3034
|
|
|
|
|
|
|
} else { |
3035
|
0
|
|
|
|
|
|
$self->$parameter( $new_val ); |
3036
|
|
|
|
|
|
|
} |
3037
|
|
|
|
|
|
|
} |
3038
|
0
|
|
|
|
|
|
++$resized; |
3039
|
|
|
|
|
|
|
} else { |
3040
|
0
|
|
|
|
|
|
my $msg = format_message( "Parameter %s not updated to '%s' for collection '%s'", $parameter, $new_val, $name ); |
3041
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3042
|
0
|
|
|
|
|
|
$self->_throw( $E_COLLECTION_DELETED, $msg ); |
3043
|
|
|
|
|
|
|
} else { |
3044
|
0
|
|
|
|
|
|
_croak( "$msg (".$ERROR{ $E_COLLECTION_DELETED }.')' ); |
3045
|
|
|
|
|
|
|
} |
3046
|
|
|
|
|
|
|
} |
3047
|
|
|
|
|
|
|
} |
3048
|
|
|
|
|
|
|
} |
3049
|
|
|
|
|
|
|
|
3050
|
0
|
|
|
|
|
|
return $resized; |
3051
|
|
|
|
|
|
|
} |
3052
|
|
|
|
|
|
|
|
3053
|
|
|
|
|
|
|
=head3 drop_collection |
3054
|
|
|
|
|
|
|
|
3055
|
|
|
|
|
|
|
drop_collection( redis => $server, name => $name ) |
3056
|
|
|
|
|
|
|
|
3057
|
|
|
|
|
|
|
Example: |
3058
|
|
|
|
|
|
|
|
3059
|
|
|
|
|
|
|
$coll->drop_collection; |
3060
|
|
|
|
|
|
|
my $redis = Redis->new( server => "$server:$port" ); |
3061
|
|
|
|
|
|
|
Redis::CappedCollection::drop_collection( redis => $redis, name => 'Some name' ); |
3062
|
|
|
|
|
|
|
|
3063
|
|
|
|
|
|
|
Use the C to remove the entire collection from the redis server, |
3064
|
|
|
|
|
|
|
including all its data and metadata. |
3065
|
|
|
|
|
|
|
|
3066
|
|
|
|
|
|
|
Before using this method, make sure that the collection is not being used by other customers. |
3067
|
|
|
|
|
|
|
|
3068
|
|
|
|
|
|
|
It can be called as either the existing C object method or a class function. |
3069
|
|
|
|
|
|
|
If invoked as the class function, C requires mandatory C and C |
3070
|
|
|
|
|
|
|
arguments. |
3071
|
|
|
|
|
|
|
These arguments are in key-value pairs as described for L method. |
3072
|
|
|
|
|
|
|
|
3073
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
3074
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
3075
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
3076
|
|
|
|
|
|
|
Avoid using C in your regular application code. |
3077
|
|
|
|
|
|
|
|
3078
|
|
|
|
|
|
|
C mat throw an exception (C) if |
3079
|
|
|
|
|
|
|
the collection contains a very large number of lists |
3080
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
3081
|
|
|
|
|
|
|
|
3082
|
|
|
|
|
|
|
An error exception is thrown (C) if an argument is not valid. |
3083
|
|
|
|
|
|
|
|
3084
|
|
|
|
|
|
|
=cut |
3085
|
|
|
|
|
|
|
sub drop_collection { |
3086
|
0
|
|
|
0
|
1
|
|
my $ret; |
3087
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3088
|
0
|
|
|
|
|
|
my $self = shift; |
3089
|
|
|
|
|
|
|
|
3090
|
0
|
|
|
|
|
|
my %arguments = @_; |
3091
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [] ); |
3092
|
|
|
|
|
|
|
|
3093
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3094
|
|
|
|
|
|
|
|
3095
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( |
3096
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'drop_collection' ), |
3097
|
|
|
|
|
|
|
0, |
3098
|
|
|
|
|
|
|
$DEBUG, |
3099
|
|
|
|
|
|
|
$self->name, |
3100
|
|
|
|
|
|
|
); |
3101
|
|
|
|
|
|
|
|
3102
|
0
|
|
|
|
|
|
$self->_clear_name; |
3103
|
|
|
|
|
|
|
} else { |
3104
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
3105
|
|
|
|
|
|
|
|
3106
|
0
|
|
|
|
|
|
my %arguments = @_; |
3107
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] ); |
3108
|
|
|
|
|
|
|
|
3109
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined $arguments{redis}; |
3110
|
0
|
0
|
|
|
|
|
_croak( "'name' argument is required" ) unless defined $arguments{name}; |
3111
|
|
|
|
|
|
|
|
3112
|
0
|
|
|
|
|
|
my $redis = _get_redis( $arguments{redis} ); |
3113
|
0
|
|
|
|
|
|
my $name = $arguments{name}; |
3114
|
|
|
|
|
|
|
|
3115
|
0
|
|
|
|
|
|
$ret = _call_redis( |
3116
|
|
|
|
|
|
|
$redis, |
3117
|
|
|
|
|
|
|
_lua_script_cmd( $redis, 'drop_collection' ), |
3118
|
|
|
|
|
|
|
0, |
3119
|
|
|
|
|
|
|
$DEBUG, |
3120
|
|
|
|
|
|
|
$name, |
3121
|
|
|
|
|
|
|
); |
3122
|
|
|
|
|
|
|
} |
3123
|
|
|
|
|
|
|
|
3124
|
0
|
|
|
|
|
|
return $ret; |
3125
|
|
|
|
|
|
|
} |
3126
|
|
|
|
|
|
|
|
3127
|
|
|
|
|
|
|
=head3 drop_list |
3128
|
|
|
|
|
|
|
|
3129
|
|
|
|
|
|
|
drop_list( $list_id ) |
3130
|
|
|
|
|
|
|
|
3131
|
|
|
|
|
|
|
Use the C method to remove the entire specified list. |
3132
|
|
|
|
|
|
|
Method removes all the structures on the Redis server associated with |
3133
|
|
|
|
|
|
|
the specified list. |
3134
|
|
|
|
|
|
|
|
3135
|
|
|
|
|
|
|
C<$list_id> must be a non-empty string. |
3136
|
|
|
|
|
|
|
|
3137
|
|
|
|
|
|
|
Method returns true if the list is removed, or false otherwise. |
3138
|
|
|
|
|
|
|
|
3139
|
|
|
|
|
|
|
=cut |
3140
|
|
|
|
|
|
|
my @_drop_list_result_keys = qw( |
3141
|
|
|
|
|
|
|
error |
3142
|
|
|
|
|
|
|
list_removed |
3143
|
|
|
|
|
|
|
); |
3144
|
|
|
|
|
|
|
|
3145
|
|
|
|
|
|
|
sub drop_list { |
3146
|
0
|
|
|
0
|
1
|
|
my ( $self, $list_id ) = @_; |
3147
|
|
|
|
|
|
|
|
3148
|
0
|
|
0
|
|
|
|
_STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' ); |
3149
|
|
|
|
|
|
|
|
3150
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3151
|
|
|
|
|
|
|
|
3152
|
0
|
|
|
|
|
|
my @ret = $self->_call_redis( |
3153
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'drop_list' ), |
3154
|
|
|
|
|
|
|
0, |
3155
|
|
|
|
|
|
|
$DEBUG, |
3156
|
|
|
|
|
|
|
$self->name, |
3157
|
|
|
|
|
|
|
$list_id, |
3158
|
|
|
|
|
|
|
); |
3159
|
0
|
|
|
|
|
|
my $results = _lists2hash( \@_drop_list_result_keys, \@ret ); |
3160
|
|
|
|
|
|
|
|
3161
|
0
|
|
|
|
|
|
my $error = $results->{error}; |
3162
|
|
|
|
|
|
|
|
3163
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $error } ) { |
3164
|
0
|
0
|
|
|
|
|
$self->_throw( $error ) if $error != $E_NO_ERROR; |
3165
|
|
|
|
|
|
|
} else { |
3166
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3167
|
|
|
|
|
|
|
} |
3168
|
|
|
|
|
|
|
|
3169
|
0
|
|
|
|
|
|
return $results->{list_removed}; |
3170
|
|
|
|
|
|
|
} |
3171
|
|
|
|
|
|
|
|
3172
|
|
|
|
|
|
|
=head3 clear_collection |
3173
|
|
|
|
|
|
|
|
3174
|
|
|
|
|
|
|
$coll->clear_collection; |
3175
|
|
|
|
|
|
|
|
3176
|
|
|
|
|
|
|
Use the C to remove the entire collection data from the redis server, |
3177
|
|
|
|
|
|
|
|
3178
|
|
|
|
|
|
|
Before using this method, make sure that the collection is not being used by other customers. |
3179
|
|
|
|
|
|
|
|
3180
|
|
|
|
|
|
|
Warning: consider C as a command that should only be used in production |
3181
|
|
|
|
|
|
|
environments with extreme care. Its performance is not optimal for large collections. |
3182
|
|
|
|
|
|
|
This command is intended for debugging and special operations. |
3183
|
|
|
|
|
|
|
Avoid using C in your regular application code. |
3184
|
|
|
|
|
|
|
|
3185
|
|
|
|
|
|
|
C mat throw an exception (C) if |
3186
|
|
|
|
|
|
|
the collection contains a very large number of lists |
3187
|
|
|
|
|
|
|
(C<'Error while reading from Redis server'>). |
3188
|
|
|
|
|
|
|
|
3189
|
|
|
|
|
|
|
=cut |
3190
|
|
|
|
|
|
|
sub clear_collection { |
3191
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
3192
|
|
|
|
|
|
|
|
3193
|
0
|
|
|
|
|
|
my $ret; |
3194
|
|
|
|
|
|
|
|
3195
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3196
|
|
|
|
|
|
|
|
3197
|
0
|
|
|
|
|
|
$ret = $self->_call_redis( |
3198
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'clear_collection' ), |
3199
|
|
|
|
|
|
|
0, |
3200
|
|
|
|
|
|
|
$DEBUG, |
3201
|
|
|
|
|
|
|
$self->name, |
3202
|
|
|
|
|
|
|
); |
3203
|
|
|
|
|
|
|
|
3204
|
0
|
|
|
|
|
|
return $ret; |
3205
|
|
|
|
|
|
|
} |
3206
|
|
|
|
|
|
|
|
3207
|
|
|
|
|
|
|
=head3 ping |
3208
|
|
|
|
|
|
|
|
3209
|
|
|
|
|
|
|
$is_alive = $coll->ping; |
3210
|
|
|
|
|
|
|
|
3211
|
|
|
|
|
|
|
This command is used to test if a connection is still alive. |
3212
|
|
|
|
|
|
|
|
3213
|
|
|
|
|
|
|
Returns 1 if a connection is still alive or 0 otherwise. |
3214
|
|
|
|
|
|
|
|
3215
|
|
|
|
|
|
|
External connections to the server object (eg, C <$redis = Redis->new( ... );>), |
3216
|
|
|
|
|
|
|
and the collection object can continue to work after calling ping only if the method returned 1. |
3217
|
|
|
|
|
|
|
|
3218
|
|
|
|
|
|
|
If there is no connection to the Redis server (methods return 0), the connection to the server closes. |
3219
|
|
|
|
|
|
|
In this case, to continue working with the collection, |
3220
|
|
|
|
|
|
|
you must re-create the C object with the L method. |
3221
|
|
|
|
|
|
|
When using an external connection to the server, |
3222
|
|
|
|
|
|
|
to check the connection to the server you can use the C<$redis-Eecho( ... )> call. |
3223
|
|
|
|
|
|
|
This is useful to avoid closing the connection to the Redis server unintentionally. |
3224
|
|
|
|
|
|
|
|
3225
|
|
|
|
|
|
|
=cut |
3226
|
|
|
|
|
|
|
sub ping { |
3227
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
3228
|
|
|
|
|
|
|
|
3229
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3230
|
|
|
|
|
|
|
|
3231
|
0
|
|
|
|
|
|
my $ret = $self->_redis->ping; |
3232
|
|
|
|
|
|
|
|
3233
|
0
|
0
|
0
|
|
|
|
return( ( $ret // '' ) eq 'PONG' ? 1 : 0 ); |
3234
|
|
|
|
|
|
|
} |
3235
|
|
|
|
|
|
|
|
3236
|
|
|
|
|
|
|
=head3 quit |
3237
|
|
|
|
|
|
|
|
3238
|
|
|
|
|
|
|
$coll->quit; |
3239
|
|
|
|
|
|
|
|
3240
|
|
|
|
|
|
|
Close the connection with the redis server. |
3241
|
|
|
|
|
|
|
|
3242
|
|
|
|
|
|
|
It does not close the connection to the Redis server if it is an external connection provided |
3243
|
|
|
|
|
|
|
to collection constructor as existing L object. |
3244
|
|
|
|
|
|
|
When using an external connection (eg, C<$redis = Redis-Enew (...);>), |
3245
|
|
|
|
|
|
|
to close the connection to the Redis server, call C<$redis-Equit> after calling this method. |
3246
|
|
|
|
|
|
|
|
3247
|
|
|
|
|
|
|
=cut |
3248
|
|
|
|
|
|
|
sub quit { |
3249
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
3250
|
|
|
|
|
|
|
|
3251
|
0
|
0
|
0
|
|
|
|
return if $] >= 5.14 && ${^GLOBAL_PHASE} eq 'DESTRUCT'; |
3252
|
|
|
|
|
|
|
|
3253
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3254
|
0
|
0
|
|
|
|
|
$self->_redis->quit unless $self->_use_external_connection; |
3255
|
|
|
|
|
|
|
|
3256
|
0
|
|
|
|
|
|
return; |
3257
|
|
|
|
|
|
|
} |
3258
|
|
|
|
|
|
|
|
3259
|
|
|
|
|
|
|
#-- private attributes --------------------------------------------------------- |
3260
|
|
|
|
|
|
|
|
3261
|
|
|
|
|
|
|
has _DEBUG => ( |
3262
|
|
|
|
|
|
|
is => 'rw', |
3263
|
|
|
|
|
|
|
init_arg => undef, |
3264
|
|
|
|
|
|
|
isa => 'Num', |
3265
|
|
|
|
|
|
|
default => 0, |
3266
|
|
|
|
|
|
|
); |
3267
|
|
|
|
|
|
|
|
3268
|
|
|
|
|
|
|
has _check_maxmemory => ( |
3269
|
|
|
|
|
|
|
is => 'ro', |
3270
|
|
|
|
|
|
|
init_arg => 'check_maxmemory', |
3271
|
|
|
|
|
|
|
isa => 'Bool', |
3272
|
|
|
|
|
|
|
default => 1, |
3273
|
|
|
|
|
|
|
); |
3274
|
|
|
|
|
|
|
|
3275
|
|
|
|
|
|
|
has _create_from_naked_new => ( |
3276
|
|
|
|
|
|
|
is => 'ro', |
3277
|
|
|
|
|
|
|
isa => 'Bool', |
3278
|
|
|
|
|
|
|
default => 1, |
3279
|
|
|
|
|
|
|
); |
3280
|
|
|
|
|
|
|
|
3281
|
|
|
|
|
|
|
has _create_from_open => ( |
3282
|
|
|
|
|
|
|
is => 'ro', |
3283
|
|
|
|
|
|
|
isa => 'Bool', |
3284
|
|
|
|
|
|
|
default => 0, |
3285
|
|
|
|
|
|
|
); |
3286
|
|
|
|
|
|
|
|
3287
|
|
|
|
|
|
|
has _use_external_connection => ( |
3288
|
|
|
|
|
|
|
is => 'rw', |
3289
|
|
|
|
|
|
|
isa => 'Bool', |
3290
|
|
|
|
|
|
|
default => 1, |
3291
|
|
|
|
|
|
|
); |
3292
|
|
|
|
|
|
|
|
3293
|
|
|
|
|
|
|
has _server => ( |
3294
|
|
|
|
|
|
|
is => 'rw', |
3295
|
|
|
|
|
|
|
isa => 'Str', |
3296
|
|
|
|
|
|
|
default => $DEFAULT_SERVER.':'.$DEFAULT_PORT, |
3297
|
|
|
|
|
|
|
trigger => sub { |
3298
|
|
|
|
|
|
|
my $self = shift; |
3299
|
|
|
|
|
|
|
$self->_server( $self->_server.':'.$DEFAULT_PORT ) |
3300
|
|
|
|
|
|
|
unless $self->_server =~ /:/; |
3301
|
|
|
|
|
|
|
}, |
3302
|
|
|
|
|
|
|
); |
3303
|
|
|
|
|
|
|
|
3304
|
|
|
|
|
|
|
has _redis => ( |
3305
|
|
|
|
|
|
|
is => 'rw', |
3306
|
|
|
|
|
|
|
# 'Maybe[Test::RedisServer]' to test only |
3307
|
|
|
|
|
|
|
isa => 'Maybe[Redis] | Maybe[Test::RedisServer]', |
3308
|
|
|
|
|
|
|
); |
3309
|
|
|
|
|
|
|
|
3310
|
|
|
|
|
|
|
has _maxmemory => ( |
3311
|
|
|
|
|
|
|
is => 'rw', |
3312
|
|
|
|
|
|
|
isa => __PACKAGE__.'::NonNegInt', |
3313
|
|
|
|
|
|
|
init_arg => undef, |
3314
|
|
|
|
|
|
|
); |
3315
|
|
|
|
|
|
|
|
3316
|
|
|
|
|
|
|
foreach my $attr_name ( qw( |
3317
|
|
|
|
|
|
|
_queue_key |
3318
|
|
|
|
|
|
|
_status_key |
3319
|
|
|
|
|
|
|
_data_keys |
3320
|
|
|
|
|
|
|
_time_keys |
3321
|
|
|
|
|
|
|
) ) { |
3322
|
|
|
|
|
|
|
has $attr_name => ( |
3323
|
|
|
|
|
|
|
is => 'rw', |
3324
|
|
|
|
|
|
|
isa => 'Str', |
3325
|
|
|
|
|
|
|
init_arg => undef, |
3326
|
|
|
|
|
|
|
); |
3327
|
|
|
|
|
|
|
} |
3328
|
|
|
|
|
|
|
|
3329
|
|
|
|
|
|
|
my $_lua_scripts = {}; |
3330
|
|
|
|
|
|
|
|
3331
|
|
|
|
|
|
|
#-- private functions ---------------------------------------------------------- |
3332
|
|
|
|
|
|
|
|
3333
|
|
|
|
|
|
|
sub _check_arguments_acceptability { |
3334
|
0
|
|
|
0
|
|
|
my ( $received_arguments, $acceptable_arguments ) = @_; |
3335
|
|
|
|
|
|
|
|
3336
|
0
|
|
|
|
|
|
my ( %legal_arguments, @unlegal_arguments ); |
3337
|
0
|
|
|
|
|
|
$legal_arguments{ $_ } = 1 foreach @$acceptable_arguments; |
3338
|
0
|
|
|
|
|
|
foreach my $argument ( keys %$received_arguments ) { |
3339
|
0
|
0
|
|
|
|
|
push @unlegal_arguments, $argument unless exists $legal_arguments{ $argument }; |
3340
|
|
|
|
|
|
|
} |
3341
|
|
|
|
|
|
|
|
3342
|
0
|
0
|
|
|
|
|
_croak( format_message( 'Unknown arguments: %s', \@unlegal_arguments ) ) if @unlegal_arguments; |
3343
|
|
|
|
|
|
|
|
3344
|
0
|
|
|
|
|
|
return; |
3345
|
|
|
|
|
|
|
} |
3346
|
|
|
|
|
|
|
|
3347
|
|
|
|
|
|
|
sub _maxmemory_policy_ok { |
3348
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3349
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3350
|
0
|
|
|
|
|
|
$self = shift; |
3351
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3352
|
|
|
|
|
|
|
} else { |
3353
|
0
|
0
|
|
|
|
|
shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar |
3354
|
|
|
|
|
|
|
} |
3355
|
|
|
|
|
|
|
|
3356
|
0
|
|
|
|
|
|
my %arguments = @_; |
3357
|
0
|
|
|
|
|
|
_check_arguments_acceptability( \%arguments, [ 'redis' ] ); |
3358
|
|
|
|
|
|
|
|
3359
|
0
|
|
|
|
|
|
my $maxmemory_policy; |
3360
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3361
|
0
|
|
|
|
|
|
( undef, $maxmemory_policy ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory-policy' ); |
3362
|
|
|
|
|
|
|
} else { |
3363
|
0
|
|
|
|
|
|
my $redis_argument = $arguments{redis}; |
3364
|
0
|
0
|
|
|
|
|
_croak( "'redis' argument is required" ) unless defined( $redis_argument ); |
3365
|
0
|
|
|
|
|
|
( undef, $maxmemory_policy ) = _call_redis( _get_redis( $redis_argument ), 'CONFIG', 'GET', 'maxmemory-policy' ) |
3366
|
|
|
|
|
|
|
} |
3367
|
|
|
|
|
|
|
|
3368
|
0
|
|
0
|
|
|
|
return( defined( $maxmemory_policy ) && $maxmemory_policy eq $USED_MEMORY_POLICY ); |
3369
|
|
|
|
|
|
|
} |
3370
|
|
|
|
|
|
|
|
3371
|
|
|
|
|
|
|
sub _lists2hash { |
3372
|
0
|
|
|
0
|
|
|
my ( $keys, $vals ) = @_; |
3373
|
|
|
|
|
|
|
|
3374
|
0
|
0
|
0
|
|
|
|
_croak( $ERROR{ $E_MISMATCH_ARG }." for internal function '_lists2hash'" ) |
|
|
|
0
|
|
|
|
|
3375
|
|
|
|
|
|
|
unless _ARRAY( $keys ) && _ARRAY0( $vals ) && scalar( @$keys ) >= scalar( @$vals ); |
3376
|
|
|
|
|
|
|
|
3377
|
0
|
|
|
|
|
|
my %hash; |
3378
|
0
|
|
|
|
|
|
for ( my $idx = 0; $idx < @$keys; $idx++ ) { |
3379
|
0
|
|
|
|
|
|
$hash{ $keys->[ $idx ] } = $vals->[ $idx ]; |
3380
|
|
|
|
|
|
|
} |
3381
|
|
|
|
|
|
|
|
3382
|
0
|
|
|
|
|
|
return \%hash; |
3383
|
|
|
|
|
|
|
} |
3384
|
|
|
|
|
|
|
|
3385
|
|
|
|
|
|
|
sub _process_unknown_error { |
3386
|
0
|
|
|
0
|
|
|
my ( $self, @args ) = @_; |
3387
|
|
|
|
|
|
|
|
3388
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3389
|
0
|
0
|
|
|
|
|
_unknown_error( @args, $self->reconnect_on_error ? _reconnect( $self->_redis, $E_UNKNOWN_ERROR ) : () ); |
3390
|
|
|
|
|
|
|
|
3391
|
0
|
|
|
|
|
|
return; |
3392
|
|
|
|
|
|
|
} |
3393
|
|
|
|
|
|
|
|
3394
|
|
|
|
|
|
|
sub _unknown_error { |
3395
|
0
|
|
|
0
|
|
|
my @args = @_; |
3396
|
|
|
|
|
|
|
|
3397
|
0
|
|
|
|
|
|
_croak( format_message( '%s: %s', $ERROR{ $E_UNKNOWN_ERROR }, \@args ) ); |
3398
|
|
|
|
|
|
|
|
3399
|
0
|
|
|
|
|
|
return; |
3400
|
|
|
|
|
|
|
} |
3401
|
|
|
|
|
|
|
|
3402
|
|
|
|
|
|
|
sub _croak { |
3403
|
0
|
|
|
0
|
|
|
my @args = @_; |
3404
|
|
|
|
|
|
|
|
3405
|
0
|
0
|
0
|
|
|
|
if ( $DEBUG == 1 || $DEBUG == 3 ) { |
3406
|
0
|
|
|
|
|
|
confess @args; |
3407
|
|
|
|
|
|
|
} else { |
3408
|
0
|
|
|
|
|
|
croak @args; |
3409
|
|
|
|
|
|
|
} |
3410
|
|
|
|
|
|
|
|
3411
|
0
|
|
|
|
|
|
return; |
3412
|
|
|
|
|
|
|
} |
3413
|
|
|
|
|
|
|
|
3414
|
|
|
|
|
|
|
sub _make_data_key { |
3415
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3416
|
0
|
|
|
|
|
|
return( $NAMESPACE.':D:'.$name ); |
3417
|
|
|
|
|
|
|
} |
3418
|
|
|
|
|
|
|
|
3419
|
|
|
|
|
|
|
sub _make_time_key { |
3420
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3421
|
0
|
|
|
|
|
|
return( $NAMESPACE.':T:'.$name ); |
3422
|
|
|
|
|
|
|
} |
3423
|
|
|
|
|
|
|
|
3424
|
|
|
|
|
|
|
sub _make_status_key { |
3425
|
0
|
|
|
0
|
|
|
my ( $name ) = @_; |
3426
|
0
|
|
|
|
|
|
return( $NAMESPACE.':S:'.$name ); |
3427
|
|
|
|
|
|
|
} |
3428
|
|
|
|
|
|
|
|
3429
|
|
|
|
|
|
|
sub _get_redis { |
3430
|
0
|
|
|
0
|
|
|
my ( $redis ) = @_; |
3431
|
|
|
|
|
|
|
|
3432
|
0
|
0
|
|
|
|
|
$redis = _redis_constructor( $redis ) |
3433
|
|
|
|
|
|
|
unless _INSTANCE( $redis, 'Redis' ); |
3434
|
|
|
|
|
|
|
$redis->connect if |
3435
|
|
|
|
|
|
|
exists( $redis->{no_auto_connect_on_new} ) |
3436
|
|
|
|
|
|
|
&& $redis->{no_auto_connect_on_new} |
3437
|
|
|
|
|
|
|
&& !$redis->{sock} |
3438
|
0
|
0
|
0
|
|
|
|
; |
|
|
|
0
|
|
|
|
|
3439
|
|
|
|
|
|
|
|
3440
|
0
|
|
|
|
|
|
return $redis; |
3441
|
|
|
|
|
|
|
} |
3442
|
|
|
|
|
|
|
|
3443
|
|
|
|
|
|
|
#-- private methods ------------------------------------------------------------ |
3444
|
|
|
|
|
|
|
|
3445
|
|
|
|
|
|
|
# for testing only |
3446
|
|
|
|
|
|
|
sub _long_term_operation { |
3447
|
0
|
|
|
0
|
|
|
my ( $self, $return_as_insert ) = @_; |
3448
|
|
|
|
|
|
|
|
3449
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3450
|
|
|
|
|
|
|
|
3451
|
0
|
0
|
|
|
|
|
my @ret = $self->_call_redis( |
3452
|
|
|
|
|
|
|
$self->_lua_script_cmd( '_long_term_operation' ), |
3453
|
|
|
|
|
|
|
0, |
3454
|
|
|
|
|
|
|
$DEBUG, |
3455
|
|
|
|
|
|
|
$self->name, |
3456
|
|
|
|
|
|
|
$return_as_insert ? 1 : 0, |
3457
|
|
|
|
|
|
|
$_MAX_WORKING_CYCLES, |
3458
|
|
|
|
|
|
|
); |
3459
|
|
|
|
|
|
|
|
3460
|
0
|
|
|
|
|
|
my ( $error ) = @ret; |
3461
|
|
|
|
|
|
|
|
3462
|
0
|
0
|
|
|
|
|
if ( $return_as_insert ) { |
3463
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 1 && exists( $ERROR{ $error } ) ) { |
3464
|
0
|
0
|
0
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
3465
|
|
|
|
|
|
|
# Normal result: Nothing to do |
3466
|
|
|
|
|
|
|
} elsif ( $error == $E_COLLECTION_DELETED ) { |
3467
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
3468
|
|
|
|
|
|
|
} elsif ( |
3469
|
|
|
|
|
|
|
$error == $E_DATA_ID_EXISTS |
3470
|
|
|
|
|
|
|
|| $error == $E_OLDER_THAN_ALLOWED |
3471
|
|
|
|
|
|
|
) { |
3472
|
0
|
|
|
|
|
|
$self->_throw( $error ); |
3473
|
|
|
|
|
|
|
} else { |
3474
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
3475
|
|
|
|
|
|
|
} |
3476
|
|
|
|
|
|
|
} else { |
3477
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3478
|
|
|
|
|
|
|
} |
3479
|
|
|
|
|
|
|
} else { |
3480
|
0
|
0
|
0
|
|
|
|
if ( scalar( @ret ) == 3 && exists( $ERROR{ $error } ) && $ret[2] eq '_long_term_operation' ) { |
|
|
|
0
|
|
|
|
|
3481
|
0
|
0
|
|
|
|
|
if ( $error == $E_NO_ERROR ) { |
3482
|
|
|
|
|
|
|
# Normal result: Nothing to do |
3483
|
|
|
|
|
|
|
} else { |
3484
|
0
|
|
|
|
|
|
$self->_throw( $error, 'Unexpected error' ); |
3485
|
|
|
|
|
|
|
} |
3486
|
|
|
|
|
|
|
} else { |
3487
|
0
|
|
|
|
|
|
$self->_process_unknown_error( @ret ); |
3488
|
|
|
|
|
|
|
} |
3489
|
|
|
|
|
|
|
} |
3490
|
|
|
|
|
|
|
|
3491
|
0
|
|
|
|
|
|
return \@ret; |
3492
|
|
|
|
|
|
|
} |
3493
|
|
|
|
|
|
|
|
3494
|
|
|
|
|
|
|
sub _data_list_key { |
3495
|
0
|
|
|
0
|
|
|
my ( $self, $list_id ) = @_; |
3496
|
|
|
|
|
|
|
|
3497
|
0
|
|
|
|
|
|
return( $self->_data_keys.':'.$list_id ); |
3498
|
|
|
|
|
|
|
} |
3499
|
|
|
|
|
|
|
|
3500
|
|
|
|
|
|
|
sub _time_list_key { |
3501
|
0
|
|
|
0
|
|
|
my ( $self, $list_id ) = @_; |
3502
|
|
|
|
|
|
|
|
3503
|
0
|
|
|
|
|
|
return( $self->_time_keys.':'.$list_id ); |
3504
|
|
|
|
|
|
|
} |
3505
|
|
|
|
|
|
|
|
3506
|
|
|
|
|
|
|
sub _verify_collection { |
3507
|
0
|
|
|
0
|
|
|
my ( $self ) = @_; |
3508
|
|
|
|
|
|
|
|
3509
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3510
|
|
|
|
|
|
|
|
3511
|
0
|
0
|
0
|
|
|
|
my ( $status_exist, $older_allowed, $cleanup_bytes, $cleanup_items, $max_list_items, $memory_reserve, $data_version ) = $self->_call_redis( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3512
|
|
|
|
|
|
|
$self->_lua_script_cmd( 'verify_collection' ), |
3513
|
|
|
|
|
|
|
0, |
3514
|
|
|
|
|
|
|
$DEBUG, |
3515
|
|
|
|
|
|
|
$self->name, |
3516
|
|
|
|
|
|
|
$self->older_allowed ? 1 : 0, |
3517
|
|
|
|
|
|
|
$self->cleanup_bytes || 0, |
3518
|
|
|
|
|
|
|
$self->cleanup_items || 0, |
3519
|
|
|
|
|
|
|
$self->max_list_items || 0, |
3520
|
|
|
|
|
|
|
$self->memory_reserve || $MIN_MEMORY_RESERVE, |
3521
|
|
|
|
|
|
|
); |
3522
|
|
|
|
|
|
|
|
3523
|
0
|
0
|
|
|
|
|
if ( $status_exist ) { |
3524
|
0
|
0
|
|
|
|
|
$self->cleanup_bytes( $cleanup_bytes ) unless $self->cleanup_bytes; |
3525
|
0
|
0
|
|
|
|
|
$self->cleanup_items( $cleanup_items ) unless $self->cleanup_items; |
3526
|
0
|
0
|
|
|
|
|
$max_list_items == $self->max_list_items or $self->_throw( $E_MISMATCH_ARG, 'max_list_items' ); |
3527
|
0
|
0
|
|
|
|
|
$older_allowed == $self->older_allowed or $self->_throw( $E_MISMATCH_ARG, 'older_allowed' ); |
3528
|
0
|
0
|
|
|
|
|
$cleanup_bytes == $self->cleanup_bytes or $self->_throw( $E_MISMATCH_ARG, 'cleanup_bytes' ); |
3529
|
0
|
0
|
|
|
|
|
$cleanup_items == $self->cleanup_items or $self->_throw( $E_MISMATCH_ARG, 'cleanup_items' ); |
3530
|
0
|
0
|
|
|
|
|
$memory_reserve == $self->memory_reserve or $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' ); |
3531
|
0
|
0
|
|
|
|
|
$data_version == $DATA_VERSION or $self->_throw( $E_INCOMP_DATA_VERSION ); |
3532
|
|
|
|
|
|
|
} |
3533
|
|
|
|
|
|
|
|
3534
|
0
|
|
|
|
|
|
return; |
3535
|
|
|
|
|
|
|
} |
3536
|
|
|
|
|
|
|
|
3537
|
|
|
|
|
|
|
sub _reconnect { |
3538
|
0
|
|
|
0
|
|
|
my $redis = shift; |
3539
|
0
|
|
0
|
|
|
|
my $err = shift // 0; |
3540
|
0
|
|
|
|
|
|
my $msg = shift; |
3541
|
|
|
|
|
|
|
|
3542
|
0
|
|
|
|
|
|
my $err_msg = ''; |
3543
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3544
|
|
|
|
|
|
|
!$err || ( |
3545
|
|
|
|
|
|
|
$err != $E_MISMATCH_ARG |
3546
|
|
|
|
|
|
|
&& $err != $E_DATA_TOO_LARGE |
3547
|
|
|
|
|
|
|
&& $err != $E_MAXMEMORY_LIMIT |
3548
|
|
|
|
|
|
|
&& $err != $E_MAXMEMORY_POLICY |
3549
|
|
|
|
|
|
|
) |
3550
|
|
|
|
|
|
|
) { |
3551
|
|
|
|
|
|
|
try { |
3552
|
0
|
|
|
0
|
|
|
$redis->quit; |
3553
|
0
|
|
|
|
|
|
$redis->connect; |
3554
|
|
|
|
|
|
|
} catch { |
3555
|
0
|
|
|
0
|
|
|
my $error = $_; |
3556
|
0
|
|
|
|
|
|
$err_msg = "(Not reconnected: $error)"; |
3557
|
0
|
|
|
|
|
|
}; |
3558
|
|
|
|
|
|
|
} |
3559
|
|
|
|
|
|
|
|
3560
|
0
|
0
|
|
|
|
|
if ( $err_msg ) { |
3561
|
0
|
0
|
|
|
|
|
$msg = defined( $msg ) |
|
|
0
|
|
|
|
|
|
3562
|
|
|
|
|
|
|
? ( $msg ? "$msg " : '' )."($err_msg)" |
3563
|
|
|
|
|
|
|
: $err_msg; |
3564
|
|
|
|
|
|
|
} |
3565
|
|
|
|
|
|
|
|
3566
|
0
|
|
|
|
|
|
return $msg; |
3567
|
|
|
|
|
|
|
} |
3568
|
|
|
|
|
|
|
|
3569
|
|
|
|
|
|
|
sub _throw { |
3570
|
0
|
|
|
0
|
|
|
my ( $self, $err, $prefix ) = @_; |
3571
|
|
|
|
|
|
|
|
3572
|
0
|
0
|
|
|
|
|
if ( exists $ERROR{ $err } ) { |
3573
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $err ); |
3574
|
0
|
0
|
|
|
|
|
_croak( format_message( '%s%s', ( $prefix ? "$prefix : " : '' ), $ERROR{ $err } ) ); |
3575
|
|
|
|
|
|
|
} else { |
3576
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3577
|
0
|
0
|
|
|
|
|
_croak( format_message( '%s: %s%s', $ERROR{ $E_UNKNOWN_ERROR }, ( $prefix ? "$prefix : " : '' ), format_message( '%s', $err ) ) ); |
3578
|
|
|
|
|
|
|
} |
3579
|
|
|
|
|
|
|
|
3580
|
0
|
|
|
|
|
|
return; |
3581
|
|
|
|
|
|
|
} |
3582
|
|
|
|
|
|
|
|
3583
|
|
|
|
|
|
|
my $_running_script_name; |
3584
|
|
|
|
|
|
|
{ |
3585
|
|
|
|
|
|
|
my $_running_script_body; |
3586
|
|
|
|
|
|
|
my %script_prepared; |
3587
|
|
|
|
|
|
|
|
3588
|
|
|
|
|
|
|
sub _lua_script_cmd { |
3589
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3590
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3591
|
0
|
|
|
|
|
|
$self = shift; |
3592
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3593
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3594
|
0
|
|
|
|
|
|
$redis = shift; |
3595
|
|
|
|
|
|
|
} |
3596
|
|
|
|
|
|
|
|
3597
|
0
|
|
|
|
|
|
$_running_script_name = shift; |
3598
|
0
|
|
|
|
|
|
my $sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name }; |
3599
|
0
|
0
|
|
|
|
|
unless ( $sha1 ) { |
3600
|
0
|
0
|
|
|
|
|
unless ( $script_prepared{ $_running_script_name } ) { |
3601
|
0
|
|
|
|
|
|
my ( $start_str, $finish_str ); |
3602
|
0
|
0
|
|
|
|
|
if ( $DEBUG ) { |
3603
|
0
|
|
|
|
|
|
$start_str = " |
3604
|
|
|
|
|
|
|
${_lua_log_work_function} |
3605
|
|
|
|
|
|
|
_log_work( 'start', '$_running_script_name', ARGV ) |
3606
|
|
|
|
|
|
|
"; |
3607
|
0
|
|
|
|
|
|
$finish_str = " |
3608
|
|
|
|
|
|
|
_log_work( 'finish' ) |
3609
|
|
|
|
|
|
|
"; |
3610
|
|
|
|
|
|
|
} else { |
3611
|
0
|
|
|
|
|
|
$finish_str = $start_str = ''; |
3612
|
|
|
|
|
|
|
} |
3613
|
|
|
|
|
|
|
|
3614
|
|
|
|
|
|
|
{ |
3615
|
0
|
|
|
|
|
|
local $/ = ''; |
|
0
|
|
|
|
|
|
|
3616
|
0
|
|
|
|
|
|
$lua_script_body{ $_running_script_name } =~ s/\n+\s*__START_STEP__\n/$start_str/g; |
3617
|
0
|
|
|
|
|
|
$lua_script_body{ $_running_script_name } =~ s/\n+\s*__FINISH_STEP__/$finish_str/g; |
3618
|
|
|
|
|
|
|
} |
3619
|
0
|
|
|
|
|
|
$script_prepared{ $_running_script_name } = 1; |
3620
|
|
|
|
|
|
|
} |
3621
|
|
|
|
|
|
|
|
3622
|
0
|
|
|
|
|
|
$_running_script_body = $lua_script_body{ $_running_script_name }; |
3623
|
0
|
|
|
|
|
|
$sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name } = sha1_hex( $_running_script_body ); |
3624
|
0
|
|
|
|
|
|
my $ret; |
3625
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3626
|
0
|
|
|
|
|
|
$ret = ( $self->_call_redis( 'SCRIPT', 'EXISTS', $sha1 ) )[0]; |
3627
|
|
|
|
|
|
|
} else { |
3628
|
0
|
|
|
|
|
|
$ret = ( _call_redis( $redis, 'SCRIPT', 'EXISTS', $sha1 ) )[0]; |
3629
|
|
|
|
|
|
|
} |
3630
|
0
|
0
|
|
|
|
|
return( 'EVAL', $_running_script_body ) |
3631
|
|
|
|
|
|
|
unless $ret; |
3632
|
|
|
|
|
|
|
} |
3633
|
0
|
|
|
|
|
|
return( 'EVALSHA', $sha1 ); |
3634
|
|
|
|
|
|
|
} |
3635
|
|
|
|
|
|
|
|
3636
|
|
|
|
|
|
|
sub _redis_exception { |
3637
|
0
|
|
|
0
|
|
|
my $self; |
3638
|
0
|
0
|
|
|
|
|
$self = shift if _INSTANCE( $_[0], __PACKAGE__ ); # allow calling $obj->bar |
3639
|
0
|
|
|
|
|
|
my ( $error ) = @_; # allow calling Foo::bar |
3640
|
|
|
|
|
|
|
|
3641
|
0
|
|
|
|
|
|
my $err_msg = ''; |
3642
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3643
|
|
|
|
|
|
|
# Use the error messages from Redis.pm |
3644
|
0
|
0
|
0
|
|
|
|
if ( |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
3645
|
|
|
|
|
|
|
$error =~ /Could not connect to Redis server at / |
3646
|
|
|
|
|
|
|
|| $error =~ /^Can't close socket: / |
3647
|
|
|
|
|
|
|
|| $error =~ /^Not connected to any server/ |
3648
|
|
|
|
|
|
|
# Maybe for pub/sub only |
3649
|
|
|
|
|
|
|
|| $error =~ /^Error while reading from Redis server: / |
3650
|
|
|
|
|
|
|
|| $error =~ /^Redis server closed connection/ |
3651
|
|
|
|
|
|
|
) { |
3652
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NETWORK ); |
3653
|
|
|
|
|
|
|
|
3654
|
|
|
|
|
|
|
# For connection problem |
3655
|
0
|
0
|
|
|
|
|
$err_msg = _reconnect( $self->_redis, $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error; |
3656
|
|
|
|
|
|
|
} elsif ( |
3657
|
|
|
|
|
|
|
$error =~ /^\[[^]]+\]\s+NOSCRIPT No matching script. Please use EVAL./ |
3658
|
|
|
|
|
|
|
) { |
3659
|
0
|
|
|
|
|
|
_clear_sha1( $self->_redis ); |
3660
|
|
|
|
|
|
|
|
3661
|
|
|
|
|
|
|
# No connection problem |
3662
|
0
|
|
|
|
|
|
return 1; |
3663
|
|
|
|
|
|
|
} elsif ( |
3664
|
|
|
|
|
|
|
$error =~ /^\[[^]]+\]\s+-?\Q$REDIS_MEMORY_ERROR_MSG\E/i |
3665
|
|
|
|
|
|
|
|| $error =~ /^\[[^]]+\]\s+-?\Q$REDIS_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }\E/i |
3666
|
|
|
|
|
|
|
) { |
3667
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_MAXMEMORY_LIMIT ); |
3668
|
|
|
|
|
|
|
|
3669
|
|
|
|
|
|
|
# No connection problem |
3670
|
|
|
|
|
|
|
} elsif ( $error =~ /^\[[^]]+\]\s+BUSY Redis is busy running a script/ ){ |
3671
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_UNKNOWN_ERROR ); |
3672
|
|
|
|
|
|
|
|
3673
|
|
|
|
|
|
|
# No connection problem - must wait... |
3674
|
|
|
|
|
|
|
} else { # external ALRM processing here |
3675
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS ); |
3676
|
|
|
|
|
|
|
|
3677
|
|
|
|
|
|
|
# For possible connection problems |
3678
|
0
|
0
|
|
|
|
|
$err_msg = _reconnect( $self->_redis, $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error; |
3679
|
|
|
|
|
|
|
} |
3680
|
|
|
|
|
|
|
} else { |
3681
|
|
|
|
|
|
|
# nothing to do now |
3682
|
|
|
|
|
|
|
} |
3683
|
|
|
|
|
|
|
|
3684
|
0
|
0
|
|
|
|
|
if ( $error =~ /\] ERR Error (?:running|compiling) script/ ) { |
3685
|
0
|
|
|
|
|
|
$error .= "\nLua script '$_running_script_name':\n$_running_script_body"; |
3686
|
|
|
|
|
|
|
} |
3687
|
0
|
|
|
|
|
|
_croak( format_message( '%s %s', $error, $err_msg ) ); |
3688
|
|
|
|
|
|
|
|
3689
|
0
|
|
|
|
|
|
return; |
3690
|
|
|
|
|
|
|
} |
3691
|
|
|
|
|
|
|
} |
3692
|
|
|
|
|
|
|
|
3693
|
|
|
|
|
|
|
sub _clear_sha1 { |
3694
|
0
|
|
|
0
|
|
|
my ( $redis ) = @_; |
3695
|
|
|
|
|
|
|
|
3696
|
0
|
0
|
|
|
|
|
delete( $_lua_scripts->{ $redis } ) if $redis; |
3697
|
|
|
|
|
|
|
|
3698
|
0
|
|
|
|
|
|
return; |
3699
|
|
|
|
|
|
|
} |
3700
|
|
|
|
|
|
|
|
3701
|
|
|
|
|
|
|
sub _redis_constructor { |
3702
|
0
|
|
|
0
|
|
|
my ( $self, $redis, $redis_parameters ); |
3703
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3704
|
0
|
|
|
|
|
|
$self = shift; |
3705
|
0
|
|
|
|
|
|
$redis_parameters = shift; |
3706
|
|
|
|
|
|
|
|
3707
|
0
|
0
|
|
|
|
|
if ( _HASH0( $redis_parameters ) ) { |
3708
|
0
|
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ); |
3709
|
0
|
|
|
|
|
|
$self->_redis_setup( $redis_parameters ); |
3710
|
|
|
|
|
|
|
$redis = try { |
3711
|
0
|
|
|
0
|
|
|
Redis->new( %$redis_parameters ); |
3712
|
|
|
|
|
|
|
} catch { |
3713
|
0
|
|
|
0
|
|
|
my $error = $_; |
3714
|
0
|
|
|
|
|
|
$self->_redis_exception( format_message( '%s; (redis_parameters = %s)', $error, _parameters_2_str( $redis_parameters ) ) ); |
3715
|
0
|
|
|
|
|
|
}; |
3716
|
|
|
|
|
|
|
} else { |
3717
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3718
|
|
|
|
|
|
|
} |
3719
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3720
|
0
|
0
|
|
|
|
|
$redis_parameters = _HASH0( shift ) or _croak( $ERROR{ $E_MISMATCH_ARG } ); |
3721
|
0
|
|
|
|
|
|
_redis_setup( $redis_parameters ); |
3722
|
|
|
|
|
|
|
$redis = try { |
3723
|
0
|
|
|
0
|
|
|
Redis->new( %$redis_parameters ); |
3724
|
|
|
|
|
|
|
} catch { |
3725
|
0
|
|
|
0
|
|
|
my $error = $_; |
3726
|
0
|
|
|
|
|
|
_croak( format_message( "'Redis' exception: %s; (redis_parameters = %s)", $error, _parameters_2_str( $redis_parameters ) ) ); |
3727
|
0
|
|
|
|
|
|
}; |
3728
|
|
|
|
|
|
|
} |
3729
|
|
|
|
|
|
|
|
3730
|
0
|
|
|
|
|
|
return $redis; |
3731
|
|
|
|
|
|
|
} |
3732
|
|
|
|
|
|
|
|
3733
|
|
|
|
|
|
|
sub _redis_setup { |
3734
|
0
|
|
|
0
|
|
|
my $self; |
3735
|
0
|
0
|
0
|
|
|
|
if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3736
|
0
|
|
|
|
|
|
$self = shift; |
3737
|
|
|
|
|
|
|
} |
3738
|
0
|
|
|
|
|
|
my $conf = shift; |
3739
|
|
|
|
|
|
|
|
3740
|
|
|
|
|
|
|
# defaults for the case when the Redis object we create |
3741
|
0
|
0
|
|
|
|
|
$conf->{reconnect} = 1 unless exists $conf->{reconnect}; |
3742
|
0
|
0
|
|
|
|
|
$conf->{every} = 1000 unless exists $conf->{every}; # 1 ms |
3743
|
0
|
0
|
|
|
|
|
$conf->{conservative_reconnect} = 1 unless exists $conf->{conservative_reconnect}; |
3744
|
|
|
|
|
|
|
|
3745
|
|
|
|
|
|
|
$conf->{cnx_timeout} = ( $self ? $self->connection_timeout : undef ) || $DEFAULT_CONNECTION_TIMEOUT |
3746
|
0
|
0
|
0
|
|
|
|
unless $conf->{cnx_timeout}; |
3747
|
|
|
|
|
|
|
$conf->{read_timeout} = ( $self ? $self->operation_timeout : undef ) || $DEFAULT_OPERATION_TIMEOUT |
3748
|
0
|
0
|
0
|
|
|
|
unless $conf->{read_timeout}; |
3749
|
0
|
|
|
|
|
|
$conf->{write_timeout} = $conf->{read_timeout}; |
3750
|
|
|
|
|
|
|
|
3751
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3752
|
0
|
|
|
|
|
|
$self->connection_timeout( $conf->{cnx_timeout} ); |
3753
|
0
|
|
|
|
|
|
$self->operation_timeout( $conf->{read_timeout} ); |
3754
|
|
|
|
|
|
|
} |
3755
|
|
|
|
|
|
|
|
3756
|
0
|
|
|
|
|
|
return; |
3757
|
|
|
|
|
|
|
} |
3758
|
|
|
|
|
|
|
|
3759
|
|
|
|
|
|
|
sub _parameters_2_str { |
3760
|
0
|
|
|
0
|
|
|
my ( $parameters_hash_ref ) = @_; |
3761
|
|
|
|
|
|
|
|
3762
|
0
|
|
|
|
|
|
my %parameters_hash = ( %$parameters_hash_ref ); |
3763
|
0
|
0
|
|
|
|
|
$parameters_hash{password} =~ s/./*/g if defined $parameters_hash{password}; |
3764
|
|
|
|
|
|
|
|
3765
|
0
|
|
|
|
|
|
return format_message( '%s', \%parameters_hash ); |
3766
|
|
|
|
|
|
|
} |
3767
|
|
|
|
|
|
|
|
3768
|
|
|
|
|
|
|
# Keep in mind the default 'redis.conf' values: |
3769
|
|
|
|
|
|
|
# Close the connection after a client is idle for N seconds (0 to disable) |
3770
|
|
|
|
|
|
|
# timeout 300 |
3771
|
|
|
|
|
|
|
|
3772
|
|
|
|
|
|
|
# Send a request to Redis |
3773
|
|
|
|
|
|
|
sub _call_redis { |
3774
|
0
|
|
|
0
|
|
|
my ( $self, $redis ); |
3775
|
0
|
0
|
|
|
|
|
if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar |
3776
|
0
|
|
|
|
|
|
$self = shift; |
3777
|
0
|
|
|
|
|
|
$redis = $self->_redis; |
3778
|
|
|
|
|
|
|
} else { # allow calling Foo::bar |
3779
|
0
|
|
|
|
|
|
$redis = shift; |
3780
|
|
|
|
|
|
|
} |
3781
|
0
|
|
|
|
|
|
my $method = shift; |
3782
|
|
|
|
|
|
|
|
3783
|
0
|
0
|
|
|
|
|
$self->_set_last_errorcode( $E_NO_ERROR ) if $self; |
3784
|
|
|
|
|
|
|
|
3785
|
0
|
0
|
0
|
|
|
|
$self->_wait_used_memory if $self && $method =~ /^EVAL/i; |
3786
|
|
|
|
|
|
|
|
3787
|
0
|
|
|
|
|
|
my @return; |
3788
|
|
|
|
|
|
|
my $try_again; |
3789
|
0
|
|
|
|
|
|
my @args = @_; |
3790
|
|
|
|
|
|
|
RUN_METHOD: { |
3791
|
0
|
|
|
|
|
|
try { |
3792
|
0
|
0
|
|
0
|
|
|
@return = $redis->$method( map { ref( $_ ) ? $$_ : $_ } @args ); |
|
0
|
|
|
|
|
|
|
3793
|
|
|
|
|
|
|
} catch { |
3794
|
0
|
|
|
0
|
|
|
my $error = $_; |
3795
|
0
|
0
|
|
|
|
|
if ( $self ) { |
3796
|
0
|
|
|
|
|
|
$try_again = $self->_redis_exception( $error ); |
3797
|
|
|
|
|
|
|
} else { |
3798
|
0
|
|
|
|
|
|
$try_again = _redis_exception( $error ); |
3799
|
|
|
|
|
|
|
} |
3800
|
0
|
|
|
|
|
|
}; |
3801
|
|
|
|
|
|
|
|
3802
|
0
|
0
|
0
|
|
|
|
if ( $try_again && $method eq 'EVALSHA' ) { |
3803
|
0
|
|
|
|
|
|
$_lua_scripts->{ $redis }->{ $_running_script_name } = $args[0]; # sha1 |
3804
|
0
|
|
|
|
|
|
$args[0] = $lua_script_body{ $_running_script_name }; |
3805
|
0
|
|
|
|
|
|
$method = 'EVAL'; |
3806
|
0
|
|
|
|
|
|
redo RUN_METHOD; |
3807
|
|
|
|
|
|
|
} |
3808
|
|
|
|
|
|
|
} |
3809
|
|
|
|
|
|
|
|
3810
|
0
|
0
|
|
|
|
|
unless ( scalar @return ) { |
3811
|
0
|
0
|
|
|
|
|
$self->_set_last_errorcode( $E_REDIS_DID_NOT_RETURN_DATA ) |
3812
|
|
|
|
|
|
|
if $self; |
3813
|
0
|
|
|
|
|
|
_croak( $ERROR{ $E_REDIS_DID_NOT_RETURN_DATA } ); |
3814
|
|
|
|
|
|
|
} |
3815
|
|
|
|
|
|
|
|
3816
|
0
|
0
|
|
|
|
|
return wantarray ? @return : $return[0]; |
3817
|
|
|
|
|
|
|
} |
3818
|
|
|
|
|
|
|
|
3819
|
|
|
|
|
|
|
sub _wait_used_memory { |
3820
|
0
|
0
|
|
0
|
|
|
return unless $WAIT_USED_MEMORY; |
3821
|
|
|
|
|
|
|
|
3822
|
0
|
|
|
|
|
|
my ( $self ) = @_; |
3823
|
|
|
|
|
|
|
|
3824
|
0
|
|
|
|
|
|
my $sleepped; |
3825
|
0
|
0
|
|
|
|
|
if ( my $maxmemory = $self->_maxmemory ) { |
3826
|
0
|
|
0
|
|
|
|
my $max_timeout = $self->operation_timeout || $DEFAULT_OPERATION_TIMEOUT; |
3827
|
0
|
|
|
|
|
|
my $timeout = 0.01; |
3828
|
0
|
|
|
|
|
|
$sleepped = 0; |
3829
|
0
|
|
|
|
|
|
my $before_memory_info = _decode_info_str( $self->_call_redis( 'INFO', 'memory' ) ); |
3830
|
0
|
|
|
|
|
|
my $after_memory_info = $before_memory_info; |
3831
|
|
|
|
|
|
|
WAIT_USED_MEMORY: { |
3832
|
0
|
|
|
|
|
|
my $used_memory = $after_memory_info->{used_memory}; |
|
0
|
|
|
|
|
|
|
3833
|
0
|
0
|
0
|
|
|
|
if ( $used_memory < $maxmemory || $sleepped > $max_timeout ) { |
3834
|
0
|
0
|
0
|
|
|
|
if ( $DEBUG && $sleepped ) { |
3835
|
0
|
|
|
|
|
|
say STDERR sprintf( "# %.5f [%d] _wait_used_memory: LAST sleepped = %.2f (sec)", |
3836
|
|
|
|
|
|
|
Time::HiRes::time(), |
3837
|
|
|
|
|
|
|
$$, |
3838
|
|
|
|
|
|
|
$sleepped, |
3839
|
|
|
|
|
|
|
); |
3840
|
|
|
|
|
|
|
|
3841
|
|
|
|
|
|
|
# leave only: |
3842
|
|
|
|
|
|
|
# maxmemory |
3843
|
|
|
|
|
|
|
# used_memory |
3844
|
|
|
|
|
|
|
# used_memory_rss |
3845
|
|
|
|
|
|
|
# used_memory_lua |
3846
|
|
|
|
|
|
|
# used_memory_peak |
3847
|
|
|
|
|
|
|
# mem_fragmentation_ratio |
3848
|
0
|
|
|
|
|
|
foreach my $key ( qw( |
3849
|
|
|
|
|
|
|
used_memory_human |
3850
|
|
|
|
|
|
|
used_memory_rss_human |
3851
|
|
|
|
|
|
|
used_memory_peak_human |
3852
|
|
|
|
|
|
|
total_system_memory |
3853
|
|
|
|
|
|
|
total_system_memory_human |
3854
|
|
|
|
|
|
|
used_memory_lua_human |
3855
|
|
|
|
|
|
|
maxmemory_human |
3856
|
|
|
|
|
|
|
maxmemory_policy |
3857
|
|
|
|
|
|
|
mem_allocator |
3858
|
|
|
|
|
|
|
lazyfree_pending_objects |
3859
|
|
|
|
|
|
|
) |
3860
|
|
|
|
|
|
|
) { |
3861
|
0
|
|
|
|
|
|
delete $before_memory_info->{ $key }; |
3862
|
0
|
|
|
|
|
|
delete $after_memory_info->{ $key }; |
3863
|
|
|
|
|
|
|
} |
3864
|
|
|
|
|
|
|
|
3865
|
104
|
|
|
104
|
|
832
|
use Data::Dumper; |
|
104
|
|
|
|
|
141
|
|
|
104
|
|
|
|
|
26639
|
|
3866
|
0
|
|
|
|
|
|
$Data::Dumper::Sortkeys = 1; |
3867
|
0
|
|
|
|
|
|
say STDERR sprintf( "# memory info:\n%s%s", |
3868
|
|
|
|
|
|
|
Data::Dumper->Dump( [ $before_memory_info ], [ 'before' ] ), |
3869
|
|
|
|
|
|
|
Data::Dumper->Dump( [ $after_memory_info ], [ 'after' ] ), |
3870
|
|
|
|
|
|
|
); |
3871
|
|
|
|
|
|
|
} |
3872
|
0
|
|
|
|
|
|
last WAIT_USED_MEMORY; |
3873
|
|
|
|
|
|
|
} |
3874
|
|
|
|
|
|
|
|
3875
|
0
|
|
|
|
|
|
Time::HiRes::sleep $timeout; |
3876
|
0
|
|
|
|
|
|
$sleepped += $timeout; |
3877
|
|
|
|
|
|
|
# $self->_redis->connect; |
3878
|
0
|
|
|
|
|
|
$after_memory_info = _decode_info_str( $self->_call_redis( 'INFO', 'memory' ) ); |
3879
|
0
|
|
|
|
|
|
redo WAIT_USED_MEMORY; |
3880
|
|
|
|
|
|
|
} |
3881
|
|
|
|
|
|
|
} |
3882
|
|
|
|
|
|
|
|
3883
|
0
|
|
|
|
|
|
return $sleepped; |
3884
|
|
|
|
|
|
|
} |
3885
|
|
|
|
|
|
|
|
3886
|
|
|
|
|
|
|
sub _decode_info_str { |
3887
|
0
|
|
|
0
|
|
|
my ( $info_str ) = @_; |
3888
|
|
|
|
|
|
|
|
3889
|
0
|
|
|
|
|
|
return { map { split( /:/, $_, 2 ) } grep { /^[^#]/ } split( /\r\n/, $info_str ) }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
3890
|
|
|
|
|
|
|
} |
3891
|
|
|
|
|
|
|
|
3892
|
|
|
|
|
|
|
sub DESTROY { |
3893
|
|
|
|
|
|
|
my ( $self ) = @_; |
3894
|
|
|
|
|
|
|
|
3895
|
|
|
|
|
|
|
$self->clear_sha1; |
3896
|
|
|
|
|
|
|
} |
3897
|
|
|
|
|
|
|
|
3898
|
|
|
|
|
|
|
#-- Closes and cleans up ------------------------------------------------------- |
3899
|
|
|
|
|
|
|
|
3900
|
104
|
|
|
104
|
|
562
|
no Mouse::Util::TypeConstraints; |
|
104
|
|
|
|
|
101
|
|
|
104
|
|
|
|
|
1251
|
|
3901
|
104
|
|
|
104
|
|
12831
|
no Mouse; # keywords are removed from the package |
|
104
|
|
|
|
|
128
|
|
|
104
|
|
|
|
|
418
|
|
3902
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |
3903
|
|
|
|
|
|
|
|
3904
|
|
|
|
|
|
|
=head2 DIAGNOSTICS |
3905
|
|
|
|
|
|
|
|
3906
|
|
|
|
|
|
|
All recognizable errors in C set corresponding value |
3907
|
|
|
|
|
|
|
into the L and throw an exception (C). |
3908
|
|
|
|
|
|
|
Unidentified errors also throw exceptions but L is not set. |
3909
|
|
|
|
|
|
|
|
3910
|
|
|
|
|
|
|
In addition to errors in the L module, detected errors are |
3911
|
|
|
|
|
|
|
L$E_MISMATCH_ARG>, L$E_DATA_TOO_LARGE>, L$E_MAXMEMORY_POLICY>, L$E_COLLECTION_DELETED>, |
3912
|
|
|
|
|
|
|
L$E_DATA_ID_EXISTS>, L$E_OLDER_THAN_ALLOWED>, L$E_NONEXISTENT_DATA_ID>, |
3913
|
|
|
|
|
|
|
L$E_INCOMP_DATA_VERSION>, L$E_REDIS_DID_NOT_RETURN_DATA>, L$E_UNKNOWN_ERROR>. |
3914
|
|
|
|
|
|
|
|
3915
|
|
|
|
|
|
|
The user has the choice: |
3916
|
|
|
|
|
|
|
|
3917
|
|
|
|
|
|
|
=over 3 |
3918
|
|
|
|
|
|
|
|
3919
|
|
|
|
|
|
|
=item * |
3920
|
|
|
|
|
|
|
|
3921
|
|
|
|
|
|
|
Use the module methods and independently analyze the situation without the use |
3922
|
|
|
|
|
|
|
of L. |
3923
|
|
|
|
|
|
|
|
3924
|
|
|
|
|
|
|
=item * |
3925
|
|
|
|
|
|
|
|
3926
|
|
|
|
|
|
|
Piece of code wrapped in C and analyze L |
3927
|
|
|
|
|
|
|
(look at the L"An Example"> section). |
3928
|
|
|
|
|
|
|
|
3929
|
|
|
|
|
|
|
=back |
3930
|
|
|
|
|
|
|
|
3931
|
|
|
|
|
|
|
=head2 Debug mode |
3932
|
|
|
|
|
|
|
|
3933
|
|
|
|
|
|
|
An error exception is thrown with C if the package variable C<$DEBUG> set to true. |
3934
|
|
|
|
|
|
|
|
3935
|
|
|
|
|
|
|
=head2 An Example |
3936
|
|
|
|
|
|
|
|
3937
|
|
|
|
|
|
|
An example of error handling. |
3938
|
|
|
|
|
|
|
|
3939
|
|
|
|
|
|
|
use 5.010; |
3940
|
|
|
|
|
|
|
use strict; |
3941
|
|
|
|
|
|
|
use warnings; |
3942
|
|
|
|
|
|
|
|
3943
|
|
|
|
|
|
|
#-- Common --------------------------------------------------------- |
3944
|
|
|
|
|
|
|
use Redis::CappedCollection qw( |
3945
|
|
|
|
|
|
|
$DEFAULT_SERVER |
3946
|
|
|
|
|
|
|
$DEFAULT_PORT |
3947
|
|
|
|
|
|
|
|
3948
|
|
|
|
|
|
|
$E_NO_ERROR |
3949
|
|
|
|
|
|
|
$E_MISMATCH_ARG |
3950
|
|
|
|
|
|
|
$E_DATA_TOO_LARGE |
3951
|
|
|
|
|
|
|
$E_NETWORK |
3952
|
|
|
|
|
|
|
$E_MAXMEMORY_LIMIT |
3953
|
|
|
|
|
|
|
$E_MAXMEMORY_POLICY |
3954
|
|
|
|
|
|
|
$E_COLLECTION_DELETED |
3955
|
|
|
|
|
|
|
$E_REDIS |
3956
|
|
|
|
|
|
|
); |
3957
|
|
|
|
|
|
|
|
3958
|
|
|
|
|
|
|
# Error handling |
3959
|
|
|
|
|
|
|
sub exception { |
3960
|
|
|
|
|
|
|
my $coll = shift; |
3961
|
|
|
|
|
|
|
my $err = shift; |
3962
|
|
|
|
|
|
|
|
3963
|
|
|
|
|
|
|
die $err unless $coll; |
3964
|
|
|
|
|
|
|
if ( $coll->last_errorcode == $E_NO_ERROR ) { |
3965
|
|
|
|
|
|
|
# For example, to ignore |
3966
|
|
|
|
|
|
|
return unless $err; |
3967
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MISMATCH_ARG ) { |
3968
|
|
|
|
|
|
|
# Necessary to correct the code |
3969
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_DATA_TOO_LARGE ) { |
3970
|
|
|
|
|
|
|
# Limit data length |
3971
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_NETWORK ) { |
3972
|
|
|
|
|
|
|
# For example, sleep |
3973
|
|
|
|
|
|
|
#sleep 60; |
3974
|
|
|
|
|
|
|
# and return code to repeat the operation |
3975
|
|
|
|
|
|
|
#return 'to repeat'; |
3976
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MAXMEMORY_LIMIT ) { |
3977
|
|
|
|
|
|
|
# For example, return code to restart the server |
3978
|
|
|
|
|
|
|
#return 'to restart the redis server'; |
3979
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_MAXMEMORY_POLICY ) { |
3980
|
|
|
|
|
|
|
# Correct Redis server 'maxmemory-policy' setting |
3981
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_COLLECTION_DELETED ) { |
3982
|
|
|
|
|
|
|
# For example, return code to ignore |
3983
|
|
|
|
|
|
|
#return "to ignore $err"; |
3984
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_REDIS ) { |
3985
|
|
|
|
|
|
|
# Independently analyze the $err |
3986
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_DATA_ID_EXISTS ) { |
3987
|
|
|
|
|
|
|
# For example, return code to reinsert the data |
3988
|
|
|
|
|
|
|
#return "to reinsert with new data ID"; |
3989
|
|
|
|
|
|
|
} elsif ( $coll->last_errorcode == $E_OLDER_THAN_ALLOWED ) { |
3990
|
|
|
|
|
|
|
# Independently analyze the situation |
3991
|
|
|
|
|
|
|
} else { |
3992
|
|
|
|
|
|
|
# Unknown error code |
3993
|
|
|
|
|
|
|
} |
3994
|
|
|
|
|
|
|
die $err if $err; |
3995
|
|
|
|
|
|
|
} |
3996
|
|
|
|
|
|
|
|
3997
|
|
|
|
|
|
|
my ( $list_id, $coll, @data ); |
3998
|
|
|
|
|
|
|
|
3999
|
|
|
|
|
|
|
eval { |
4000
|
|
|
|
|
|
|
$coll = Redis::CappedCollection->create( |
4001
|
|
|
|
|
|
|
redis => $DEFAULT_SERVER.':'.$DEFAULT_PORT, |
4002
|
|
|
|
|
|
|
name => 'Some name', |
4003
|
|
|
|
|
|
|
); |
4004
|
|
|
|
|
|
|
}; |
4005
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
4006
|
|
|
|
|
|
|
say "'", $coll->name, "' collection created."; |
4007
|
|
|
|
|
|
|
|
4008
|
|
|
|
|
|
|
#-- Producer ------------------------------------------------------- |
4009
|
|
|
|
|
|
|
#-- New data |
4010
|
|
|
|
|
|
|
|
4011
|
|
|
|
|
|
|
eval { |
4012
|
|
|
|
|
|
|
$list_id = $coll->insert( |
4013
|
|
|
|
|
|
|
'Some List_id', # list id |
4014
|
|
|
|
|
|
|
123, # data id |
4015
|
|
|
|
|
|
|
'Some data', |
4016
|
|
|
|
|
|
|
); |
4017
|
|
|
|
|
|
|
say "Added data in a list with '", $list_id, "' id" ); |
4018
|
|
|
|
|
|
|
|
4019
|
|
|
|
|
|
|
# Change the "zero" element of the list with the ID $list_id |
4020
|
|
|
|
|
|
|
if ( $coll->update( $list_id, 0, 'New data' ) ) { |
4021
|
|
|
|
|
|
|
say 'Data updated successfully'; |
4022
|
|
|
|
|
|
|
} else { |
4023
|
|
|
|
|
|
|
say 'Failed to update element'; |
4024
|
|
|
|
|
|
|
} |
4025
|
|
|
|
|
|
|
}; |
4026
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
4027
|
|
|
|
|
|
|
|
4028
|
|
|
|
|
|
|
#-- Consumer ------------------------------------------------------- |
4029
|
|
|
|
|
|
|
#-- Fetching the data |
4030
|
|
|
|
|
|
|
|
4031
|
|
|
|
|
|
|
eval { |
4032
|
|
|
|
|
|
|
@data = $coll->receive( $list_id ); |
4033
|
|
|
|
|
|
|
say "List '$list_id' has '$_'" foreach @data; |
4034
|
|
|
|
|
|
|
# or to obtain records in the order they were placed |
4035
|
|
|
|
|
|
|
while ( my ( $list_id, $data ) = $coll->pop_oldest ) { |
4036
|
|
|
|
|
|
|
say "List '$list_id' had '$data'"; |
4037
|
|
|
|
|
|
|
} |
4038
|
|
|
|
|
|
|
}; |
4039
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
4040
|
|
|
|
|
|
|
|
4041
|
|
|
|
|
|
|
#-- Utility -------------------------------------------------------- |
4042
|
|
|
|
|
|
|
#-- Getting statistics |
4043
|
|
|
|
|
|
|
|
4044
|
|
|
|
|
|
|
my ( $lists, $items ); |
4045
|
|
|
|
|
|
|
eval { |
4046
|
|
|
|
|
|
|
my $info = $coll->collection_info; |
4047
|
|
|
|
|
|
|
say 'An existing collection uses ', $info->{cleanup_bytes}, " byte of 'cleanup_bytes', ", |
4048
|
|
|
|
|
|
|
'in ', $info->{items}, ' items are placed in ', |
4049
|
|
|
|
|
|
|
$info->{lists}, ' lists'; |
4050
|
|
|
|
|
|
|
|
4051
|
|
|
|
|
|
|
say "The collection has '$list_id' list" |
4052
|
|
|
|
|
|
|
if $coll->list_exists( 'Some_id' ); |
4053
|
|
|
|
|
|
|
}; |
4054
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
4055
|
|
|
|
|
|
|
|
4056
|
|
|
|
|
|
|
#-- Closes and cleans up ------------------------------------------- |
4057
|
|
|
|
|
|
|
|
4058
|
|
|
|
|
|
|
eval { |
4059
|
|
|
|
|
|
|
$coll->quit; |
4060
|
|
|
|
|
|
|
|
4061
|
|
|
|
|
|
|
# Before use, make sure that the collection |
4062
|
|
|
|
|
|
|
# is not being used by other clients |
4063
|
|
|
|
|
|
|
#$coll->drop_collection; |
4064
|
|
|
|
|
|
|
}; |
4065
|
|
|
|
|
|
|
exception( $coll, $@ ) if $@; |
4066
|
|
|
|
|
|
|
|
4067
|
|
|
|
|
|
|
=head2 CappedCollection data structure |
4068
|
|
|
|
|
|
|
|
4069
|
|
|
|
|
|
|
Using currently selected database (default = 0). |
4070
|
|
|
|
|
|
|
|
4071
|
|
|
|
|
|
|
CappedCollection package creates the following data structures on Redis: |
4072
|
|
|
|
|
|
|
|
4073
|
|
|
|
|
|
|
#-- To store collection status: |
4074
|
|
|
|
|
|
|
# HASH Namespace:S:Collection_id |
4075
|
|
|
|
|
|
|
# For example: |
4076
|
|
|
|
|
|
|
$ redis-cli |
4077
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:S:* |
4078
|
|
|
|
|
|
|
1) "C:S:Some collection name" |
4079
|
|
|
|
|
|
|
# | | | |
4080
|
|
|
|
|
|
|
# | +-------+ +------------+ |
4081
|
|
|
|
|
|
|
# | | | |
4082
|
|
|
|
|
|
|
# Namespace | | |
4083
|
|
|
|
|
|
|
# Fixed symbol of a properties hash | |
4084
|
|
|
|
|
|
|
# Capped Collection id |
4085
|
|
|
|
|
|
|
... |
4086
|
|
|
|
|
|
|
redis 127.0.0.1:6379> HGETALL "C:S:Some collection name" |
4087
|
|
|
|
|
|
|
1) "lists" # hash key |
4088
|
|
|
|
|
|
|
2) "1" # the key value |
4089
|
|
|
|
|
|
|
3) "items" # hash key |
4090
|
|
|
|
|
|
|
4) "1" # the key value |
4091
|
|
|
|
|
|
|
5) "older_allowed" # hash key |
4092
|
|
|
|
|
|
|
6) "0" # the key value |
4093
|
|
|
|
|
|
|
7) "cleanup_bytes" # hash key |
4094
|
|
|
|
|
|
|
8) "0" # the key value |
4095
|
|
|
|
|
|
|
9) "cleanup_items" # hash key |
4096
|
|
|
|
|
|
|
10) "0" # the key value |
4097
|
|
|
|
|
|
|
11) "max_list_items" # hash key |
4098
|
|
|
|
|
|
|
12) "100" # the key value |
4099
|
|
|
|
|
|
|
13) "memory_reserve" # hash key |
4100
|
|
|
|
|
|
|
14) "0.05" # the key value |
4101
|
|
|
|
|
|
|
15) "data_version" # hash key |
4102
|
|
|
|
|
|
|
16) "3" # the key value |
4103
|
|
|
|
|
|
|
17) "last_removed_time" # hash key |
4104
|
|
|
|
|
|
|
18) "0" # the key value |
4105
|
|
|
|
|
|
|
... |
4106
|
|
|
|
|
|
|
|
4107
|
|
|
|
|
|
|
#-- To store collection queue: |
4108
|
|
|
|
|
|
|
# ZSET Namespace:Q:Collection_id |
4109
|
|
|
|
|
|
|
# For example: |
4110
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:Q:* |
4111
|
|
|
|
|
|
|
1) "C:Q:Some collection name" |
4112
|
|
|
|
|
|
|
# | | | |
4113
|
|
|
|
|
|
|
# | +------+ +-----------+ |
4114
|
|
|
|
|
|
|
# | | | |
4115
|
|
|
|
|
|
|
# Namespace | | |
4116
|
|
|
|
|
|
|
# Fixed symbol of a queue | |
4117
|
|
|
|
|
|
|
# Capped Collection id |
4118
|
|
|
|
|
|
|
... |
4119
|
|
|
|
|
|
|
redis 127.0.0.1:6379> ZRANGE "C:Q:Some collection name" 0 -1 WITHSCORES |
4120
|
|
|
|
|
|
|
1) "Some list id" ----------+ |
4121
|
|
|
|
|
|
|
2) "1348252575.6651001" | |
4122
|
|
|
|
|
|
|
# | | |
4123
|
|
|
|
|
|
|
# Score: oldest data_time | |
4124
|
|
|
|
|
|
|
# Member: Data List id |
4125
|
|
|
|
|
|
|
... |
4126
|
|
|
|
|
|
|
|
4127
|
|
|
|
|
|
|
#-- To store CappedCollection data: |
4128
|
|
|
|
|
|
|
# HASH Namespace:D:Collection_id:DataList_id |
4129
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
4130
|
|
|
|
|
|
|
# ZSET Namespace:T:Collection_id:DataList_id |
4131
|
|
|
|
|
|
|
# For example: |
4132
|
|
|
|
|
|
|
redis 127.0.0.1:6379> KEYS C:[DT]:* |
4133
|
|
|
|
|
|
|
1) "C:D:Some collection name:Some list id" |
4134
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
4135
|
|
|
|
|
|
|
2) "C:T:Some collection name:Some list id" |
4136
|
|
|
|
|
|
|
# | | | | |
4137
|
|
|
|
|
|
|
# | +-----+ +-------+ + ---------+ |
4138
|
|
|
|
|
|
|
# | | | | |
4139
|
|
|
|
|
|
|
# Namespace | | | |
4140
|
|
|
|
|
|
|
# Fixed symbol of a list of data | | |
4141
|
|
|
|
|
|
|
# Capped Collection id | |
4142
|
|
|
|
|
|
|
# Data list id |
4143
|
|
|
|
|
|
|
... |
4144
|
|
|
|
|
|
|
redis 127.0.0.1:6379> HGETALL "C:D:Some collection name:Some list id" |
4145
|
|
|
|
|
|
|
1) "0" # hash key: Data id |
4146
|
|
|
|
|
|
|
2) "Some stuff" # the key value: Data |
4147
|
|
|
|
|
|
|
... |
4148
|
|
|
|
|
|
|
# If the amount of data in the list is greater than 1 |
4149
|
|
|
|
|
|
|
redis 127.0.0.1:6379> ZRANGE "C:T:Some collection name:Some list id" 0 -1 WITHSCORES |
4150
|
|
|
|
|
|
|
1) "0" ---------------+ |
4151
|
|
|
|
|
|
|
2) "1348252575.5906" | |
4152
|
|
|
|
|
|
|
# | | |
4153
|
|
|
|
|
|
|
# Score: data_time | |
4154
|
|
|
|
|
|
|
# Member: Data id |
4155
|
|
|
|
|
|
|
... |
4156
|
|
|
|
|
|
|
|
4157
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
4158
|
|
|
|
|
|
|
|
4159
|
|
|
|
|
|
|
In order to install and use this package Perl version 5.010 or better is |
4160
|
|
|
|
|
|
|
required. Redis::CappedCollection module depends on other packages |
4161
|
|
|
|
|
|
|
that are distributed separately from Perl. We recommend the following packages |
4162
|
|
|
|
|
|
|
to be installed before installing Redis::CappedCollection : |
4163
|
|
|
|
|
|
|
|
4164
|
|
|
|
|
|
|
Const::Fast |
4165
|
|
|
|
|
|
|
Digest::SHA1 |
4166
|
|
|
|
|
|
|
Mouse |
4167
|
|
|
|
|
|
|
Params::Util |
4168
|
|
|
|
|
|
|
Redis |
4169
|
|
|
|
|
|
|
Try::Tiny |
4170
|
|
|
|
|
|
|
|
4171
|
|
|
|
|
|
|
The Redis::CappedCollection module has the following optional dependencies: |
4172
|
|
|
|
|
|
|
|
4173
|
|
|
|
|
|
|
Data::UUID |
4174
|
|
|
|
|
|
|
JSON::XS |
4175
|
|
|
|
|
|
|
Net::EmptyPort |
4176
|
|
|
|
|
|
|
Test::Exception |
4177
|
|
|
|
|
|
|
Test::NoWarnings |
4178
|
|
|
|
|
|
|
Test::RedisServer |
4179
|
|
|
|
|
|
|
|
4180
|
|
|
|
|
|
|
If the optional modules are missing, some "prereq" tests are skipped. |
4181
|
|
|
|
|
|
|
|
4182
|
|
|
|
|
|
|
The installation of the missing dependencies can either be accomplished |
4183
|
|
|
|
|
|
|
through your OS package manager or through CPAN (or downloading the source |
4184
|
|
|
|
|
|
|
for all dependencies and compiling them manually). |
4185
|
|
|
|
|
|
|
|
4186
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
4187
|
|
|
|
|
|
|
|
4188
|
|
|
|
|
|
|
Redis server version 2.8 or higher is required. |
4189
|
|
|
|
|
|
|
|
4190
|
|
|
|
|
|
|
The use of C in the F file could lead to |
4191
|
|
|
|
|
|
|
a serious (and hard to detect) problem as Redis server may delete |
4192
|
|
|
|
|
|
|
the collection element. Therefore the C does not work with |
4193
|
|
|
|
|
|
|
mode C in the F. |
4194
|
|
|
|
|
|
|
|
4195
|
|
|
|
|
|
|
It may not be possible to use this module with the cluster of Redis servers |
4196
|
|
|
|
|
|
|
because full name of some Redis keys may not be known at the time of the call |
4197
|
|
|
|
|
|
|
the Redis Lua script (C<'EVAL'> or C<'EVALSHA'> command). |
4198
|
|
|
|
|
|
|
So the Redis server may not be able to correctly forward the request |
4199
|
|
|
|
|
|
|
to the appropriate node in the cluster. |
4200
|
|
|
|
|
|
|
|
4201
|
|
|
|
|
|
|
We strongly recommend setting C option in the F file. |
4202
|
|
|
|
|
|
|
|
4203
|
|
|
|
|
|
|
WARN: Not use C less than for example 70mb (60 connections) for avoid 'used_memory > maxmemory' problem. |
4204
|
|
|
|
|
|
|
|
4205
|
|
|
|
|
|
|
Old data with the same time will be forced out in no specific order. |
4206
|
|
|
|
|
|
|
|
4207
|
|
|
|
|
|
|
The collection API does not support deleting a single data item. |
4208
|
|
|
|
|
|
|
|
4209
|
|
|
|
|
|
|
UTF-8 data should be serialized before passing to C for storing in Redis. |
4210
|
|
|
|
|
|
|
|
4211
|
|
|
|
|
|
|
According to L documentation: |
4212
|
|
|
|
|
|
|
|
4213
|
|
|
|
|
|
|
=over 3 |
4214
|
|
|
|
|
|
|
|
4215
|
|
|
|
|
|
|
=item * |
4216
|
|
|
|
|
|
|
|
4217
|
|
|
|
|
|
|
This module consider that any data sent to the Redis server is a raw octets string, |
4218
|
|
|
|
|
|
|
even if it has utf8 flag set. |
4219
|
|
|
|
|
|
|
And it doesn't do anything when getting data from the Redis server. |
4220
|
|
|
|
|
|
|
|
4221
|
|
|
|
|
|
|
TODO: implement tests for |
4222
|
|
|
|
|
|
|
|
4223
|
|
|
|
|
|
|
=over 3 |
4224
|
|
|
|
|
|
|
|
4225
|
|
|
|
|
|
|
=item * |
4226
|
|
|
|
|
|
|
|
4227
|
|
|
|
|
|
|
memory errors (working with internal ROLLBACK commands) |
4228
|
|
|
|
|
|
|
|
4229
|
|
|
|
|
|
|
=item * |
4230
|
|
|
|
|
|
|
|
4231
|
|
|
|
|
|
|
working when maxmemory = 0 (in the F file) |
4232
|
|
|
|
|
|
|
|
4233
|
|
|
|
|
|
|
=back |
4234
|
|
|
|
|
|
|
|
4235
|
|
|
|
|
|
|
WARNING: According to C function in F : |
4236
|
|
|
|
|
|
|
|
4237
|
|
|
|
|
|
|
/* 32 bit instances are limited to 4GB of address space, so if there is |
4238
|
|
|
|
|
|
|
* no explicit limit in the user provided configuration we set a limit |
4239
|
|
|
|
|
|
|
* at 3 GB using maxmemory with 'noeviction' policy'. This avoids |
4240
|
|
|
|
|
|
|
* useless crashes of the Redis instance for out of memory. */ |
4241
|
|
|
|
|
|
|
|
4242
|
|
|
|
|
|
|
The C module was written, tested, and found working |
4243
|
|
|
|
|
|
|
on recent Linux distributions. |
4244
|
|
|
|
|
|
|
|
4245
|
|
|
|
|
|
|
There are no known bugs in this package. |
4246
|
|
|
|
|
|
|
|
4247
|
|
|
|
|
|
|
Please report problems to the L"AUTHOR">. |
4248
|
|
|
|
|
|
|
|
4249
|
|
|
|
|
|
|
Patches are welcome. |
4250
|
|
|
|
|
|
|
|
4251
|
|
|
|
|
|
|
=back |
4252
|
|
|
|
|
|
|
|
4253
|
|
|
|
|
|
|
=head1 MORE DOCUMENTATION |
4254
|
|
|
|
|
|
|
|
4255
|
|
|
|
|
|
|
All modules contain detailed information on the interfaces they provide. |
4256
|
|
|
|
|
|
|
|
4257
|
|
|
|
|
|
|
=head1 SEE ALSO |
4258
|
|
|
|
|
|
|
|
4259
|
|
|
|
|
|
|
The basic operation of the Redis::CappedCollection package module: |
4260
|
|
|
|
|
|
|
|
4261
|
|
|
|
|
|
|
L - Object interface to create |
4262
|
|
|
|
|
|
|
a collection, addition of data and data manipulation. |
4263
|
|
|
|
|
|
|
|
4264
|
|
|
|
|
|
|
L - String manipulation utilities. |
4265
|
|
|
|
|
|
|
|
4266
|
|
|
|
|
|
|
L - Perl binding for Redis database. |
4267
|
|
|
|
|
|
|
|
4268
|
|
|
|
|
|
|
=head1 SOURCE CODE |
4269
|
|
|
|
|
|
|
|
4270
|
|
|
|
|
|
|
Redis::CappedCollection is hosted on GitHub: |
4271
|
|
|
|
|
|
|
L |
4272
|
|
|
|
|
|
|
|
4273
|
|
|
|
|
|
|
=head1 AUTHOR |
4274
|
|
|
|
|
|
|
|
4275
|
|
|
|
|
|
|
Sergey Gladkov, Esgladkov@trackingsoft.comE |
4276
|
|
|
|
|
|
|
|
4277
|
|
|
|
|
|
|
Please use GitHub project link above to report problems or contact authors. |
4278
|
|
|
|
|
|
|
|
4279
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
4280
|
|
|
|
|
|
|
|
4281
|
|
|
|
|
|
|
Alexander Solovey |
4282
|
|
|
|
|
|
|
|
4283
|
|
|
|
|
|
|
Jeremy Jordan |
4284
|
|
|
|
|
|
|
|
4285
|
|
|
|
|
|
|
Vlad Marchenko |
4286
|
|
|
|
|
|
|
|
4287
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
4288
|
|
|
|
|
|
|
|
4289
|
|
|
|
|
|
|
Copyright (C) 2012-2016 by TrackingSoft LLC. |
4290
|
|
|
|
|
|
|
|
4291
|
|
|
|
|
|
|
This package is free software; you can redistribute it and/or modify it under |
4292
|
|
|
|
|
|
|
the same terms as Perl itself. See I at |
4293
|
|
|
|
|
|
|
L. |
4294
|
|
|
|
|
|
|
|
4295
|
|
|
|
|
|
|
This program is |
4296
|
|
|
|
|
|
|
distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
4297
|
|
|
|
|
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A |
4298
|
|
|
|
|
|
|
PARTICULAR PURPOSE. |
4299
|
|
|
|
|
|
|
|
4300
|
|
|
|
|
|
|
=cut |
4301
|
|
|
|
|
|
|
|
4302
|
|
|
|
|
|
|
__DATA__ |