File: ReplicationReporter.php

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (179 lines) | stat: -rw-r--r-- 5,870 bytes parent folder | download
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
174
175
176
177
178
179
<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */
namespace Wikimedia\Rdbms\Replication;

use Psr\Log\LoggerInterface;
use Wikimedia\ObjectCache\BagOStuff;
use Wikimedia\Rdbms\DBError;
use Wikimedia\Rdbms\DBPrimaryPos;
use Wikimedia\Rdbms\IDatabase;

/**
 * @internal
 * @ingroup Database
 * @since 1.40
 */
class ReplicationReporter {
	/** @var string Replication topology role of the server; one of the class ROLE_* constants */
	protected $topologyRole;
	/** @var LoggerInterface */
	protected $logger;
	/** @var BagOStuff */
	protected $srvCache;
	/** @var array|null Replication lag estimate at the time of BEGIN for the last transaction */
	private $trxReplicaLagStatus = null;

	public function __construct( $topologyRole, $logger, $srvCache ) {
		$this->topologyRole = $topologyRole;
		$this->logger = $logger;
		$this->srvCache = $srvCache;
	}

	public function getTopologyRole() {
		return $this->topologyRole;
	}

	public function getLag( IDatabase $conn ) {
		if ( $this->topologyRole === IDatabase::ROLE_STREAMING_MASTER ) {
			return 0; // this is the primary DB
		} elseif ( $this->topologyRole === IDatabase::ROLE_STATIC_CLONE ) {
			return 0; // static dataset
		}

		return $this->doGetLag( $conn );
	}

	/**
	 * Get the amount of replication lag for this database server
	 *
	 * Callers should avoid using this method while a transaction is active
	 *
	 * @see getLag()
	 *
	 * @param IDatabase $conn To make queries
	 * @return float|int|false Database replication lag in seconds or false on error
	 * @throws DBError
	 */
	protected function doGetLag( IDatabase $conn ) {
		return 0;
	}

	/**
	 * Get a replica DB lag estimate for this server at the start of a transaction
	 *
	 * This is a no-op unless the server is known a priori to be a replica DB
	 *
	 * @param IDatabase $conn To make queries
	 * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
	 * @since 1.27 in Database, moved to ReplicationReporter in 1.40
	 */
	protected function getApproximateLagStatus( IDatabase $conn ) {
		if ( $this->topologyRole === IDatabase::ROLE_STREAMING_REPLICA ) {
			// Avoid exceptions as this is used internally in critical sections
			try {
				$lag = $this->getLag( $conn );
			} catch ( DBError $e ) {
				$lag = false;
			}
		} else {
			$lag = 0;
		}

		return [ 'lag' => $lag, 'since' => microtime( true ) ];
	}

	public function primaryPosWait( IDatabase $conn, DBPrimaryPos $pos, $timeout ) {
		// Real waits are implemented in the subclass.
		return 0;
	}

	public function getReplicaPos( IDatabase $conn ) {
		// Stub
		return false;
	}

	public function getPrimaryPos( IDatabase $conn ) {
		// Stub
		return false;
	}

	/**
	 * @return array|null Tuple of (reason string, "role") if read-only; null otherwise
	 */
	public function getTopologyBasedReadOnlyReason() {
		if ( $this->topologyRole === IDatabase::ROLE_STREAMING_REPLICA ) {
			return [ 'Server is configured as a read-only replica database.', 'role' ];
		} elseif ( $this->topologyRole === IDatabase::ROLE_STATIC_CLONE ) {
			return [ 'Server is configured as a read-only static clone database.', 'role' ];
		}

		return null;
	}

	public function resetReplicationLagStatus( IDatabase $conn ) {
		// With REPEATABLE-READ isolation, the first SELECT establishes the read snapshot,
		// so get the replication lag estimate before any transaction SELECT queries come in.
		// This way, the lag estimate reflects what will actually be read. Also, if heartbeat
		// tables are used, this avoids counting snapshot lag as part of replication lag.
		$this->trxReplicaLagStatus = null; // clear cached value first
		$this->trxReplicaLagStatus = $this->getApproximateLagStatus( $conn );
	}

	/**
	 * Get the replica DB lag when the current transaction started
	 *
	 * This is useful given that transactions might use point-in-time read snapshots,
	 * in which case the lag estimate should be recorded just before the transaction
	 * establishes the read snapshot (either BEGIN or the first SELECT/write query).
	 *
	 * If snapshots are not used, it is still safe to be pessimistic.
	 *
	 * This returns null if there is no transaction or the lag status was not yet recorded.
	 *
	 * @param IDatabase $conn To make queries
	 * @return array|null ('lag': seconds or false, 'since': UNIX timestamp of BEGIN) or null
	 * @since 1.27 in Database, moved to ReplicationReporter in 1.40
	 */
	final protected function getRecordedTransactionLagStatus( IDatabase $conn ) {
		return $conn->trxLevel() ? $this->trxReplicaLagStatus : null;
	}

	public function getSessionLagStatus( IDatabase $conn ) {
		return $this->getRecordedTransactionLagStatus( $conn ) ?: $this->getApproximateLagStatus( $conn );
	}

	/**
	 * Create a log context to pass to PSR-3 logger functions.
	 *
	 * @param IDatabase $conn To make queries
	 * @param array $extras Additional data to add to context
	 * @return array
	 */
	protected function getLogContext( IDatabase $conn, array $extras = [] ) {
		return array_merge(
			[
				'db_server' => $conn->getServerName(),
				'db_name' => $conn->getDBname(),
				// TODO: Add db_user
			],
			$extras
		);
	}
}