1<?php
2/**
3 * Generator and manager of database load balancing instances
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Database
22 */
23
24namespace Wikimedia\Rdbms;
25
26use InvalidArgumentException;
27
28/**
29 * An interface for generating database load balancers
30 * @ingroup Database
31 * @since 1.28
32 */
33interface ILBFactory {
34	/** Idion for "no special shutdown flags" */
35	public const SHUTDOWN_NORMAL = 0;
36	/** Do not save "session consistency" DB replication positions */
37	public const SHUTDOWN_NO_CHRONPROT = 1;
38
39	/** @var string Default main LB cluster name (do not change this) */
40	public const CLUSTER_MAIN_DEFAULT = 'DEFAULT';
41
42	/**
43	 * Construct a manager of ILoadBalancer instances
44	 *
45	 * Sub-classes will extend the required keys in $conf with additional parameters
46	 *
47	 * @param array $conf Array with keys:
48	 *  - localDomain: A DatabaseDomain or domain ID string.
49	 *  - readOnlyReason: Reason the primary DB is read-only if so [optional]
50	 *  - srvCache: BagOStuff instance for server cache [optional]
51	 *  - cpStash: BagOStuff instance for ChronologyProtector store [optional]
52	 *    See [ChronologyProtector requirements](@ref ChronologyProtector-storage-requirements).
53	 *  - wanCache: WANObjectCache instance [optional]
54	 *  - cliMode: Whether the execution context is a CLI script. [optional]
55	 *  - maxLag: Try to avoid DB replicas with lag above this many seconds [optional]
56	 *  - profiler: Class name or instance with profileIn()/profileOut() methods. [optional]
57	 *  - trxProfiler: TransactionProfiler instance. [optional]
58	 *  - replLogger: PSR-3 logger instance. [optional]
59	 *  - connLogger: PSR-3 logger instance. [optional]
60	 *  - queryLogger: PSR-3 logger instance. [optional]
61	 *  - perfLogger: PSR-3 logger instance. [optional]
62	 *  - errorLogger: Callback that takes an Exception and logs it. [optional]
63	 *  - deprecationLogger: Callback to log a deprecation warning. [optional]
64	 *  - secret: Secret string to use for HMAC hashing [optional]
65	 *  - criticalSectionProvider: CriticalSectionProvider instance [optional]
66	 * @throws InvalidArgumentException
67	 */
68	public function __construct( array $conf );
69
70	/**
71	 * Close all connections and make further attempts to open connections result in DBAccessError
72	 *
73	 * This only applies to the tracked load balancer instances.
74	 *
75	 * @see ILoadBalancer::disable()
76	 */
77	public function destroy();
78
79	/**
80	 * Get the local (and default) database domain ID of connection handles
81	 *
82	 * @see DatabaseDomain
83	 * @return string Database domain ID; this specifies DB name, schema, and table prefix
84	 * @since 1.32
85	 */
86	public function getLocalDomainID();
87
88	/**
89	 * @param DatabaseDomain|string|bool $domain Database domain
90	 * @return string Value of $domain if provided or the local domain otherwise
91	 * @since 1.32
92	 */
93	public function resolveDomainID( $domain );
94
95	/**
96	 * Close all connections and redefine the local domain for testing or schema creation
97	 *
98	 * This only applies to the tracked load balancer instances.
99	 *
100	 * @param DatabaseDomain|string $domain
101	 * @since 1.33
102	 */
103	public function redefineLocalDomain( $domain );
104
105	/**
106	 * Create a new load balancer instance for a main cluster
107	 *
108	 * The resulting object will be untracked and the caller is responsible for cleaning it up.
109	 * Database replication positions will not be saved by ChronologyProtector.
110	 *
111	 * This method is for only advanced usage and callers should almost always use
112	 * getMainLB() instead. This method can be useful when a table is used as a key/value
113	 * store. In that cases, one might want to query it in autocommit mode (DBO_TRX off)
114	 * but still use DBO_TRX transaction rounds on other tables.
115	 *
116	 * @param bool|string $domain Domain ID, or false for the current domain
117	 * @param int|null $owner Owner ID of the new instance (e.g. this LBFactory ID)
118	 * @return ILoadBalancer
119	 */
120	public function newMainLB( $domain = false, $owner = null ): ILoadBalancer;
121
122	/**
123	 * Get the tracked load balancer instance for a main cluster
124	 *
125	 * If no tracked instances exists, then one will be instantiated
126	 *
127	 * @param bool|string $domain Domain ID, or false for the current domain
128	 * @return ILoadBalancer
129	 */
130	public function getMainLB( $domain = false ): ILoadBalancer;
131
132	/**
133	 * Create a new load balancer instance for an external cluster
134	 *
135	 * The resulting object will be untracked and the caller is responsible for cleaning it up.
136	 * Database replication positions will not be saved by ChronologyProtector.
137	 *
138	 * This method is for only advanced usage and callers should almost always use
139	 * getExternalLB() instead. This method can be useful when a table is used as a
140	 * key/value store. In that cases, one might want to query it in autocommit mode
141	 * (DBO_TRX off) but still use DBO_TRX transaction rounds on other tables.
142	 *
143	 * @param string $cluster External cluster name
144	 * @param int|null $owner Owner ID of the new instance (e.g. this LBFactory ID)
145	 * @return ILoadBalancer
146	 */
147	public function newExternalLB( $cluster, $owner = null ): ILoadBalancer;
148
149	/**
150	 * Get the tracked load balancer instance for an external cluster
151	 *
152	 * If no tracked instances exists, then one will be instantiated
153	 *
154	 * @param string $cluster External cluster name
155	 * @return ILoadBalancer
156	 */
157	public function getExternalLB( $cluster ): ILoadBalancer;
158
159	/**
160	 * Get the tracked load balancer instances for all main clusters
161	 *
162	 * If no tracked instance exists for a cluster, then one will be instantiated
163	 *
164	 * Note that default main cluster name is ILoadBalancer::CLUSTER_MAIN_DEFAULT
165	 *
166	 * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
167	 * @since 1.29
168	 */
169	public function getAllMainLBs(): array;
170
171	/**
172	 * Get the tracked load balancer instances for all external clusters
173	 *
174	 * If no tracked instance exists for a cluster, then one will be instantiated
175	 *
176	 * @return ILoadBalancer[] Map of (cluster name => ILoadBalancer)
177	 * @since 1.29
178	 */
179	public function getAllExternalLBs(): array;
180
181	/**
182	 * Execute a function for each instantiated tracked load balancer instance
183	 *
184	 * The callback is called with the load balancer as the first parameter,
185	 * and $params passed as the subsequent parameters.
186	 *
187	 * @param callable $callback
188	 * @param array $params
189	 */
190	public function forEachLB( $callback, array $params = [] );
191
192	/**
193	 * Prepare all instantiated tracked load balancer instances for shutdown
194	 *
195	 * @param int $flags Bit field of ILBFactory::SHUTDOWN_* constants
196	 * @param callable|null $workCallback Work to mask ChronologyProtector writes
197	 * @param int|null &$cpIndex Position key write counter for ChronologyProtector [returned]
198	 * @param string|null &$cpClientId Client ID hash for ChronologyProtector [returned]
199	 */
200	public function shutdown(
201		$flags = self::SHUTDOWN_NORMAL,
202		callable $workCallback = null,
203		&$cpIndex = null,
204		&$cpClientId = null
205	);
206
207	/**
208	 * Commit all replica DB transactions so as to flush any REPEATABLE-READ or SSI snapshot
209	 *
210	 * This only applies to the instantiated tracked load balancer instances.
211	 *
212	 * This is useful for getting rid of stale data from an implicit transaction round
213	 *
214	 * @param string $fname Caller name
215	 */
216	public function flushReplicaSnapshots( $fname = __METHOD__ );
217
218	/**
219	 * Commit open transactions on all connections
220	 *
221	 * This only applies to the instantiated tracked load balancer instances.
222	 *
223	 * This is useful for two main cases:
224	 *   - a) Committing changes to the masters
225	 *   - b) Releasing the snapshot on all connections, primary and replica DBs
226	 *
227	 * @param string $fname Caller name
228	 * @param array $options Options map:
229	 *   - maxWriteDuration: abort if more than this much time was spent in write queries
230	 */
231	public function commitAll( $fname = __METHOD__, array $options = [] );
232
233	/**
234	 * Flush any primary transaction snapshots and set DBO_TRX (if DBO_DEFAULT is set)
235	 *
236	 * The DBO_TRX setting will be reverted to the default in each of these methods:
237	 *   - commitPrimaryChanges()
238	 *   - rollbackPrimaryChanges()
239	 *   - commitAll()
240	 *
241	 * This only applies to the tracked load balancer instances.
242	 *
243	 * This allows for custom transaction rounds from any outer transaction scope.
244	 *
245	 * @param string $fname
246	 * @throws DBTransactionError
247	 * @since 1.37
248	 */
249	public function beginPrimaryChanges( $fname = __METHOD__ );
250
251	/**
252	 * @deprecated since 1.37 Use beginPrimaryChanges() instead.
253	 * @param string $fname
254	 * @throws DBTransactionError
255	 */
256	public function beginMasterChanges( $fname = __METHOD__ );
257
258	/**
259	 * Commit changes and clear view snapshots on all primary connections
260	 *
261	 * This only applies to the instantiated tracked load balancer instances.
262	 *
263	 * @param string $fname Caller name
264	 * @param array $options Options map:
265	 *   - maxWriteDuration: abort if more than this much time was spent in write queries
266	 * @throws DBTransactionError
267	 * @since 1.37
268	 */
269	public function commitPrimaryChanges( $fname = __METHOD__, array $options = [] );
270
271	/**
272	 * @deprecated since 1.37; please use commitPrimaryChanges() instead.
273	 * @param string $fname Caller name
274	 * @param array $options Options map:
275	 *   - maxWriteDuration: abort if more than this much time was spent in write queries
276	 * @throws DBTransactionError
277	 */
278	public function commitMasterChanges( $fname = __METHOD__, array $options = [] );
279
280	/**
281	 * Rollback changes on all primary connections
282	 *
283	 * This only applies to the instantiated tracked load balancer instances.
284	 *
285	 * @param string $fname Caller name
286	 * @since 1.37
287	 */
288	public function rollbackPrimaryChanges( $fname = __METHOD__ );
289
290	/**
291	 * @deprecated since 1.37; please use rollbackPrimaryChanges() instead.
292	 * @param string $fname Caller name
293	 */
294	public function rollbackMasterChanges( $fname = __METHOD__ );
295
296	/**
297	 * Check if an explicit transaction round is active
298	 *
299	 * @return bool
300	 * @since 1.29
301	 */
302	public function hasTransactionRound();
303
304	/**
305	 * Check if transaction rounds can be started, committed, or rolled back right now
306	 *
307	 * This can be used as a recusion guard to avoid exceptions in transaction callbacks.
308	 *
309	 * @return bool
310	 * @since 1.32
311	 */
312	public function isReadyForRoundOperations();
313
314	/**
315	 * Determine if any primary connection has pending changes
316	 *
317	 * This only applies to the instantiated tracked load balancer instances.
318	 *
319	 * @return bool
320	 * @since 1.37
321	 */
322	public function hasPrimaryChanges();
323
324	/**
325	 * @deprecated since 1.37; please use hasPrimaryChanges() instead.
326	 * @return bool
327	 */
328	public function hasMasterChanges();
329
330	/**
331	 * Detemine if any lagged replica DB connection was used
332	 *
333	 * This only applies to the instantiated tracked load balancer instances.
334	 *
335	 * @return bool
336	 */
337	public function laggedReplicaUsed();
338
339	/**
340	 * Determine if any primary connection has pending/written changes from this request
341	 *
342	 * This only applies to the instantiated tracked load balancer instances.
343	 *
344	 * @param float|null $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
345	 * @return bool
346	 */
347	public function hasOrMadeRecentPrimaryChanges( $age = null );
348
349	/**
350	 * @deprecated since 1.37; please use hasOrMadeRecentPrimaryChanges() instead.
351	 * @param float|null $age How many seconds ago is "recent" [defaults to LB lag wait timeout]
352	 * @return bool
353	 */
354	public function hasOrMadeRecentMasterChanges( $age = null );
355
356	/**
357	 * Waits for the replica DBs to catch up to the current primary position
358	 *
359	 * Use this when updating very large numbers of rows, as in maintenance scripts,
360	 * to avoid causing too much lag. Of course, this is a no-op if there are no replica DBs.
361	 *
362	 * By default this waits on all DB clusters actually used in this request.
363	 * This makes sense when lag being waiting on is caused by the code that does this check.
364	 * In that case, setting "ifWritesSince" can avoid the overhead of waiting for clusters
365	 * that were not changed since the last wait check. To forcefully wait on a specific cluster
366	 * for a given domain, use the 'domain' parameter. To forcefully wait on an "external" cluster,
367	 * use the "cluster" parameter.
368	 *
369	 * Never call this function after a large DB write that is *still* in a transaction.
370	 * It only makes sense to call this after the possible lag inducing changes were committed.
371	 *
372	 * This only applies to the instantiated tracked load balancer instances.
373	 *
374	 * @param array $opts Optional fields that include:
375	 *   - domain: Wait on the load balancer DBs that handles the given domain ID.
376	 *   - cluster: Wait on the given external load balancer DBs.
377	 *   - timeout: Max wait time. Default: 60 seconds for CLI, 1 second for web.
378	 *   - ifWritesSince: Only wait if writes were done since this UNIX timestamp.
379	 * @return bool True on success, false if a timeout or error occurred while waiting
380	 */
381	public function waitForReplication( array $opts = [] );
382
383	/**
384	 * Add a callback to be run in every call to waitForReplication() before waiting
385	 *
386	 * Callbacks must clear any transactions that they start.
387	 *
388	 * @param string $name Callback name
389	 * @param callable|null $callback Use null to unset a callback
390	 */
391	public function setWaitForReplicationListener( $name, callable $callback = null );
392
393	/**
394	 * Get a token asserting that no transaction writes are active on tracked load balancers
395	 *
396	 * @param string $fname Caller name (e.g. __METHOD__)
397	 * @return mixed A value to pass to commitAndWaitForReplication()
398	 */
399	public function getEmptyTransactionTicket( $fname );
400
401	/**
402	 * Call commitPrimaryChanges() and waitForReplication() if $ticket indicates it is safe
403	 *
404	 * The ticket is used to check that the caller owns the transaction round or can act on
405	 * behalf of the caller that owns the transaction round.
406	 *
407	 * @see commitPrimaryChanges()
408	 * @see waitForReplication()
409	 *
410	 * @param string $fname Caller name (e.g. __METHOD__)
411	 * @param mixed $ticket Result of getEmptyTransactionTicket()
412	 * @param array $opts Options to waitForReplication()
413	 * @return bool True if the wait was successful, false on timeout
414	 */
415	public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] );
416
417	/**
418	 * Get the UNIX timestamp when the client last touched the DB, if they did so recently
419	 *
420	 * @param DatabaseDomain|string|bool $domain Domain ID, or false for the current domain
421	 * @return float|false UNIX timestamp; false if not recent or on record
422	 */
423	public function getChronologyProtectorTouched( $domain = false );
424
425	/**
426	 * Disable the ChronologyProtector on all instantiated tracked load balancer instances
427	 *
428	 * This can be called at the start of special API entry points.
429	 */
430	public function disableChronologyProtection();
431
432	/**
433	 * Set a new table prefix for the existing local domain ID for testing
434	 *
435	 * @param string $prefix
436	 * @since 1.33
437	 */
438	public function setLocalDomainPrefix( $prefix );
439
440	/**
441	 * Close all connections on instantiated tracked load balancer instances
442	 */
443	public function closeAll();
444
445	/**
446	 * @param string $agent Agent name for query profiling
447	 */
448	public function setAgentName( $agent );
449
450	/**
451	 * Append ?cpPosIndex parameter to a URL for ChronologyProtector purposes if needed
452	 *
453	 * Note that unlike cookies, this works across domains.
454	 *
455	 * @param string $url
456	 * @param int $index Write counter index
457	 * @return string
458	 */
459	public function appendShutdownCPIndexAsQuery( $url, $index );
460
461	/**
462	 * Get the client ID of the ChronologyProtector instance
463	 *
464	 * @return string Client ID
465	 * @since 1.34
466	 */
467	public function getChronologyProtectorClientId();
468
469	/**
470	 * Inject HTTP request header/cookie information during setup of this instance
471	 *
472	 * @param array $info Map of fields, including:
473	 *   - IPAddress : IP address
474	 *   - UserAgent : User-Agent HTTP header
475	 *   - ChronologyProtection : cookie/header value specifying ChronologyProtector usage
476	 *   - ChronologyPositionIndex: timestamp used to get up-to-date DB positions for the agent
477	 */
478	public function setRequestInfo( array $info );
479
480	/**
481	 * Set the default timeout for replication wait checks
482	 *
483	 * @param int $seconds Timeout, in seconds
484	 * @return int The previous default timeout
485	 * @since 1.35
486	 */
487	public function setDefaultReplicationWaitTimeout( $seconds );
488
489	/**
490	 * Make certain table names use their own database, schema, and table prefix
491	 * when passed into SQL queries pre-escaped and without a qualified database name
492	 *
493	 * For example, "user" can be converted to "myschema.mydbname.user" for convenience.
494	 * Appearances like `user`, somedb.user, somedb.someschema.user will used literally.
495	 *
496	 * Calling this twice will completely clear any old table aliases. Also, note that
497	 * callers are responsible for making sure the schemas and databases actually exist.
498	 *
499	 * @param array[] $aliases Map of (table => (dbname, schema, prefix) map)
500	 * @since 1.31
501	 */
502	public function setTableAliases( array $aliases );
503
504	/**
505	 * Convert certain index names to alternative names before querying the DB
506	 *
507	 * Note that this applies to indexes regardless of the table they belong to.
508	 *
509	 * This can be employed when an index was renamed X => Y in code, but the new Y-named
510	 * indexes were not yet built on all DBs. After all the Y-named ones are added by the DBA,
511	 * the aliases can be removed, and then the old X-named indexes dropped.
512	 *
513	 * @param string[] $aliases Map of (index alias => index name)
514	 * @since 1.31
515	 */
516	public function setIndexAliases( array $aliases );
517
518	/**
519	 * Convert certain database domains to alternative ones
520	 *
521	 * This can be used for backwards compatibility logic.
522	 *
523	 * @param DatabaseDomain[]|string[] $aliases Map of (domain alias => domain)
524	 * @since 1.35
525	 */
526	public function setDomainAliases( array $aliases );
527
528	/**
529	 * Get the TransactionProfiler used by this instance
530	 *
531	 * @return TransactionProfiler
532	 * @since 1.35
533	 */
534	public function getTransactionProfiler(): TransactionProfiler;
535}
536