Database & Distributed Cache Setup
Enterprise deployments can replace the default in-memory repositories with PostgreSQL-backed persistence and Redis-backed distributed caching. Both modules are gated by @ConditionalOnEnterpriseLicense and disabled by default — standalone and OSS-only deployments need no external dependencies.
Prerequisites
| Dependency | Minimum Version | Purpose |
|---|---|---|
| PostgreSQL | 14+ | Durable storage for all repository interfaces (tenants, routes, API keys, policies, audit events, cost records, etc.) |
| Redis | 6+ | Distributed cache-aside decorators, distributed rate limiting, semantic cache |
Both are optional. You can enable one without the other.
Local Development Setup
PostgreSQL via Docker
docker run -d \
--name dvara-postgres \
-e POSTGRES_DB=dvara \
-e POSTGRES_USER=dvara \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
postgres:16-alpine
Redis via Docker
docker run -d \
--name dvara-redis \
-p 6379:6379 \
redis:7-alpine
Docker Compose (both)
Create a docker-compose.dev.yml:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: dvara
POSTGRES_USER: dvara
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
pgdata:
docker compose -f docker-compose.dev.yml up -d
Configuration
For the full property reference, see Configuration Reference sections gateway.persistence.* and gateway.caching.distributed.*.
PostgreSQL Persistence (enterprise-persistence)
gateway:
persistence:
enabled: true
batch-size: 100
spring:
datasource:
url: jdbc:postgresql://localhost:5432/dvara
username: dvara
password: ${DB_PASSWORD}
flyway:
enabled: true
Environment variables:
export DB_PASSWORD=secret
export GATEWAY_PERSISTENCE_ENABLED=true
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/dvara
export SPRING_DATASOURCE_USERNAME=dvara
export SPRING_DATASOURCE_PASSWORD=secret
Redis Distributed Caching (enterprise-caching)
gateway:
caching:
distributed:
enabled: true
ttl-seconds: 300
key-prefix: dvara
ratelimit:
distributed:
enabled: true # Redis sliding-window rate limiter
spring:
data:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
Schema Management (Flyway)
Flyway migrations are bundled in the enterprise-persistence module and run automatically on startup when a datasource is configured. No manual schema setup is required.
Migration Files
8 versioned migration files create 31 tables:
| Migration | Tables | Key Design Notes |
|---|---|---|
V1__core_config.sql | tenants, api_keys, routes, route_versions, users | metadata/scopes/providers as JSONB |
V2__policies.sql | policies, policy_versions, shadow_policy_events | dsl as TEXT, indexed on (policy_id, timestamp) |
V3__audit.sql | audit_events, signed_audit_envelopes | sequence_number BIGSERIAL for HMAC chain integrity |
V4__metering_cost.sql | token_usage_records, model_pricing, cost_records, budget_caps | NUMERIC(20,10) for monetary precision |
V5__webhooks.sql | webhooks, webhook_delivery_logs | event_types JSONB array |
V6__mcp.sql | mcp_servers, mcp_tool_calls | tool_catalog JSONB, indexed (tenant_id, session_id) |
V7__compliance_chargeback.sql | compliance_reports, chargeback_reports | sections JSONB, pdf_content BYTEA |
V8__cache_quality.sql | semantic_cache_configs, golden_prompts, model_fingerprints, drift_reports, eval_prompts, eval_results, eval_reports, latency_records, canary_metrics, shadow_routing_events, output_schema_configs | embedding BYTEA for vector data |
Schema Conventions
- Primary keys:
id VARCHAR(36)(UUID strings) - Timestamps:
TIMESTAMPTZ(timezone-aware) - Flexible data: JSONB for metadata, providers, tool catalogs, scopes, tags
- Monetary values:
NUMERIC(20,10)for precise cost tracking - Binary data: BYTEA for embeddings and PDF content
- Large text: TEXT for policy DSL YAML
Upgrading
Subsequent application upgrades apply incremental Flyway migrations without data loss. Flyway tracks applied migrations in the flyway_schema_history table.
Running with Both Modules
For a full production-like local deployment with PostgreSQL persistence and Redis caching:
# Start infrastructure
docker compose -f docker-compose.dev.yml up -d
# Run enterprise server
export DB_PASSWORD=secret
export GATEWAY_ENTERPRISE_LICENSE_KEY=<jwt-token>
export GATEWAY_PERSISTENCE_ENABLED=true
export GATEWAY_CACHING_DISTRIBUTED_ENABLED=true
./mvnw -pl enterprise-server spring-boot:run
Both modules follow the standard override pattern:
- PostgreSQL repositories replace the in-memory defaults
- Redis cache-aside decorators wrap whichever repository implementation is active
When disabled (default), all repositories use in-memory implementations with no external dependencies.
How It Works
PostgreSQL Persistence
The enterprise-persistence module provides 28 JdbcClient-based repository implementations that replace the default in-memory repositories. All use ON CONFLICT ... DO UPDATE upserts for idempotent writes.
Key implementation details:
- Audit chain integrity —
PostgresAuditEventRepositoryuses@TransactionalwithSELECT ... FOR UPDATEto serialize HMAC chain appends - JSONB round-tripping —
JsonbHelperutility handlesMap/List/Set↔ JSONB conversion via Jackson - Embedding storage —
FloatArrayCodechandlesfloat[]↔ BYTEA conversion for vector data
Redis Distributed Caching
The enterprise-caching module wraps 9 read-heavy repositories with cache-aside decorators:
| Repository | TTL | Rationale |
|---|---|---|
TenantRepository | 60s | Every request: auth, PII config, guardrail config |
ApiKeyRepository | 30s | Every request: API key authentication |
RouteRepository | 60s | Every request: route resolution |
PolicyRepository | 30s | Every request: policy evaluation |
ModelPricingRepository | 60s | Every request: cost estimation |
BudgetCapRepository | 30s | Every request: budget enforcement |
McpServerRepository | 60s | MCP requests: server lookup |
WebhookRepository | 60s | Audit events: webhook dispatch |
OutputSchemaRepository | 60s | Schema validation lookups |
Cache-aside pattern: findById() reads cache first, populates on miss; save()/deleteById() delegate then evict; list queries always hit the database.
Redis key format: \{keyPrefix\}:\{cacheName\}:\{key\} (e.g. dvara:tenants:abc-123)
Fail-safe: Redis errors are logged as warnings and the request falls through to the database — Redis unavailability never blocks requests.
Redis Distributed Rate Limiting
When gateway.ratelimit.distributed.enabled=true, the RedisRateLimiter replaces the in-memory rate limiter with a Redis-backed sliding window using an atomic Lua script. This ensures consistent rate limiting across multiple gateway instances.
Key format: \{keyPrefix\}:ratelimit:\{key\}
Fail-open: If Redis is unavailable, requests are allowed through.
Cloud Deployments
For managed PostgreSQL and Redis on cloud providers (AWS RDS + ElastiCache, GCP Cloud SQL + Memorystore, Azure Database for PostgreSQL + Azure Cache for Redis), see Cloud Provider Deployment.
Connection Tuning
PostgreSQL
HikariCP defaults with maximumPoolSize=10. For high-throughput deployments:
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
Redis
Spring Data Redis defaults are generally sufficient. For custom tuning:
spring:
data:
redis:
timeout: 2000
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Failed to obtain JDBC connection | PostgreSQL not reachable | Verify spring.datasource.url, check network/firewall |
Flyway migration failed | Schema conflict or corruption | Check flyway_schema_history table, consider flyway repair |
RedisConnectionFailureException | Redis not reachable | Verify spring.data.redis.host/port, check connectivity |
| In-memory repos used despite config | Missing enterprise license | Set GATEWAY_ENTERPRISE_LICENSE_KEY with a valid JWT |
| In-memory repos used despite license | gateway.persistence.enabled not set | Set GATEWAY_PERSISTENCE_ENABLED=true |
| Cache not working | gateway.caching.distributed.enabled not set | Set GATEWAY_CACHING_DISTRIBUTED_ENABLED=true |
| Stale cached data | TTL too long for your use case | Reduce gateway.caching.distributed.ttl-seconds |