MediaWiki master
Wikimedia\Rdbms\ChronologyProtector Class Reference

Provide a given client with protection against visible database lag. More...

Inherits LoggerAwareInterface.

Collaboration diagram for Wikimedia\Rdbms\ChronologyProtector:

Public Member Functions

 __construct ( $cpStash=null, $secret=null, $cliMode=null, $logger=null)
 
 getClientId ()
 
 getSessionPrimaryPos (ILoadBalancer $lb)
 Yield client "session consistency" replication position for a new ILoadBalancer.
 
 getTouched (ILoadBalancer $lb)
 Get the UNIX timestamp when the client last touched the DB, if they did so recently.
 
 persistSessionReplicationPositions (&$clientPosIndex=null)
 Persist any staged client "session consistency" replication positions.
 
 setEnabled ( $enabled)
 
 setLogger (LoggerInterface $logger)
 
 setMockTime (&$time)
 
 setRequestInfo (array $info)
 
 stageSessionPrimaryPos (ILoadBalancer $lb)
 Update client "session consistency" replication position for an end-of-life ILoadBalancer.
 

Static Public Member Functions

static getCPInfoFromCookieValue (?string $value, int $minTimestamp)
 Parse a string conveying the client and write index of the chronology protector data.
 
static makeCookieValueFromCPIndex (int $writeIndex, int $time, string $clientId)
 Build a string conveying the client and write index of the chronology protector data.
 

Public Attributes

const POSITION_COOKIE_TTL = 10
 Seconds to store position write index cookies (safely less than POSITION_STORE_TTL)
 

Protected Member Functions

 getCurrentTime ()
 
 getStartupSessionPositions ()
 
 getStartupSessionTimestamps ()
 
 lazyStartup ()
 Load the stored replication positions and touch timestamps for the client.
 
 mergePositions ( $storedValue, array $shutdownPositions, array $shutdownTimestamps, ?int &$clientPosIndex=null)
 Merge the new replication positions with the currently stored ones (highest wins)
 

Protected Attributes

string $clientId
 Hash of client parameters.
 
string[] $clientLogInfo
 Map of client information fields for logging.
 
bool $enabled = true
 Whether reading/writing session consistency replication positions is enabled.
 
string $key
 Storage key name.
 
LoggerInterface $logger
 
array< string, DBPrimaryPos$shutdownPositionsByPrimary = []
 Map of (primary server name => position)
 
array< string, float > $shutdownTimestampsByCluster = []
 Map of (DB cluster name => UNIX timestamp)
 
array< string, DBPrimaryPos$startupPositionsByPrimary = []
 Map of (primary server name => position)
 
float null $startupTimestamp
 UNIX timestamp when the client data was loaded.
 
array< string, float > $startupTimestampsByCluster = []
 Map of (DB cluster name => UNIX timestamp)
 
int null $waitForPosIndex
 Expected minimum index of the last write to the position store.
 

Detailed Description

Provide a given client with protection against visible database lag.

In a nut shell

This class tries to hide visible effects of database lag. It does this by temporarily remembering the database positions after a client makes a write, and on their next web request we will prefer non-lagged database replicas. When replica connections are established, we wait up to a few seconds for sufficient replication to have occurred, if they were not yet caught up to that same point.

This ensures a consistent ordering of events as seen by a client. Kind of like Hawking's Chronology Protection Agency.

Purpose

For performance and scalability reasons, almost all data is queried from replica databases. Only queries relating to writing data, are sent to a primary database. When rendering a web page with content or activity feeds on it, the very latest information may thus not yet be there. That's okay in general, but if, for example, a client recently changed their preferences or submitted new data, we do our best to make sure their next web response does reflect at least their own recent changes.

How

To explain how it works, we will look at an example lifecycle for a client.

A client is browsing the site. Their web requests are generally read-only and display data from database replicas, which may be a few seconds out of date if a client elsewhere in the world recently modified that same data. If the application is run from multiple data centers, then these web requests may be served from the nearest secondary DC.

A client performs a POST request, perhaps to publish an edit or change their preferences. This request is routed to the primary DC (this is the responsibility of infrastructure outside the web app). There, the data is saved to the primary database, after which the database host will asynchronously replicate this to its replicas in the same and any other DCs.

