Skip to main content

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

DependencyMinimum VersionPurpose
PostgreSQL14+Durable storage for all repository interfaces (tenants, routes, API keys, policies, audit events, cost records, etc.)
Redis6+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:

MigrationTablesKey Design Notes
V1__core_config.sqltenants, api_keys, routes, route_versions, usersmetadata/scopes/providers as JSONB
V2__policies.sqlpolicies, policy_versions, shadow_policy_eventsdsl as TEXT, indexed on (policy_id, timestamp)
V3__audit.sqlaudit_events, signed_audit_envelopessequence_number BIGSERIAL for HMAC chain integrity
V4__metering_cost.sqltoken_usage_records, model_pricing, cost_records, budget_capsNUMERIC(20,10) for monetary precision
V5__webhooks.sqlwebhooks, webhook_delivery_logsevent_types JSONB array
V6__mcp.sqlmcp_servers, mcp_tool_callstool_catalog JSONB, indexed (tenant_id, session_id)
V7__compliance_chargeback.sqlcompliance_reports, chargeback_reportssections JSONB, pdf_content BYTEA
V8__cache_quality.sqlsemantic_cache_configs, golden_prompts, model_fingerprints, drift_reports, eval_prompts, eval_results, eval_reports, latency_records, canary_metrics, shadow_routing_events, output_schema_configsembedding 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:

  1. PostgreSQL repositories replace the in-memory defaults
  2. 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 integrityPostgresAuditEventRepository uses @Transactional with SELECT ... FOR UPDATE to serialize HMAC chain appends
  • JSONB round-trippingJsonbHelper utility handles Map/List/Set ↔ JSONB conversion via Jackson
  • Embedding storageFloatArrayCodec handles float[] ↔ BYTEA conversion for vector data

Redis Distributed Caching

The enterprise-caching module wraps 9 read-heavy repositories with cache-aside decorators:

RepositoryTTLRationale
TenantRepository60sEvery request: auth, PII config, guardrail config
ApiKeyRepository30sEvery request: API key authentication
RouteRepository60sEvery request: route resolution
PolicyRepository30sEvery request: policy evaluation
ModelPricingRepository60sEvery request: cost estimation
BudgetCapRepository30sEvery request: budget enforcement
McpServerRepository60sMCP requests: server lookup
WebhookRepository60sAudit events: webhook dispatch
OutputSchemaRepository60sSchema 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

SymptomCauseFix
Failed to obtain JDBC connectionPostgreSQL not reachableVerify spring.datasource.url, check network/firewall
Flyway migration failedSchema conflict or corruptionCheck flyway_schema_history table, consider flyway repair
RedisConnectionFailureExceptionRedis not reachableVerify spring.data.redis.host/port, check connectivity
In-memory repos used despite configMissing enterprise licenseSet GATEWAY_ENTERPRISE_LICENSE_KEY with a valid JWT
In-memory repos used despite licensegateway.persistence.enabled not setSet GATEWAY_PERSISTENCE_ENABLED=true
Cache not workinggateway.caching.distributed.enabled not setSet GATEWAY_CACHING_DISTRIBUTED_ENABLED=true
Stale cached dataTTL too long for your use caseReduce gateway.caching.distributed.ttl-seconds