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