Completed
Branch master (05c729)
by
unknown
20:00
created

LBFactoryMulti::getAllExternalLBs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
rs 9.4285
1
<?php
2
/**
3
 * Advanced generator of database load balancing objects for database farms.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup Database
22
 */
23
24
/**
25
 * A multi-database, multi-master factory for Wikimedia and similar installations.
26
 * Ignores the old configuration globals.
27
 *
28
 * @ingroup Database
29
 */
30
class LBFactoryMulti extends LBFactory {
31
	/** @var array A map of database names to section names */
32
	private $sectionsByDB;
33
34
	/**
35
	 * @var array A 2-d map. For each section, gives a map of server names to
36
	 * load ratios
37
	 */
38
	private $sectionLoads;
39
40
	/**
41
	 * @var array[] Server info associative array
42
	 * @note The host, hostName and load entries will be overridden
43
	 */
44
	private $serverTemplate;
45
46
	// Optional settings
47
48
	/** @var array A 3-d map giving server load ratios for each section and group */
49
	private $groupLoadsBySection = [];
50
51
	/** @var array A 3-d map giving server load ratios by DB name */
52
	private $groupLoadsByDB = [];
53
54
	/** @var array A map of hostname to IP address */
55
	private $hostsByName = [];
56
57
	/** @var array A map of external storage cluster name to server load map */
58
	private $externalLoads = [];
59
60
	/**
61
	 * @var array A set of server info keys overriding serverTemplate for
62
	 * external storage
63
	 */
64
	private $externalTemplateOverrides;
65
66
	/**
67
	 * @var array A 2-d map overriding serverTemplate and
68
	 * externalTemplateOverrides on a server-by-server basis. Applies to both
69
	 * core and external storage
70
	 */
71
	private $templateOverridesByServer;
72
73
	/** @var array A 2-d map overriding the server info by section */
74
	private $templateOverridesBySection;
75
76
	/** @var array A 2-d map overriding the server info by external storage cluster */
77
	private $templateOverridesByCluster;
78
79
	/** @var array An override array for all master servers */
80
	private $masterTemplateOverrides;
81
82
	/**
83
	 * @var array|bool A map of section name to read-only message. Missing or
84
	 * false for read/write
85
	 */
86
	private $readOnlyBySection = [];
87
88
	/** @var array Load balancer factory configuration */
89
	private $conf;
90
91
	/** @var LoadBalancer[] */
92
	private $mainLBs = [];
93
94
	/** @var LoadBalancer[] */
95
	private $extLBs = [];
96
97
	/** @var string */
98
	private $loadMonitorClass = 'LoadMonitor';
99
100
	/** @var string */
101
	private $lastDomain;
102
103
	/** @var string */
104
	private $lastSection;
105
106
	/**
107
	 * @see LBFactory::__construct()
108
	 *
109
	 * Template override precedence (highest => lowest):
110
	 *   - templateOverridesByServer
111
	 *   - masterTemplateOverrides
112
	 *   - templateOverridesBySection/templateOverridesByCluster
113
	 *   - externalTemplateOverrides
114
	 *   - serverTemplate
115
	 * Overrides only work on top level keys (so nested values will not be merged).
116
	 *
117
	 * Server configuration maps should be of the format Database::factory() requires.
118
	 * Additionally, a 'max lag' key should also be set on server maps, indicating how stale the
119
	 * data can be before the load balancer tries to avoid using it. The map can have 'is static'
120
	 * set to disable blocking  replication sync checks (intended for archive servers with
121
	 * unchanging data).
122
	 *
123
	 * @param array $conf Parameters of LBFactory::__construct() as well as:
124
	 *   - sectionsByDB                Map of database names to section names.
125
	 *   - sectionLoads                2-d map. For each section, gives a map of server names to
126
	 *                                 load ratios. For example:
127
	 *                                 [
128
	 *                                     'section1' => [
129
	 *                                         'db1' => 100,
130
	 *                                         'db2' => 100
131
	 *                                     ]
132
	 *                                 ]
133
	 *   - serverTemplate              Server configuration map intended for Database::factory().
134
	 *                                 Note that "host", "hostName" and "load" entries will be
135
	 *                                 overridden by "sectionLoads" and "hostsByName".
136
	 *   - groupLoadsBySection         3-d map giving server load ratios for each section/group.
137
	 *                                 For example:
138
	 *                                 [
139
	 *                                     'section1' => [
140
	 *                                         'group1' => [
141
	 *                                             'db1' => 100,
142
	 *                                             'db2' => 100
143
	 *                                         ]
144
	 *                                     ]
145
	 *                                 ]
146
	 *   - groupLoadsByDB              3-d map giving server load ratios by DB name.
147
	 *   - hostsByName                 Map of hostname to IP address.
148
	 *   - externalLoads               Map of external storage cluster name to server load map.
149
	 *   - externalTemplateOverrides   Set of server configuration maps overriding
150
	 *                                 "serverTemplate" for external storage.
151
	 *   - templateOverridesByServer   2-d map overriding "serverTemplate" and
152
	 *                                 "externalTemplateOverrides" on a server-by-server basis.
153
	 *                                 Applies to both core and external storage.
154
	 *   - templateOverridesBySection  2-d map overriding the server configuration maps by section.
155
	 *   - templateOverridesByCluster  2-d map overriding the server configuration maps by external
156
	 *                                 storage cluster.
157
	 *   - masterTemplateOverrides     Server configuration map overrides for all master servers.
158
	 *   - loadMonitorClass            Name of the LoadMonitor class to always use.
159
	 *   - readOnlyBySection           A map of section name to read-only message.
160
	 *                                 Missing or false for read/write.
161
	 */
162
	public function __construct( array $conf ) {
163
		parent::__construct( $conf );
164
165
		$this->conf = $conf;
166
		$required = [ 'sectionsByDB', 'sectionLoads', 'serverTemplate' ];
167
		$optional = [ 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
168
			'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
169
			'templateOverridesByCluster', 'templateOverridesBySection', 'masterTemplateOverrides',
170
			'readOnlyBySection', 'loadMonitorClass' ];
171
172 View Code Duplication
		foreach ( $required as $key ) {
173
			if ( !isset( $conf[$key] ) ) {
174
				throw new InvalidArgumentException( __CLASS__ . ": $key is required." );
175
			}
176
			$this->$key = $conf[$key];
177
		}
178
179
		foreach ( $optional as $key ) {
180
			if ( isset( $conf[$key] ) ) {
181
				$this->$key = $conf[$key];
182
			}
183
		}
184
	}
185
186
	/**
187
	 * @param bool|string $domain
188
	 * @return string
189
	 */
190
	private function getSectionForDomain( $domain = false ) {
191
		if ( $this->lastDomain === $domain ) {
192
			return $this->lastSection;
193
		}
194
		list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
195
		if ( isset( $this->sectionsByDB[$dbName] ) ) {
196
			$section = $this->sectionsByDB[$dbName];
197
		} else {
198
			$section = 'DEFAULT';
199
		}
200
		$this->lastSection = $section;
201
		$this->lastDomain = $domain;
0 ignored issues
show
Documentation Bug introduced by
It seems like $domain can also be of type boolean. However, the property $lastDomain is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
202
203
		return $section;
204
	}
205
206
	/**
207
	 * @param bool|string $domain
208
	 * @return LoadBalancer
209
	 */
210
	public function newMainLB( $domain = false ) {
211
		list( $dbName, ) = $this->getDBNameAndPrefix( $domain );
212
		$section = $this->getSectionForDomain( $domain );
213
		if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
214
			$groupLoads = $this->groupLoadsByDB[$dbName];
215
		} else {
216
			$groupLoads = [];
217
		}
218
219
		if ( isset( $this->groupLoadsBySection[$section] ) ) {
220
			$groupLoads = array_merge_recursive(
221
				$groupLoads, $this->groupLoadsBySection[$section] );
222
		}
223
224
		$readOnlyReason = $this->readOnlyReason;
225
		// Use the LB-specific read-only reason if everything isn't already read-only
226
		if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
227
			$readOnlyReason = $this->readOnlyBySection[$section];
228
		}
