MediaWiki master
LBFactoryMulti.php
Go to the documentation of this file.
1<?php
20namespace Wikimedia\Rdbms;
21
22use InvalidArgumentException;
23use LogicException;
24use UnexpectedValueException;
25
55 private $mainLBs = [];
57 private $externalLBs = [];
58
60 private $hostsByServerName;
62 private $sectionsByDB;
64 private $groupLoadsBySection;
66 private $externalLoadsByCluster;
68 private $serverTemplate;
70 private $externalTemplateOverrides;
72 private $templateOverridesBySection;
74 private $templateOverridesByCluster;
76 private $masterTemplateOverrides;
78 private $templateOverridesByServer;
80 private $readOnlyBySection;
82 private $loadMonitorConfig;
84 private $nonLocalDomainCache = [];
85
135 public function __construct( array $conf ) {
136 parent::__construct( $conf );
137
138 $this->hostsByServerName = $conf['hostsByName'] ?? [];
139 $this->sectionsByDB = $conf['sectionsByDB'];
140 $this->sectionsByDB += [ self::CLUSTER_MAIN_DEFAULT => self::CLUSTER_MAIN_DEFAULT ];
141 $this->groupLoadsBySection = $conf['groupLoadsBySection'] ?? [];
142 foreach ( ( $conf['sectionLoads'] ?? [] ) as $section => $loadsByServerName ) {
143 $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] = $loadsByServerName;
144 }
145 $this->externalLoadsByCluster = $conf['externalLoads'] ?? [];
146 $this->serverTemplate = $conf['serverTemplate'] ?? [];
147 $this->externalTemplateOverrides = $conf['externalTemplateOverrides'] ?? [];
148 $this->templateOverridesBySection = $conf['templateOverridesBySection'] ?? [];
149 $this->templateOverridesByCluster = $conf['templateOverridesByCluster'] ?? [];
150 $this->masterTemplateOverrides = $conf['masterTemplateOverrides'] ?? [];
151 $this->templateOverridesByServer = $conf['templateOverridesByServer'] ?? [];
152 $this->readOnlyBySection = $conf['readOnlyBySection'] ?? [];
153
154 if ( isset( $conf['loadMonitor'] ) ) {
155 $this->loadMonitorConfig = $conf['loadMonitor'];
156 } elseif ( isset( $conf['loadMonitorClass'] ) ) { // b/c
157 $this->loadMonitorConfig = [ 'class' => $conf['loadMonitorClass'] ];
158 } else {
159 $this->loadMonitorConfig = [ 'class' => LoadMonitor::class ];
160 }
161
162 foreach ( $this->externalLoadsByCluster as $cluster => $_ ) {
163 if ( isset( $this->groupLoadsBySection[$cluster] ) ) {
164 throw new LogicException(
165 "External cluster '$cluster' has the same name as a main section/cluster"
166 );
167 }
168 }
169 }
170
171 public function newMainLB( $domain = false ): ILoadBalancerForOwner {
172 $domainInstance = $this->resolveDomainInstance( $domain );
173 $database = $domainInstance->getDatabase();
174 $section = $this->getSectionFromDatabase( $database );
175
176 if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) {
177 throw new UnexpectedValueException( "Section '$section' has no hosts defined." );
178 }
179
180 return $this->newLoadBalancer(
181 $section,
182 array_merge(
183 $this->serverTemplate,
184 $this->templateOverridesBySection[$section] ?? []
185 ),
186 $this->groupLoadsBySection[$section],
187 // Use the LB-specific read-only reason if everything isn't already read-only
188 is_string( $this->readOnlyReason )
189 ? $this->readOnlyReason
190 : ( $this->readOnlyBySection[$section] ?? false )
191 );
192 }
193
198 private function resolveDomainInstance( $domain ) {
199 if ( $domain instanceof DatabaseDomain ) {
200 return $domain; // already a domain instance
201 } elseif ( $domain === false || $domain === $this->localDomain->getId() ) {
202 return $this->localDomain;
203 } elseif ( isset( $this->domainAliases[$domain] ) ) {
204 // This array acts as both the original map and as instance cache.
205 // Instances pass-through DatabaseDomain::newFromId as-is.
206 $this->domainAliases[$domain] =
207 DatabaseDomain::newFromId( $this->domainAliases[$domain] );
208
209 return $this->domainAliases[$domain];
210 }
211
212 $cachedDomain = $this->nonLocalDomainCache[$domain] ?? null;
213 if ( $cachedDomain === null ) {
214 $cachedDomain = DatabaseDomain::newFromId( $domain );
215 $this->nonLocalDomainCache = [ $domain => $cachedDomain ];
216 }
217
218 return $cachedDomain;
219 }
220
221 public function getMainLB( $domain = false ): ILoadBalancer {
222 $domainInstance = $this->resolveDomainInstance( $domain );
223 $section = $this->getSectionFromDatabase( $domainInstance->getDatabase() );
224
225 if ( !isset( $this->mainLBs[$section] ) ) {
226 $this->mainLBs[$section] = $this->newMainLB( $domain );
227 }
228
229 return $this->mainLBs[$section];
230 }
231
232 public function newExternalLB( $cluster ): ILoadBalancerForOwner {
233 if ( !isset( $this->externalLoadsByCluster[$cluster] ) ) {
234 throw new InvalidArgumentException( "Unknown cluster '$cluster'" );
235 }
236 return $this->newLoadBalancer(
237 $cluster,
238 array_merge(
239 $this->serverTemplate,
240 $this->externalTemplateOverrides,
241 $this->templateOverridesByCluster[$cluster] ?? []
242 ),
243 [ ILoadBalancer::GROUP_GENERIC => $this->externalLoadsByCluster[$cluster] ],
244 $this->readOnlyReason
245 );
246 }
247
248 public function getExternalLB( $cluster ): ILoadBalancer {
249 if ( !isset( $this->externalLBs[$cluster] ) ) {
250 $this->externalLBs[$cluster] = $this->newExternalLB(
251 $cluster
252 );
253 }
254
255 return $this->externalLBs[$cluster];
256 }
257
258 public function getAllMainLBs(): array {
259 $lbs = [];
260 foreach ( $this->sectionsByDB as $db => $section ) {
261 if ( !isset( $lbs[$section] ) ) {
262 $lbs[$section] = $this->getMainLB( $db );
263 }
264 }
265
266 return $lbs;
267 }
268
269 public function getAllExternalLBs(): array {
270 $lbs = [];
271 foreach ( $this->externalLoadsByCluster as $cluster => $unused ) {
272 $lbs[$cluster] = $this->getExternalLB( $cluster );
273 }
274
275 return $lbs;
276 }
277
278 protected function getLBsForOwner() {
279 foreach ( $this->mainLBs as $lb ) {
280 yield $lb;
281 }
282 foreach ( $this->externalLBs as $lb ) {
283 yield $lb;
284 }
285 }
286
296 private function newLoadBalancer(
297 string $clusterName,
298 array $serverTemplate,
299 array $groupLoads,
300 $readOnlyReason
301 ) {
302 $lb = new LoadBalancer( array_merge(
303 $this->baseLoadBalancerParams(),
304 [
305 'servers' => $this->makeServerConfigArrays( $serverTemplate, $groupLoads ),
306 'loadMonitor' => $this->loadMonitorConfig,
307 'readOnlyReason' => $readOnlyReason,
308 'clusterName' => $clusterName
309 ]
310 ) );
311 $this->initLoadBalancer( $lb );
312
313 return $lb;
314 }
315
323 private function makeServerConfigArrays( array $serverTemplate, array $groupLoads ) {
324 // The primary DB server is the first host explicitly listed in the generic load group
325 if ( !$groupLoads[ILoadBalancer::GROUP_GENERIC] ) {
326 throw new UnexpectedValueException( "Empty generic load array; no primary DB defined." );
327 }
328 $groupLoadsByServerName = [];
329 foreach ( $groupLoads as $group => $loadByServerName ) {
330 foreach ( $loadByServerName as $serverName => $load ) {
331 $groupLoadsByServerName[$serverName][$group] = $load;
332 }
333 }
334
335 // Get the ordered map of (server name => load); the primary DB server is first
336 $genericLoads = $groupLoads[ILoadBalancer::GROUP_GENERIC];
337 // Implicitly append any hosts that only appear in custom load groups
338 $genericLoads += array_fill_keys( array_keys( $groupLoadsByServerName ), 0 );
339 $servers = [];
340 foreach ( $genericLoads as $serverName => $load ) {
341 $servers[] = array_merge(
342 $serverTemplate,
343 $servers ? [] : $this->masterTemplateOverrides,
344 $this->templateOverridesByServer[$serverName] ?? [],
345 [
346 'host' => $this->hostsByServerName[$serverName] ?? $serverName,
347 'serverName' => $serverName,
348 'load' => $load,
349 'groupLoads' => $groupLoadsByServerName[$serverName] ?? []
350 ]
351 );
352 }
353
354 return $servers;
355 }
356
361 private function getSectionFromDatabase( $database ) {
362 return $this->sectionsByDB[$database]
363 ?? $this->sectionsByDB[self::CLUSTER_MAIN_DEFAULT]
364 ?? self::CLUSTER_MAIN_DEFAULT;
365 }
366
367 public function reconfigure( array $conf ): void {
368 if ( !$conf ) {
369 return;
370 }
371
372 foreach ( $this->mainLBs as $lb ) {
373 // Approximate what LBFactoryMulti::__construct does (T346365)
374 $groupLoads = $conf['groupLoadsBySection'][$lb->getClusterName()] ?? [];
375 $groupLoads[ILoadBalancer::GROUP_GENERIC] = $conf['sectionLoads'][$lb->getClusterName()];
376 $config = [
377 'servers' => $this->makeServerConfigArrays( $conf['serverTemplate'] ?? [], $groupLoads )
378 ];
379 $lb->reconfigure( $config );
380
381 }
382 foreach ( $this->externalLBs as $lb ) {
383 $groupLoads = [
384 ILoadBalancer::GROUP_GENERIC => $conf['externalLoads'][$lb->getClusterName()]
385 ];
386 $config = [
387 'servers' => $this->makeServerConfigArrays( $conf['serverTemplate'] ?? [], $groupLoads )
388 ];
389 $lb->reconfigure( $config );
390 }
391 }
392}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
Class to handle database/schema/prefix specifications for IDatabase.
LoadBalancer manager for sites with several "main" database clusters.
newMainLB( $domain=false)
Create a new load balancer instance for the main cluster that handles the given domain.
getAllMainLBs()
Get the tracked load balancer instances for all main clusters.
getAllExternalLBs()
Get the tracked load balancer instances for all external clusters.
getExternalLB( $cluster)
Get the tracked load balancer instance for an external cluster.
getMainLB( $domain=false)
Get the tracked load balancer instance for the main cluster that handles the given domain.
newExternalLB( $cluster)
Create a new load balancer instance for an external cluster.
getLBsForOwner()
Get all tracked load balancers with the internal "for owner" interface.
reconfigure(array $conf)
Reconfigure using the given config array.
__construct(array $conf)
Template override precedence (highest => lowest):
Internal interface for load balancer instances exposed to their owner.
This class is a delegate to ILBFactory for a given database cluster.
const GROUP_GENERIC
The generic query group.