summaryrefslogtreecommitdiff
path: root/includes/db/LoadMonitor.php
blob: ad7b3b2fd6f45b073d44b179cbc5d85e3901abcb (plain)
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<?php
/**
 * Database load monitoring.
 *
 * 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
 * @ingroup Database
 */

/**
 * An interface for database load monitoring
 *
 * @ingroup Database
 */
interface LoadMonitor {
	/**
	 * Construct a new LoadMonitor with a given LoadBalancer parent
	 *
	 * @param LoadBalancer $parent
	 */
	function __construct( $parent );

	/**
	 * Perform pre-connection load ratio adjustment.
	 * @param $loads array
	 * @param string $group the selected query group
	 * @param $wiki String
	 */
	function scaleLoads( &$loads, $group = false, $wiki = false );

	/**
	 * Perform post-connection backoff.
	 *
	 * If the connection is in overload, this should return a backoff factor
	 * which will be used to control polling time. The number of threads
	 * connected is a good measure.
	 *
	 * If there is no overload, zero can be returned.
	 *
	 * A threshold thread count is given, the concrete class may compare this
	 * to the running thread count. The threshold may be false, which indicates
	 * that the sysadmin has not configured this feature.
	 *
	 * @param $conn DatabaseBase
	 * @param $threshold Float
	 */
	function postConnectionBackoff( $conn, $threshold );

	/**
	 * Return an estimate of replication lag for each server
	 *
	 * @param $serverIndexes
	 * @param $wiki
	 *
	 * @return array
	 */
	function getLagTimes( $serverIndexes, $wiki );
}

class LoadMonitor_Null implements LoadMonitor {
	function __construct( $parent ) {
	}

	function scaleLoads( &$loads, $group = false, $wiki = false ) {
	}

	function postConnectionBackoff( $conn, $threshold ) {
	}

	/**
	 * @param $serverIndexes
	 * @param $wiki
	 * @return array
	 */
	function getLagTimes( $serverIndexes, $wiki ) {
		return array_fill_keys( $serverIndexes, 0 );
	}
}

/**
 * Basic MySQL load monitor with no external dependencies
 * Uses memcached to cache the replication lag for a short time
 *
 * @ingroup Database
 */
class LoadMonitor_MySQL implements LoadMonitor {

	/**
	 * @var LoadBalancer
	 */
	var $parent;

	/**
	 * @param LoadBalancer $parent
	 */
	function __construct( $parent ) {
		$this->parent = $parent;
	}

	/**
	 * @param $loads
	 * @param $group bool
	 * @param $wiki bool
	 */
	function scaleLoads( &$loads, $group = false, $wiki = false ) {
	}

	/**
	 * @param $serverIndexes
	 * @param $wiki
	 * @return array
	 */
	function getLagTimes( $serverIndexes, $wiki ) {
		if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
			// Single server only, just return zero without caching
			return array( 0 => 0 );
		}

		wfProfileIn( __METHOD__ );
		$expiry = 5;
		$requestRate = 10;

		global $wgMemc;
		if ( empty( $wgMemc ) )
			$wgMemc = wfGetMainCache();

		$masterName = $this->parent->getServerName( 0 );
		$memcKey = wfMemcKey( 'lag_times', $masterName );
		$times = $wgMemc->get( $memcKey );
		if ( $times ) {
			# Randomly recache with probability rising over $expiry
			$elapsed = time() - $times['timestamp'];
			$chance = max( 0, ( $expiry - $elapsed ) * $requestRate );
			if ( mt_rand( 0, $chance ) != 0 ) {
				unset( $times['timestamp'] );
				wfProfileOut( __METHOD__ );
				return $times;
			}
			wfIncrStats( 'lag_cache_miss_expired' );
		} else {
			wfIncrStats( 'lag_cache_miss_absent' );
		}

		# Cache key missing or expired

		$times = array();
		foreach ( $serverIndexes as $i ) {
			if ( $i == 0 ) { # Master
				$times[$i] = 0;
			} elseif ( false !== ( $conn = $this->parent->getAnyOpenConnection( $i ) ) ) {
				$times[$i] = $conn->getLag();
			} elseif ( false !== ( $conn = $this->parent->openConnection( $i, $wiki ) ) ) {
				$times[$i] = $conn->getLag();
			}
		}

		# Add a timestamp key so we know when it was cached
		$times['timestamp'] = time();
		$wgMemc->set( $memcKey, $times, $expiry );

		# But don't give the timestamp to the caller
		unset( $times['timestamp'] );
		$lagTimes = $times;

		wfProfileOut( __METHOD__ );
		return $lagTimes;
	}

	/**
	 * @param $conn DatabaseBase
	 * @param $threshold
	 * @return int
	 */
	function postConnectionBackoff( $conn, $threshold ) {
		if ( !$threshold ) {
			return 0;
		}
		$status = $conn->getMysqlStatus( "Thread%" );
		if ( $status['Threads_running'] > $threshold ) {
			$server = $conn->getProperty( 'mServer' );
			wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
			return $status['Threads_connected'];
		} else {
			return 0;
		}
	}
}