Skip to content

Commit

Permalink
NEW Allow database read-only replicas
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Sep 12, 2024
1 parent 9788a97 commit 8acffb4
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 14 deletions.
81 changes: 68 additions & 13 deletions src/Core/CoreKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,58 @@ protected function bootDatabaseEnvVars()
$databaseConfig = $this->getDatabaseConfig();
$databaseConfig['database'] = $this->getDatabaseName();
DB::setConfig($databaseConfig);

// Set database replicas config
for ($i = 1; $i <= 99; $i++) {
$replicaKey = $this->getReplicaKey('SS_DATABASE_SERVER', $i);
if (!Environment::hasEnv($replicaKey)) {
break;
}
$replicaDatabaseConfig = $this->getDatabaseReplicaConfig($i);
$name = 'replica_' . str_pad($i, 2, '0', STR_PAD_LEFT);
DB::setConfig($replicaDatabaseConfig, $name);
}
}

/**
* Load database config from environment
*
* @return array
* Convert a database key to a replica key
* e.g. SS_DATABASE_SERVER -> SS_DATABASE_SERVER_REPLICA_01
*/
protected function getDatabaseConfig()
private function getReplicaKey(string $key, int $replica): string
{
// Left pad replica number with a zero if it's less than 10
return $key . '_REPLICA_' . str_pad($replica, 2, '0', STR_PAD_LEFT);
}

/**
* Reads a single database configuration variable from the environment
* For replica databases, it will first attempt to find replica-specific configuration
* before falling back to the default configuration.
*
* Replicate specific configuration has `_REPLICA_01` appended to the key
* where 01 is the replica number.
*/
private function getDatabaseConfigVariable(string $key, int $replica): string
{
if ($replica > 0) {
$replicaKey = $this->getReplicaKey($key, $replica);
if (Environment::hasEnv($replicaKey)) {
return Environment::getEnv($replicaKey);
}
}
if (Environment::hasEnv($key)) {
return Environment::getEnv($key);
}
return '';
}

private function getSingleDataBaseConfig(int $replica): array
{
$databaseConfig = [
"type" => Environment::getEnv('SS_DATABASE_CLASS') ?: 'MySQLDatabase',
"server" => Environment::getEnv('SS_DATABASE_SERVER') ?: 'localhost',
"username" => Environment::getEnv('SS_DATABASE_USERNAME') ?: null,
"password" => Environment::getEnv('SS_DATABASE_PASSWORD') ?: null,
"type" => $this->getDatabaseConfigVariable('SS_DATABASE_CLASS', $replica) ?: 'MySQLDatabase',
"server" => $this->getDatabaseConfigVariable('SS_DATABASE_SERVER', $replica) ?: 'localhost',
"username" => $this->getDatabaseConfigVariable('SS_DATABASE_USERNAME', $replica) ?: null,
"password" => $this->getDatabaseConfigVariable('SS_DATABASE_PASSWORD', $replica) ?: null,
];

// Only add SSL keys in the array if there is an actual value associated with them
Expand All @@ -122,7 +160,7 @@ protected function getDatabaseConfig()
'ssl_cipher' => 'SS_DATABASE_SSL_CIPHER',
];
foreach ($sslConf as $key => $envVar) {
$envValue = Environment::getEnv($envVar);
$envValue = $this->getDatabaseConfigVariable($envVar, $replica);
if ($envValue) {
$databaseConfig[$key] = $envValue;
}
Expand All @@ -138,25 +176,25 @@ protected function getDatabaseConfig()
}

// Set the port if called for
$dbPort = Environment::getEnv('SS_DATABASE_PORT');
$dbPort = $this->getDatabaseConfigVariable('SS_DATABASE_PORT', $replica);
if ($dbPort) {
$databaseConfig['port'] = $dbPort;
}

// Set the timezone if called for
$dbTZ = Environment::getEnv('SS_DATABASE_TIMEZONE');
$dbTZ = $this->getDatabaseConfigVariable('SS_DATABASE_TIMEZONE', $replica);
if ($dbTZ) {
$databaseConfig['timezone'] = $dbTZ;
}

// For schema enabled drivers:
$dbSchema = Environment::getEnv('SS_DATABASE_SCHEMA');
$dbSchema = $this->getDatabaseConfigVariable('SS_DATABASE_SCHEMA', $replica);
if ($dbSchema) {
$databaseConfig["schema"] = $dbSchema;
}

// For SQlite3 memory databases (mainly for testing purposes)
$dbMemory = Environment::getEnv('SS_DATABASE_MEMORY');
$dbMemory = $this->getDatabaseConfigVariable('SS_DATABASE_MEMORY', $replica);
if ($dbMemory) {
$databaseConfig["memory"] = $dbMemory;
}
Expand All @@ -166,6 +204,22 @@ protected function getDatabaseConfig()
return $databaseConfig;
}

/**
* Load database config from environment
*
* @return array
*/
protected function getDatabaseConfig()
{
return $this->getSingleDataBaseConfig(0);
}

protected function getDatabaseReplicaConfig(int $replica)
{
$replica = 1;
return $this->getSingleDataBaseConfig($replica);
}

/**
* @return string
*/
Expand All @@ -184,6 +238,7 @@ protected function getDatabaseSuffix()

/**
* Get name of database
* Note that any replicas must have the same database name
*
* @return string
*/
Expand Down
10 changes: 9 additions & 1 deletion src/ORM/DB.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,19 @@ public static function setConfig($databaseConfig, $name = 'default')
*/
public static function getConfig($name = 'default')
{
if (isset(static::$configs[$name])) {
if (static::hasConfig($name)) {
return static::$configs[$name];
}
}

/**
* Check if a named connection config exists
*/
public static function hasConfig($name = 'default'): bool
{
return isset(static::$configs[$name]);
}

/**
* Returns true if a database connection has been attempted.
* In particular, it lets the caller know if we're still so early in the execution pipeline that
Expand Down

0 comments on commit 8acffb4

Please sign in to comment.