a17e08d7c0ac9ee768519a2a0f4a502b9d6111e2
[lhc/web/wiklou.git] / includes / LoadBalancer.php
1 <?php
2 # Database load balancing object
3
4 # Valid database indexes
5 # Operation-based indexes
6 define( "DB_READ", -1 ); # Read from the slave (or only server)
7 define( "DB_WRITE", -2 ); # Write to master (or only server)
8 define( "DB_LAST", -3 ); # Whatever database was used last
9
10 # Task-based indexes
11 # ***NOT USED YET, EXPERIMENTAL***
12 # These may be defined in $wgDBservers. If they aren't, the default reader or writer will be used
13 # Even numbers are always readers, odd numbers are writers
14 define( "DB_TASK_FIRST", 1000 ); # First in list
15 define( "DB_SEARCH_R", 1000 ); # Search read
16 define( "DB_SEARCH_W", 1001 ); # Search write
17 define( "DB_ASKSQL_R", 1002 ); # Special:Asksql read
18 define( "DB_WATCHLIST_R", 1004 ); # Watchlist read
19 define( "DB_TASK_LAST", 1004) ; # Last in list
20
21 class LoadBalancer {
22 /* private */ var $mServers, $mConnections, $mLoads;
23 /* private */ var $mUser, $mPassword, $mDbName, $mFailFunction;
24 /* private */ var $mForce, $mReadIndex, $mLastConn;
25
26 function LoadBalancer()
27 {
28 $this->mServers = array();
29 $this->mLoads = array();
30 $this->mConnections = array();
31 $this->mUser = false;
32 $this->mPassword = false;
33 $this->mDbName = false;
34 $this->mFailFunction = false;
35 $this->mReadIndex = -1;
36 $this->mForce = -1;
37 $this->mLastConn = false;
38 }
39
40 function newFromParams( $servers, $loads, $user, $password, $dbName, $failFunction = false )
41 {
42 $lb = new LoadBalancer;
43 $lb->initialise( $servers, $loads, $user, $password, $dbName, $failFunction = false );
44 return $lb;
45 }
46
47 function initialise( $servers, $loads, $user, $password, $dbName, $failFunction = false )
48 {
49 $this->mServers = $servers;
50 $this->mLoads = $loads;
51 $this->mUser = $user;
52 $this->mPassword = $password;
53 $this->mDbName = $dbName;
54 $this->mFailFunction = $failFunction;
55 $this->mReadIndex = -1;
56 $this->mWriteIndex = -1;
57 $this->mForce = -1;
58 $this->mConnections = array();
59 $this->mLastConn = false;
60 wfSeedRandom();
61 }
62
63 # Given an array of non-normalised probabilities, this function will select
64 # an element and return the appropriate key
65 function pickRandom( $weights )
66 {
67 if ( !is_array( $weights ) || count( $weights ) == 0 ) {
68 return false;
69 }
70
71 $sum = 0;
72 foreach ( $weights as $w ) {
73 $sum += $w;
74 }
75 $rand = mt_rand() / RAND_MAX * $sum;
76
77 $sum = 0;
78 foreach ( $weights as $i => $w ) {
79 $sum += $w;
80 if ( $sum >= $rand ) {
81 break;
82 }
83 }
84 return $i;
85 }
86
87 function &getReader()
88 {
89 if ( $this->mForce >= 0 ) {
90 $conn =& $this->getConnection( $this->mForce );
91 } else {
92 if ( $this->mReadIndex >= 0 ) {
93 $conn =& $this->getConnection( $this->mReadIndex );
94 } else {
95 # $loads is $this->mLoads except with elements knocked out if they
96 # don't work
97 $loads = $this->mLoads;
98 do {
99 $i = pickRandom( $loads );
100 if ( $i !== false ) {
101 $conn =& $this->getConnection( $i );
102 if ( !$conn->isOpen() ) {
103 unset( $loads[$i] );
104 }
105 }
106 } while ( $i !== false && !$conn->isOpen() );
107 if ( $conn->isOpen() ) {
108 $this->mReadIndex = $i;
109 }
110 }
111 }
112 if ( $conn === false || !$conn->isOpen() ) {
113 $this->reportConnectionError( $conn );
114 $conn = false;
115 }
116 return $conn;
117 }
118
119 function &getConnection( $i, $fail = false )
120 {
121 /*
122 # Task-based index
123 if ( $i >= DB_TASK_FIRST && $i < DB_TASK_LAST ) {
124 if ( $i % 2 ) {
125 # Odd index use writer
126 $i = DB_WRITE;
127 } else {
128 # Even index use reader
129 $i = DB_READ;
130 }
131 }*/
132
133 # Operation-based index
134 # Note, getReader() and getWriter() will re-enter this function
135 if ( $i == DB_READ ) {
136 $this->mLastConn =& $this->getReader();
137 } elseif ( $i == DB_WRITE ) {
138 $this->mLastConn =& $this->getWriter();
139 } elseif ( $i == DB_LAST ) {
140 # Just use $this->mLastConn, which should already be set
141 if ( $this->mLastConn === false ) {
142 # Oh dear, not set, best to use the writer for safety
143 $this->mLastConn =& $this->getWriter();
144 }
145 } else {
146 # Explicit index
147 if ( !array_key_exists( $i, $this->mConnections) || !$this->mConnections[$i]->isOpen() ) {
148 $this->mConnections[$i] = Database::newFromParams( $this->mServers[$i], $this->mUser,
149 $this->mPassword, $this->mDbName, 1 );
150 }
151 if ( !$this->mConnections[$i]->isOpen() ) {
152 wfDebug( "Failed to connect to database $i at {$this->mServers[$i]}\n" );
153 if ( $fail ) {
154 $this->reportConnectionError( $this->mConnections[$i] );
155 }
156 $this->mConnections[$i] = false;
157 }
158 $this->mLastConn =& $this->mConnections[$i];
159 }
160 return $this->mLastConn;
161 }
162
163 function reportConnectionError( &$conn )
164 {
165 if ( !is_object( $conn ) ) {
166 $conn = new Database;
167 }
168 if ( $this->mFailFunction ) {
169 $conn->setFailFunction( $this->mFailFunction );
170 } else {
171 $conn->setFailFunction( "wfEmergencyAbort" );
172 }
173 $conn->reportConnectionError();
174 }
175
176 function &getWriter()
177 {
178 $c =& $this->getConnection( 0 );
179 if ( $c === false || !$c->isOpen() ) {
180 reportConnectionError( $conn );
181 $c = false;
182 }
183 return $c;
184 }
185
186 function force( $i )
187 {
188 $this->mForce = $i;
189 }
190
191 function haveIndex( $i )
192 {
193 return array_key_exists( $i, $this->mServers );
194 }
195 }