229
230
		$template = $this->serverTemplate;
231
		if ( isset( $this->templateOverridesBySection[$section] ) ) {
232
			$template = $this->templateOverridesBySection[$section] + $template;
233
		}
234
235
		return $this->newLoadBalancer(
236
			$template,
237
			$this->sectionLoads[$section],
238
			$groupLoads,
239
			$readOnlyReason
240
		);
241
	}
242
243
	/**
244
	 * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
245
	 * @return LoadBalancer
246
	 */
247
	public function getMainLB( $domain = false ) {
248
		$section = $this->getSectionForDomain( $domain );
249
		if ( !isset( $this->mainLBs[$section] ) ) {
250
			$lb = $this->newMainLB( $domain );
251
			$this->getChronologyProtector()->initLB( $lb );
252
			$this->mainLBs[$section] = $lb;
253
		}
254
255
		return $this->mainLBs[$section];
256
	}
257
258
	public function newExternalLB( $cluster ) {
259
		if ( !isset( $this->externalLoads[$cluster] ) ) {
260
			throw new InvalidArgumentException( __METHOD__ . ": Unknown cluster \"$cluster\"" );
261
		}
262
		$template = $this->serverTemplate;
263
		if ( isset( $this->externalTemplateOverrides ) ) {
264
			$template = $this->externalTemplateOverrides + $template;
265
		}
266
		if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
267
			$template = $this->templateOverridesByCluster[$cluster] + $template;
268
		}
269
270
		return $this->newLoadBalancer(
271
			$template,
272
			$this->externalLoads[$cluster],
273
			[],
274
			$this->readOnlyReason
275
		);
