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