[FEATURE] Route entity-store reads to optional read-only JDBC URL (read replica)
- Dominant language
- Java
- Stars
- 3.2k
- Forks
- 935
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 298
Description
### Describe the feature
Gravitino’s relational entity store (JDBCBackend) can use an optional second JDBC pool aimed at read replicas. When any read-replica-related setting is configured, standalone metadata reads (via SessionUtils.getWithoutCommit) use the read pool; writes and transactional work use the primary pool. If no read-replica settings are set, a single pool is used for both reads and writes (unchanged behavior).
Supported options include a read-only JDBC URL, optional read-only user/password, optional read pool size and wait (or inherit from the primary pool), startup logging when the read pool is enabled, and separate metrics for the read datasource when it is distinct from the primary.
### Motivation
Today all entity-store traffic goes through one connection pool to the primary database. For deployments with MySQL (or similar) read replicas, operators want to shift read-heavy metadata traffic off the primary to improve scalability and availability under load, without running a second Gravitino stack or misconfiguring the server to point only at a replica (which would break writes).
This feature adds an opt-in read path to a replica while keeping writes and in-transaction reads on the primary, preserving correctness for transactional flows and remaining backward compatible when replica settings are not used.
### Describe the solution
#### Proposal
- New configuration (all optional; defaults preserve current behavior)
- gravitino.entity.store.relational.jdbcReadOnlyUrl — replica URL; if unset, single pool for reads and writes.
- gravitino.entity.store.relational.jdbcReadOnlyUser / jdbcReadOnlyPassword — optional; fall back to primary user/password.
- gravitino.entity.store.relational.readOnlyMaxConnections / readOnlyMaxWaitMillis — optional read pool sizing; -1 means inherit from primary pool settings.
- Separate read pool only when needed
- If no read-only-related config is set, keep a single datasource/factory.
- If any read-only config is set, create a second pooled datasource for reads.
- Session routing (convention-based)
- Write path: doWithCommit, doWithCommitAndFetchResult, doWithoutCommit, doMultipleWithCommit, beginTransaction → primary pool.
- Read path: getWithoutCommit → read pool when no write session is active on the thread; if a write transaction is already open (e.g. nested inside doMultipleWithCommit), use the primary session so DML/transactional steps stay correct and replicas are not used for in-txn work.
- Operational visibility
- Log at startup when a separate read replica pool is enabled (e.g. Read replica JDBC pool enabled for entity store).
- Metrics
- Register a second datasource metrics source for the read pool when it is separate (e.g. gravitino-relational-store-read).
- Docs / template
- Document keys in gravitino.conf.template and (recommended) in docs/gravitino-server-config.md / relational backend how-to.
##### Pros
1. Offloads read-heavy metadata traffic to a replica, reducing primary load.
2. Fully backward compatible; all new settings are optional.
3. Replica can use its own URL, credentials, and pool size.
4. Writes and in-transaction reads stay on the primary; no API changes for callers.
##### Cons
1. Standalone reads can see replica lag (eventual consistency).
2. Routing is by API convention, not SQL analysis. Read vs write is inferred from getWithoutCommit vs commit/transaction APIs, not from SQL. Misuse (e.g. DML via getWithoutCommit at top level) could in theory hit the read pool; in this codebase those calls are under an active write txn, so they stay on primary.
**Note:** If strong consistency is required (e.g. read-your-writes across requests), the read replica must use synchronous replication (e.g. MySQL semi-sync / group replication, or equivalent in other DBs); otherwise metadata reads may be briefly stale.
Contributor guide
Research direction
Start by tracing JDBCBackend and SessionUtils.getWithoutCommit, then review the existing relational datasource and transaction paths. Update gravitino.conf.template and, if applicable, docs/gravitino-server-config.md for the listed settings. Done means optional read-pool routing preserves primary use for writes and transactions, with startup logging and separate metrics when enabled.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, mysql
- Domain
- backend, databases
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100