summaryrefslogtreecommitdiff
path: root/includes/db/LoadMonitor.php
blob: 9b959728c31ecf33f752998b78c054d7e8a46802 (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
<?php
/**
 * Database load monitoring
 *
 * @file
 * @ingroup Database
 */

/**
 * An interface for database load monitoring
 *
 * @ingroup Database
 */
interface LoadMonitor {
	/**
	 * Construct a new LoadMonitor with a given LoadBalancer parent
	 */
	function __construct( $parent );
	
	/**
	 * Perform pre-connection load ratio adjustment.
	 * @param $loads Array
	 * @param $group String: 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
	 */
	function getLagTimes( $serverIndexes, $wiki );
}


/**
 * 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 $parent; // LoadBalancer

	function __construct( $parent ) {
		$this->parent = $parent;
	}

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

	function getLagTimes( $serverIndexes, $wiki ) {
		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;
	}

	function postConnectionBackoff( $conn, $threshold ) {
		if ( !$threshold ) {
			return 0;
		}
		$status = $conn->getStatus("Thread%");
		if ( $status['Threads_running'] > $threshold ) {
			return $status['Threads_connected'];
		} else {
			return 0;
		}
	}
}