276
	}
277
278 View Code Duplication
	public function getExternalLB( $cluster ) {
279
		if ( !isset( $this->extLBs[$cluster] ) ) {
280
			$this->extLBs[$cluster] = $this->newExternalLB( $cluster );
281
			$this->getChronologyProtector()->initLB( $this->extLBs[$cluster] );
282
		}
283
284
		return $this->extLBs[$cluster];
285
	}
286
287
	public function getAllMainLBs() {
288
		$lbs = [];
289
		foreach ( $this->sectionsByDB as $db => $section ) {
290
			if ( !isset( $lbs[$section] ) ) {
291
				$lbs[$section] = $this->getMainLB( $db );
292
			}
293
		}
294
295
		return $lbs;
296
	}
297
298
	public function getAllExternalLBs() {
299
		$lbs = [];
300
		foreach ( $this->externalLoads as $cluster => $unused ) {
301
			$lbs[$cluster] = $this->getExternalLB( $cluster );
302
		}
303
304
		return $lbs;
305
	}
306
307
	/**
308
	 * Make a new load balancer object based on template and load array
309
	 *
310
	 * @param array $template
311
	 * @param array $loads
312
	 * @param array $groupLoads
313
	 * @param string|bool $readOnlyReason
314
	 * @return LoadBalancer
315
	 */
316
	private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
317
		$lb = new LoadBalancer( array_merge(
318
			$this->baseLoadBalancerParams(),
319
			[
320
				'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
321
				'loadMonitor' => [ 'class' => $this->loadMonitorClass ],
322
				'readOnlyReason' => $readOnlyReason
323
			]
324
		) );
325
		$this->initLoadBalancer( $lb );
326
327
		return $lb;
328
	}
329
330
	/**
331
	 * Make a server array as expected by LoadBalancer::__construct, using a template and load array
332
	 *
333
	 * @param array $template
334
	 * @param array $loads
335
	 * @param array $groupLoads
336
	 * @return array
337
	 */
338
	private function makeServerArray( $template, $loads, $groupLoads ) {
339
		$servers = [];
340
		$master = true;
341
		$groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
342
		foreach ( $groupLoadsByServer as $server => $stuff ) {
343
			if ( !isset( $loads[$server] ) ) {
344
				$loads[$server] = 0;
345
			}
346
		}
347
		foreach ( $loads as $serverName => $load ) {
348
			$serverInfo = $template;
349
			if ( $master ) {
350
				$serverInfo['master'] = true;
351
				if ( isset( $this->masterTemplateOverrides ) ) {
352
					$serverInfo = $this->masterTemplateOverrides + $serverInfo;
353
				}
354
				$master = false;
355
			} else {
356
				$serverInfo['replica'] = true;
357
			}
358
			if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
359
				$serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
360
			}
361
			if ( isset( $groupLoadsByServer[$serverName] ) ) {
362
				$serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
363
			}
364
			if ( isset( $this->hostsByName[$serverName] ) ) {
365
				$serverInfo['host'] = $this->hostsByName[$serverName];
366
			} else {
367
				$serverInfo['host'] = $serverName;
368
			}
369
			$serverInfo['hostName'] = $serverName;
370
			$serverInfo['load'] = $load;
371
			$serverInfo += [ 'flags' => IDatabase::DBO_DEFAULT ];
372
373
			$servers[] = $serverInfo;
374
		}
375
376
		return $servers;
377
	}
378
379
	/**
380
	 * Take a group load array indexed by group then server, and reindex it by server then group
381
	 * @param array $groupLoads
382
	 * @return array
383
	 */
384
	private function reindexGroupLoads( $groupLoads ) {
385
		$reindexed = [];
386
		foreach ( $groupLoads as $group => $loads ) {
387
			foreach ( $loads as $server => $load ) {
388
				$reindexed[$server][$group] = $load;
389
			}
390
		}
391
392
		return $reindexed;
393
	}
394
395
	/**
396
	 * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
397
	 * @return array [database name, table prefix]
398
	 */
399
	private function getDBNameAndPrefix( $domain = false ) {
400
		$domain = ( $domain === false )
401
			? $this->localDomain
402
			: DatabaseDomain::newFromId( $domain );
403
404
		return [ $domain->getDatabase(), $domain->getTablePrefix() ];
405
	}
406
407
	/**
408
	 * Execute a function for each tracked load balancer
409
	 * The callback is called with the load balancer as the first parameter,
410
	 * and $params passed as the subsequent parameters.
411
	 * @param callable $callback
412
	 * @param array $params
413
	 */
414
	public function forEachLB( $callback, array $params = [] ) {
415
		foreach ( $this->mainLBs as $lb ) {
416
			call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
417
		}
418
		foreach ( $this->extLBs as $lb ) {
419
			call_user_func_array( $callback, array_merge( [ $lb ], $params ) );
420
		}
421
	}
422
}
423