Toward the end of the response to this POST request, the application takes note of the primary database's current "position", and save this under a "clientId" key in the ChronologyProtector store. The web response will also set two cookies that are similarly short-lived (about ten seconds): UseDC=master and cpPosIndex=<posIndex>@<write time>#<clientId>.

The ten seconds window is meant to account for the time needed for the database writes to have replicated across all active database replicas, including the cross-dc latency for those further away in any secondary DCs. The "clientId" is placed in the cookie to handle the case where the client IP addresses frequently changes between web requests.

Future web requests from the client should fall in one of two categories:

  1. Within the ten second window. Their UseDC cookie will make them return to the primary DC where we access the ChronologyProtector store and use the database "position" to decide which local database replica to use and on-demand wait a split second for replication to catch up if needed.
  2. After the ten second window. They will be routed to the nearest and possibly different DC. Any local ChronologyProtector store existing there will not be interacted with. A random database replica may be used as the client's own writes are expected to have been applied here by now.

Storage requirements

The store used by ChronologyProtector, as configured via $wgChronologyProtectorStash, should meet the following requirements:

  • Low latencies. Nearly all web requests that involve a database connection will unconditionally query this store first. It is expected to respond within the order of one millisecond.
  • Best effort persistence, without active eviction pressure. Data stored here cannot be obtained elsewhere or recomputed. As such, under normal operating conditions, this store should not be full, and should not evict values before their intended expiry time elapsed.
  • No replication, local consistency. Each DC may have a fully independent dc-local store associated with ChronologyProtector (no replication across DCs is needed). Local writes must be immediately reflected in subsequent local reads. No intra-dc read lag is allowed.
  • No redundancy, fast failure. Loss of data will likely be noticeable and disruptive to clients, but the data is not considered essential. Under maintenance or unprecedented load, it is recommended to lose some data, instead of compromising other requirements such as latency or availability for new writes. The fallback is that users may be temporary confused as they observe their own actions as not being immediately reflected. For example, they might change their skin or language preference but still get a one or two page views afterward with the old settings. Or they might have published an edit and briefly not yet see it appear in their contribution history.

Operational requirements

These are the expectations a site administrator must meet for chronology protection:

  • If the application is run from multiple data centers, then you must designate one of them as the "primary DC". The primary DC is where the primary database is located, from which replication propagates to replica databases in that same DC and any other DCs.
  • Web requests that use the POST verb, or carry a UseDC=master cookie, must be routed to the primary DC only.

    An exception is requests carrying the Promise-Non-Write-API-Action: true header, which use the POST verb for large read queries, but don't actually require the primary DC.

    If you have legacy extensions deployed that perform queries on the primary database during GET requests, then you will have to identify a way to route any of its relevant URLs to the primary DC as well, or to accept that their reads do not enjoy chronology protection, and that writes may be slower (due to cross-dc latency). See T91820 for Wikimedia Foundation's routing.

Access: internal

Definition at line 134 of file ChronologyProtector.php.

Constructor & Destructor Documentation

◆ __construct()

Wikimedia\Rdbms\ChronologyProtector::__construct ( $cpStash = null,
$secret = null,
$cliMode = null,
$logger = null )
Parameters
BagOStuff | null$cpStash
string | null$secretSecret string for HMAC hashing [optional]
bool | null$cliModeWhether the context is CLI or not, setting it to true would disable CP
LoggerInterface | null$logger
Since
1.27

Definition at line 205 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\$logger.

Member Function Documentation

◆ getClientId()

Wikimedia\Rdbms\ChronologyProtector::getClientId ( )
Returns
string Client ID hash
Since
1.32

Definition at line 272 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\$clientId.

◆ getCPInfoFromCookieValue()

static Wikimedia\Rdbms\ChronologyProtector::getCPInfoFromCookieValue ( ?string $value,
int $minTimestamp )
static

Parse a string conveying the client and write index of the chronology protector data.

Parameters
string | null$valueValue of "cpPosIndex" cookie
int$minTimestampLowest UNIX timestamp that a non-expired value can have
Returns
array (index: int or null, clientId: string or null)
Since
1.32 in LBFactory, moved to CP in 1.41

Definition at line 645 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\$clientId.

◆ getCurrentTime()

Wikimedia\Rdbms\ChronologyProtector::getCurrentTime ( )
protected
Access: internal
For testing only
Returns
float UNIX timestamp

