Integrating ElastiCache with a Java+Oracle Stack
This is a technical example of the savings we discussed yesterday, in this blog post
1. Why Caching Reduces Licensing Costs
Oracle’s licensing model often depends on the number of CPU cores and instances running the database engine. When your Java application (especially a web app or microservice) scales up to handle heavy read traffic, you end up paying more in licensing fees for Oracle as it handles every query directly.
By introducing a high-performance in-memory caching layer, you can:
- Offload read traffic from Oracle.
- Reduce the required number of CPU cores allocated to Oracle DB.
- Consolidate instances of Oracle DB or decrease its compute footprint.
This results in quantifiable cost savings because you can potentially renegotiate or restructure your Oracle licensing based on the smaller CPU footprint needed.
2. Architecture Overview
Below is a simplified architecture showing how a Java microservice might connect to both Oracle and ElastiCache for Redis (and I love me some ASCII art!):
+--------------------------+
| Java Service |
| (Spring Boot, Quarkus, |
| or other framework) |
+-----------+-------------+
|
(1) Check Cache
(3) Store/Refresh Cache
|
+-------v-------+ (2) Cache MISS
| ElastiCache |---+
| Redis | |
+---------------+ |
|
| (4) Oracle used only for
+-------------------v----------+ non-cached queries
| Oracle DB |
| (Possible RDS or on-prem) |
+----------------------------+
- Java application checks Redis for cached data.
- If the data is missing or stale, the application queries Oracle.
- The application stores or refreshes the result in Redis.
- Oracle usage is greatly reduced, lowering license costs.
3. Provisioning ElastiCache for Redis
You can provision an AWS ElastiCache for Redis cluster through:
- The AWS Management Console
- The AWS CLI
- AWS CloudFormation or Terraform for Infrastructure as Code
Example using AWS CLI
aws elasticache create-replication-group \
--replication-group-id my-redis-rg \
--replication-group-description "Redis cluster for caching Oracle data" \
--engine redis \
--cache-node-type cache.r6g.large \
--replicas-per-shard 1 \
--num-node-groups 1 \
--automatic-failover-enabled
--cache-node-type
selects an instance class (e.g.,cache.r6g.large
) that fits your throughput needs.- You can fine-tune the replication group, failover, and shard configurations depending on your environment.
4. Java Application Integration (Code Example)
Below is a simplified example of using Jedis (a popular Java Redis client) within a Spring Boot application to cache Oracle query results.
Note: You can also use Lettuce, Redisson, or Spring’s built-in Redis integrations. The concepts remain the same.
4.1 Maven/Gradle Dependencies
Use these dependencies in your Maven pom.xml
or Gradle build.gradle
:
Maven (pom.xml):
<dependencies>
<!-- Spring Boot dependencies omitted for brevity -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.5</version>
</dependency>
<!-- Oracle JDBC driver -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>23.6.0.24.10</version>
</dependency>
</dependencies>
Gradle (build.gradle):
dependencies {
implementation 'redis.clients:jedis:4.3.1'
implementation 'com.oracle.database.jdbc:ojdbc8:19.9.0.0'
// Other Spring Boot dependencies
}
4.2 Configuration for Redis
In application.properties (or application.yml
), store your ElastiCache endpoint (e.g., my-redis-rg.xxxxxx.0001.use1.cache.amazonaws.com
) and port.
spring.redis.host=my-redis-rg.xxxxxx.0001.use1.cache.amazonaws.com
spring.redis.port=6379
spring.redis.password= # if using auth token
4.3 Creating a Redis Client Bean (Spring Boot)
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.password:}")
private String redisPassword;
@Bean
public JedisPool jedisPool() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// Tweak pool settings based on concurrency
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(30);
poolConfig.setMinIdle(10);
if (redisPassword != null && !redisPassword.isEmpty()) {
return new JedisPool(poolConfig, redisHost, redisPort, 2000, redisPassword);
} else {
return new JedisPool(poolConfig, redisHost, redisPort, 2000);
}
}
}
4.4 Sample DAO Layer with Cache Logic
Below is a simplified DAO that first checks Redis for a user record before querying Oracle. Then, after retrieving the record from Oracle, it caches the result in Redis.
@Repository
public class UserDao {
@Autowired
private DataSource dataSource; // Oracle DataSource
@Autowired
private JedisPool jedisPool;
private static final String USER_CACHE_PREFIX = "user:";
public Optional<User> getUserById(String userId) {
try (Jedis jedis = jedisPool.getResource()) {
String cacheKey = USER_CACHE_PREFIX + userId;
// 1. Check cache
String cachedJson = jedis.get(cacheKey);
if (cachedJson != null) {
// If found in cache, deserialize and return
return Optional.ofNullable(JsonUtil.fromJson(cachedJson, User.class));
}
// 2. Cache miss => Query Oracle
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(
"SELECT ID, NAME, EMAIL FROM USERS WHERE ID = ?")) {
ps.setString(1, userId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
User user = new User();
user.setId(rs.getString("ID"));
user.setName(rs.getString("NAME"));
user.setEmail(rs.getString("EMAIL"));
// 3. Store to cache
String json = JsonUtil.toJson(user);
jedis.set(cacheKey, json);
// Set TTL to 24 hours or some app-specific value
jedis.expire(cacheKey, 86400);
return Optional.of(user);
}
}
}
} catch (SQLException ex) {
throw new RuntimeException("Error querying Oracle", ex);
}
return Optional.empty();
}
}
In the snippet above:
- We attempt to read from Redis first.
- On a cache miss, we query Oracle only once.
- We populate the cache with the user record, setting a TTL (time to live).
- Subsequent requests for the same user in the next 24 hours do not hit Oracle again.
5. Caching Policy and Expiration
Whether you cache all query results for 24 hours (like the snippet above) or use a write-through or read-through policy depends on your data-consistency requirements. For mission-critical or frequently updated data, a shorter TTL or a cache invalidation approach (on updates) may be necessary.
Best practices:
- Shorter TTL for highly dynamic data.
- Longer TTL or refresh-ahead strategy for read-heavy, relatively static data.
- Consider eviction policies: e.g.,
allkeys-lru
orvolatile-ttl
for Redis.
6. Analyzing the Cost Savings
Suppose your Oracle database tier currently requires a 16-core license to handle peak read throughput. By introducing ElastiCache for Redis, you reduce the average load on Oracle by ~50–70%. This can mean:
- Fewer CPU cores needed to maintain SLA. You might scale down to an 8-core license.
- Fewer Oracle instances if you were previously using Oracle-based read replicas.
- Reduced overall resource usage: memory, storage IOPS, and support costs.
For example, if your Oracle license is $47,500 per processor (a ballpark figure for certain editions), cutting your CPU cores by half could yield tens (or hundreds) of thousands of dollars in yearly savings. This is particularly impactful in large enterprise environments.
7. Monitoring and Tuning
To ensure your caching strategy translates into real-world licensing savings, you’ll need robust monitoring and optimization:
- CloudWatch Metrics: Monitor Redis CPU/memory usage, evictions, and average latency.
- Oracle AWR (Automatic Workload Repository) Reports: Track how the new caching tier affects read IOPS, concurrency, or CPU usage.
- Application Metrics: Monitor response times, cache hit ratio, and DB call counts.
A consistently high cache-hit ratio signals that you can safely downsize Oracle or reduce CPU allocations, thereby cutting licensing costs.
8. Putting It All Together
By introducing AWS ElastiCache for Redis into your Java+Oracle stack, you:
- Reduce Oracle licensing costs by lowering CPU and instance usage.
- Improve application response times due to in-memory read speeds.
- Simplify the system by removing or reducing the load on existing Oracle read replicas.
Crucially, these performance gains and cost reductions can happen with minimal changes to your application code. As shown in the sample DAO, you just wrap your Oracle queries with caching logic. For organizations paying hefty Oracle licensing fees, the ROI is often compelling.
Conclusion
Moving to an in-memory caching architecture isn’t just about performance—it can have a direct impact on your monthly or annual database licensing bills. By diligently monitoring metrics and caching your most read-intensive queries, you can downsize Oracle’s CPU footprint, consolidate instances, and cut expensive licensing outlays.
For a comprehensive assessment of which services and queries are best suited for caching (and how to optimize Oracle licensing), Tidal’s Optimization and Modernization Assessment (OMA) can help you uncover these opportunities quickly. With minimal code changes and the right TTL strategy, your Java application will be faster and more cost-efficient—an all-around win for enterprise IT.
Additional Resources
- AWS ElastiCache for Redis Documentation
- Oracle JDBC Documentation
- Jedis GitHub Repo
- Tidal OMA Overview (No-cost assessment that identifies your caching opportunities)
Ready to reduce your Oracle licensing bill? Start exploring caching strategies with AWS ElastiCache today—or check out Tidal’s OMA to see if you qualify for a fully funded assessment.