Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOC DB read-only replicas #587

Open
wants to merge 1 commit into
base: 6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions en/00_Getting_Started/03_Environment_Management.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ $loader->loadFile($env);

Silverstripe core environment variables are listed here, though you're free to define any you need for your application.

If you want to configure environment variables for read-only replica databases, then view the [Read-only database replicas](/developer_guides/performance/db_read_only_replicas) documentation.

| Name | Description |
| ---- | ----------- |
| `SS_DATABASE_CLASS` | The database class to use. Only `MySQLDatabase` is included by default, but other values are available in optional modules such as [`PostgreSQLDatabase`](https://github.com/silverstripe/silverstripe-postgresql). Defaults to `MySQLDatabase`.|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: Read-only database replicas
summary: Using read-only database replicas to improve performance
---

# Read-only database replicas

Read-only replicas are additional databases that are used to offload read queries from the primary database, which can improve performance by reducing the load on the primary database.

Read-only replicas are configured by adding environment variables that match the primary environment variable and suffixing `_REPLICA_<replica-number>` to the variable name, where `<replica_number>` is the replica number padding by a zero if it's less than 10, for example `SS_DATABASE_SERVER` becomes `SS_DATABASE_SERVER_REPLICA_01` for the first replica, or `SS_DATABASE_SERVER_REPLICA_12` for the 12th replica. Replias must be numbered sequentially starting from `01`.

```bash
# Primary database
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_SERVER="my-db-server"
SS_DATABASE_PORT="3306"
SS_DATABASE_USERNAME="my-user"
SS_DATABASE_PASSWORD="my-password"
SS_DATABASE_NAME="db"

# Read-only replica
SS_DATABASE_SERVER_REPLICA_01="my-db-replica"
SS_DATABASE_PORT_REPLICA_01="3306"
SS_DATABASE_USERNAME_REPLICA_01="my-replica-user"
SS_DATABASE_PASSWORD_REPLICA_01="my-replica-password"
```

Replicas cannot define different configuration values for `SS_DATABASE_CLASS`, `SS_DATABASE_NAME`, or `SS_DATABASE_CHOOSE_NAME`. They are restricted to prevent strange issues that could arise from having inconsistent database configurations across replicas.

If one or more read-only replicas have been configured, then for each request one of the read-only replicas will be randomly selected from the pool of available replicas to handle queries for the rest of the request cycle. However the primary database will be used instead if one of the follow criteria has been met:

- The current query includes any mutable SQL such as `INSERT` or `DELETE`. The primary database will be used for the current query, as well as any future queries, including read queries, for the rest of the current request cycle. Mutable SQL is defined on [`DBConnector::isQueryMutable()`](api:SilverStripe\ORM\Connect\DBConnector::isQueryMutable()).
- The HTTP request matches a routing rule defined in [`Director.rule_patterns_must_use_primary_db`](api:SilverStripe\Control\Director->rule_patterns_must_use_primary_db). By default the URL paths `Security`, `dev`, and `admin` (if `silverstripe/admin` is installed) are covered by this by default.
- A user with CMS access is logged in. This is done to ensure that logged in users will correctly see any CMS updates on the website frontend. Users without CMS access will still use a read-only replica.
- For any query that goes through a call to [`DataQuery::execute()`](api:SilverStripe\ORM\DataQuery::execute()), the `DataObject` subclass being queried is configured with [`DataObject.must_use_primary_db`](api:SilverStripe\ORM\DataObject->must_use_primary_db) set to `true`. This includes most commonly used ORM methods such as [`DataObject::get()`](api:SilverStripe\ORM\DataObject::get()), and excludes [`SQLSelect`](api:SilverStripe\ORM\Queries\SQLSelect) methods. By default all core security related `DataObject` subclasses have `must_use_primary_db` set to `true`.
- Any code wrapped in a call to [`DB::withPrimary()`](api:SilverStripe\ORM\DB::withPrimary()).
- All queries that result from using the CLI.

## Forcing use of the primary database

When using database replicas you may need to force the use of the primary database to ensure there are no issues with the data being out of sync. The following methods are available to force the use of the primary database:

[`DB::setMustUsePrimaryDB()`](api:SilverStripe\ORM\DB::setMustUsePrimaryDB()) will force the use of the primary database for the rest of current request cycle. Once it has ben set it cannot be unset.

```php
// Code here can use a replica

DB::setMustUsePrimaryDB();

// Code here will only use the primary database
```

Code wrapped in a call to [`DB::withPrimary()`](api:SilverStripe\ORM\DB::withPrimary()) will always use the primary database.

```php
// Code here can use a replica

DB::withPrimary(function () {
// Code here will only use the primary database
});

// Code here can use a replica
```
19 changes: 19 additions & 0 deletions en/08_Changelogs/6.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ title: 6.0.0 (unreleased)

- [Features and enhancements](#features-and-enhancements)
- [Changes to `sake`, `BuildTask`, CLI interaction in general](#cli-changes)
- [Read-only replica database support](#db-read-only-replicas)
- [Run `CanonicalURLMiddleware` in all environments by default](#url-middleware)
- [Changes to default cache adapters](#caching)
- [Changes to scaffolded form fields](#scaffolded-fields)
Expand Down Expand Up @@ -273,6 +274,24 @@ Sake used to have functionality to make daemon processes for your application. T

We've removed this functionality. Please use an appropriate daemon tool such as `systemctl` to manage these instead.

### Read-only replica database support {#db-read-only-replicas}

Read-only replicas are additional databases that are used to offload read queries from the primary database, which can improve performance by reducing the load on the primary database.

Read-only replicas are configured by adding environment variables that match the primary environment variable and suffixing `_REPLICA_<replica-number>` to the variable name, where `<replica_number>` is the replica number padding by a zero if it's less than 10, for example `SS_DATABASE_SERVER` becomes `SS_DATABASE_SERVER_REPLICA_01` for the first replica, or `SS_DATABASE_SERVER_REPLICA_12` for the 12th replica. Replias must be numbered sequentially starting from `01`.

Replicas cannot define different configuration values for `SS_DATABASE_CLASS`, `SS_DATABASE_NAME`, or `SS_DATABASE_CHOOSE_NAME`. They are restricted to prevent strange issues that could arise from having inconsistent database configurations across replicas.

If one or more read-only replicas have been configured, then for each request one of the read-only replicas will be randomly selected from the pool of available replicas to handle queries for the rest of the request cycle, unless criteria has been met to use the primary database instead, for example a write operation.

See [read-only database replicas](/developer_guides/performance/read_only_database_replicas/) for more details.

Calling the methods [`DB::get_conn()`](api:SilverStripe\ORM\DB::get_conn()), [`DB::set_conn()`](api:SilverStripe\ORM\DB::set_conn()), [`DB::getConfig()`](api:SilverStripe\ORM\DB::getConfig()), and [`DB::setConfig()`](api:SilverStripe\ORM\DB::setConfig()) using the default `$name` parameter of "default" will now emit a deprecation notice and will be internall converted to "primary". Pass [`DB::CONN_PRIMARY`](api:SilverStripe\ORM\DB::CONN_PRIMARY) or simply "primary" for the `$name` parameter instead. The default value for `$name` will be changed to "primary" in a future major release.

When replicas are configured, calling the method [`DB::get_conn()`](api:SilverStripe\ORM\DB::get_conn()) will now give a replica by default if one is able to be used. To get the primary database connection, call `DB::get_conn(DB::CONN_PRIMARY)` instead.

Note that some [`DataQuery`](api:SilverStripe\ORM\DataQuery) methods such as [`DataQuery::execute()`](api:SilverStripe\ORM\DataQuery::execute()) now work slightly differently as they will use the replica database if the queried `DataObject` has the [`DataObject.must_use_primary_db`](api:SilverStripe\ORM\DataObject->must_use_primary_db) configuration set to `true`. However calling the equivalent [`SQLSelect`](api:SilverStripe\ORM\Queries\SQLSelect) method via a `DataQuery` e.g. `$dataQuery->query()->execute()` will not respect the `DataObject.must_use_primary_db` configuration.

### Run `CanonicalURLMiddleware` in all environments by default {#url-middleware}

In Silverstripe CMS 5 [`CanonicalURLMiddleware`](api:SilverStripe\Control\Middleware\CanonicalURLMiddleware) only runs in production by default. This lead to issues with `fetch` and APIs behaving differently in production environments to development. Silverstripe 6.0 changes this default to run the rules in `dev`, `test`, and `live` by default.
Expand Down
Loading