Definition at line 573 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\lazyStartup(), and Wikimedia\Rdbms\ChronologyProtector\stageSessionPrimaryPos().

◆ getSessionPrimaryPos()

Wikimedia\Rdbms\ChronologyProtector::getSessionPrimaryPos ( ILoadBalancer $lb)

Yield client "session consistency" replication position for a new ILoadBalancer.

If the stash has a previous primary position recorded, this will try to make sure that the next query to a replica server of that primary will see changes up to that position by delaying execution. The delay may timeout and allow stale data if no non-lagged replica servers are available.

Access: internal
This method should only be called from LBFactory.
Parameters
ILoadBalancer$lb
Returns
DBPrimaryPos|null

Definition at line 298 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ILoadBalancer\getClusterName(), Wikimedia\Rdbms\ILoadBalancer\getServerName(), Wikimedia\Rdbms\ChronologyProtector\getStartupSessionPositions(), and Wikimedia\Rdbms\ILoadBalancer\getWriterIndex().

◆ getStartupSessionPositions()

Wikimedia\Rdbms\ChronologyProtector::getStartupSessionPositions ( )
protected

◆ getStartupSessionTimestamps()

Wikimedia\Rdbms\ChronologyProtector::getStartupSessionTimestamps ( )
protected

◆ getTouched()

Wikimedia\Rdbms\ChronologyProtector::getTouched ( ILoadBalancer $lb)

Get the UNIX timestamp when the client last touched the DB, if they did so recently.

Access: internal
This method should only be called from LBFactory.
Parameters
ILoadBalancer$lb
Returns
float|false UNIX timestamp; false if not recent or on record
Since
1.35

Definition at line 416 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ILoadBalancer\getClusterName(), and Wikimedia\Rdbms\ChronologyProtector\getStartupSessionTimestamps().

◆ lazyStartup()

Wikimedia\Rdbms\ChronologyProtector::lazyStartup ( )
protected

Load the stored replication positions and touch timestamps for the client.

Returns
void

Definition at line 466 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\getCurrentTime().

Referenced by Wikimedia\Rdbms\ChronologyProtector\getStartupSessionPositions(), and Wikimedia\Rdbms\ChronologyProtector\getStartupSessionTimestamps().

◆ makeCookieValueFromCPIndex()

static Wikimedia\Rdbms\ChronologyProtector::makeCookieValueFromCPIndex ( int $writeIndex,
int $time,
string $clientId )
static

Build a string conveying the client and write index of the chronology protector data.

Parameters
int$writeIndex
int$timeUNIX timestamp; can be used to detect stale cookies (T190082)
string$clientIdClient ID hash from ILBFactory::shutdown()
Returns
string Value to use for "cpPosIndex" cookie
Since
1.32 in LBFactory, moved to CP in 1.41

Definition at line 628 of file ChronologyProtector.php.

◆ mergePositions()

Wikimedia\Rdbms\ChronologyProtector::mergePositions ( $storedValue,
array $shutdownPositions,
array $shutdownTimestamps,
?int & $clientPosIndex = null )
protected

Merge the new replication positions with the currently stored ones (highest wins)

Parameters
array<string,mixed>|false$storedValue Current replication position data
array<string,DBPrimaryPos>$shutdownPositions New replication positions
array<string,float>$shutdownTimestamps New DB post-commit shutdown timestamps
int | null&$clientPosIndexNew position write index
Returns
array<string,mixed> Combined replication position data

Definition at line 528 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\persistSessionReplicationPositions().

◆ persistSessionReplicationPositions()

Wikimedia\Rdbms\ChronologyProtector::persistSessionReplicationPositions ( & $clientPosIndex = null)

Persist any staged client "session consistency" replication positions.

Access: internal
This method should only be called from LBFactory.
Parameters
int | null&$clientPosIndexDB position key write counter; incremented on update
Returns
DBPrimaryPos[] Empty on success; map of (db name => unsaved position) on failure

Definition at line 361 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\$shutdownPositionsByPrimary, and Wikimedia\Rdbms\ChronologyProtector\mergePositions().

◆ setEnabled()

Wikimedia\Rdbms\ChronologyProtector::setEnabled ( $enabled)
Parameters
bool$enabledWhether reading/writing session replication positions is enabled
Since
1.27

Definition at line 281 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ChronologyProtector\$enabled.

◆ setLogger()

