* Split off DB load monitoring logic into a LoadMonitor class hierarchy, to allow...
[lhc/web/wiklou.git] / includes / db / LBFactory_Multi.php
1 <?php
2 /**
3 * @file
4 * @ingroup Database
5 */
6
7
8 /**
9 * A multi-wiki, multi-master factory for Wikimedia and similar installations.
10 * Ignores the old configuration globals
11 *
12 * Configuration:
13 * sectionsByDB A map of database names to section names
14 *
15 * sectionLoads A 2-d map. For each section, gives a map of server names to load ratios.
16 * For example: array( 'section1' => array( 'db1' => 100, 'db2' => 100 ) )
17 *
18 * serverTemplate A server info associative array as documented for $wgDBservers. The host,
19 * hostName and load entries will be overridden.
20 *
21 * groupLoadsBySection A 3-d map giving server load ratios for each section and group. For example:
22 * array( 'section1' => array( 'group1' => array( 'db1' => 100, 'db2' => 100 ) ) )
23 *
24 * groupLoadsByDB A 3-d map giving server load ratios by DB name.
25 *
26 * hostsByName A map of hostname to IP address.
27 *
28 * externalLoads A map of external storage cluster name to server load map
29 *
30 * externalTemplateOverrides A set of server info keys overriding serverTemplate for external storage
31 *
32 * templateOverridesByServer A 2-d map overriding serverTemplate and externalTemplateOverrides on a
33 * server-by-server basis. Applies to both core and external storage.
34 *
35 * templateOverridesByCluster A 2-d map overriding the server info by external storage cluster
36 *
37 * masterTemplateOverrides An override array for all master servers.
38 *
39 * @ingroup Database
40 */
41 class LBFactory_Multi extends LBFactory {
42 // Required settings
43 var $sectionsByDB, $sectionLoads, $serverTemplate;
44 // Optional settings
45 var $groupLoadsBySection = array(), $groupLoadsByDB = array(), $hostsByName = array();
46 var $externalLoads = array(), $externalTemplateOverrides, $templateOverridesByServer;
47 var $templateOverridesByCluster, $masterTemplateOverrides;
48 // Other stuff
49 var $conf, $mainLBs = array(), $extLBs = array();
50 var $localSection = null;
51
52 function __construct( $conf ) {
53 $this->chronProt = new ChronologyProtector;
54 $this->conf = $conf;
55 $required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
56 $optional = array( 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
57 'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
58 'templateOverridesByCluster', 'masterTemplateOverrides' );
59
60 foreach ( $required as $key ) {
61 if ( !isset( $conf[$key] ) ) {
62 throw new MWException( __CLASS__.": $key is required in configuration" );
63 }
64 $this->$key = $conf[$key];
65 }
66
67 foreach ( $optional as $key ) {
68 if ( isset( $conf[$key] ) ) {
69 $this->$key = $conf[$key];
70 }
71 }
72 }
73
74 function getSectionForWiki( $wiki ) {
75 list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
76 if ( isset( $this->sectionsByDB[$dbName] ) ) {
77 return $this->sectionsByDB[$dbName];
78 } else {
79 return 'DEFAULT';
80 }
81 }
82
83 function getMainLB( $wiki = false ) {
84 // Determine section
85 if ( $wiki === false ) {
86 if ( $this->localSection === null ) {
87 $this->localSection = $this->getSectionForWiki( $wiki );
88 }
89 $section = $this->localSection;
90 } else {
91 $section = $this->getSectionForWiki( $wiki );
92 }
93
94 if ( !isset( $this->mainLBs[$section] ) ) {
95 list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
96 $groupLoads = array();
97 if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
98 $groupLoads = $this->groupLoadsByDB[$dbName];
99 }
100 if ( isset( $this->groupLoadsBySection[$section] ) ) {
101 $groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
102 }
103 $this->mainLBs[$section] = $this->newLoadBalancer( $this->serverTemplate,
104 $this->sectionLoads[$section], $groupLoads, "main-$section" );
105 $this->chronProt->initLB( $this->mainLBs[$section] );
106 }
107 return $this->mainLBs[$section];
108 }
109
110 function &getExternalLB( $cluster, $wiki = false ) {
111 if ( !isset( $this->extLBs[$cluster] ) ) {
112 if ( !isset( $this->externalLoads[$cluster] ) ) {
113 throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
114 }
115 $template = $this->serverTemplate;
116 if ( isset( $this->externalTemplateOverrides ) ) {
117 $template = $this->externalTemplateOverrides + $template;
118 }
119 if ( isset( $this->templateOverridesByCluster[$cluster] ) ) {
120 $template = $this->templateOverridesByCluster[$cluster] + $template;
121 }
122 $this->extLBs[$cluster] = $this->newLoadBalancer( $template,
123 $this->externalLoads[$cluster], array(), "ext-$cluster" );
124 }
125 return $this->extLBs[$cluster];
126 }
127
128 /**
129 * Make a new load balancer object based on template and load array
130 */
131 function newLoadBalancer( $template, $loads, $groupLoads, $id ) {
132 global $wgMasterWaitTimeout;
133 $servers = $this->makeServerArray( $template, $loads, $groupLoads );
134 $lb = new LoadBalancer( array(
135 'servers' => $servers,
136 'masterWaitTimeout' => $wgMasterWaitTimeout
137 ));
138 $lb->parentInfo( array( 'id' => $id ) );
139 return $lb;
140 }
141
142 /**
143 * Make a server array as expected by LoadBalancer::__construct, using a template and load array
144 */
145 function makeServerArray( $template, $loads, $groupLoads ) {
146 $servers = array();
147 $master = true;
148 $groupLoadsByServer = $this->reindexGroupLoads( $groupLoads );
149 foreach ( $groupLoadsByServer as $server => $stuff ) {
150 if ( !isset( $loads[$server] ) ) {
151 $loads[$server] = 0;
152 }
153 }
154 foreach ( $loads as $serverName => $load ) {
155 $serverInfo = $template;
156 if ( $master ) {
157 $serverInfo['master'] = true;
158 if ( isset( $this->masterTemplateOverrides ) ) {
159 $serverInfo = $this->masterTemplateOverrides + $serverInfo;
160 }
161 $master = false;
162 }
163 if ( isset( $this->templateOverridesByServer[$serverName] ) ) {
164 $serverInfo = $this->templateOverridesByServer[$serverName] + $serverInfo;
165 }
166 if ( isset( $groupLoadsByServer[$serverName] ) ) {
167 $serverInfo['groupLoads'] = $groupLoadsByServer[$serverName];
168 }
169 if ( isset( $this->hostsByName[$serverName] ) ) {
170 $serverInfo['host'] = $this->hostsByName[$serverName];
171 } else {
172 $serverInfo['host'] = $serverName;
173 }
174 $serverInfo['hostName'] = $serverName;
175 $serverInfo['load'] = $load;
176 $servers[] = $serverInfo;
177 }
178 return $servers;
179 }
180
181 /**
182 * Take a group load array indexed by group then server, and reindex it by server then group
183 */
184 function reindexGroupLoads( $groupLoads ) {
185 $reindexed = array();
186 foreach ( $groupLoads as $group => $loads ) {
187 foreach ( $loads as $server => $load ) {
188 $reindexed[$server][$group] = $load;
189 }
190 }
191 return $reindexed;
192 }
193
194 /**
195 * Get the database name and prefix based on the wiki ID
196 */
197 function getDBNameAndPrefix( $wiki = false ) {
198 if ( $wiki === false ) {
199 global $wgDBname, $wgDBprefix;
200 return array( $wgDBname, $wgDBprefix );
201 } else {
202 return wfSplitWikiID( $wiki );
203 }
204 }
205
206 /**
207 * Execute a function for each tracked load balancer
208 * The callback is called with the load balancer as the first parameter,
209 * and $params passed as the subsequent parameters.
210 */
211 function forEachLB( $callback, $params = array() ) {
212 foreach ( $this->mainLBs as $lb ) {
213 call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
214 }
215 foreach ( $this->extLBs as $lb ) {
216 call_user_func_array( $callback, array_merge( array( $lb ), $params ) );
217 }
218 }
219
220 function shutdown() {
221 foreach ( $this->mainLBs as $lb ) {
222 $this->chronProt->shutdownLB( $lb );
223 }
224 $this->chronProt->shutdown();
225 $this->commitMasterChanges();
226 }
227 }