Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.09% covered (warning)
59.09%
26 / 44
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
LBFactorySimple
59.09% covered (warning)
59.09%
26 / 44
33.33% covered (danger)
33.33%
3 / 9
40.18
0.00% covered (danger)
0.00%
0 / 1
 __construct
70.00% covered (warning)
70.00%
7 / 10
0.00% covered (danger)
0.00%
0 / 1
5.68
 newMainLB
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getMainLB
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newExternalLB
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getExternalLB
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getAllMainLBs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllExternalLBs
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 newLoadBalancer
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getLBsForOwner
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20namespace Wikimedia\Rdbms;
21
22use InvalidArgumentException;
23
24/**
25 * Manager for sites with a single "main" cluster and any number of "external" clusters
26 *
27 * @see LBFactoryMulti
28 *
29 * The class allows for large site farms to split up their data in the following ways:
30 *   - Vertically shard compact site-specific data by site (e.g. page/comment metadata)
31 *   - Vertically shard compact global data by module (e.g. account/notification data)
32 *   - Horizontally shard any bulk data by blob key (e.g. page/comment content)
33 *
34 * @ingroup Database
35 */
36class LBFactorySimple extends LBFactory {
37    /** @var LoadBalancer */
38    private $mainLB;
39    /** @var LoadBalancer[] */
40    private $externalLBs = [];
41
42    /** @var array Configuration for the LoadMonitor to use within LoadBalancer instances */
43    private $loadMonitorConfig;
44
45    /** @var array[] Map of (server index => server config map) */
46    private $mainServers;
47    /** @var array[][] Map of (cluster => server index => server config map) */
48    private $externalServersByCluster = [];
49
50    /**
51     * @see LBFactory::__construct()
52     * @param array $conf Additional parameters include:
53     *   - servers : list of server config maps to Database::factory().
54     *      Additionally, the server maps should have a 'load' key, which is used to decide
55     *      how often clients connect to one server verses the others. A 'max lag' key should
56     *      also be set on server maps, indicating how stale the data can be before the load
57     *      balancer tries to avoid using it. The map can have 'is static' set to disable blocking
58     *      replication sync checks (intended for archive servers with unchanging data).
59     *   - externalClusters : map of cluster names to server arrays. The servers arrays have the
60     *      same format as "servers" above.
61     *   - loadMonitor: LoadMonitor::__construct() parameters with "class" field. [optional]
62     */
63    public function __construct( array $conf ) {
64        parent::__construct( $conf );
65
66        $this->mainServers = $conf['servers'] ?? [];
67        foreach ( ( $conf['externalClusters'] ?? [] ) as $cluster => $servers ) {
68            foreach ( $servers as $index => $server ) {
69                $this->externalServersByCluster[$cluster][$index] = $server;
70            }
71        }
72
73        if ( isset( $conf['loadMonitor'] ) ) {
74            $this->loadMonitorConfig = $conf['loadMonitor'];
75        } elseif ( isset( $conf['loadMonitorClass'] ) ) { // b/c
76            $this->loadMonitorConfig = [ 'class' => $conf['loadMonitorClass'] ];
77        } else {
78            $this->loadMonitorConfig = [ 'class' => LoadMonitor::class ];
79        }
80    }
81
82    public function newMainLB( $domain = false ): ILoadBalancerForOwner {
83        return $this->newLoadBalancer(
84            self::CLUSTER_MAIN_DEFAULT,
85            $this->mainServers
86        );
87    }
88
89    public function getMainLB( $domain = false ): ILoadBalancer {
90        $this->mainLB ??= $this->newMainLB( $domain );
91
92        return $this->mainLB;
93    }
94
95    public function newExternalLB( $cluster ): ILoadBalancerForOwner {
96        if ( !isset( $this->externalServersByCluster[$cluster] ) ) {
97            throw new InvalidArgumentException( "Unknown cluster '$cluster'." );
98        }
99
100        return $this->newLoadBalancer(
101            $cluster,
102            $this->externalServersByCluster[$cluster]
103        );
104    }
105
106    public function getExternalLB( $cluster ): ILoadBalancer {
107        if ( !isset( $this->externalLBs[$cluster] ) ) {
108            $this->externalLBs[$cluster] = $this->newExternalLB( $cluster );
109        }
110
111        return $this->externalLBs[$cluster];
112    }
113
114    public function getAllMainLBs(): array {
115        return [ self::CLUSTER_MAIN_DEFAULT => $this->getMainLB() ];
116    }
117
118    public function getAllExternalLBs(): array {
119        $lbs = [];
120        foreach ( $this->externalServersByCluster as $cluster => $_ ) {
121            $lbs[$cluster] = $this->getExternalLB( $cluster );
122        }
123
124        return $lbs;
125    }
126
127    private function newLoadBalancer( string $clusterName, array $servers ) {
128        $lb = new LoadBalancer( array_merge(
129            $this->baseLoadBalancerParams(),
130            [
131                'servers' => $servers,
132                'loadMonitor' => $this->loadMonitorConfig,
133                'clusterName' => $clusterName
134            ]
135        ) );
136        $this->initLoadBalancer( $lb );
137
138        return $lb;
139    }
140
141    protected function getLBsForOwner() {
142        if ( $this->mainLB !== null ) {
143            yield $this->mainLB;
144        }
145        foreach ( $this->externalLBs as $lb ) {
146            yield $lb;
147        }
148    }
149}