MediaWiki  master
LBFactoryMulti.php
Go to the documentation of this file.
1 <?php
24 namespace Wikimedia\Rdbms;
25 
26 use InvalidArgumentException;
27 use LogicException;
28 use UnexpectedValueException;
29 
35 class LBFactoryMulti extends LBFactory {
37  private $mainLBs = [];
39  private $externalLBs = [];
40 
44  private $sectionsByDB;
50  private $serverTemplate;
65 
110  public function __construct( array $conf ) {
111  parent::__construct( $conf );
112 
113  $this->hostsByServerName = $conf['hostsByName'] ?? [];
114  $this->sectionsByDB = $conf['sectionsByDB'];
115  $this->groupLoadsBySection = $conf['groupLoadsBySection'] ?? [];
116  foreach ( ( $conf['sectionLoads'] ?? [] ) as $section => $loadsByServerName ) {
117  $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] = $loadsByServerName;
118  }
119  $this->externalLoadsByCluster = $conf['externalLoads'] ?? [];
120  $this->serverTemplate = $conf['serverTemplate'] ?? [];
121  $this->externalTemplateOverrides = $conf['externalTemplateOverrides'] ?? [];
122  $this->templateOverridesBySection = $conf['templateOverridesBySection'] ?? [];
123  $this->templateOverridesByCluster = $conf['templateOverridesByCluster'] ?? [];
124  $this->masterTemplateOverrides = $conf['masterTemplateOverrides'] ?? [];
125  $this->templateOverridesByServer = $conf['templateOverridesByServer'] ?? [];
126  $this->readOnlyBySection = $conf['readOnlyBySection'] ?? [];
127 
128  if ( isset( $conf['loadMonitor'] ) ) {
129  $this->loadMonitorConfig = $conf['loadMonitor'];
130  } elseif ( isset( $conf['loadMonitorClass'] ) ) { // b/c
131  $this->loadMonitorConfig = [ 'class' => $conf['loadMonitorClass'] ];
132  } else {
133  $this->loadMonitorConfig = [ 'class' => LoadMonitor::class ];
134  }
135 
136  foreach ( array_keys( $this->externalLoadsByCluster ) as $cluster ) {
137  if ( isset( $this->groupLoadsBySection[$cluster] ) ) {
138  throw new LogicException(
139  "External cluster '$cluster' has the same name as a main section/cluster"
140  );
141  }
142  }
143  }
144 
145  public function newMainLB( $domain = false ): ILoadBalancerForOwner {
146  $domainInstance = $this->resolveDomainInstance( $domain );
147  $database = $domainInstance->getDatabase();
148  $section = $this->getSectionFromDatabase( $database );
149 
150  if ( !isset( $this->groupLoadsBySection[$section][ILoadBalancer::GROUP_GENERIC] ) ) {
151  throw new UnexpectedValueException( "Section '$section' has no hosts defined." );
152  }
153 
154  return $this->newLoadBalancer(
155  $section,
156  array_merge(
157  $this->serverTemplate,
158  $this->templateOverridesBySection[$section] ?? []
159  ),
160  $this->groupLoadsBySection[$section],
161  // Use the LB-specific read-only reason if everything isn't already read-only
162  is_string( $this->readOnlyReason )
163  ? $this->readOnlyReason
164  : ( $this->readOnlyBySection[$section] ?? false )
165  );
166  }
167 
168  public function getMainLB( $domain = false ): ILoadBalancer {
169  $domainInstance = $this->resolveDomainInstance( $domain );
170  $section = $this->getSectionFromDatabase( $domainInstance->getDatabase() );
171 
172  if ( !isset( $this->mainLBs[$section] ) ) {
173  $this->mainLBs[$section] = $this->newMainLB( $domain );
174  }
175 
176  return $this->mainLBs[$section];
177  }
178 
179  public function newExternalLB( $cluster ): ILoadBalancerForOwner {
180  if ( !isset( $this->externalLoadsByCluster[$cluster] ) ) {
181  throw new InvalidArgumentException( "Unknown cluster '$cluster'" );
182  }
183  return $this->newLoadBalancer(
184  $cluster,
185  array_merge(
186  $this->serverTemplate,
187  $this->externalTemplateOverrides,
188  $this->templateOverridesByCluster[$cluster] ?? []
189  ),
190  [ ILoadBalancer::GROUP_GENERIC => $this->externalLoadsByCluster[$cluster] ],
191  $this->readOnlyReason
192  );
193  }
194 
195  public function getExternalLB( $cluster ): ILoadBalancer {
196  if ( !isset( $this->externalLBs[$cluster] ) ) {
197  $this->externalLBs[$cluster] = $this->newExternalLB(
198  $cluster
199  );
200  }
201 
202  return $this->externalLBs[$cluster];
203  }
204 
205  public function getAllMainLBs(): array {
206  $lbs = [];
207  foreach ( $this->sectionsByDB as $db => $section ) {
208  if ( !isset( $lbs[$section] ) ) {
209  $lbs[$section] = $this->getMainLB( $db );
210  }
211  }
212 
213  return $lbs;
214  }
215 
216  public function getAllExternalLBs(): array {
217  $lbs = [];
218  foreach ( $this->externalLoadsByCluster as $cluster => $unused ) {
219  $lbs[$cluster] = $this->getExternalLB( $cluster );
220  }
221 
222  return $lbs;
223  }
224 
225  public function forEachLB( $callback, array $params = [] ) {
226  wfDeprecated( __METHOD__, '1.39' );
227  foreach ( $this->mainLBs as $lb ) {
228  $callback( $lb, ...$params );
229  }
230  foreach ( $this->externalLBs as $lb ) {
231  $callback( $lb, ...$params );
232  }
233  }
234 
235  protected function getLBsForOwner() {
236  foreach ( $this->mainLBs as $lb ) {
237  yield $lb;
238  }
239  foreach ( $this->externalLBs as $lb ) {
240  yield $lb;
241  }
242  }
243 
253  private function newLoadBalancer(
254  string $clusterName,
255  array $serverTemplate,
256  array $groupLoads,
257  $readOnlyReason
258  ) {
259  $lb = new LoadBalancer( array_merge(
260  $this->baseLoadBalancerParams(),
261  [
262  'servers' => $this->makeServerConfigArrays( $serverTemplate, $groupLoads ),
263  'loadMonitor' => $this->loadMonitorConfig,
264  'readOnlyReason' => $readOnlyReason,
265  'clusterName' => $clusterName
266  ]
267  ) );
268  $this->initLoadBalancer( $lb );
269 
270  return $lb;
271  }
272 
280  private function makeServerConfigArrays( array $serverTemplate, array $groupLoads ) {
281  // The primary DB server is the first host explicitly listed in the generic load group
282  if ( !$groupLoads[ILoadBalancer::GROUP_GENERIC] ) {
283  throw new UnexpectedValueException( "Empty generic load array; no primary DB defined." );
284  }
285  $groupLoadsByServerName = $this->reindexGroupLoadsByServerName( $groupLoads );
286  // Get the ordered map of (server name => load); the primary DB server is first
287  $genericLoads = $groupLoads[ILoadBalancer::GROUP_GENERIC];
288  // Implicitly append any hosts that only appear in custom load groups
289  $genericLoads += array_fill_keys( array_keys( $groupLoadsByServerName ), 0 );
290  $servers = [];
291  foreach ( $genericLoads as $serverName => $load ) {
292  $servers[] = array_merge(
293  $serverTemplate,
294  $servers ? [] : $this->masterTemplateOverrides,
295  $this->templateOverridesByServer[$serverName] ?? [],
296  [
297  'host' => $this->hostsByServerName[$serverName] ?? $serverName,
298  'serverName' => $serverName,
299  'load' => $load,
300  'groupLoads' => $groupLoadsByServerName[$serverName] ?? []
301  ]
302  );
303  }
304 
305  return $servers;
306  }
307 
314  private function reindexGroupLoadsByServerName( array $groupLoads ) {
315  $groupLoadsByServerName = [];
316  foreach ( $groupLoads as $group => $loadByServerName ) {
317  foreach ( $loadByServerName as $serverName => $load ) {
318  $groupLoadsByServerName[$serverName][$group] = $load;
319  }
320  }
321 
322  return $groupLoadsByServerName;
323  }
324 
329  private function getSectionFromDatabase( $database ) {
330  return $this->sectionsByDB[$database] ?? self::CLUSTER_MAIN_DEFAULT;
331  }
332 }
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
A multi-database, multi-primary DB factory for Wikimedia and similar installations.
int[][][] $groupLoadsBySection
Map of (main section => group => server name => load ratio)
newMainLB( $domain=false)
Create a new load balancer instance for the main cluster that handles the given domain.
string[] bool[] $readOnlyBySection
A map of (main section => read-only message)
array[] $templateOverridesByCluster
Map of (external cluster => server config map overrides)
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.
newLoadBalancer(string $clusterName, array $serverTemplate, array $groupLoads, $readOnlyReason)
Make a new load balancer object based on template and load array.
forEachLB( $callback, array $params=[])
Execute a function for each instantiated tracked load balancer instance.
array $externalTemplateOverrides
Server config map overriding "serverTemplate" for all external servers.
array $masterTemplateOverrides
Server config override map for all main/external primary DB servers.
newExternalLB( $cluster)
Create a new load balancer instance for an external cluster.
string[] $hostsByServerName
Map of (server name => IP address)
array $serverTemplate
Server config map ("host", "serverName", "load", and "groupLoads" ignored)
int[][] $externalLoadsByCluster
Map of (external cluster => server name => load ratio)
makeServerConfigArrays(array $serverTemplate, array $groupLoads)
Make a server array as expected by LoadBalancer::__construct()
array< string, LoadBalancer > $externalLBs
Map of (external cluster => tracked LoadBalancer)
getLBsForOwner()
Get all tracked load balancers with the internal "for owner" interface.
array[] $templateOverridesBySection
Map of (main section => server config map overrides)
__construct(array $conf)
Template override precedence (highest => lowest):
array[] $templateOverridesByServer
Map of (server name => server config map overrides) for all servers.
string[] $sectionsByDB
Map of (database name => main section)
array< string, LoadBalancer > $mainLBs
Map of (main section => tracked LoadBalancer)
reindexGroupLoadsByServerName(array $groupLoads)
Take a group load array indexed by (group,server) and reindex it by (server,group)
array $loadMonitorConfig
Configuration for the LoadMonitor to use within LoadBalancer instances.
An interface for generating database load balancers.
Definition: LBFactory.php:43
resolveDomainInstance( $domain)
Definition: LBFactory.php:197
Database connection, tracking, load balancing, and transaction manager for a cluster.
Interface for internal LoadBalancer methods, suitable for use on untracked objects returned by newMai...
Database cluster connection, tracking, load balancing, and transaction manager interface.
const GROUP_GENERIC
The generic query group.