3 namespace Wikimedia\Rdbms
;
5 use InvalidArgumentException
;
8 * DBMasterPos class for MySQL/MariaDB
10 * Note that master positions and sync logic here make some assumptions:
11 * - Binlog-based usage assumes single-source replication and non-hierarchical replication.
12 * - GTID-based usage allows getting/syncing with multi-source replication. It is assumed
13 * that GTID sets are complete (e.g. include all domains on the server).
15 class MySQLMasterPos
implements DBMasterPos
{
16 /** @var string|null Binlog file base name */
18 /** @var int[]|null Binglog file position tuple */
20 /** @var string[] GTID list */
22 /** @var float UNIX timestamp */
23 public $asOfTime = 0.0;
26 * @param string $position One of (comma separated GTID list, <binlog file>/<integer>)
27 * @param float $asOfTime UNIX timestamp
29 public function __construct( $position, $asOfTime ) {
31 if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', $position, $m ) ) {
32 $this->binlog
= $m[1]; // ideally something like host name
33 $this->pos
= [ (int)$m[2], (int)$m[3] ];
35 $this->gtids
= array_map( 'trim', explode( ',', $position ) );
36 if ( !$this->gtids
) {
37 throw new InvalidArgumentException( "GTID set should not be empty." );
41 $this->asOfTime
= $asOfTime;
44 public function asOfTime() {
45 return $this->asOfTime
;
48 public function hasReached( DBMasterPos
$pos ) {
49 if ( !( $pos instanceof self
) ) {
50 throw new InvalidArgumentException( "Position not an instance of " . __CLASS__
);
53 // Prefer GTID comparisons, which work with multi-tier replication
54 $thisPosByDomain = $this->getGtidCoordinates();
55 $thatPosByDomain = $pos->getGtidCoordinates();
56 if ( $thisPosByDomain && $thatPosByDomain ) {
58 // Check that this has positions GTE all of those in $pos for all domains in $pos
59 foreach ( $thatPosByDomain as $domain => $thatPos ) {
60 $thisPos = isset( $thisPosByDomain[$domain] ) ?
$thisPosByDomain[$domain] : -1;
61 $reached = $reached && ( $thatPos <= $thisPos );
67 // Fallback to the binlog file comparisons
68 $thisBinPos = $this->getBinlogCoordinates();
69 $thatBinPos = $pos->getBinlogCoordinates();
70 if ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] ) {
71 return ( $thisBinPos['pos'] >= $thatBinPos['pos'] );
74 // Comparing totally different binlogs does not make sense
78 public function channelsMatch( DBMasterPos
$pos ) {
79 if ( !( $pos instanceof self
) ) {
80 throw new InvalidArgumentException( "Position not an instance of " . __CLASS__
);
83 // Prefer GTID comparisons, which work with multi-tier replication
84 $thisPosDomains = array_keys( $this->getGtidCoordinates() );
85 $thatPosDomains = array_keys( $pos->getGtidCoordinates() );
86 if ( $thisPosDomains && $thatPosDomains ) {
87 // Check that this has GTIDs for all domains in $pos
88 return !array_diff( $thatPosDomains, $thisPosDomains );
91 // Fallback to the binlog file comparisons
92 $thisBinPos = $this->getBinlogCoordinates();
93 $thatBinPos = $pos->getBinlogCoordinates();
95 return ( $thisBinPos && $thatBinPos && $thisBinPos['binlog'] === $thatBinPos['binlog'] );
101 public function getLogFile() {
102 return $this->gtids ?
null : "{$this->binlog}.{$this->pos[0]}";
106 * @return string GTID set or <binlog file>/<position> (e.g db1034-bin.000976/843431247)
108 public function __toString() {
110 ?
implode( ',', $this->gtids
)
111 : $this->getLogFile() . "/{$this->pos[1]}";
115 * @note: this returns false for multi-source replication GTID sets
116 * @see https://mariadb.com/kb/en/mariadb/gtid
117 * @see https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-concepts.html
118 * @return array Map of (domain => integer position); possibly empty
120 protected function getGtidCoordinates() {
122 foreach ( $this->gtids
as $gtid ) {
124 // MariaDB style: <domain>-<server id>-<sequence number>
125 if ( preg_match( '!^(\d+)-\d+-(\d+)$!', $gtid, $m ) ) {
126 $gtidInfos[(int)$m[1]] = (int)$m[2];
127 // MySQL style: <UUID domain>:<sequence number>
128 } elseif ( preg_match( '!^(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}):(\d+)$!', $gtid, $m ) ) {
129 $gtidInfos[$m[1]] = (int)$m[2];
132 break; // unrecognized GTID
141 * @see https://dev.mysql.com/doc/refman/5.7/en/show-master-status.html
142 * @see https://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html
143 * @return array|bool (binlog, (integer file number, integer position)) or false
145 protected function getBinlogCoordinates() {
146 return ( $this->binlog
!== null && $this->pos
!== null )
147 ?
[ 'binlog' => $this->binlog
, 'pos' => $this->pos
]