Wikimedia\Rdbms\ChronologyProtector::setLogger ( LoggerInterface $logger)

◆ setMockTime()

Wikimedia\Rdbms\ChronologyProtector::setMockTime ( & $time)
Access: internal
For testing only
Parameters
float | null&$timeMock UNIX timestamp

Definition at line 589 of file ChronologyProtector.php.

◆ setRequestInfo()

Wikimedia\Rdbms\ChronologyProtector::setRequestInfo ( array $info)

Definition at line 255 of file ChronologyProtector.php.

◆ stageSessionPrimaryPos()

Wikimedia\Rdbms\ChronologyProtector::stageSessionPrimaryPos ( ILoadBalancer $lb)

Update client "session consistency" replication position for an end-of-life ILoadBalancer.

This remarks the replication position of the primary DB if this request made writes to it using the provided ILoadBalancer instance.

Access: internal
This method should only be called from LBFactory.
Parameters
ILoadBalancer$lb
Returns
void

Definition at line 328 of file ChronologyProtector.php.

References Wikimedia\Rdbms\ILoadBalancer\getClusterName(), Wikimedia\Rdbms\ChronologyProtector\getCurrentTime(), Wikimedia\Rdbms\ILoadBalancer\getPrimaryPos(), Wikimedia\Rdbms\ILoadBalancer\getServerName(), Wikimedia\Rdbms\ILoadBalancer\getWriterIndex(), Wikimedia\Rdbms\ILoadBalancer\hasOrMadeRecentPrimaryChanges(), and Wikimedia\Rdbms\ILoadBalancer\hasStreamingReplicaServers().

Member Data Documentation

◆ $clientId

string Wikimedia\Rdbms\ChronologyProtector::$clientId
protected

◆ $clientLogInfo

string [] Wikimedia\Rdbms\ChronologyProtector::$clientLogInfo
protected

Map of client information fields for logging.

Definition at line 150 of file ChronologyProtector.php.

◆ $enabled

bool Wikimedia\Rdbms\ChronologyProtector::$enabled = true
protected

Whether reading/writing session consistency replication positions is enabled.

Definition at line 155 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\setEnabled().

◆ $key

string Wikimedia\Rdbms\ChronologyProtector::$key
protected

Storage key name.

Definition at line 146 of file ChronologyProtector.php.

◆ $logger

LoggerInterface Wikimedia\Rdbms\ChronologyProtector::$logger
protected

◆ $shutdownPositionsByPrimary

array<string,DBPrimaryPos> Wikimedia\Rdbms\ChronologyProtector::$shutdownPositionsByPrimary = []
protected

Map of (primary server name => position)

Definition at line 162 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\persistSessionReplicationPositions().

◆ $shutdownTimestampsByCluster

array<string,float> Wikimedia\Rdbms\ChronologyProtector::$shutdownTimestampsByCluster = []
protected

Map of (DB cluster name => UNIX timestamp)

Definition at line 166 of file ChronologyProtector.php.

◆ $startupPositionsByPrimary

array<string,DBPrimaryPos> Wikimedia\Rdbms\ChronologyProtector::$startupPositionsByPrimary = []
protected

Map of (primary server name => position)

Definition at line 160 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\getStartupSessionPositions().

◆ $startupTimestamp

float null Wikimedia\Rdbms\ChronologyProtector::$startupTimestamp
protected

UNIX timestamp when the client data was loaded.

Definition at line 157 of file ChronologyProtector.php.

◆ $startupTimestampsByCluster

array<string,float> Wikimedia\Rdbms\ChronologyProtector::$startupTimestampsByCluster = []
protected

Map of (DB cluster name => UNIX timestamp)

Definition at line 164 of file ChronologyProtector.php.

Referenced by Wikimedia\Rdbms\ChronologyProtector\getStartupSessionTimestamps().

◆ $waitForPosIndex

int null Wikimedia\Rdbms\ChronologyProtector::$waitForPosIndex
protected

Expected minimum index of the last write to the position store.

Definition at line 152 of file ChronologyProtector.php.

◆ POSITION_COOKIE_TTL

const Wikimedia\Rdbms\ChronologyProtector::POSITION_COOKIE_TTL = 10

Seconds to store position write index cookies (safely less than POSITION_STORE_TTL)

Definition at line 185 of file ChronologyProtector.php.


The documentation for this class was generated from the following file: