1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
<?php
namespace Wikimedia\Rdbms;
use InvalidArgumentException;
use UnexpectedValueException;
/**
* Container for accessing information about the database servers in a database cluster
*
* @internal
* @ingroup Database
*/
class ServerInfo {
/**
* Default 'maxLag' when unspecified
* @internal Only for use within LoadBalancer/LoadMonitor
*/
public const MAX_LAG_DEFAULT = 6;
public const WRITER_INDEX = 0;
/** @var array[] Map of (server index => server config array) */
private $servers;
public function addServer( $i, $server ) {
$this->servers[$i] = $server;
}
public function getServerMaxLag( $i ) {
return $this->servers[$i]['max lag'] ?? self::MAX_LAG_DEFAULT;
}
public function getServerDriver( $i ) {
return $this->servers[$i]['driver'] ?? null;
}
public function getServerType( $i ) {
return $this->servers[$i]['type'] ?? 'unknown';
}
public function getServerName( $i ): string {
return $this->servers[$i]['serverName'] ?? 'localhost';
}
public function getServerInfo( $i ) {
return $this->servers[$i] ?? false;
}
public function getServerCount() {
return count( $this->servers );
}
public function hasServerIndex( $i ) {
return isset( $this->servers[$i] );
}
public function getLagTimes() {
$knownLagTimes = []; // map of (server index => 0 seconds)
$indexesWithLag = [];
foreach ( $this->servers as $i => $server ) {
if ( empty( $server['is static'] ) ) {
$indexesWithLag[] = $i; // DB server might have replication lag
} else {
$knownLagTimes[$i] = 0; // DB server is a non-replicating and read-only archive
}
}
return [ $indexesWithLag, $knownLagTimes ];
}
/**
* @param int $i Server index
* @param string|null $field Server index field [optional]
* @return mixed
* @throws InvalidArgumentException
*/
public function getServerInfoStrict( $i, $field = null ) {
if ( !isset( $this->servers[$i] ) || !is_array( $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No server with index '$i'" );
}
if ( $field !== null ) {
if ( !array_key_exists( $field, $this->servers[$i] ) ) {
throw new InvalidArgumentException( "No field '$field' in server index '$i'" );
}
return $this->servers[$i][$field];
}
return $this->servers[$i];
}
/**
* @return int[] List of replica server indexes
*/
public function getStreamingReplicaIndexes() {
$indexes = [];
foreach ( $this->servers as $i => $server ) {
if ( $i !== self::WRITER_INDEX && empty( $server['is static'] ) ) {
$indexes[] = $i;
}
}
return $indexes;
}
public function hasStreamingReplicaServers() {
return (bool)$this->getStreamingReplicaIndexes();
}
public function reconfigureServers( $paramServers ) {
$newIndexBySrvName = [];
$this->normalizeServerMaps( $paramServers, $newIndexBySrvName );
// Map of (existing server index => corresponding index in new config or null)
$newIndexByServerIndex = [];
// Remove servers that no longer exist in the new config and preserve those that
// still exist, even if they switched replication roles (e.g. primary/secondary).
// Note that if the primary server is depooled and a replica server is promoted
// to primary, then DB_PRIMARY handles will fail with server index errors. Note
// that if the primary server swaps roles with a replica server, then write queries
// to DB_PRIMARY handles will fail with read-only errors.
foreach ( $this->servers as $i => $server ) {
$srvName = $this->getServerName( $i );
// Since pooling or depooling of servers causes the remaining servers to be
// assigned different indexes, find the corresponding index by server name.
// Also, note that the primary can be reconfigured as a replica (moved from
// the writer index) and vice versa (moved to the writer index).
$newIndex = $newIndexByServerIndex[$i] = $newIndexBySrvName[$srvName] ?? null;
if ( $newIndex === null ) {
unset( $this->servers[$i] );
}
}
return $newIndexByServerIndex;
}
public function normalizeServerMaps( array $servers, array &$indexBySrvName = null ) {
if ( !$servers ) {
throw new InvalidArgumentException( 'Missing or empty "servers" parameter' );
}
$listKey = -1;
$indexBySrvName = [];
foreach ( $servers as $i => $server ) {
if ( ++$listKey !== $i ) {
throw new UnexpectedValueException( 'List expected for "servers" parameter' );
}
$srvName = $server['serverName'] ?? $server['host'] ?? '';
$srvName = ( $srvName !== '' ) ? $srvName : 'localhost';
if ( isset( $indexBySrvName[$srvName] ) ) {
// Duplicate server names confuse caching, logging, and reconfigure()
throw new UnexpectedValueException( 'Duplicate server name "' . $srvName . '"' );
}
$indexBySrvName[$srvName] = $i;
$servers[$i]['serverName'] = $srvName;
$servers[$i]['groupLoads'] ??= [];
}
return $servers;
}
/**
* @return string Name of the primary DB server of the relevant DB cluster (e.g. "db1052")
*/
public function getPrimaryServerName() {
return $this->getServerName( self::WRITER_INDEX );
}
public function hasReplicaServers() {
return ( $this->getServerCount() > 1 );
}
}
|