Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b216661
feat: Phase 1 read/write splitting configuration infrastructure
Copilot Apr 25, 2026
b96a0ae
chore: remove accidentally committed .class files from readwrite package
Copilot Apr 25, 2026
2cd0d5d
refactor: rename rwRegistry to readWriteRegistry for clarity in Conne…
Copilot Apr 25, 2026
38b67ab
Merge remote-tracking branch 'origin/main' into copilot/implement-rea…
Copilot Apr 25, 2026
6862076
fix: update existing tests to pass ReadWriteDataSourceRegistry to Act…
Copilot Apr 25, 2026
4afb87e
feat: Phase 2 routing + Sonar fixes — SQL classifier, replica selecto…
Copilot Apr 25, 2026
31c924b
fix: address code review issues — word boundary for SQL keywords, int…
Copilot Apr 25, 2026
af4719b
fix: default sticky-session to 0 and prevent session UUID leak in Exe…
Copilot Apr 25, 2026
8800af1
refactor: condense ExecuteQueryAction startSessionIfNone comment
Copilot Apr 25, 2026
d40c55e
docs: document read/write splitting and stickySessionSeconds=0 default
Copilot Apr 25, 2026
bf42be6
Merge main into branch: apply checkstyle enforcement, fix violations …
Copilot Apr 25, 2026
39eef44
feat: implement dual-lazy connection design for read/write splitting
Copilot Apr 25, 2026
cb4cdbc
fix: address code review issues in dual-lazy connection design
Copilot Apr 25, 2026
e4ff631
fix: forward ojp.* info properties to server; add replica fallback fo…
Copilot Apr 25, 2026
4430b81
fix: improve logging for replica datasource source; clarify test comm…
Copilot Apr 25, 2026
31fb014
feat: forward setAutoCommit to server; restore testAfterTransactionCo…
Copilot Apr 25, 2026
21c7030
Revert "feat: forward setAutoCommit to server; restore testAfterTrans…
Copilot Apr 25, 2026
6a33bbe
test: disable ReadsGoToReplica test with limitation comment, add Read…
Copilot Apr 25, 2026
a31fb07
Add INFO investigation logs for read/write splitting routing decision…
Copilot Apr 26, 2026
ceba2f2
fix: use request connHash in createSession to avoid stale connectionH…
Copilot Apr 26, 2026
80e3730
refactor: convert RW-split investigation logs from INFO to DEBUG, rem…
Copilot Apr 26, 2026
6925229
fix: remove spurious throws InterruptedException from shouldExpireAft…
Copilot Apr 27, 2026
18d8313
docs: clarify LEAST_CONNECTIONS falls back to ROUND_ROBIN, RANDOM is …
Copilot Apr 27, 2026
a804375
test: add OjpSystemPropertiesBridgeTest coverage for read/write split…
Copilot Apr 27, 2026
1e6b0cc
refactor: clean up H2 test Javadoc and remove setAutoCommit(true) cal…
Copilot Apr 27, 2026
07c94c5
refactor: extract ParsedConfig holder + parseReadWriteConfig helper i…
Copilot Apr 28, 2026
0b1a88f
chore: fix spelling centralises -> centralizes in javadoc comment
Copilot Apr 28, 2026
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
92 changes: 92 additions & 0 deletions documents/configuration/ojp-jdbc-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,98 @@ The multi-datasource feature is fully backward compatible:
- Existing `ojp.properties` files without datasource prefixes continue to work as "default" datasource
- No changes required for existing deployments unless you want to use multi-datasource features

## Read/Write Splitting Configuration

OJP supports automatic read/write traffic splitting. Write operations (INSERT, UPDATE, DELETE, DDL) are always routed to the primary datasource. Stateless (auto-commit) read operations (SELECT, WITH, EXPLAIN, SHOW, DESCRIBE) are routed to a replica datasource when read/write splitting is enabled. All operations inside an explicit transaction are always routed to the primary.

Read/write splitting is configured entirely through `ojp.properties`. No server-side configuration changes are needed.

> **Note:** Sticky sessions are **opt-in** (default: `0` — disabled). Do not set a non-zero `stickySessionSeconds` unless your application requires read-your-writes guarantees after writes outside of a transaction.

### Primary Datasource Properties

Use the format `{primaryName}.ojp.readwrite.*` to configure the primary:

