Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
ReplicationReporter
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 13
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getTopologyRole
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLag
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 doGetLag
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getApproximateLagStatus
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 primaryPosWait
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReplicaPos
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPrimaryPos
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTopologyBasedReadOnlyReason
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 resetReplicationLagStatus
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getRecordedTransactionLagStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getSessionLagStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getLogContext
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6namespace Wikimedia\Rdbms\Replication;
7
8use Psr\Log\LoggerInterface;
9use Wikimedia\ObjectCache\BagOStuff;
10use Wikimedia\Rdbms\DBError;
11use Wikimedia\Rdbms\DBPrimaryPos;
12use Wikimedia\Rdbms\IDatabase;
13
14/**
15 * @internal
16 * @ingroup Database
17 * @since 1.40
18 */
19class ReplicationReporter {
20    /** @var string Replication topology role of the server; one of the class ROLE_* constants */
21    protected $topologyRole;
22    /** @var LoggerInterface */
23    protected $logger;
24    /** @var BagOStuff */
25    protected $srvCache;
26    /** @var array|null Replication lag estimate at the time of BEGIN for the last transaction */
27    private $trxReplicaLagStatus = null;
28
29    /**
30     * @param string $topologyRole
31     * @param LoggerInterface $logger
32     * @param BagOStuff $srvCache
33     */
34    public function __construct( $topologyRole, $logger, $srvCache ) {
35        $this->topologyRole = $topologyRole;
36        $this->logger = $logger;
37        $this->srvCache = $srvCache;
38    }
39
40    /**
41     * @return string
42     */
43    public function getTopologyRole() {
44        return $this->topologyRole;
45    }
46
47    /**
48     * @return float|int|false
49     */
50    public function getLag( IDatabase $conn ) {
51        if ( $this->topologyRole === IDatabase::ROLE_STREAMING_MASTER ) {
52            return 0; // this is the primary DB
53        } elseif ( $this->topologyRole === IDatabase::ROLE_STATIC_CLONE ) {
54            return 0; // static dataset
55        }
56
57        return $this->doGetLag( $conn );
58    }
59
60    /**
61     * Get the amount of replication lag for this database server
62     *
63     * Callers should avoid using this method while a transaction is active
64     *
65     * @see getLag()
66     *
67     * @param IDatabase $conn To make queries
68     * @return float|int|false Database replication lag in seconds or false on error
69     * @throws DBError
70     */
71    protected function doGetLag( IDatabase $conn ) {
72        return 0;
73    }
74
75    /**
76     * Get a replica DB lag estimate for this server at the start of a transaction
77     *
78     * This is a no-op unless the server is known a priori to be a replica DB
79     *
80     * @param IDatabase $conn To make queries
81     * @return array ('lag': seconds or false on error, 'since': UNIX timestamp of estimate)
82     * @since 1.27 in Database, moved to ReplicationReporter in 1.40
83     */
84    protected function getApproximateLagStatus( IDatabase $conn ) {
85        if ( $this->topologyRole === IDatabase::ROLE_STREAMING_REPLICA ) {
86            // Avoid exceptions as this is used internally in critical sections
87            try {
88                $lag = $this->getLag( $conn );
89            } catch ( DBError ) {
90                $lag = false;
91            }
92        } else {
93            $lag = 0;
94        }
95
96        return [ 'lag' => $lag, 'since' => microtime( true ) ];
97    }
98
99    /**
100     * @param IDatabase $conn
101     * @param DBPrimaryPos $pos
102     * @param int $timeout
103     * @return int|null
104     */
105    public function primaryPosWait( IDatabase $conn, DBPrimaryPos $pos, $timeout ) {
106        // Real waits are implemented in the subclass.
107        return 0;
108    }
109
110    /**
111     * @return DBPrimaryPos|false
112     */
113    public function getReplicaPos( IDatabase $conn ) {
114        // Stub
115        return false;
116    }
117
118    /**
119     * @return DBPrimaryPos|false
120     */
121    public function getPrimaryPos( IDatabase $conn ) {
122        // Stub
123        return false;
124    }
125
126    /**
127     * @return array|null Tuple of (reason string, "role") if read-only; null otherwise
128     */
129    public function getTopologyBasedReadOnlyReason() {
130        if ( $this->topologyRole === IDatabase::ROLE_STREAMING_REPLICA ) {
131            return [ 'Server is configured as a read-only replica database.', 'role' ];
132        } elseif ( $this->topologyRole === IDatabase::ROLE_STATIC_CLONE ) {
133            return [ 'Server is configured as a read-only static clone database.', 'role' ];
134        }
135
136        return null;
137    }
138
139    public function resetReplicationLagStatus( IDatabase $conn ) {
140        // With REPEATABLE-READ isolation, the first SELECT establishes the read snapshot,
141        // so get the replication lag estimate before any transaction SELECT queries come in.
142        // This way, the lag estimate reflects what will actually be read. Also, if heartbeat
143        // tables are used, this avoids counting snapshot lag as part of replication lag.
144        $this->trxReplicaLagStatus = null; // clear cached value first
145        $this->trxReplicaLagStatus = $this->getApproximateLagStatus( $conn );
146    }
147
148    /**
149     * Get the replica DB lag when the current transaction started
150     *
151     * This is useful given that transactions might use point-in-time read snapshots,
152     * in which case the lag estimate should be recorded just before the transaction
153     * establishes the read snapshot (either BEGIN or the first SELECT/write query).
154     *
155     * If snapshots are not used, it is still safe to be pessimistic.
156     *
157     * This returns null if there is no transaction or the lag status was not yet recorded.
158     *
159     * @param IDatabase $conn To make queries
160     * @return array|null ('lag': seconds or false, 'since': UNIX timestamp of BEGIN) or null
161     * @since 1.27 in Database, moved to ReplicationReporter in 1.40
162     */
163    final protected function getRecordedTransactionLagStatus( IDatabase $conn ) {
164        return $conn->trxLevel() ? $this->trxReplicaLagStatus : null;
165    }
166
167    /**
168     * @return array
169     */
170    public function getSessionLagStatus( IDatabase $conn ) {
171        return $this->getRecordedTransactionLagStatus( $conn ) ?: $this->getApproximateLagStatus( $conn );
172    }
173
174    /**
175     * Create a log context to pass to PSR-3 logger functions.
176     *
177     * @param IDatabase $conn To make queries
178     * @param array $extras Additional data to add to context
179     * @return array
180     */
181    protected function getLogContext( IDatabase $conn, array $extras = [] ) {
182        return $extras + [
183            'db_server' => $conn->getServerName(),
184            'db_name' => $conn->getDBname(),
185            // TODO: Add db_user
186        ];
187    }
188}