MediaWiki REL1_31
MySQLMasterPos.php
Go to the documentation of this file.
1<?php
2
3namespace Wikimedia\Rdbms;
4
5use InvalidArgumentException;
6use UnexpectedValueException;
7
19class MySQLMasterPos implements DBMasterPos {
21 private $style;
23 private $binLog;
25 private $logPos;
27 private $gtids = [];
35 private $asOfTime = 0.0;
36
37 const BINARY_LOG = 'binary-log';
38 const GTID_MARIA = 'gtid-maria';
39 const GTID_MYSQL = 'gtid-mysql';
40
42 const CORD_INDEX = 0;
44 const CORD_EVENT = 1;
45
50 public function __construct( $position, $asOfTime ) {
51 $this->init( $position, $asOfTime );
52 }
53
58 protected function init( $position, $asOfTime ) {
59 $m = [];
60 if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', $position, $m ) ) {
61 $this->binLog = $m[1]; // ideally something like host name
62 $this->logPos = [ self::CORD_INDEX => (int)$m[2], self::CORD_EVENT => (int)$m[3] ];
63 $this->style = self::BINARY_LOG;
64 } else {
65 $gtids = array_filter( array_map( 'trim', explode( ',', $position ) ) );
66 foreach ( $gtids as $gtid ) {
67 $components = self::parseGTID( $gtid );
68 if ( !$components ) {
69 throw new InvalidArgumentException( "Invalid GTID '$gtid'." );
70 }
71
72 list( $domain, $pos ) = $components;
73 if ( isset( $this->gtids[$domain] ) ) {
74 // For MySQL, handle the case where some past issue caused a gap in the
75 // executed GTID set, e.g. [last_purged+1,N-1] and [N+1,N+2+K]. Ignore the
76 // gap by using the GTID with the highest ending sequence number.
77 list( , $otherPos ) = self::parseGTID( $this->gtids[$domain] );
78 if ( $pos > $otherPos ) {
79 $this->gtids[$domain] = $gtid;
80 }
81 } else {
82 $this->gtids[$domain] = $gtid;
83 }
84
85 if ( is_int( $domain ) ) {
86 $this->style = self::GTID_MARIA; // gtid_domain_id
87 } else {
88 $this->style = self::GTID_MYSQL; // server_uuid
89 }
90 }
91 if ( !$this->gtids ) {
92 throw new InvalidArgumentException( "GTID set cannot be empty." );
93 }
94 }
95
96 $this->asOfTime = $asOfTime;
97 }
98
99 public function asOfTime() {
100 return $this->asOfTime;
101 }
102
103 public function hasReached( DBMasterPos $pos ) {
104 if ( !( $pos instanceof self ) ) {
105 throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
106 }
107
108 // Prefer GTID comparisons, which work with multi-tier replication
109 $thisPosByDomain = $this->getActiveGtidCoordinates();
110 $thatPosByDomain = $pos->getActiveGtidCoordinates();
111 if ( $thisPosByDomain && $thatPosByDomain ) {
112 $comparisons = [];
113 // Check that this has positions reaching those in $pos for all domains in common
114 foreach ( $thatPosByDomain as $domain => $thatPos ) {
115 if ( isset( $thisPosByDomain[$domain] ) ) {
116 $comparisons[] = ( $thatPos <= $thisPosByDomain[$domain] );
117 }
118 }
119 // Check that $this has a GTID for at least one domain also in $pos; due to MariaDB
120 // quirks, prior master switch-overs may result in inactive garbage GTIDs that cannot
121 // be cleaned up. Assume that the domains in both this and $pos cover the relevant
122 // active channels.
123 return ( $comparisons && !in_array( false, $comparisons, true ) );
124 }
125
126 // Fallback to the binlog file comparisons
127 $thisBinPos = $this->getBinlogCoordinates();
128 $thatBinPos = $pos->getBinlogCoordinates();
129 if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
130 return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
131 }
132
133 // Comparing totally different binlogs does not make sense
134 return false;
135 }
136
137 public function channelsMatch( DBMasterPos $pos ) {
138 if ( !( $pos instanceof self ) ) {
139 throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
140 }
141
142 // Prefer GTID comparisons, which work with multi-tier replication
143 $thisPosDomains = array_keys( $this->getActiveGtidCoordinates() );
144 $thatPosDomains = array_keys( $pos->getActiveGtidCoordinates() );
145 if ( $thisPosDomains && $thatPosDomains ) {
146 // Check that $this has a GTID for at least one domain also in $pos; due to MariaDB
147 // quirks, prior master switch-overs may result in inactive garbage GTIDs that cannot
148 // easily be cleaned up. Assume that the domains in both this and $pos cover the
149 // relevant active channels.
150 return array_intersect( $thatPosDomains, $thisPosDomains ) ? true : false;
151 }
152
153 // Fallback to the binlog file comparisons
154 $thisBinPos = $this->getBinlogCoordinates();
155 $thatBinPos = $pos->getBinlogCoordinates();
156
157 return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
158 }
159
164 public function getLogName() {
165 return $this->gtids ? null : $this->binLog;
166 }
167
172 public function getLogPosition() {
173 return $this->gtids ? null : $this->logPos;
174 }
175
180 public function getLogFile() {
181 return $this->gtids ? null : "{$this->binLog}.{$this->logPos[self::CORD_INDEX]}";
182 }
183
188 public function getGTIDs() {
189 return $this->gtids;
190 }
191
196 public function setActiveDomain( $id ) {
197 $this->activeDomain = (int)$id;
198 }
199
204 public function setActiveOriginServerId( $id ) {
205 $this->activeServerId = (int)$id;
206 }
207
212 public function setActiveOriginServerUUID( $id ) {
213 $this->activeServerUUID = $id;
214 }
215
222 public static function getCommonDomainGTIDs( MySQLMasterPos $pos, MySQLMasterPos $refPos ) {
223 return array_values(
224 array_intersect_key( $pos->gtids, $refPos->getActiveGtidCoordinates() )
225 );
226 }
227
233 protected function getActiveGtidCoordinates() {
234 $gtidInfos = [];
235
236 foreach ( $this->gtids as $domain => $gtid ) {
237 list( $domain, $pos, $server ) = self::parseGTID( $gtid );
238
239 $ignore = false;
240 // Filter out GTIDs from non-active replication domains
241 if ( $this->style === self::GTID_MARIA && $this->activeDomain !== null ) {
242 $ignore |= ( $domain !== $this->activeDomain );
243 }
244 // Likewise for GTIDs from non-active replication origin servers
245 if ( $this->style === self::GTID_MARIA && $this->activeServerId !== null ) {
246 $ignore |= ( $server !== $this->activeServerId );
247 } elseif ( $this->style === self::GTID_MYSQL && $this->activeServerUUID !== null ) {
248 $ignore |= ( $server !== $this->activeServerUUID );
249 }
250
251 if ( !$ignore ) {
252 $gtidInfos[$domain] = $pos;
253 }
254 }
255
256 return $gtidInfos;
257 }
258
263 protected static function parseGTID( $id ) {
264 $m = [];
265 if ( preg_match( '!^(\d+)-(\d+)-(\d+)$!', $id, $m ) ) {
266 // MariaDB style: <domain>-<server id>-<sequence number>
267 return [ (int)$m[1], (int)$m[3], (int)$m[2] ];
268 } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(?:\d+-|)(\d+)$!', $id, $m ) ) {
269 // MySQL style: <server UUID>:<sequence number>-<sequence number>
270 // Normally, the first number should reflect the point (gtid_purged) where older
271 // binary logs where purged to save space. When doing comparisons, it may as well
272 // be 1 in that case. Assume that this is generally the situation.
273 return [ $m[1], (int)$m[2], $m[1] ];
274 }
275
276 return null;
277 }
278
284 protected function getBinlogCoordinates() {
285 return ( $this->binLog !== null && $this->logPos !== null )
286 ? [ 'binlog' => $this->binLog, 'pos' => $this->logPos ]
287 : false;
288 }
289
290 public function serialize() {
291 return serialize( [
292 'position' => $this->__toString(),
293 'activeDomain' => $this->activeDomain,
294 'activeServerId' => $this->activeServerId,
295 'activeServerUUID' => $this->activeServerUUID,
296 'asOfTime' => $this->asOfTime
297 ] );
298 }
299
300 public function unserialize( $serialized ) {
301 $data = unserialize( $serialized );
302 if ( !is_array( $data ) ) {
303 throw new UnexpectedValueException( __METHOD__ . ": cannot unserialize position" );
304 }
305
306 $this->init( $data['position'], $data['asOfTime'] );
307 if ( isset( $data['activeDomain'] ) ) {
308 $this->setActiveDomain( $data['activeDomain'] );
309 }
310 if ( isset( $data['activeServerId'] ) ) {
311 $this->setActiveOriginServerId( $data['activeServerId'] );
312 }
313 if ( isset( $data['activeServerUUID'] ) ) {
314 $this->setActiveOriginServerUUID( $data['activeServerUUID'] );
315 }
316 }
317
321 public function __toString() {
322 return $this->gtids
323 ? implode( ',', $this->gtids )
324 : $this->getLogFile() . "/{$this->logPos[self::CORD_EVENT]}";
325 }
326}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
DBMasterPos class for MySQL/MariaDB.
string null $binLog
Base name of all Binary Log files.
init( $position, $asOfTime)
float $asOfTime
UNIX timestamp.
static getCommonDomainGTIDs(MySQLMasterPos $pos, MySQLMasterPos $refPos)
__construct( $position, $asOfTime)
int $style
One of (BINARY_LOG, GTID_MYSQL, GTID_MARIA)
string[] $gtids
Map of (server_uuid/gtid_domain_id => GTID)
int null $activeDomain
Active GTID domain ID.
int null $activeServerId
ID of the server were DB writes originate.
string null $activeServerUUID
UUID of the server were DB writes originate.
int[] null $logPos
Binary Log position tuple (index number, event number)
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:2006
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
An object representing a master or replica DB position in a replicated setup.
Bar style
foreach( $res as $row) $serialized