| Property | Default | Description |
|---|---|---|
| `{primary}.ojp.readwrite.role` | — | Must be `primary` to enable read/write splitting for this datasource |
| `{primary}.ojp.readwrite.enabled` | `false` | Enable (`true`) or disable read/write splitting for this primary |
| `{primary}.ojp.readwrite.replicaSelectionStrategy` | `ROUND_ROBIN` | Replica selection strategy: `ROUND_ROBIN` or `RANDOM`. `LEAST_CONNECTIONS` is accepted but currently falls back to `ROUND_ROBIN`; metrics-based selection is planned for a future phase. |
| `{primary}.ojp.readwrite.stickySessionSeconds` | `0` | Read-your-writes window in seconds. `0` = disabled (opt-in). After a write, reads continue going to the primary for this many seconds before reverting to replica routing |
| `{primary}.ojp.readwrite.replicaFailoverToPrimary` | `true` | Fall back to the primary when no healthy replica is available |

### Replica Datasource Properties

Use the format `{replicaName}.ojp.*` to configure each replica:

| Property | Default | Description |
|---|---|---|
| `{replica}.ojp.readwrite.role` | — | Must be `replica` |
| `{replica}.ojp.readwrite.primary` | — | Name of the primary datasource this replica belongs to |
| `{replica}.ojp.connection.url` | — | **Required.** JDBC URL of the replica database |
| `{replica}.ojp.connection.user` | `""` | Replica database user |
| `{replica}.ojp.connection.password` | `""` | Replica database password |
| `{replica}.ojp.pool.maxPoolSize` | `10` | Maximum connections in the replica pool |
| `{replica}.ojp.pool.minIdle` | `2` | Minimum idle connections |
| `{replica}.ojp.pool.connectionTimeout` | `30000` | Connection acquire timeout (ms) |
| `{replica}.ojp.pool.idleTimeout` | `600000` | Idle connection timeout (ms) |
| `{replica}.ojp.pool.maxLifetime` | `1800000` | Maximum connection lifetime (ms) |

### Example Configuration

```properties
# Primary datasource (read/write splitting enabled, no sticky session)
mydb.ojp.readwrite.role=primary
mydb.ojp.readwrite.enabled=true
mydb.ojp.readwrite.replicaSelectionStrategy=ROUND_ROBIN
# stickySessionSeconds defaults to 0 (disabled) — opt-in only when needed

# Replica 1
replica1.ojp.readwrite.role=replica
replica1.ojp.readwrite.primary=mydb
replica1.ojp.connection.url=jdbc:postgresql://replica1-host:5432/mydb
replica1.ojp.connection.user=app_ro
replica1.ojp.connection.password=secret
replica1.ojp.pool.maxPoolSize=15
replica1.ojp.pool.minIdle=3

# Replica 2
replica2.ojp.readwrite.role=replica
replica2.ojp.readwrite.primary=mydb
replica2.ojp.connection.url=jdbc:postgresql://replica2-host:5432/mydb
replica2.ojp.connection.user=app_ro
replica2.ojp.connection.password=secret
replica2.ojp.pool.maxPoolSize=15
replica2.ojp.pool.minIdle=3
```

### Sticky Sessions (Read-Your-Writes)

Sticky sessions guarantee that a client which just executed a write will continue reading from the primary for a configurable window, giving replicas time to catch up. This is an opt-in behaviour because it introduces latency on the read path and is only necessary when your application requires seeing its own writes immediately outside of a transaction.

```properties
# Enable 3-second sticky window (reads stay on primary for 3 s after every write)
mydb.ojp.readwrite.stickySessionSeconds=3
```

**When to use sticky sessions:**
- Application executes a write and then immediately queries without a transaction, and must see the write
- Replication lag is measurable and the application is not latency-sensitive

**When to leave sticky sessions disabled (`0`, the default):**
- All writes and their subsequent reads are wrapped in the same transaction (the transaction itself guarantees read-your-writes via the primary)
- The application tolerates eventual consistency for reads outside transactions

### Routing Rules Summary

| Operation | Inside Transaction | Sticky Window Active | Routes To |
|---|---|---|---|
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | — | — | Replica |
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | ✓ | — | Primary |
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | — | ✓ | Primary |
| INSERT / UPDATE / DELETE / DDL | — | — | Primary |
| INSERT / UPDATE / DELETE / DDL | ✓ | — | Primary |

## Related Documentation

- **[SSL/TLS Certificate Configuration Guide](ssl-tls-certificate-placeholders.md)** - Complete guide for configuring SSL/TLS certificates with property placeholders
Expand Down
15 changes: 14 additions & 1 deletion documents/configuration/ojp-server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,18 @@ INFO SchemaCache - Schema cache updated with 42 tables

