Index: trunk/phase3/includes/objectcache/SqlBagOStuff.php |
— | — | @@ -19,6 +19,8 @@ |
20 | 20 | var $serverInfo; |
21 | 21 | var $lastExpireAll = 0; |
22 | 22 | var $purgePeriod = 100; |
| 23 | + var $shards = 1; |
| 24 | + var $tableName = 'objectcache'; |
23 | 25 | |
24 | 26 | /** |
25 | 27 | * Constructor. Parameters are: |
— | — | @@ -32,6 +34,16 @@ |
33 | 35 | * request. If this is set to zero, purging will never be |
34 | 36 | * done. |
35 | 37 | * |
| 38 | + * - tableName: The table name to use, default is "objectcache". |
| 39 | + * |
| 40 | + * - shards: The number of tables to use for data storage. If this is |
| 41 | + * more than 1, table names will be formed in the style |
| 42 | + * objectcacheNNN where NNN is the shard index, between 0 and |
| 43 | + * shards-1. The number of digits will be the minimum number |
| 44 | + * required to hold the largest shard index. Data will be |
| 45 | + * distributed across all tables by key hash. This is for |
| 46 | + * MySQL bugs 61735 and 61736. |
| 47 | + * |
36 | 48 | * @param $params array |
37 | 49 | */ |
38 | 50 | public function __construct( $params ) { |
— | — | @@ -42,6 +54,12 @@ |
43 | 55 | if ( isset( $params['purgePeriod'] ) ) { |
44 | 56 | $this->purgePeriod = intval( $params['purgePeriod'] ); |
45 | 57 | } |
| 58 | + if ( isset( $params['tableName'] ) ) { |
| 59 | + $this->tableName = $params['tableName']; |
| 60 | + } |
| 61 | + if ( isset( $params['shards'] ) ) { |
| 62 | + $this->shards = intval( $params['shards'] ); |
| 63 | + } |
46 | 64 | } |
47 | 65 | |
48 | 66 | /** |
— | — | @@ -72,11 +90,37 @@ |
73 | 91 | return $this->db; |
74 | 92 | } |
75 | 93 | |
| 94 | + /** |
| 95 | + * Get the table name for a given key |
| 96 | + */ |
| 97 | + protected function getTableByKey( $key ) { |
| 98 | + if ( $this->shards > 1 ) { |
| 99 | + $hash = hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff; |
| 100 | + return $this->getTableByShard( $hash % $this->shards ); |
| 101 | + } else { |
| 102 | + return $this->tableName; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + /** |
| 107 | + * Get the table name for a given shard index |
| 108 | + */ |
| 109 | + protected function getTableByShard( $index ) { |
| 110 | + if ( $this->shards > 1 ) { |
| 111 | + $decimals = strlen( $this->shards - 1 ); |
| 112 | + return $this->tableName . |
| 113 | + sprintf( "%0{$decimals}d", $index ); |
| 114 | + } else { |
| 115 | + return $this->tableName; |
| 116 | + } |
| 117 | + } |
| 118 | + |
76 | 119 | public function get( $key ) { |
77 | 120 | # expire old entries if any |
78 | 121 | $this->garbageCollect(); |
79 | 122 | $db = $this->getDB(); |
80 | | - $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), |
| 123 | + $tableName = $this->getTableByKey( $key ); |
| 124 | + $row = $db->selectRow( $tableName, array( 'value', 'exptime' ), |
81 | 125 | array( 'keyname' => $key ), __METHOD__ ); |
82 | 126 | |
83 | 127 | if ( !$row ) { |
— | — | @@ -92,7 +136,7 @@ |
93 | 137 | $db->begin(); |
94 | 138 | # Put the expiry time in the WHERE condition to avoid deleting a |
95 | 139 | # newly-inserted value |
96 | | - $db->delete( 'objectcache', |
| 140 | + $db->delete( $tableName, |
97 | 141 | array( |
98 | 142 | 'keyname' => $key, |
99 | 143 | 'exptime' => $row->exptime |
— | — | @@ -129,7 +173,9 @@ |
130 | 174 | $db->begin(); |
131 | 175 | // (bug 24425) use a replace if the db supports it instead of |
132 | 176 | // delete/insert to avoid clashes with conflicting keynames |
133 | | - $db->replace( 'objectcache', array( 'keyname' ), |
| 177 | + $db->replace( |
| 178 | + $this->getTableByKey( $key ), |
| 179 | + array( 'keyname' ), |
134 | 180 | array( |
135 | 181 | 'keyname' => $key, |
136 | 182 | 'value' => $db->encodeBlob( $this->serialize( $value ) ), |
— | — | @@ -150,7 +196,10 @@ |
151 | 197 | |
152 | 198 | try { |
153 | 199 | $db->begin(); |
154 | | - $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); |
| 200 | + $db->delete( |
| 201 | + $this->getTableByKey( $key ), |
| 202 | + array( 'keyname' => $key ), |
| 203 | + __METHOD__ ); |
155 | 204 | $db->commit(); |
156 | 205 | } catch ( DBQueryError $e ) { |
157 | 206 | $this->handleWriteError( $e ); |
— | — | @@ -163,19 +212,24 @@ |
164 | 213 | |
165 | 214 | public function incr( $key, $step = 1 ) { |
166 | 215 | $db = $this->getDB(); |
| 216 | + $tableName = $this->getTableByKey( $key ); |
167 | 217 | $step = intval( $step ); |
168 | 218 | |
169 | 219 | try { |
170 | 220 | $db->begin(); |
171 | | - $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), |
172 | | - array( 'keyname' => $key ), __METHOD__, array( 'FOR UPDATE' ) ); |
| 221 | + $row = $db->selectRow( |
| 222 | + $tableName, |
| 223 | + array( 'value', 'exptime' ), |
| 224 | + array( 'keyname' => $key ), |
| 225 | + __METHOD__, |
| 226 | + array( 'FOR UPDATE' ) ); |
173 | 227 | if ( $row === false ) { |
174 | 228 | // Missing |
175 | 229 | $db->commit(); |
176 | 230 | |
177 | 231 | return null; |
178 | 232 | } |
179 | | - $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); |
| 233 | + $db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ ); |
180 | 234 | if ( $this->isExpired( $row->exptime ) ) { |
181 | 235 | // Expired, do not reinsert |
182 | 236 | $db->commit(); |
— | — | @@ -185,7 +239,7 @@ |
186 | 240 | |
187 | 241 | $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) ); |
188 | 242 | $newValue = $oldValue + $step; |
189 | | - $db->insert( 'objectcache', |
| 243 | + $db->insert( $tableName, |
190 | 244 | array( |
191 | 245 | 'keyname' => $key, |
192 | 246 | 'value' => $db->encodeBlob( $this->serialize( $newValue ) ), |
— | — | @@ -208,11 +262,14 @@ |
209 | 263 | |
210 | 264 | public function keys() { |
211 | 265 | $db = $this->getDB(); |
212 | | - $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ ); |
213 | 266 | $result = array(); |
214 | 267 | |
215 | | - foreach ( $res as $row ) { |
216 | | - $result[] = $row->keyname; |
| 268 | + for ( $i = 0; $i < $this->shards; $i++ ) { |
| 269 | + $res = $db->select( $this->getTableByShard( $i ), |
| 270 | + array( 'keyname' ), false, __METHOD__ ); |
| 271 | + foreach ( $res as $row ) { |
| 272 | + $result[] = $row->keyname; |
| 273 | + } |
217 | 274 | } |
218 | 275 | |
219 | 276 | return $result; |
— | — | @@ -252,9 +309,14 @@ |
253 | 310 | $now = $db->timestamp(); |
254 | 311 | |
255 | 312 | try { |
256 | | - $db->begin(); |
257 | | - $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ ); |
258 | | - $db->commit(); |
| 313 | + for ( $i = 0; $i < $this->shards; $i++ ) { |
| 314 | + $db->begin(); |
| 315 | + $db->delete( |
| 316 | + $this->getTableByShard( $i ), |
| 317 | + array( 'exptime < ' . $db->addQuotes( $now ) ), |
| 318 | + __METHOD__ ); |
| 319 | + $db->commit(); |
| 320 | + } |
259 | 321 | } catch ( DBQueryError $e ) { |
260 | 322 | $this->handleWriteError( $e ); |
261 | 323 | } |
— | — | @@ -264,9 +326,11 @@ |
265 | 327 | $db = $this->getDB(); |
266 | 328 | |
267 | 329 | try { |
268 | | - $db->begin(); |
269 | | - $db->delete( 'objectcache', '*', __METHOD__ ); |
270 | | - $db->commit(); |
| 330 | + for ( $i = 0; $i < $this->shards; $i++ ) { |
| 331 | + $db->begin(); |
| 332 | + $db->delete( $this->getTableByShard( $i ), '*', __METHOD__ ); |
| 333 | + $db->commit(); |
| 334 | + } |
271 | 335 | } catch ( DBQueryError $e ) { |
272 | 336 | $this->handleWriteError( $e ); |
273 | 337 | } |
— | — | @@ -328,6 +392,27 @@ |
329 | 393 | wfDebug( __METHOD__ . ": ignoring query error\n" ); |
330 | 394 | $db->ignoreErrors( false ); |
331 | 395 | } |
| 396 | + |
| 397 | + /** |
| 398 | + * Create shard tables. For use from eval.php. |
| 399 | + */ |
| 400 | + public function createTables() { |
| 401 | + $db = $this->getDB(); |
| 402 | + if ( $db->getType() !== 'mysql' |
| 403 | + || version_compare( $db->getServerVersion(), '4.1.0', '<' ) ) |
| 404 | + { |
| 405 | + throw new MWException( __METHOD__ . ' is not supported on this DB server' ); |
| 406 | + } |
| 407 | + |
| 408 | + for ( $i = 0; $i < $this->shards; $i++ ) { |
| 409 | + $db->begin(); |
| 410 | + $db->query( |
| 411 | + 'CREATE TABLE ' . $db->tableName( $this->getTableByShard( $i ) ) . |
| 412 | + ' LIKE ' . $db->tableName( 'objectcache' ), |
| 413 | + __METHOD__ ); |
| 414 | + $db->commit(); |
| 415 | + } |
| 416 | + } |
332 | 417 | } |
333 | 418 | |
334 | 419 | /** |