File Coverage

blib/lib/Suggest/PrePop.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Suggest::PrePop;
2              
3 1     1   133889 use strict;
  1         1  
  1         21  
4 1     1   3 use warnings;
  1         1  
  1         28  
5             our $VERSION = '1.0.0';
6              
7 1     1   1265 use Moose;
  0            
  0            
8              
9             use Cache::RedisDB;
10              
11             has cache_namespace => (
12             is => 'ro',
13             isa => 'Str',
14             default => 'SUGGEST-PREPOP',
15             );
16              
17             my $key_sep = '<>';
18              
19             has _lex_key => (
20             is => 'ro',
21             isa => 'Str',
22             lazy => 1,
23             default => sub {
24             my $self = shift;
25             return join($key_sep, $self->cache_namespace, 'ITEMS_BY_LEX');
26             },
27             );
28              
29             has _cnt_key => (
30             is => 'ro',
31             isa => 'Str',
32             lazy => 1,
33             default => sub {
34             my $self = shift;
35             return join($key_sep, $self->cache_namespace, 'ITEMS_BY_COUNT');
36             },
37             );
38              
39             has min_activity => (
40             is => 'ro',
41             isa => 'Int',
42             default => 5,
43             );
44              
45             has entries_limit => (
46             is => 'ro',
47             isa => 'Int',
48             default => 32768,
49             );
50              
51             has top_count => (
52             is => 'ro',
53             isa => 'Int',
54             default => 5,
55             );
56              
57             # Convenience
58             sub _redis { Cache::RedisDB->redis }
59              
60             sub add {
61             my ($self, $item, $count) = @_;
62              
63             $count //= 1; # Most of the time we'll just get a single entry
64              
65             # For now, we just assume supplied items are well-formed
66             my $redis = $self->_redis;
67              
68             # Lexically sorted items are all zero-scored
69             $redis->zadd($self->_lex_key, 0, $item);
70              
71             # Score sorted items get incremented.
72             return $redis->zincrby($self->_cnt_key, $count, $item);
73             }
74              
75             sub ask {
76             my ($self, $prefix, $count) = @_;
77              
78             $count //= $self->top_count; # If they don't say we try to find the 5 best.
79              
80             my $redis = $self->_redis;
81              
82             my @full =
83             map { $_->[0] }
84             sort { $b->[1] <=> $a->[1] }
85             grep { $_->[1] >= $self->min_activity }
86             map { [$_, $redis->zscore($self->_cnt_key, $_)] } @{
87             $redis->zrangebylex(
88             $self->_lex_key,
89             '[' . $prefix,
90             '[' . $prefix . "\xff"
91             ) // []};
92              
93             return [scalar(@full <= $count) ? @full : @full[0 .. $count - 1]];
94             }
95              
96             sub prune {
97             my ($self, $keep) = @_;
98              
99             $keep //= $self->entries_limit;
100              
101             my $redis = $self->_redis;
102              
103             # Count key is the one from which results are collated, so even
104             # if things are out of sync, this is the one about which we care.
105             return 0 if ($redis->zcard($self->_cnt_key) <= $keep);
106              
107             my $final_index = -1 * $keep - 1; # Range below is inclusive.
108             my @to_prune = @{$redis->zrange($self->_cnt_key, 0, $final_index)};
109             my $count = scalar @to_prune;
110              
111             # We're going to do this the slow way to keep them in sync.
112             foreach my $item (@to_prune) {
113             $redis->zrem($self->_cnt_key, $item);
114             $redis->zrem($self->_lex_key, $item);
115             }
116              
117             return $count;
118             }
119              
120             1;
121              
122             __END__