For JDBC driver and client-side connection pool configuration, see:

- **[OJP JDBC Configuration](ojp-jdbc-configuration.md)** - JDBC driver setup and client connection pool settings
- **[OJP JDBC Configuration](ojp-jdbc-configuration.md)** — JDBC driver setup, client connection pool settings, and read/write splitting configuration

### Read/Write Splitting

OJP supports automatic read/write traffic splitting configured entirely through `ojp.properties` on the client side. No server-side settings are required. The server reads the `*.ojp.readwrite.*` properties forwarded by the driver and creates isolated replica connection pools automatically.

Key points:
- Stateless auto-commit SELECTs are routed to a replica; all other operations go to the primary
- Operations inside an explicit transaction always use the primary
- **Sticky sessions are opt-in** — `stickySessionSeconds` defaults to `0` (disabled). Enable only when the application must read its own writes outside a transaction. A non-zero value keeps reads on the primary for that many seconds after every write.

See **[OJP JDBC Configuration — Read/Write Splitting](ojp-jdbc-configuration.md#readwrite-splitting-configuration)** for the full property reference and examples.

## Configuration Methods

Expand Down Expand Up @@ -557,8 +568,10 @@ INFO org.openjproxy.grpc.server.ServerConfiguration - Slow Query Slot Percenta
- Increase timeouts in environments with occasional very slow queries
4. **Connection Pools**: Configure client-side pool sizes based on application requirements
5. **Request Size**: Increase for applications that handle large result sets
6. **Read/Write Splitting**: Size replica pools (`{replica}.ojp.pool.maxPoolSize`) to handle peak read traffic; leave `stickySessionSeconds` at `0` unless read-your-writes outside transactions is required

## Related Documentation

- **[Slow Query Segregation Documentation](../designs/SLOW_QUERY_SEGREGATION.md)** - Detailed guide to the slow query segregation feature
- **[OJP JDBC Configuration](ojp-jdbc-configuration.md)** - Client-side pool settings and read/write splitting configuration
- **[Example Configuration Properties](ojp-server-example.properties)** - Complete example configuration file with all settings
2 changes: 1 addition & 1 deletion documents/ebook/appendix-c-glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ The separation of slow queries into a dedicated connection pool to prevent them
The mechanism by which clients find available service instances. In OJP multinode deployments, discovery is configuration-based (JDBC URL lists all servers).

**Session Affinity**
Also called "sticky sessions," the routing of related requests to the same backend server. OJP implements session affinity for transactions and temporary tables.
Also called "sticky sessions," the routing of related requests to the same backend server. OJP implements session affinity for transactions and temporary tables. In the context of read/write splitting, sticky sessions ensure reads go to the primary for a configurable window after a write (see `stickySessionSeconds`). Sticky sessions are **opt-in** (default: disabled) — only enable when the application must see its own writes immediately outside of an explicit transaction.

**Slow Query**
A database query that takes significantly longer than average to execute. OJP automatically detects slow queries and can segregate them to prevent resource contention.
Expand Down
75 changes: 73 additions & 2 deletions documents/ebook/part2-chapter6-server-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,83 @@ export ojp.server.port=9059

The server logs its active configuration at INFO level during startup. Review this output to confirm your settings were applied correctly. If you see unexpected defaults, it means your configuration wasn't recognized—check for typos, case sensitivity, and format issues.

## 6.8 Read/Write Splitting

OJP can automatically route read and write traffic to separate database instances. Write operations (INSERT, UPDATE, DELETE, DDL) always go to the primary. Stateless auto-commit reads (SELECT, WITH, EXPLAIN, SHOW, DESCRIBE) are routed to a replica chosen according to a configurable selection strategy. All operations inside an explicit transaction stay on the primary.

Read/write splitting is configured entirely through the client's `ojp.properties` file — no server-side configuration changes are required. The OJP server reads the `*.ojp.readwrite.*` properties forwarded by the driver on first connection and creates isolated replica connection pools automatically.

### 6.8.1 Enabling Read/Write Splitting

Mark the primary datasource and each replica in `ojp.properties`:

```properties
# Primary datasource
mydb.ojp.readwrite.role=primary
mydb.ojp.readwrite.enabled=true
mydb.ojp.readwrite.replicaSelectionStrategy=ROUND_ROBIN

# Replica 1
replica1.ojp.readwrite.role=replica
replica1.ojp.readwrite.primary=mydb
replica1.ojp.connection.url=jdbc:postgresql://replica1:5432/mydb
replica1.ojp.connection.user=app_ro
replica1.ojp.connection.password=secret

# Replica 2
replica2.ojp.readwrite.role=replica
replica2.ojp.readwrite.primary=mydb
replica2.ojp.connection.url=jdbc:postgresql://replica2:5432/mydb
replica2.ojp.connection.user=app_ro
replica2.ojp.connection.password=secret
```

Three replica selection strategies are available:

- **`ROUND_ROBIN`** (default) — distributes reads evenly across all replicas in order
- **`RANDOM`** — picks a replica at random for each request
- **`LEAST_CONNECTIONS`** — selects the replica with fewest active connections (Phase 3)

### 6.8.2 Sticky Sessions (Read-Your-Writes)

Sticky sessions keep reads on the primary for a short window after every write, giving replicas time to catch up. This is **opt-in**: the default `stickySessionSeconds` is `0` (disabled).

```properties
# Keep reads on primary for 3 seconds after every write
mydb.ojp.readwrite.stickySessionSeconds=3
```

> **Important:** Only enable sticky sessions when the application must see its own writes immediately outside of a transaction. If all reads following a write are in the same transaction, the transaction itself already guarantees read-your-writes via the primary, and sticky sessions add unnecessary overhead.

### 6.8.3 Routing Rules

| Operation | Inside Transaction | Sticky Window Active | Routes To |
|---|---|---|---|
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | — | — | Replica |
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | ✓ | — | Primary |
| SELECT / WITH / EXPLAIN / SHOW / DESCRIBE | — | ✓ | Primary |
| INSERT / UPDATE / DELETE / DDL | any | any | Primary |

### 6.8.4 Replica Pool Configuration

Each replica has its own connection pool. Size it to handle peak read traffic independently of the primary pool.

| Property | Default | Description |
|---|---|---|
| `{replica}.ojp.pool.maxPoolSize` | `10` | Maximum replica pool size |
| `{replica}.ojp.pool.minIdle` | `2` | Minimum idle connections |
| `{replica}.ojp.pool.connectionTimeout` | `30000` | Acquire timeout (ms) |
| `{replica}.ojp.pool.idleTimeout` | `600000` | Idle connection timeout (ms) |
| `{replica}.ojp.pool.maxLifetime` | `1800000` | Maximum connection lifetime (ms) |

For the complete property reference see [OJP JDBC Configuration — Read/Write Splitting](../../documents/configuration/ojp-jdbc-configuration.md#readwrite-splitting-configuration).

## Summary

OJP server configuration gives you precise control over server behavior, security, performance, and observability. The hierarchical configuration system with JVM properties and environment variables provides flexibility for different deployment scenarios. Default settings work well for most use cases, but understanding the available options lets you optimize for your specific workload.

Key configuration areas include core server settings for network and threading, security controls through IP whitelisting, logging levels for operational visibility, OpenTelemetry integration for observability, circuit breakers for resilience, and slow query segregation for performance under mixed workloads. Each area offers sensible defaults that you can refine based on monitoring data.
Key configuration areas include core server settings for network and threading, security controls through IP whitelisting, logging levels for operational visibility, OpenTelemetry integration for observability, circuit breakers for resilience, slow query segregation for performance under mixed workloads, and read/write splitting for scaling read traffic across replicas. Each area offers sensible defaults that you can refine based on monitoring data.

Start simple, monitor closely, and adjust based on observed behavior. Good configuration emerges from understanding your workload and using OJP's flexibility to match it, not from cargo-culting settings from other environments.

**[IMAGE PROMPT: Create a summary mind map with "OJP Server Configuration" at the center. Six main branches radiating outward: "Core Settings" (server icon), "Security" (lock icon), "Logging" (document icon), "Telemetry" (graph icon), "Circuit Breaker" (shield icon), and "Slow Query Segregation" (speedometer icon). Each branch has 2-3 sub-branches with key points. Use colors to group related concepts and make it visually hierarchical. Style: Modern mind map with icons and color coding.]**
**[IMAGE PROMPT: Create a summary mind map with "OJP Server Configuration" at the center. Seven main branches radiating outward: "Core Settings" (server icon), "Security" (lock icon), "Logging" (document icon), "Telemetry" (graph icon), "Circuit Breaker" (shield icon), "Slow Query Segregation" (speedometer icon), and "Read/Write Splitting" (fork/branch icon). Each branch has 2-3 sub-branches with key points. Use colors to group related concepts and make it visually hierarchical. Style: Modern mind map with icons and color coding.]**
Loading
Loading