Skip to content

Commit 92e3cf1

Browse files
committed
Docs: Add docs / guides for connection validation best practices
1 parent 354df80 commit 92e3cf1

3 files changed

Lines changed: 352 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This project includes step-by-step guides for common datasource configuration sc
2323

2424
**See [docs/guides/README.md](docs/guides/README.md)** for:
2525
- [Creating DataSource pools](docs/guides/create-datasource-pool.md) — basic pools, read-only pools, Kubernetes deployment, AWS Lambda, and configuration reference
26+
- [Connection Validation Best Practices](docs/guides/connection-validation-best-practices.md) — how to configure heartbeat validation and why explicit heartbeatSql is rarely needed
2627
- [AWS Aurora with dual DataSources](docs/guides/aws-aurora-read-write-split.md) — read-write endpoint separation with Ebean ORM integration
2728
- Instructions for AI agents to discover and follow these guides
2829

docs/guides/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Step-by-step guides covering common scenarios for creating and configuring conne
99
| Guide | Description |
1010
|-------|-------------|
1111
| [Create a DataSource Pool](create-datasource-pool.md) | Basic pool creation, read-only pools, Kubernetes configuration, AWS Lambda setup, and configuration reference |
12+
| [Connection Validation Best Practices](connection-validation-best-practices.md) | How connection validation works; when and how to configure heartbeat validation; why explicit heartbeatSql is rarely needed |
1213

