MediaWiki  master
LBFactoryMulti.php
Go to the documentation of this file.
1 <?php
20 namespace Wikimedia\Rdbms;
21 
22 use InvalidArgumentException;
23 use LogicException;
24 use UnexpectedValueException;
25 
53 class LBFactoryMulti extends LBFactory {
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;
83 
131  public function __construct( array $conf ) {
132  parent::__construct( $conf );
133 
134  $this->hostsByServerName = $conf['hostsByName'] ?? [];
135  $this->sectionsByDB = $conf['sectionsByDB'];
136  $this->groupLoadsBySection = $conf['groupLoadsBySection'] ?? [];
137  foreach ( ( $conf['sectionLoads'] ?? [] ) as $section => $loadsByServerName ) {
138  $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] = $loadsByServerName;
139  }
140  $this->externalLoadsByCluster = $conf['externalLoads'] ?? [];
141  $this->serverTemplate = $conf['serverTemplate'] ?? [];
142  $this->externalTemplateOverrides = $conf['externalTemplateOverrides'] ?? [];
143  $this->templateOverridesBySection = $conf['templateOverridesBySection'] ?? [];
144  $this->templateOverridesByCluster = $conf['templateOverridesByCluster'] ?? [];
145  $this->masterTemplateOverrides = $conf['masterTemplateOverrides'] ?? [];
146  $this->templateOverridesByServer = $conf['templateOverridesByServer'] ?? [];
147  $this->readOnlyBySection = $conf['readOnlyBySection'] ?? [];
148 
149  if ( isset( $conf['loadMonitor'] ) ) {
150  $this->loadMonitorConfig = $conf['loadMonitor'];
151  } elseif ( isset( $conf['loadMonitorClass'] ) ) { // b/c
152  $this->loadMonitorConfig = [ 'class' => $conf['loadMonitorClass'] ];
153  } else {
154  $this->loadMonitorConfig = [ 'class' => LoadMonitor::class ];
155  }
156 
157  foreach ( array_keys( $this->externalLoadsByCluster ) as $cluster ) {
158  if ( isset( $this->groupLoadsBySection[$cluster] ) ) {
159  throw new LogicException(
160  "External cluster '$cluster' has the same name as a main section/cluster"
161  );
162  }
163  }
164  }
165 
166  public function newMainLB( $domain = false ): ILoadBalancerForOwner {
167  $domainInstance = $this->resolveDomainInstance( $domain );
168  $database = $domainInstance->getDatabase();
169  $section = $this->getSectionFromDatabase( $database );
170 
171  if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) {
172  throw new UnexpectedValueException( "Section '$section' has no hosts defined." );
173  }
174 
175  return $this->newLoadBalancer(
176  $section,
177  array_merge(
178  $this->serverTemplate,
179  $this->templateOverridesBySection[$section] ?? []
180  ),
181  $this->groupLoadsBySection[$section],
182  // Use the LB-specific read-only reason if everything isn't already read-only
183  is_string( $this->readOnlyReason )
184  ? $this->readOnlyReason
185  : ( $this->readOnlyBySection[$section] ?? false )
186  );
187  }
188 
189  public function getMainLB( $domain = false ): ILoadBalancer {
190  $domainInstance = $this->resolveDomainInstance( $domain );
191  $section = $this->getSectionFromDatabase( $domainInstance->getDatabase() );
192 
193  if ( !isset( $this->mainLBs[$section] ) ) {
194  $this->mainLBs[$section] = $this->newMainLB( $domain );
195  }
196 
197  return $this->mainLBs[$section];
198  }
199 
200  public function newExternalLB( $cluster ): ILoadBalancerForOwner {
201  if ( !isset( $this->externalLoadsByCluster[$cluster] ) ) {
202  throw new InvalidArgumentException( "Unknown cluster '$cluster'" );
203  }
204  return $this->newLoadBalancer(
205  $cluster,
206  array_merge(
207  $this->serverTemplate,
208  $this->externalTemplateOverrides,
209  $this->templateOverridesByCluster[$cluster] ?? []
210  ),
211  [ ILoadBalancer::GROUP_GENERIC => $this->externalLoadsByCluster[$cluster] ],
212  $this->readOnlyReason
213  );
214  }
215 
216  public function getExternalLB( $cluster ): ILoadBalancer {
217  if ( !isset( $this->externalLBs[$cluster] ) ) {
218  $this->externalLBs[$cluster] = $this->newExternalLB(
219  $cluster
220  );
221  }
222 
223  return $this->externalLBs[$cluster];
224  }
225 
226  public function getAllMainLBs(): array {
227  $lbs = [];
228  foreach ( $this->sectionsByDB as $db => $section ) {
229  if ( !isset( $lbs[$section] ) ) {
230  $lbs[$section] = $this->getMainLB( $db );
231  }
232  }
233 
234  return $lbs;
235  }
236 
237  public function getAllExternalLBs(): array {
238  $lbs = [];
239  foreach ( $this->externalLoadsByCluster as $cluster => $unused ) {
240  $lbs[$cluster] = $this->getExternalLB( $cluster );
241  }
242 
243  return $lbs;
244  }
245 
246  public function forEachLB( $callback, array $params = [] ) {
247  wfDeprecated( __METHOD__, '1.39' );
248  foreach ( $this->mainLBs as $lb ) {
249  $callback( $lb, ...$params );
250  }
251  foreach ( $this->externalLBs as $lb ) {
252  $callback( $lb, ...$params );
253  }
254  }
255 
256  protected function getLBsForOwner() {
257  foreach ( $this->mainLBs as $lb ) {
258  yield $lb;
259  }
260  foreach ( $this->externalLBs as $lb ) {
261  yield $lb;
262  }
263  }
264 
274  private function newLoadBalancer(
275  string $clusterName,
276  array $serverTemplate,
277  array $groupLoads,
278  $readOnlyReason
279  ) {
280  $lb = new LoadBalancer( array_merge(
281  $this->baseLoadBalancerParams(),
282  [
283  'servers' => $this->makeServerConfigArrays( $serverTemplate, $groupLoads ),
284  'loadMonitor' => $this->loadMonitorConfig,
285  'readOnlyReason' => $readOnlyReason,
286  'clusterName' => $clusterName
287  ]
288  ) );
289  $this->initLoadBalancer( $lb );
290 
291  return $lb;
292  }
293 
301  private function makeServerConfigArrays( array $serverTemplate, array $groupLoads ) {
302  // The primary DB server is the first host explicitly listed in the generic load group
303  if ( !$groupLoads[ILoadBalancer::GROUP_GENERIC] ) {
304  throw new UnexpectedValueException( "Empty generic load array; no primary DB defined." );
305  }
306  $groupLoadsByServerName = $this->reindexGroupLoadsByServerName( $groupLoads );
307  // Get the ordered map of (server name => load); the primary DB server is first
308  $genericLoads = $groupLoads[ILoadBalancer::GROUP_GENERIC];
309  // Implicitly append any hosts that only appear in custom load groups
310  $genericLoads += array_fill_keys( array_keys( $groupLoadsByServerName ), 0 );
311  $servers = [];
312  foreach ( $genericLoads as $serverName => $load ) {
313  $servers[] = array_merge(
314  $serverTemplate,
315  $servers ? [] : $this->masterTemplateOverrides,
316  $this->templateOverridesByServer[$serverName] ?? [],
317  [
318  'host' => $this->hostsByServerName[$serverName] ?? $serverName,
319  'serverName' => $serverName,
320  'load' => $load,
321  'groupLoads' => $groupLoadsByServerName[$serverName] ?? []
322  ]
323  );
324  }
325 
326  return $servers;
327  }
328 
335  private function reindexGroupLoadsByServerName( array $groupLoads ) {
336  $groupLoadsByServerName = [];
337  foreach ( $groupLoads as $group => $loadByServerName ) {
338  foreach ( $loadByServerName as $serverName => $load ) {
339  $groupLoadsByServerName[$serverName][$group] = $load;
340  }
341  }
342 
343  return $groupLoadsByServerName;
344  }
345 
350  private function getSectionFromDatabase( $database ) {
351  return $this->sectionsByDB[$database] ?? self::CLUSTER_MAIN_DEFAULT;
352  }
353 
354  public function reconfigure( array $conf ): void {
355  if ( !$conf ) {
356  return;
357  }
358 
359  foreach ( $this->mainLBs as $lb ) {
360  $groupLoads = $conf['groupLoadsBySection'][$lb->getClusterName()];
361  $groupLoads[ILoadBalancer::GROUP_GENERIC] = $conf['sectionLoads'][$lb->getClusterName()] ?? [];
362  $config = [ 'servers' => $this->makeServerConfigArrays( $conf['serverTemplate'] ?? [], $groupLoads ) ];
363  $lb->reconfigure( $config );
364 
365  }
366  foreach ( $this->externalLBs as $lb ) {
367  $groupLoads = [ ILoadBalancer::GROUP_GENERIC => $conf['externalLoads'][$lb->getClusterName()] ];
368  $config = [ 'servers' => $this->makeServerConfigArrays( $conf['serverTemplate'] ?? [], $groupLoads ) ];
369  $lb->reconfigure( $config );
370  }
371  }
372 }
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
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.
forEachLB( $callback, array $params=[])
Execute a function for each instantiated tracked load balancer instance.
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):
resolveDomainInstance( $domain)
Definition: LBFactory.php:279
Internal interface for LoadBalancer methods used by LBFactory.
Create and track the database connections and transactions for a given database cluster.
const GROUP_GENERIC
The generic query group.