File Coverage

blib/lib/Data/Chronicle/Writer.pm
Criterion Covered Total %
statement 43 50 86.0
branch 8 14 57.1
condition 3 8 37.5
subroutine 9 10 90.0
pod 1 1 100.0
total 64 83 77.1


line stmt bran cond sub pod time code
1             package Data::Chronicle::Writer;
2              
3 2     2   921299 use 5.014;
  2         4  
4 2     2   9 use strict;
  2         3  
  2         35  
5 2     2   6 use warnings;
  2         2  
  2         50  
6 2     2   371 use Data::Chronicle;
  2         3  
  2         72  
7              
8             =head1 NAME
9              
10             Data::Chronicle::Writer - Provides writing to an efficient data storage for volatile and time-based data
11              
12             =cut
13              
14             our $VERSION = '0.15'; ## VERSION
15              
16             =head1 DESCRIPTION
17              
18             This module contains helper methods which can be used to store and retrieve information
19             on an efficient storage with below properties:
20              
21             =over 4
22              
23             =item B<Timeliness>
24              
25             It is assumed that data to be stored are time-based meaning they change over time and the latest version is most important for us.
26              
27             =item B<Efficient>
28              
29             The module uses Redis cache to provide efficient data storage and retrieval.
30              
31             =item B<Persistent>
32              
33             In addition to caching every incoming data, it is also stored in PostgreSQL for future retrieval.
34              
35             =item B<Transparent>
36              
37             This modules hides all the details about distribution, caching, database structure and ... from developer. He only needs to call a method
38             to save data and another method to retrieve it. All the underlying complexities are handled by the module.
39              
40             =back
41              
42             =head1 Example
43              
44             my $d = get_some_log_data();
45              
46             my $chronicle_w = Data::Chronicle::Writer->new(
47             cache_writer => $writer,
48             db_handle => $dbh,
49             ttl => 86400);
50              
51             my $chronicle_r = Data::Chronicle::Reader->new(
52             cache_reader => $reader,
53             db_handle => $dbh);
54              
55              
56             #store data into Chronicle - each time we call `set` it will also store
57             #a copy of the data for historical data retrieval
58             $chronicle_w->set("log_files", "syslog", $d);
59              
60             #retrieve latest data stored for syslog under log_files category
61             my $dt = $chronicle_r->get("log_files", "syslog");
62              
63             #find historical data for `syslog` at given point in time
64             my $some_old_data = $chronicle_r->get_for("log_files", "syslog", $epoch1);
65              
66             =cut
67              
68 2     2   632 use JSON;
  2         10019  
  2         11  
69 2     2   244 use Date::Utility;
  2         3  
  2         31  
70 2     2   7 use Moose;
  2         13  
  2         15  
71              
72             has [qw(cache_writer db_handle)] => (
73             is => 'ro',
74             default => undef,
75             );
76              
77             =head1 METHODS
78              
79             =head2 ttl
80              
81             If a TTL value is provided when constructing the instance, this will be used as the expiry time for the data.
82              
83             Expiry time is not currently recorded in the PostgreSQL database backend - it is only used for the cache layer.
84              
85             This represents the seconds until expiry, and default is C<undef>, meaning that keys will not expire.
86              
87             =cut
88              
89             has 'ttl' => (
90             isa => 'Maybe[Int]',
91             is => 'ro',
92             default => undef,
93             );
94              
95             =head2 publish_on_set
96              
97             Will invoke
98              
99             $cache_writer->publish("$category::$name1", $value);
100              
101             if set to true. This is useful, if to provide redis or postgres notificaitons on new data.
102              
103             Default value: 0 (false)
104              
105             =cut
106              
107             has 'publish_on_set' => (
108             isa => 'Int',
109             is => 'ro',
110             default => sub { 0 },
111             );
112              
113             =head2 set
114              
115             Example:
116              
117             $chronicle_writer->set("category1", "name1", $value1);
118              
119             Store a piece of data "value1" under key "category1::name1" in Pg and Redis. Will
120             publish "category1::name1" in Redis if C<publish_on_set> is true.
121              
122             =cut
123              
124             sub set {
125 2     2 1 12006 my $self = shift;
126 2         6 my $category = shift;
127 2         5 my $name = shift;
128 2         4 my $value = shift;
129 2         6 my $rec_date = shift;
130 2         2 my $archive = shift;
131              
132 2   50     11 $archive //= 1; #default to true
133 2 50       15 die "Recorded date is undefined" unless $rec_date;
134 2 50       14 die "Recorded date is not a Date::Utility object" if ref $rec_date ne 'Date::Utility';
135 2 50       9 die "Cannot store undefined values in Chronicle!" unless defined $value;
136 2 50 33     23 die "You can only store hash-ref or array-ref in Chronicle!" unless (ref $value eq 'ARRAY' or ref $value eq 'HASH');
137              
138 2         13 $value = JSON::to_json($value);
139              
140 2         90 my $key = $category . '::' . $name;
141 2         323 my $writer = $self->cache_writer;
142              
143             # publish & set in transaction
144 2         12 $writer->multi;
145 2 100       90 $writer->publish($key, $value) if $self->publish_on_set;
146 2 50       112 $writer->set(
147             $key => $value,
148             $self->ttl ? ('EX' => $self->ttl) : ());
149 2         75 $writer->exec;
150              
151 2 50 33     13 $self->_archive($category, $name, $value, $rec_date) if $archive and $self->db_handle;
152              
153 2         7 return 1;
154             }
155              
156             sub _archive {
157 0     0     my $self = shift;
158 0           my $category = shift;
159 0           my $name = shift;
160 0           my $value = shift;
161 0           my $rec_date = shift;
162              
163 0           my $db_timestamp = $rec_date->db_timestamp;
164              
165 0           return $self->db_handle->prepare(<<'SQL')->execute($category, $name, $value, $db_timestamp);
166             WITH ups AS (
167             UPDATE chronicle
168             SET value=$3
169             WHERE timestamp=$4
170             AND category=$1
171             AND name=$2
172             RETURNING *
173             )
174             INSERT INTO chronicle (timestamp, category, name, value)
175             SELECT $4, $1, $2, $3
176             WHERE NOT EXISTS (SELECT * FROM ups)
177             SQL
178             }
179              
180 2     2   9995 no Moose;
  2         3  
  2         11  
181              
182             =head1 AUTHOR
183              
184             Binary.com, C<< <support at binary.com> >>
185              
186             =head1 BUGS
187              
188             Please report any bugs or feature requests to C<bug-data-chronicle at rt.cpan.org>, or through
189             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Data-Chronicle>. I will be notified, and then you'll
190             automatically be notified of progress on your bug as I make changes.
191              
192              
193              
194              
195             =head1 SUPPORT
196              
197             You can find documentation for this module with the perldoc command.
198              
199             perldoc Data::Chronicle::Writer
200              
201              
202             You can also look for information at:
203              
204             =over 4
205              
206             =item * RT: CPAN's request tracker (report bugs here)
207              
208             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Data-Chronicle>
209              
210             =item * AnnoCPAN: Annotated CPAN documentation
211              
212             L<http://annocpan.org/dist/Data-Chronicle>
213              
214             =item * CPAN Ratings
215              
216             L<http://cpanratings.perl.org/d/Data-Chronicle>
217              
218             =item * Search CPAN
219              
220             L<http://search.cpan.org/dist/Data-Chronicle/>
221              
222             =back
223              
224              
225             =head1 ACKNOWLEDGEMENTS
226              
227             =cut
228              
229             1;