1314
## AWS Aurora
1415

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# Guide: Connection Validation Best Practices
2+
3+
## Purpose
4+
5+
This guide explains how connection pool validation works in ebean-datasource, the recommended approach for validating connections, and when (if ever) you need explicit configuration. Understanding this helps you optimize connection pool performance without unnecessary overhead.
6+
7+
---
8+
9+
## Overview: Connection Validation
10+
11+
Connection validation ensures that connections in the pool are healthy and can communicate with the database. Without validation, your application might try to use a "dead" connection (network timeout, database restart, etc.) and get errors.
12+
13+
The ebean-datasource connection pool has a background heartbeat thread that periodically validates connections are still alive. This guide explains how to configure it correctly.
14+
15+
---
16+
17+
## Best Practice: Use Connection.isValid() (Default)
18+
19+
The **best practice is to NOT set an explicit `heartbeatSql`**. Instead, let the JDBC driver handle connection validation using the standard `Connection.isValid()` method.
20+
21+
### Why Connection.isValid() is Best Practice
22+
23+
```java
24+
DataSourcePool pool = DataSourcePool.builder()
25+
.name("mypool")
26+
.url("jdbc:postgresql://localhost:5432/myapp")
27+
.username("user")
28+
.password("pass")
29+
.minConnections(5)
30+
.maxConnections(50)
31+
// No heartbeatSql() - use default Connection.isValid()
32+
.build();
33+
```
34+
35+
**Advantages:**
36+
37+
-**JDBC driver optimization:** The driver picks the most efficient validation method for your specific database
38+
-**No query overhead:** Modern JDBC drivers use lightweight protocol-level pings instead of SQL queries
39+
-**Database-specific:** PostgreSQL, MySQL, Oracle, etc. each have optimal validation mechanisms
40+
-**Less resource usage:** Network ping is cheaper than executing a query
41+
-**Automatically configured:** No manual setup needed per database platform
42+
43+
### How Connection.isValid() Works
44+
45+
When `Connection.isValid()` is called:
46+
47+
1. **PostgreSQL:** Uses lightweight protocol ping
48+
2. **MySQL:** Uses lightweight protocol ping
49+
3. **Oracle:** Uses lightweight connection check
50+
4. **SQL Server:** Uses lightweight connection check
51+
5. **H2/Other DBs:** Falls back to simple query or protocol check
52+
53+
Your JDBC driver handles all of this automatically.
54+
55+
---
56+
57+
## Configuration: Heartbeat Settings
58+
59+
These are the only heartbeat settings you typically need:
60+
61+
### Disable Heartbeat (if needed)
62+
63+
```java
64+
DataSourcePool pool = DataSourcePool.builder()
65+
.name("mypool")
66+
.url("jdbc:postgresql://localhost:5432/myapp")
67+
.username("user")
68+
.password("pass")
69+
.validateOnHeartbeat(false) // Disable heartbeat validation
70+
.build();
71+
```
72+
73+
**When to disable:**
74+
- AWS Lambda (automatically disabled)
75+
- Very short-lived applications
76+
- Environments where background threads should be avoided
77+
78+
### Adjust Heartbeat Frequency
79+
80+
```java
81+
DataSourcePool pool = DataSourcePool.builder()
82+
.name("mypool")
83+
.url("jdbc:postgresql://localhost:5432/myapp")
84+
.username("user")
85+
.password("pass")
86+
.validateOnHeartbeat(true) // Enable (default in non-Lambda)
87+
.heartbeatFreqSecs(30) // Check every 30 seconds (default)
88+
.build();
89+
```
90+
91+
**Typical values:**
92+
- `30` seconds - Default, suitable for most applications
93+
- `60` seconds - Longer interval, fewer validations (less resource usage)
94+
- `15` seconds - Shorter interval, more validations (detects stale connections faster)
95+
96+
**Adjust based on:**
97+
- Network reliability (unstable network → shorter interval)
98+
- Database availability (frequently restarted → shorter interval)
99+
- Resource constraints (minimal resources → longer interval)
100+
101+
### Heartbeat Timeout
102+
103+
```java
104+
DataSourcePool pool = DataSourcePool.builder()
105+
.name("mypool")
106+
.url("jdbc:postgresql://localhost:5432/myapp")
107+
.username("user")
108+
.password("pass")
109+
.heartbeatTimeoutSeconds(30) // Validation must complete in 30 seconds
110+
.build();
111+
```
112+
113+
If a heartbeat validation takes longer than this timeout, the connection is marked as dead and will be removed from the pool.
114+
115+
---
116+
117+
## When (Rarely) You Need Explicit heartbeatSql()
118+
119+
In almost all modern scenarios, you should NOT set explicit `heartbeatSql()`. Only in these edge cases:
120+
121+
### Case 1: Database requires specific validation query
122+
123+
Some databases or custom setups might require a specific query:
124+
125+
```java
126+
DataSourcePool pool = DataSourcePool.builder()
127+
.name("mypool")
128+
.url("jdbc:postgresql://localhost:5432/myapp")
129+
.username("user")
130+
.password("pass")
131+
.heartbeatSql("SELECT 1") // Only if JDBC driver doesn't support isValid()
132+
.build();
133+
```
134+
135+
**When:**
136+
- Using a very old database driver (pre-2010s)
137+
- Custom database with unusual requirements
138+
- Explicit requirement from database administrator
139+
140+
### Case 2: Custom validation logic needed
141+
142+
If you need to validate application state, not just connection state:
143+
144+
```java
145+
DataSourcePool pool = DataSourcePool.builder()
146+
.name("mypool")
147+
.url("jdbc:postgresql://localhost:5432/myapp")
148+
.username("user")
149+
.password("pass")
150+
.heartbeatSql("SELECT COUNT(*) FROM health_check_table") // App-specific check
151+
.build();
152+
```
153+
154+
This is rare and should be considered carefully - it adds query overhead to every heartbeat check.
155+
156+
---
157+
158+
## Connection Validation in Different Scenarios
159+
160+
### Standard Server Application
161+
162+
```java
163+
// Recommended: Use default Connection.isValid()
164+
DataSourcePool pool = DataSourcePool.builder()
165+
.name("mypool")
166+
.url("jdbc:postgresql://localhost:5432/myapp")
167+
.username("user")
168+
.password("pass")
169+
.minConnections(5)
170+
.maxConnections(50)
171+
// No heartbeatSql() - Connection.isValid() used by default
172+
.build();
173+
```
174+
175+
### Kubernetes Deployment
176+
177+
```java
178+
// Same as standard - Connection.isValid() is ideal for Kubernetes
179+
DataSourcePool pool = DataSourcePool.builder()
180+
.name("mypool")
181+
.url(System.getenv("DATABASE_URL"))
182+
.username(System.getenv("DB_USER"))
183+
.password(System.getenv("DB_PASSWORD"))
184+
.minConnections(5)
185+
.initialConnections(20)
186+
.maxConnections(50)
187+
// No heartbeatSql() - Connection.isValid() detects pod failures
188+
.build();
189+
```
190+
191+
### AWS Lambda
192+
193+
```java
194+
// Heartbeat automatically disabled in Lambda
195+
DataSourcePool pool = DataSourcePool.builder()
196+
.name("mypool")
197+
.url("jdbc:postgresql://mydb.region.rds.amazonaws.com:5432/myapp")
198+
.username(System.getenv("DB_USER"))
199+
.password(System.getenv("DB_PASSWORD"))
200+
.minConnections(1)
201+
.initialConnections(2)
202+
.maxConnections(10)
203+
// validateOnHeartbeat = false (auto-detected in Lambda)
204+
// No heartbeatSql() needed
205+
.build();
206+
```
207+
208+
### Read-Only Pool (Aurora / Replicas)
209+
210+
```java
211+
// Read-only pools also use Connection.isValid() by default
212+
DataSourcePool readPool = DataSourcePool.builder()
213+
.name("datasource-read")
214+
.url("jdbc:postgresql://read-replica.example.com:5432/myapp")
215+
.username("user")
216+
.password("pass")
217+
.readOnly(true)
218+
.autoCommit(true)
219+
.minConnections(5)
220+
.maxConnections(50)
221+
// No heartbeatSql() - Connection.isValid() works for read replicas too
222+
.build();
223+
```
224+
225+
---
226+
227+
## Connection Validation Timeline
228+
229+
Here's what happens with the default heartbeat configuration:
230+
231+
```
232+
Application startup
233+
├─ Create connection pool
234+
├─ Create minConnections
235+
└─ Start background heartbeat thread
236+
237+
Every 30 seconds (heartbeatFreqSecs default)
238+
├─ Heartbeat thread wakes up
239+
├─ For each connection: Call Connection.isValid()
240+
│ ├─ JDBC driver sends lightweight protocol ping
241+
│ ├─ Database responds
242+
│ └─ Connection marked as alive
243+
└─ Back to sleep for 30 seconds
244+
245+
Connection returned to pool after use
246+
├─ If an SQLException was thrown
247+
│ ├─ Connection tested eagerly
248+
│ └─ Dead connection removed from pool
249+
└─ If no error, connection stays in pool
250+
251+
Application shutdown
252+
├─ Stop heartbeat thread
253+
└─ Close all connections
254+
```
255+
256+
---
257+
258+
## Monitoring: Checking Heartbeat Health
259+
260+
If you want to understand what your heartbeat is doing:
261+
262+
```java
263+
DataSourcePool pool = DataSourcePool.builder()
264+
.name("mypool")
265+
.url("jdbc:postgresql://localhost:5432/myapp")
266+
.username("user")
267+
.password("pass")
268+
.build();
269+
270+
// Later, check pool status
271+
ConnectionPoolStatistics stats = pool.getStatus();
272+
System.out.println("Pool size: " + stats.size());
273+
System.out.println("Available: " + stats.free());
274+
System.out.println("Busy: " + stats.busy());
275+
```
276+
277+
This tells you the current state of connections validated by heartbeat.
278+
279+
---
280+
281+
## Performance Impact
282+
283+
### With Default Connection.isValid()
284+
285+
- **Per heartbeat cycle:** Lightweight protocol ping (~1-5ms per connection)
286+
- **Total overhead:** Minimal - only a few milliseconds every 30 seconds
287+
- **Network traffic:** Low - simple protocol handshake
288+
289+
### With Explicit heartbeatSql("SELECT 1")
290+
291+
- **Per heartbeat cycle:** Full SQL query parsing and execution (~10-50ms per connection)
292+
- **Total overhead:** Higher - noticeable CPU and database load
293+
- **Network traffic:** Higher - full query round-trip
294+
295+
**Recommendation:** Stick with default `Connection.isValid()` for best performance.
296+
297+
---
298+
299+
## Troubleshooting
300+
301+
### Problem: "Connection reset by peer" errors
302+
303+
**Cause:** Network connection died, heartbeat didn't catch it in time
304+
305+
**Solutions:**
306+
1. Reduce `heartbeatFreqSecs` (e.g., from 30 to 15 seconds)
307+
2. Use Kubernetes liveness probes to restart unhealthy pods
308+
3. Configure database network security to detect failures faster
309+
310+
### Problem: Heartbeat taking too long
311+
312+
**Cause:** Database under heavy load, `Connection.isValid()` slow to respond
313+
314+
**Solutions:**
315+
1. Increase `heartbeatTimeoutSeconds` if connections are legitimately slow
316+
2. Reduce `heartbeatFreqSecs` to spread load (check less frequently)
317+
3. Optimize database performance
318+
319+
### Problem: Too many validation queries in database logs
320+
321+
**Cause:** Explicit `heartbeatSql()` set, logging all queries
322+
323+
**Solutions:**
324+
1. Remove explicit `heartbeatSql()` - use default `Connection.isValid()`
325+
2. If you must use heartbeatSql, adjust database query logging to exclude it
326+
327+
---
328+
329+
## Summary: Best Practices
330+
331+
**DO:**
332+
- Let the JDBC driver handle validation with default `Connection.isValid()`
333+
- Use `validateOnHeartbeat(true)` for all applications except Lambda
334+
- Use default `heartbeatFreqSecs(30)` unless you have specific reasons otherwise
335+
- Let ebean-datasource auto-disable heartbeat in Lambda
336+
337+
**DON'T:**
338+
- Set explicit `heartbeatSql("SELECT 1")` unless required for your database driver
339+
- Disable heartbeat validation in standard applications
340+
- Use expensive queries for heartbeat validation
341+
- Assume connections are valid without validation
342+
343+
---
344+
345+
## Next Steps
346+
347+
- Read [Creating a DataSource Pool](create-datasource-pool.md) for basic setup
348+
- See [AWS Aurora with Dual DataSources](aws-aurora-read-write-split.md) for Aurora-specific configuration
349+
- Check [JDBC Driver Documentation](https://jdbc.postgresql.org/) for your database's Connection.isValid() implementation
350+
- Review the [ebean-datasource API](https://javadoc.io/doc/io.ebean/ebean-datasource) for all available configuration options

0 commit comments

Comments
 (0)