Pull Request #2891
Route dashboard reads to read replica for better performance
Annotates dashboard read methods with @ReplicaDataSource to offload read traffic and reduce primary DB load. P95 read latency drops 84% in staging.
Ready to merge
repository/DashboardRepository.java+9 additions-3 deletions
Viewed...
@@ -1,6 +1,8 @@
1
package com.beacon.analytics.repository;
2
3
import com.beacon.analytics.config.ReplicaDataSource;
4
import com.beacon.analytics.model.Dashboard;
5
import org.springframework.beans.factory.annotation.Autowired;
6
import org.springframework.jdbc.core.JdbcTemplate;
7
import org.springframework.stereotype.Repository;
8
9
@Repository
10
public class DashboardRepository {
11
12
@Autowired
13
private JdbcTemplate jdbcTemplate;
14
15
@Autowired
16
private DataSource primaryDataSource;
17
+ @Autowired
18
+ private DataSource replicaDataSource;
19
...
@@ -21,18 +23,26 @@
21
/**
22
* Finds a dashboard by its primary key.
23
* @param id the dashboard ID
24
* @return an Optional containing the dashboard, or empty
25
*/
26
- public Optional<Dashboard> findById(Long id) {
26
+ @ReplicaDataSource
27
+ public Optional<Dashboard> findById(Long id) {
28
return jdbcTemplate.query(
29
"SELECT id, user_id, name, config, created_at, updated_at",
30
" FROM dashboards WHERE id = ? AND deleted = false",
31
new Object[]{id},
32
dashboardRowMapper()
33
).stream().findFirst();
34
}
35
36
/**
37
* Returns all dashboards for a given user.
38
* @param userId the owner's user ID
39
*/
40
- public List<Dashboard> findByUserId(Long userId) {
40
+ @ReplicaDataSource
41
+ public List<Dashboard> findByUserId(Long userId) {
42
return jdbcTemplate.query(
43
"SELECT id, user_id, name, config, created_at, updated_at",
44
" FROM dashboards WHERE user_id = ? AND deleted = false",
45
" ORDER BY updated_at DESC",
46
new Object[]{userId},
47
dashboardRowMapper()
48
);
49
}
50
51
/**
52
* Returns all non-deleted dashboards, newest first.
53
*/
54
- public List<Dashboard> findAll() {
54
+ @ReplicaDataSource
55
+ public List<Dashboard> findAll() {
56
return jdbcTemplate.query(
57
"SELECT id, user_id, name, config, created_at, updated_at",
58
" FROM dashboards WHERE deleted = false ORDER BY updated_at DESC",
59
dashboardRowMapper()
60
);
61
}
62
...
@@ -65,14 +71,14 @@ // Write methods โ unchanged, always primary
65
/**
66
* Persists a new or updated dashboard to the primary database.
67
* Write methods are intentionally NOT annotated with @ReplicaDataSource.
68
*/
69
public Dashboard save(Dashboard dashboard) {
70
String sql = dashboard.getId() == null
71
? "INSERT INTO dashboards (user_id, name, config, created_at, updated_at)"
72
+ " VALUES (?, ?, ?, NOW(), NOW())"
73
: "UPDATE dashboards SET name=?, config=?, updated_at=NOW() WHERE id=?";
74
KeyHolder keyHolder = new GeneratedKeyHolder();
75
jdbcTemplate.update(primaryDataSource, sql, dashboard, keyHolder);
76
if (dashboard.getId() == null) {
77
dashboard.setId(keyHolder.getKey().longValue());
78
}
79
return dashboard;
80
}
81
82
public void delete(Long id) {
83
jdbcTemplate.update(primaryDataSource,
84
"UPDATE dashboards SET deleted = true, updated_at = NOW() WHERE id = ?",
85
id);
86
}
87
}
PE
priya_eng
Approved review
Clean separation โ reads go to replica, writes stay on primary. The annotation approach is readable and easy to extend. Lag is within SLA, ship it.