Skip to content

Commit 368f6d6

Browse files
authored
#245 Support Cache PostgreSQL (#453)
* #245 Support Cache PostgreSQL * #245 Support Cache PostgreSQL * #245 Support Cache PostgreSQL * #245 Support Cache PostgreSQL
1 parent 8518775 commit 368f6d6

26 files changed

Lines changed: 911 additions & 169 deletions

File tree

README.adoc

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
:url-repo: https://github.com/MarcGiffing/bucket4j-spring-boot-starter
22
:url: https://github.com/MarcGiffing/bucket4j-spring-boot-starter/tree/master
33
:url-examples: {url}/examples
4-
:url-config-cache: {url}/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache
4+
:url-config-cache: {url}/cache/
55

66
image:{url-repo}/actions/workflows/maven.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/maven.yml]
77
image:{url-repo}/actions/workflows/codeql.yml/badge.svg[Build Status,link={url-repo}/actions/workflows/codeql.yml]
@@ -684,35 +684,40 @@ The following list contains the Caching implementation which will be autoconfigu
684684
|*cache-to-use*
685685
686686
|N
687-
|{url-config-cache}/jcache/JCacheBucket4jConfiguration.java[JSR 107 -JCache]
687+
|{url-config-cache}/cache-jcache[JSR 107 -JCache]
688688
|jcache
689689
690690
|Yes
691-
|{url-config-cache}/ignite/IgniteBucket4jCacheConfiguration.java[Ignite]
691+
|{url-config-cache}/cache-ignite[Ignite]
692692
|jcache-ignite
693693
694+
|N
695+
|{url-config-cache}/cache-postgresql[PostgreSQL]
696+
|jcache
697+
698+
694699
|no
695-
|{url-config-cache}/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java[Hazelcast]
700+
|{url-config-cache}/cache-hazelcast[Hazelcast]
696701
|hazelcast-spring
697702
698703
|yes
699-
|{url-config-cache}/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java[Hazelcast]
704+
|{url-config-cache}/cache-hazelcast[Hazelcast]
700705
|hazelcast-reactive
701706
702707
|Yes
703-
|{url-config-cache}/infinispan/InfinispanBucket4jCacheConfiguration.java[Infinispan]
708+
|{url-config-cache}/cache-infinispan[Infinispan]
704709
|infinispan
705710
706711
|No
707-
|{url-config-cache}/redis/jedis/JedisBucket4jConfiguration.java[Redis-Jedis]
712+
|{url-config-cache}/cache-redis-jedis[Redis-Jedis]
708713
|redis-jedis
709714
710715
|Yes
711-
|{url-config-cache}/redis/lettuce/LettuceBucket4jConfiguration.java[Redis-Lettuce]
716+
|{url-config-cache}/cache-lettuce[Redis-Lettuce]
712717
|redis-lettuce
713718
714719
|Yes
715-
|{url-config-cache}/redis/redisson/RedissonBucket4jConfiguration.java[Redis-Redisson]
720+
|{url-config-cache}/cache-redis-resdisson[Redis-Redisson]
716721
|redis-redisson
717722
718723
|===

cache/cache-postgresql/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/target/
2+
/.settings/
3+
.classpath
4+
.project
5+
.idea/
6+
*.iml
7+
.factorypath
8+
.apt_generated
9+
.springBeans

cache/cache-postgresql/README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Bucket4j PostgreSQL Cache Module
2+
3+
This module provides PostgreSQL database support for Bucket4j rate limiting with Spring Boot.
4+
5+
## Overview
6+
7+
The `cache-postgresql` module integrates Bucket4j with PostgreSQL, allowing you to store rate limit data in a PostgreSQL relational database. This is useful for distributed systems that already use PostgreSQL and want to leverage it for centralized rate limiting.
8+
9+
## Features
10+
11+
- **Synchronous Cache Access**: Provides synchronous rate limit token bucket operations
12+
- **JDBC-based**: Uses Bucket4j's JDBC proxy manager for PostgreSQL connectivity
13+
- **Spring Boot Auto-Configuration**: Automatic configuration when dependencies are present
14+
- **Event Publishing**: Publishes cache update events to Spring's ApplicationEventPublisher
15+
- **Configuration Caching**: Optional caching of Bucket4j configuration
16+
17+
## Dependencies
18+
19+
The module requires the following:
20+
21+
- `spring-boot-starter-data-jpa`: For JPA support (optional)
22+
- `postgresql`: PostgreSQL JDBC driver
23+
- `bucket4j_jdk17-jdbc`: Bucket4j JDBC support
24+
25+
## Configuration
26+
27+
To use the PostgreSQL cache module, add the following to your `application.properties` or `application.yml`:
28+
29+
```properties
30+
bucket4j.enabled=true
31+
bucket4j.cache-type=postgresql
32+
```
33+
34+
### Database Setup
35+
36+
Before using the PostgreSQL cache, you need to create the necessary table for storing bucket tokens. Bucket4j uses a standard schema for JDBC storage.
37+
38+
Create the table with the following SQL:
39+
40+
```sql
41+
CREATE TABLE IF NOT EXISTS bucket (
42+
id VARCHAR(20) PRIMARY KEY,
43+
state BYTEA,
44+
expires_at BIGINT,
45+
explicit_lock BIGINT);
46+
47+
CREATE INDEX IF NOT EXISTS idx_bucket4j_id ON bucket(id);
48+
```
49+
50+
## Components
51+
52+
### PostgreSQLCacheResolver
53+
54+
Implements `SyncCacheResolver` and uses Bucket4j's `JdbcProxyManager` to manage rate limit buckets through JDBC connections.
55+
56+
**Key Features:**
57+
- Synchronous access to rate limit tokens
58+
- Automatic connection pooling through DataSource
59+
- Direct integration with PostgreSQL via JDBC
60+
61+
### PostgreSQLCacheManager
62+
63+
Implements `CacheManager` for managing cache entries in the PostgreSQL database.
64+
65+
**Key Methods:**
66+
- `getValue(K key)`: Retrieves cached values from the database
67+
- `setValue(K key, V value)`: Stores or updates values in the database using PostgreSQL's `ON CONFLICT` clause
68+
69+
### PostgreSQLCacheListener
70+
71+
Listens to cache updates and publishes `CacheUpdateEvent` to the Spring ApplicationEventPublisher.
72+
73+
### PostgreSQLBucket4jConfiguration
74+
75+
Spring Boot auto-configuration class that:
76+
- Checks if Bucket4j is enabled
77+
- Validates DataSource availability
78+
- Registers the `PostgreSQLCacheResolver` bean
79+
- Optionally registers configuration cache manager
80+
- Registers cache listener for event publishing
81+
82+
## Usage Example
83+
84+
```java
85+
@RestController
86+
@RequestMapping("/api")
87+
public class MyController {
88+
89+
@GetMapping("/data")
90+
@Bucket4j(bucketName = "main", capacityDescription = "10 requests per minute")
91+
public ResponseEntity<String> getData() {
92+
return ResponseEntity.ok("Hello World");
93+
}
94+
}
95+
```
96+
97+
## Configuration Properties
98+
99+
The following properties can be configured in `application.properties`:
100+
101+
```properties
102+
# Enable Bucket4j
103+
bucket4j.enabled=true
104+
105+
# Set cache type to PostgreSQL
106+
bucket4j.cache-type=postgresql
107+
108+
# Optional: Cache configuration in PostgreSQL
109+
bucket4j.filter-config-cache-enabled=true
110+
111+
# DataSource configuration (Spring Boot standard)
112+
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
113+
spring.datasource.username=postgres
114+
spring.datasource.password=password
115+
spring.datasource.driver-class-name=org.postgresql.Driver
116+
```
117+
118+
## Performance Considerations
119+
120+
1. **Connection Pooling**: Ensure proper HikariCP (or other connection pool) configuration for optimal performance
121+
2. **Table Indexing**: The `idx_bucket4j_tokens_id` index improves lookup performance
122+
3. **Network Latency**: PostgreSQL-based rate limiting incurs network round-trip time, making it slower than in-memory solutions
123+
4. **Distributed Systems**: Best suited for distributed systems where a centralized database is already in use
124+
125+
## Advantages
126+
127+
- **Centralized Rate Limiting**: All instances share the same rate limit state
128+
- **Data Persistence**: Rate limit data survives application restarts
129+
- **Simplicity**: Leverages existing PostgreSQL infrastructure
130+
- **Scalability**: Works well in containerized and cloud environments
131+
132+
## Limitations
133+
134+
- **Performance**: Slower than in-memory cache solutions due to database I/O
135+
- **Synchronous Only**: This module only provides synchronous cache access
136+
- **Database Dependency**: Requires PostgreSQL to be available and operational
137+
138+
## Related Modules
139+
140+
- `cache-jcache`: JCache (JSR-107) implementation
141+
- `cache-redis-lettuce`: Redis Lettuce driver support
142+
- `cache-redis-jedis`: Redis Jedis driver support
143+
- `cache-redis-redisson`: Redis Redisson driver support
144+
- `cache-hazelcast`: Hazelcast distributed cache support
145+
- `cache-infinispan`: Infinispan cache support
146+
147+
## License
148+
149+
Apache License 2.0
150+

cache/cache-postgresql/pom.xml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
8+
<artifactId>parent</artifactId>
9+
<version>${revision}</version>
10+
<relativePath>../../pom.xml</relativePath>
11+
</parent>
12+
13+
<artifactId>cache-postgresql</artifactId>
14+
15+
<properties>
16+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
17+
</properties>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
22+
<artifactId>starter-autoconfigure</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>com.bucket4j</groupId>
26+
<artifactId>bucket4j_jdk17-postgresql</artifactId>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-data-jpa</artifactId>
31+
<scope>test</scope>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.postgresql</groupId>
35+
<artifactId>postgresql</artifactId>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.projectlombok</groupId>
39+
<artifactId>lombok</artifactId>
40+
<scope>provided</scope>
41+
</dependency>
42+
</dependencies>
43+
44+
</project>
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.giffing.bucket4j.spring.boot.starter.cache.postgresql;
2+
3+
import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnBucket4jEnabled;
4+
import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnCache;
5+
import com.giffing.bucket4j.spring.boot.starter.autoconfigure.conditional.ConditionalOnSynchronousPropertyCondition;
6+
import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties;
7+
import com.giffing.bucket4j.spring.boot.starter.core.cache.SyncCacheResolver;
8+
import io.github.bucket4j.postgresql.Bucket4jPostgreSQL;
9+
import org.springframework.boot.autoconfigure.AutoConfiguration;
10+
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
11+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
12+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
13+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
14+
import org.springframework.context.annotation.Bean;
15+
import org.springframework.core.Ordered;
16+
17+
import javax.sql.DataSource;
18+
19+
@AutoConfiguration
20+
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
21+
@ConditionalOnBucket4jEnabled
22+
@ConditionalOnSynchronousPropertyCondition
23+
@ConditionalOnClass(Bucket4jPostgreSQL.class)
24+
@ConditionalOnCache("postgresql")
25+
@EnableConfigurationProperties({Bucket4JBootProperties.class})
26+
public class PostgreSQLBucket4jConfiguration {
27+
28+
private final DataSource dataSource;
29+
30+
public PostgreSQLBucket4jConfiguration(DataSource dataSource) {
31+
this.dataSource = dataSource;
32+
}
33+
34+
@Bean
35+
@ConditionalOnMissingBean(SyncCacheResolver.class)
36+
public SyncCacheResolver bucket4jCacheResolver() {
37+
return new PostgreSQLCacheResolver(dataSource);
38+
}
39+
40+
}
41+
42+
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.giffing.bucket4j.spring.boot.starter.cache.postgresql;
2+
3+
import com.giffing.bucket4j.spring.boot.starter.core.cache.AbstractCacheResolverTemplate;
4+
import com.giffing.bucket4j.spring.boot.starter.core.cache.CacheResolver;
5+
import com.giffing.bucket4j.spring.boot.starter.core.cache.SyncCacheResolver;
6+
import io.github.bucket4j.distributed.jdbc.PrimaryKeyMapper;
7+
import io.github.bucket4j.distributed.proxy.AbstractProxyManager;
8+
import io.github.bucket4j.distributed.proxy.ProxyManager;
9+
import io.github.bucket4j.postgresql.Bucket4jPostgreSQL;
10+
11+
import javax.sql.DataSource;
12+
13+
/**
14+
* This class is the PostgreSQL (JDBC) implementation of the {@link CacheResolver}.
15+
* It uses Bucket4Js {@link io.github.bucket4j.postgresql.PostgreSQLadvisoryLockBasedProxyManager} to implement the {@link ProxyManager}.
16+
*/
17+
public class PostgreSQLCacheResolver extends AbstractCacheResolverTemplate<String> implements SyncCacheResolver {
18+
19+
private final DataSource dataSource;
20+
21+
public PostgreSQLCacheResolver(DataSource dataSource) {
22+
this.dataSource = dataSource;
23+
}
24+
25+
@Override
26+
public String castStringToCacheKey(String key) {
27+
return key;
28+
}
29+
30+
@Override
31+
public boolean isAsync() {
32+
return false;
33+
}
34+
35+
@Override
36+
public AbstractProxyManager<String> getProxyManager(String cacheName) {
37+
return Bucket4jPostgreSQL.selectForUpdateBasedBuilder(dataSource)
38+
.primaryKeyMapper(PrimaryKeyMapper.STRING)
39+
.build();
40+
}
41+
}
42+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.giffing.bucket4j.spring.boot.starter.cache.postgresql.PostgreSQLBucket4jConfiguration

examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java renamed to examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuite.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.giffing.bucket4j.spring.boot.starter.examples.caffeine;
22

3+
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletUpdateFilterTestSuite;
34
import com.giffing.bucket4j.spring.boot.starter.general.tests.method.method.MethodTestSuite;
45
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite;
56
import org.junit.platform.suite.api.SelectClasses;
@@ -8,7 +9,8 @@
89
@Suite
910
@SelectClasses({
1011
ServletTestSuite.class,
12+
ServletUpdateFilterTestSuite.class,
1113
MethodTestSuite.class
1214
})
13-
public class CaffeineGeneralSuiteTest {
15+
public class CaffeineGeneralSuite {
1416
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.giffing.bucket4j.spring.boot.starter.examples.ehcache;
22

33
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite;
4+
import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletUpdateFilterTestSuite;
45
import org.junit.platform.suite.api.SelectClasses;
56
import org.junit.platform.suite.api.Suite;
67

78
@Suite
89
@SelectClasses({
910
ServletTestSuite.class,
11+
ServletUpdateFilterTestSuite.class,
1012
})
1113
public class EhcacheGeneralSuite {
1214
}

examples/general-tests/pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@
3434
<artifactId>starter-autoconfigure</artifactId>
3535
<scope>provided</scope>
3636
</dependency>
37-
<dependency>
38-
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
39-
<artifactId>cache-jcache</artifactId>
40-
<scope>provided</scope>
41-
</dependency>
4237
<dependency>
4338
<groupId>org.springframework.boot</groupId>
4439
<artifactId>spring-boot-starter-webmvc</artifactId>

0 commit comments

Comments
 (0)