English | Türkçe
Why CacheDB | Quick start | Relations | Projections | Production | Documentation
CacheDB is a Redis-first Java data-layer framework that keeps the selected SQL database as the durable source of truth. PostgreSQL and SQL Server are explicit, first-class providers with separate starters and provider-specific evidence lanes. CacheDB is built for teams that want ORM-like developer ergonomics without hiding operational read, write, warm, or archive behavior behind runtime magic.
The core design rule is simple:
- do not move the whole database into Redis
- explicitly define what is hot
- keep the selected SQL provider responsible for durable history
- use projections/read models for relation-heavy and globally sorted screens
- generate metadata at compile time instead of discovering it with runtime reflection
Both providers cover the same CacheDB application model: generated repositories, bounded active routes, projections, warm/backfill, write-behind, outbox integration, and explicit source routes. Database-specific connection, locking, timeout, indexing, and HA behavior must still be proven in the application's own staging topology.
| Release information | Value |
|---|---|
| Latest published release | v0.10.1 |
| Repository version | 0.10.1 |
| Library bytecode | Java 17 |
| Runnable samples | Java 21 |
| Local evidence topology | Redis 8.2.1, PostgreSQL 16, SQL Server 2022 |
| Application API | Compile-time generated @CacheRepository interfaces |
0.10.1 keeps the 0.10.0 runtime feature set and closes the last
distribution inconsistency: embedded and standalone samples now build against
the same anonymous Maven contract, and their CI proves that application
certification fails closed when evidence is missing.
- Migration scaffold generation emits compile-ready projection records, entity registration hooks, and partitioned relation loaders from discovered SQL columns. UUID and common SQL date/time types are supported end to end.
- SQL Server write-behind batches same-shape upserts into update, locked version probe, and insert phases, with a live throughput regression gate.
cachedb:certifyfails a consumer build when route coverage, parity, memory, failover, canary, rollback, or commit-bound evidence is missing.- Stable artifacts are published to an anonymous Maven2 repository with POM, BOM, source, Javadoc, SHA-1, and SHA-256 files. GitHub Packages remains an optional authenticated mirror.
- Production maturity has one authoritative contract instead of separate, potentially stale readiness verdicts.
Read the complete v0.10.1 release notes before upgrading.
CacheDB is not a transparent read-through cache that sits between the application and SQL. A Redis miss does not mean CacheDB will automatically scan the database, fill Redis, and return the result for every query shape.
CacheDB is also not a drop-in Hibernate/JPA replacement for arbitrary dynamic queries. It is a Redis-first active-data persistence and read-model layer for bounded operational routes.
| Statement | Runtime meaning |
|---|---|
| Redis is the online read path | Entity and projection repositories read the active Redis data set. They do not automatically scan SQL on every miss. |
| SQL is the durable source of truth | PostgreSQL or MSSQL keeps the durable history through write-behind. Archive, export, audit, and full-history reads should use explicit SQL routes. |
| Hot policy is a contract | If a row is outside the active policy, an entity or projection read may return empty. That is expected behavior, not data loss. |
| Projection is part of the model | Relation-heavy lists, dashboards, timelines, top-N, and globally sorted screens should use compact read models. |
| Cold paths must be explicit | Use a bounded SQL endpoint, registered page loader, warm/backfill job, or migration route for data outside the active set. |
| Classification | Use CacheDB this way |
|---|---|
| BEST | Active-set ORM/read-model layer for high-throughput operational reads and controlled write-behind durability. |
| ACCEPTABLE | Redis-first persistence with explicit SQL cold paths and route-level guardrails. |
| ANTI-PATTERN | Put Redis in front of the database and expect every broad ORM query to miss Redis, scan SQL, refill Redis, and stay memory-safe. |
The design burden is intentional: before a route goes live, decide what belongs in Redis, what stays only in SQL, which projection serves the screen, and what happens when the requested data is outside the active set.
| Problem | CacheDB approach |
|---|---|
| Low-latency reads for hot entities | Redis-first entity repositories |
| Durable writes | SQL write-behind flush |
| Growing relation fan-out | Relation limits, projections, and summary-first reads |
| Global top-N dashboards | Ranked projections and route contracts |
| Migration from existing SQL database/ORM systems | Migration Planner, warm-up, dry-run, side-by-side comparison |
| Redis memory growth | Hot policies, tenant quotas, payload budgets, admission telemetry |
| Multi-pod Kubernetes operation | Pod-unique consumers, Redis leader leases, coordination evidence |
| Question | Read |
|---|---|
| "Where is the full documentation map?" | Documentation Map |
| "Is CacheDB the right fit?" | ORM Alternative Guide |
| "How do I start from zero?" | Getting Started |
| "How do I declare and operate repositories safely?" | Declarative Repositories |
| "What changed in the current release?" | v0.10.1 Release Notes |
| "Where is a runnable REST API sample?" | PostgreSQL Sample or MSSQL Sample |
| "Which Spring Boot dependency do I need?" | Spring Boot Starter |
| "How do multiple pods refresh and clean a hot set periodically?" | Scheduled Warm and Hot-Set Reconciliation |
| "What are entity, relation, projection, and route contract?" | Concepts and Assumptions |
| "How do I model real production cases?" | Use Case Examples |
| "How should I tune Redis memory and performance?" | Production Tuning Guide |
| "Where are all properties and defaults?" | Tuning Parameters |
| "How do I migrate an existing SQL database system?" | Migration Planner |
| "What must be proven before production?" | Production Recipes |
| "How does my application prove cutover readiness?" | Production Certification |
| "Which checks define a production release?" | Production Readiness Contract |
| "How do I decide whether a GA release can ship?" | Production GA Release Runbook |
| Situation | Recommended path | Why |
|---|---|---|
| I want to run a complete sample first | PostgreSQL Sample or MSSQL Sample | REST API, Docker Compose, schema, seed data, Postman collection |
| New Spring Boot service | cachedb-spring-boot-starter-postgres or cachedb-spring-boot-starter-mssql |
Explicit provider selection and Spring DataSource integration |
| Existing Spring Boot app with JPA | Starter plus existing DataSource |
JPA usually already creates the DataSource; do not duplicate JDBC setup |
| Plain Java service | cachedb-starter |
You own bootstrap, shutdown, and connection lifecycle |
| Existing SQL database + ORM system | Migration Planner | Discover schema, warm Redis, compare the source database vs CacheDB, generate a cutover report |
| Relation-heavy list screen | Projection/read model | Avoid loading the full object graph on first paint |
| Internal worker, replay, repair, or batch job | Direct repository | Lower abstraction and more predictable operational behavior |
BEST: choose one hot route, define the Redis hot-set decision, warm it in staging, compare it against the source database, and cut over only when parity and latency are proven.
ANTI-PATTERN: mark every table as an entity and expect Redis to automatically make every dynamic query fast.
- Run either the PostgreSQL sample
or the SQL Server sample with its
demoprofile. - Seed durable rows and wait for the distributed seed job to complete.
- Call an archive endpoint to prove the SQL source route.
- Run a projection-only warm job and wait for route coverage.
- Call the matching Redis active route and compare membership and ordering.
- Inspect
/api/tuning, readiness, and the admin UI before changing any limits.
That sequence teaches the product contract more accurately than beginning with unbounded CRUD methods.
Keep cachedb.version aligned with the release you use. Version 0.10.1 is an
immutable release available from the anonymous CacheDB Maven repository and
the GitHub Release bundle.
<properties>
<cachedb.version>0.10.1</cachedb.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.reactor.cachedb</groupId>
<artifactId>cachedb-bom</artifactId>
<version>${cachedb.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.reactor.cachedb</groupId>
<artifactId>cachedb-spring-boot-starter-postgres</artifactId>
</dependency>
<dependency>
<groupId>com.reactor.cachedb</groupId>
<artifactId>cachedb-annotations</artifactId>
</dependency>
<!-- Optional: operations UI and migration planner -->
<dependency>
<groupId>com.reactor.cachedb</groupId>
<artifactId>cachedb-spring-boot-starter-admin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.reactor.cachedb</groupId>
<artifactId>cachedb-processor</artifactId>
<version>${cachedb.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>Add the public repository to the consumer POM when it is not inherited from a
company parent. It does not require a GitHub account, token, or settings.xml:
<repositories>
<repository>
<id>cachedb-public</id>
<url>https://esasmer-dou.github.io/cache-database/maven2</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>cachedb-public</id>
<url>https://esasmer-dou.github.io/cache-database/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>repositories resolves CacheDB dependencies and pluginRepositories resolves
cachedb-maven-plugin. GitHub Packages may still be used as an authenticated
mirror, but it is not required for normal public consumption. A consumer build
does not need the CacheDB source checkout.
JDBC rule:
| SQL provider | Provider starter | JDBC driver | Runnable sample |
|---|---|---|---|
| PostgreSQL | cachedb-spring-boot-starter-postgres |
org.postgresql:postgresql |
PostgreSQL sample |
| SQL Server | cachedb-spring-boot-starter-mssql |
com.microsoft.sqlserver:mssql-jdbc |
SQL Server sample |
- Add
spring-boot-starter-jdbcif your application does not already create a SpringDataSource. - If your app already uses
spring-boot-starter-data-jpaor another starter that creates aDataSource, do not add JDBC again only for CacheDB. - CacheDB needs a working Spring
DataSourcebean. cachedb-annotationsand thecachedb-processorannotation processor are still required.- Choose exactly one provider starter. Use
cachedb-spring-boot-starter-postgresfor PostgreSQL orcachedb-spring-boot-starter-mssqlfor SQL Server. - With one provider on the classpath,
cachedb.sql.provider=AUTOselects it. Multiple providers fail startup instead of being resolved silently. - Add
cachedb-spring-boot-starter-adminonly when the operations console is required. It is not part of the core runtime starter. - See Declarative Repositories for the preferred application API and Database Provider SPI for provider-specific tuning.
Minimal application.yml:
spring:
datasource:
url: jdbc:postgresql://127.0.0.1:5432/app
username: app
password: app
cachedb:
enabled: true
profile: production
redis:
uri: redis://127.0.0.1:6379
registration:
source: jdbc
fail-on-unknown-entity: true
entities:
CustomerEntity:
hot-entity-limit: 50000
page-size: 100
hot-policy:
mode: STATE_WINDOW
state-column: status
state-values: [ACTIVE]
admin:
http-enabled: trueAdmin UI:
- dashboard:
/cachedb-admin - migration planner:
/cachedb-admin/migration-planner - health API:
/cachedb-admin/api/health
Production rule: do not expose /cachedb-admin/** directly to the public
internet. Put it behind a gateway or reverse proxy, and use gateway auth or
CacheDB token auth.
CacheDB entities use explicit field metadata. The important rule for new users:
persisted fields must not be private or final.
@CacheEntity(table = "customers", redisNamespace = "customers")
public class CustomerEntity {
@CacheId(column = "customer_id")
public Long customerId;
@CacheColumn("tax_number")
public String taxNumber;
@CacheColumn("customer_type")
public String customerType;
@CacheColumn("status")
public String status;
public CustomerEntity() {
}
}After compilation, the annotation processor generates binding classes. CacheDB does not rely on runtime reflection to discover persisted fields.
Declare the repository contract. The processor validates route fields, parameters, limits, and return types, then generates the Spring bean and reflection-free implementation:
@CacheRepository(entity = CustomerEntity.class)
public interface CustomerRepository extends CacheDbRepository<CustomerEntity, Long> {
@CacheLookup(idParameter = "customerId")
HotLookup<CustomerEntity> detail(Long customerId);
}Inject the generated repository into the application service:
CustomerEntity customer = new CustomerEntity();
customer.customerId = 42L;
customer.taxNumber = "1234567890";
customer.customerType = "RETAIL";
customer.status = "ACTIVE";
WriteReceipt<CustomerEntity, Long> receipt = customers.save(customer);
CustomerEntity loaded = customers.detail(42L).orElseThrow(status ->
new IllegalStateException("Customer is not available in Redis: " + status)
);Behavior:
savewrites the entity to Redis when its policy admits it.- Durable persistence is sent to the selected SQL write-behind path.
detailis Redis-only;NOT_CACHEDdoes not mean the SQL row is absent.- If the entity does not satisfy the hot policy, it may be rejected or evicted from Redis.
- Archive or out-of-window reads must use an explicit bounded
@SourceRoute. - Routes that require preloaded Redis coverage should declare a
@WarmRouteand useHotWindow.completeItems()at application endpoints after cutover.
GeneratedCacheModule remains supported for compatibility and low-level jobs.
New service code should normally use generated repositories. Continue with
Declarative Repositories.
CacheDB relations are not Hibernate-style transparent lazy loading. Relations are loaded only when requested explicitly.
Think about relation in three separate layers:
| Layer | What it does | Required for CacheDB preload? |
|---|---|---|
| Source database primary/foreign key | Protects durable data integrity and prevents orphan rows | Recommended, but not enough by itself |
@CacheRelation metadata |
Tells CacheDB that a parent field represents a relation and which target field joins it | Yes |
Generated/custom loader + @CacheLookup |
Executes the bounded batch load when the caller asks for the relation | Yes |
So the rule is precise:
- A database foreign key does not automatically create a CacheDB relation.
@CacheRelationdoes not create a database constraint.kind = ONE_TO_MANYis relation-shape metadata, not a DDL declaration.mappedBypoints to the target entity field that carries the parent id.- A typed target plus bounded ordering lets the processor generate the standard
partitioned loader. Use
@CacheEntity.relationLoaderonly for custom loading. - The repository contract must request the relation through a bounded
@CacheLookup.
@CacheEntity(table = "customers", redisNamespace = "customers")
public class CustomerEntity {
@CacheId(column = "customer_id")
public Long customerId;
@CacheRelation(
target = OrderEntity.class,
// OrderEntity.customerId, mapped to the order table's customer_id column.
mappedBy = "customerId",
kind = CacheRelation.RelationKind.ONE_TO_MANY,
batchLoadOnly = true,
maxRowsPerParent = 100,
parentBatchSize = 32,
orderBy = {"orderDate DESC", "orderId DESC"}
)
public List<OrderEntity> orders;
}Read with a bounded preview:
@CacheLookup(idParameter = "customerId", relation = "orders",
relationLimitParameter = "orderPreview", maxRelationRows = 25)
HotLookup<CustomerEntity> detail(Long customerId, int orderPreview);
CustomerEntity customer = customers.detail(customerId, 20)
.orElseThrow(status -> mapHotLookupFailure(customerId, status));What happens in common cases:
| Database FK | @CacheRelation |
Generated/custom loader | Result |
|---|---|---|---|
| Yes | No | No | The database is consistent, but CacheDB has no relation path to preload. |
| No | Yes | Yes | CacheDB can preload if mappedBy is queryable, but orphan or inconsistent rows are your risk. Use only for legacy or soft relations. |
| Yes | Yes | No | A batch-only relation without generated or custom loading information is rejected at compile time. |
| Yes | Yes | Yes | BEST: durable integrity, explicit metadata, and bounded batch preload. |
BEST: use a bounded @CacheLookup for a small detail-page preview.
ANTI-PATTERN: load a customer's full order history as a relation on a list screen.
A projection stores the small, stable read model required by a screen instead of hydrating the full entity payload.
Use projections for:
- customer-level lists such as latest 1,000 orders
- dashboard top-N cards
- global business-priority rankings
- first-paint summary screens
- flows where full entity details are fetched only after the user opens a row
Example decision:
| Screen | Model |
|---|---|
| Customer card | CustomerEntity |
| Latest customer orders | CustomerOrderSummaryProjection |
| Order detail | OrderEntity |
| Order-line preview | @CacheLookup with linePreview=8 |
| Global highest-risk orders | Ranked projection |
CacheDB should not be operated as "set a TTL and hope Redis stays small." Production memory control needs four layers:
- entity hot policy: which rows may enter Redis?
- route contract: how many rows may this endpoint read?
- tenant quota: can one tenant or customer consume the memory budget?
- Redis
maxmemoryand eviction policy: what is the infrastructure limit?
Hot policy examples:
| Need | Approach |
|---|---|
| Keep latest 100,000 rows hot | COUNT_WINDOW |
| Keep last 90 days of orders hot | TIME_WINDOW on order_date |
Keep only OPEN/PENDING work hot |
STATE_WINDOW |
| Last 90 days plus open state plus tenant quota | COMPOSITE plus tenant quota |
Read Production Tuning Guide together with Tuning Parameters for configuration details.
The Migration Planner is not a one-click production cutover tool. Its job is to prove the route shape before cutover:
- Should this route use entity, projection, or ranked projection?
- Which Redis hot window should be warmed?
- Which data stays in the durable SQL database as full history?
- How many rows does warm-up read?
- Does CacheDB return the same IDs and ordering as the source database?
- Is p95 latency acceptable?
- What is the rollback plan?
Recommended flow:
- Open
/cachedb-admin/migration-planner. - Discover the source database schema.
- Select a route candidate and apply it to the form.
- Generate the plan.
- Generate scaffold.
- Run dry-run warm; Redis must not change.
- Run staging warm; Redis hot set should be filled.
- Run side-by-side comparison.
- Download the report.
- Repeat for every production screen, API, batch, and report route.
Full conversion coverage comes from a route inventory, not from one selected table.
- Is Redis HA/failover planned and tested?
- Does the selected SQL provider remain the durable source of truth?
- If external systems mutate the source database, is outbox/CDC configured?
- Does every hot route have a route contract?
- Can projection-required routes fail fast instead of falling back to entity scans?
- Do hot policy and tenant quota protect memory?
- Can warm-up resume from checkpoints?
- Has side-by-side comparison proven data membership and ordering?
- Is the admin UI behind a trusted operations network or gateway?
- Do benchmark thresholds and public API compatibility checks run in CI?
| Topic | CacheDB | Traditional ORM |
|---|---|---|
| Primary active-data read path | Redis | Database |
| Durable source | SQL database | Database |
| Metadata | Compile-time generated | Usually runtime metadata/reflection |
| Relation behavior | Explicit FetchPlan, loaders, projections |
Often lazy/eager object graph behavior |
| Large list screens | Projection/read-model | Often entity graph or SQL join first |
| Best fit | Low-latency hot routes | SQL-centric relational workloads |
| Main risk | Poor hot-set or projection design | N+1, wide joins, runtime ORM cost |
Benchmark results should not be read as "CacheDB is always faster." The right reading is:
- generated bindings can stay in a low-overhead band
- direct repositories give more control on critical hot paths
- production cost usually comes from query shape, relation hydration, Redis contention, and write-behind pressure
- relation-heavy screens need projection design before measurement
Re-run the local recipe benchmark with:
mvn -q -f cachedb-production-tests/pom.xml exec:java `
"-Dexec.mainClass=com.reactor.cachedb.prodtest.scenario.RepositoryRecipeBenchmarkMain"