File Coverage

blib/lib/DBIx/Class/Storage/DBI/Replicated/Balancer.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package DBIx::Class::Storage::DBI::Replicated::Balancer;
2              
3 3     3   3935 use Moose::Role;
  0            
  0            
4             requires 'next_storage';
5             use MooseX::Types::Moose qw/Int/;
6             use DBIx::Class::Storage::DBI::Replicated::Pool;
7             use DBIx::Class::Storage::DBI::Replicated::Types qw/DBICStorageDBI/;
8             use namespace::clean -except => 'meta';
9              
10             =head1 NAME
11              
12             DBIx::Class::Storage::DBI::Replicated::Balancer - A Software Load Balancer
13              
14             =head1 SYNOPSIS
15              
16             This role is used internally by L.
17              
18             =head1 DESCRIPTION
19              
20             Given a pool (L) of replicated
21             database's (L), defines a
22             method by which query load can be spread out across each replicant in the pool.
23              
24             =head1 ATTRIBUTES
25              
26             This class defines the following attributes.
27              
28             =head2 auto_validate_every ($seconds)
29              
30             If auto_validate has some sort of value, run
31             L
32             every $seconds. Be careful with this, because if you set it to 0 you
33             will end up validating every query.
34              
35             =cut
36              
37             has 'auto_validate_every' => (
38             is=>'rw',
39             isa=>Int,
40             predicate=>'has_auto_validate_every',
41             );
42              
43             =head2 master
44              
45             The L object that is the master database all the
46             replicants are trying to follow. The balancer needs to know it since it's the
47             ultimate fallback.
48              
49             =cut
50              
51             has 'master' => (
52             is=>'ro',
53             isa=>DBICStorageDBI,
54             required=>1,
55             );
56              
57             =head2 pool
58              
59             The L object that we are trying to
60             balance.
61              
62             =cut
63              
64             has 'pool' => (
65             is=>'ro',
66             isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
67             required=>1,
68             );
69              
70             =head2 current_replicant
71              
72             Replicant storages (slaves) handle all read only traffic. The assumption is
73             that your database will become readbound well before it becomes write bound
74             and that being able to spread your read only traffic around to multiple
75             databases is going to help you to scale traffic.
76              
77             This attribute returns the next slave to handle a read request. Your L
78             attribute has methods to help you shuffle through all the available replicants
79             via its balancer object.
80              
81             =cut
82              
83             has 'current_replicant' => (
84             is=> 'rw',
85             isa=>DBICStorageDBI,
86             lazy_build=>1,
87             handles=>[qw/
88             select
89             select_single
90             columns_info_for
91             /],
92             );
93              
94             =head1 METHODS
95              
96             This class defines the following methods.
97              
98             =head2 _build_current_replicant
99              
100             Lazy builder for the L attribute.
101              
102             =cut
103              
104             sub _build_current_replicant {
105             my $self = shift @_;
106             $self->next_storage;
107             }
108              
109             =head2 next_storage
110              
111             This method should be defined in the class which consumes this role.
112              
113             Given a pool object, return the next replicant that will serve queries. The
114             default behavior is to grab the first replicant it finds but you can write
115             your own subclasses of L to
116             support other balance systems.
117              
118             This returns from the pool of active replicants. If there are no active
119             replicants, then you should have it return the master as an ultimate fallback.
120              
121             =head2 around: next_storage
122              
123             Advice on next storage to add the autovalidation. We have this broken out so
124             that it's easier to break out the auto validation into a role.
125              
126             This also returns the master in the case that none of the replicants are active
127             or just forgot to create them :)
128              
129             =cut
130              
131             my $on_master;
132              
133             around 'next_storage' => sub {
134             my ($next_storage, $self, @args) = @_;
135             my $now = time;
136              
137             ## Do we need to validate the replicants?
138             if(
139             $self->has_auto_validate_every &&
140             ($self->auto_validate_every + $self->pool->last_validated) <= $now
141             ) {
142             $self->pool->validate_replicants;
143             }
144              
145             ## Get a replicant, or the master if none
146             if(my $next = $self->$next_storage(@args)) {
147             $self->master->debugobj->print("Moved back to slave\n") if $on_master;
148             $on_master = 0;
149             return $next;
150             } else {
151             $self->master->debugobj->print("No Replicants validate, falling back to master reads.\n")
152             unless $on_master++;
153              
154             return $self->master;
155             }
156             };
157              
158             =head2 increment_storage
159              
160             Rolls the Storage to whatever is next in the queue, as defined by the Balancer.
161              
162             =cut
163              
164             sub increment_storage {
165             my $self = shift @_;
166             my $next_replicant = $self->next_storage;
167             $self->current_replicant($next_replicant);
168             }
169              
170             =head2 around: select
171              
172             Advice on the select attribute. Each time we use a replicant
173             we need to change it via the storage pool algorithm. That way we are spreading
174             the load evenly (hopefully) across existing capacity.
175              
176             =cut
177              
178             around 'select' => sub {
179             my ($select, $self, @args) = @_;
180              
181             if (my $forced_pool = $args[-1]->{force_pool}) {
182             delete $args[-1]->{force_pool};
183             return $self->_get_forced_pool($forced_pool)->select(@args);
184             } elsif($self->master->{transaction_depth}) {
185             return $self->master->select(@args);
186             } else {
187             $self->increment_storage;
188             return $self->$select(@args);
189             }
190             };
191              
192             =head2 around: select_single
193              
194             Advice on the select_single attribute. Each time we use a replicant
195             we need to change it via the storage pool algorithm. That way we are spreading
196             the load evenly (hopefully) across existing capacity.
197              
198             =cut
199              
200             around 'select_single' => sub {
201             my ($select_single, $self, @args) = @_;
202              
203             if (my $forced_pool = $args[-1]->{force_pool}) {
204             delete $args[-1]->{force_pool};
205             return $self->_get_forced_pool($forced_pool)->select_single(@args);
206             } elsif($self->master->{transaction_depth}) {
207             return $self->master->select_single(@args);
208             } else {
209             $self->increment_storage;
210             return $self->$select_single(@args);
211             }
212             };
213              
214             =head2 before: columns_info_for
215              
216             Advice on the current_replicant_storage attribute. Each time we use a replicant
217             we need to change it via the storage pool algorithm. That way we are spreading
218             the load evenly (hopefully) across existing capacity.
219              
220             =cut
221              
222             before 'columns_info_for' => sub {
223             my $self = shift @_;
224             $self->increment_storage;
225             };
226              
227             =head2 _get_forced_pool ($name)
228              
229             Given an identifier, find the most correct storage object to handle the query.
230              
231             =cut
232              
233             sub _get_forced_pool {
234             my ($self, $forced_pool) = @_;
235             if(blessed $forced_pool) {
236             return $forced_pool;
237             } elsif($forced_pool eq 'master') {
238             return $self->master;
239             } elsif(my $replicant = $self->pool->replicants->{$forced_pool}) {
240             return $replicant;
241             } else {
242             $self->master->throw_exception("'$forced_pool' is not a named replicant.");
243             }
244             }
245              
246             =head1 FURTHER QUESTIONS?
247              
248             Check the list of L.
249              
250             =head1 COPYRIGHT AND LICENSE
251              
252             This module is free software L
253             by the L. You can
254             redistribute it and/or modify it under the same terms as the
255             L.
256              
257             =cut
258              
259             1;