Skip to content

[8.x] Support read replicas via play.ebean.readOnlyDatasources - #893

Merged
mkurz merged 2 commits into
playframework:8.xfrom
mkurz:read-only-datasources-8.x
Oct 8, 2026
Merged

mkurz merged 2 commits into
playframework:8.xfrom
mkurz:read-only-datasources-8.x

Conversation

@mkurz

@mkurz mkurz commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Backport of #892 to 8.x.

Ebean can run queries that are executed outside of a transaction, as well as explicitly read-only transactions, against a separate read-only DataSource, typically a read replica. However, play-ebean only ever handed Ebean the main DataSource (Play's connection pool), so there was no way to set up a read replica via Play's configuration:

  • db.default.readOnlyUrl is not a Play setting and gets silently ignored.
  • Setting datasource.default.readOnlyUrl via Ebean's own properties makes Ebean create a separate connection pool, which fails with DataSource user is not set? unless the credentials get duplicated into Ebean's properties as well, because play-ebean never fills Ebean's main DataSourceConfig.
  • Doing the same in a ServerConfigStartup additionally requires a plain JDBC URL. A Play style postgres://... URL makes the JDBC driver return a null connection, which leads to the NullPointerException reported in Cannot configure Ebean read replica with Play configuration #566.

This PR adds the play.ebean.readOnlyDatasources setting, which maps an Ebean server to a Play database. That database's DataSource then becomes the read-only DataSource of the Ebean server:

db.default.url = "jdbc:postgresql://primary-host/app"
db.replica.url = "jdbc:postgresql://replica-host/app"
db.replica.hikaricp.readOnly = true

ebean.default = ["models.*"]
play.ebean.readOnlyDatasources.default = "replica"

This way Play manages the read replica's connection pool like that of any other database (credentials, URL formats, hikaricp settings, shutdown, ...). The DataSource is passed to Ebean without the wrapper play-ebean uses for the main DataSource (which disables auto-commit), since Ebean's implicit read-only transactions work best with auto-commit (Play's default).

Misconfigurations fail at startup with a ConfigException instead of being silently ignored: an unknown Play database name as well as an unknown Ebean server name.

Ebean supports only one read-only DataSource per Ebean server. The docs explain how to spread reads across multiple read replicas via the JDBC URL (e.g. PostgreSQL's loadBalanceHosts/targetServerType), a load balancer or a reader endpoint.

The tests now run sequentially (Test / parallelExecution := false), because Ebean registers databases JVM-wide by name and the test classes start applications using the same Ebean server name.

Testing

Ebean can run queries that are executed outside of a transaction against
a separate read-only DataSource, typically a read replica. However,
play-ebean only ever provided the main DataSource to Ebean, so there was
no way to set up a read replica via Play's configuration.

The new play.ebean.readOnlyDatasources setting maps an Ebean server to a
Play database, whose DataSource then becomes the read-only DataSource of
that Ebean server, e.g.:

    db.replica.url = "jdbc:postgresql://replica-host/app"
    play.ebean.readOnlyDatasources.default = "replica"

This way Play manages the read replica's connection pool like that of
any other database.

Also run the tests sequentially, because Ebean registers databases
JVM-wide by name and the tests start applications using the same names.
@mergify

mergify Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

Explicitly read-only transactions, e.g. TxScope.required().setReadOnly(true),
use the read-only DataSource as well. Say so in the docs and cover it in
the test.
@mkurz
mkurz merged commit b42381d into playframework:8.x Oct 8, 2026
21 checks passed
@mkurz
mkurz deleted the read-only-datasources-8.x branch October 8, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant