-
Notifications
You must be signed in to change notification settings - Fork 0
Roadmap
This document outlines the planned features and improvements for the axum-oidc-client project.
Transform the current single-tier caching architecture into a two-tier caching system to improve performance and reduce latency for frequently accessed authentication data.
Goals:
- Reduce latency for authentication session lookups
- Decrease load on remote cache systems (Redis/SQL databases)
- Provide flexible cache backend options with multiple database support
- Maintain backward compatibility with existing
AuthCachetrait
Priority: High
Status: Not Started
Implement a first-level (L1) in-memory cache using Moka, a high-performance concurrent caching library for Rust. This will serve as a fast, local cache layer that reduces the need to query the second-level cache for frequently accessed sessions.
-
Add Moka Dependency
- ✅ Already added in
Cargo.tomlas optional feature - Ensure proper feature flag configuration
- ✅ Already added in
-
Create L1 Cache Wrapper
- Create a new module:
src/cache/l1_moka.rs - Implement a wrapper around Moka's
Cache<String, T>type - Support for both code verifiers and auth sessions
- Configure cache policies:
- Max Capacity: Configurable (default: 10,000 entries)
- TTL: Match or slightly exceed L2 cache TTL
- Time-to-Idle: Optional eviction for inactive entries
- Eviction Policy: LRU (Least Recently Used)
- Create a new module:
-
Implement Cache Operations
-
get_code_verifier()- Check L1 before L2 -
set_code_verifier()- Write to both L1 and L2 -
invalidate_code_verifier()- Remove from both L1 and L2 -
get_auth_session()- Check L1 before L2, populate L1 on L2 hit -
set_auth_session()- Write to both L1 and L2 -
invalidate_auth_session()- Remove from both L1 and L2 -
extend_auth_session()- Update TTL in both L1 and L2
-
-
Create Two-Tier Cache Implementation
- New struct:
TwoTierAuthCache<L2: AuthCache> - Combine Moka (L1) with any existing
AuthCacheimplementation (L2) - Implement cache-aside pattern:
- Read: Check L1 → if miss, check L2 → if hit, populate L1
- Write: Write to both L1 and L2
- Invalidate: Remove from both L1 and L2
- New struct:
-
Configuration Options
pub struct TwoTierCacheConfig { pub l1_max_capacity: u64, pub l1_ttl_sec: u64, pub l1_time_to_idle_sec: Option<u64>, pub enable_l1: bool, // Allow disabling L1 for testing }
- Performance: Sub-microsecond cache hits for frequently accessed sessions
- Reduced Network Latency: Fewer round-trips to Redis/SQL databases
- Scalability: Reduced load on L2 cache backends
- Thread-Safe: Moka provides excellent concurrent access performance
File Structure:
src/
├── cache/
│ ├── mod.rs # Cache module entry point
│ ├── two_tier.rs # TwoTierAuthCache implementation
│ └── config.rs # Cache configuration structs
├── moka/
│ └── mod.rs # Moka L1 cache wrapper (if needed)
Key Considerations:
- Ensure serialization/deserialization overhead is minimized
- Consider using Moka's async API for better integration
- Implement metrics/logging for cache hit rates
- Handle cache stampede scenarios (multiple concurrent requests for same key)
- Ensure proper cleanup on session invalidation
- Unit tests for L1 cache operations
- Integration tests with existing Redis cache
- Performance benchmarks comparing single-tier vs two-tier
- Concurrent access tests
- Cache eviction behavior tests
- TTL expiration tests
None - This will be an additive feature with backward compatibility.
Priority: High
Status: Not Started
Implement a second-level (L2) cache backend using SQL databases as an alternative to Redis. This implementation will use sqlx with ANSI SQL for maximum portability and support multiple database backends through cargo features. The cache table schema is intentionally simple with just a few columns.
-
Add sqlx Dependencies
- Add
sqlxwith async runtime support - Use cargo features to select database type:
-
sql-cache-postgres- PostgreSQL support -
sql-cache-mysql- MySQL/MariaDB support -
sql-cache-sqlite- SQLite support
-
- Each feature will enable the corresponding sqlx database feature
- Use connection pooling provided by sqlx
- Add
-
Create Simple Database Schema
-
Single unified table for all cache entries (code verifiers and sessions)
-
Keep schema simple with minimal columns:
CREATE TABLE IF NOT EXISTS oidc_cache ( cache_key VARCHAR(255) PRIMARY KEY, cache_value TEXT NOT NULL, expires_at BIGINT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_oidc_cache_expires ON oidc_cache(expires_at);
-
Design Notes:
-
cache_key: Unique identifier (prefixed:cv:for code verifiers,session:for sessions) -
cache_value: Serialized data (JSON or other format) -
expires_at: Unix timestamp for expiration - Use ANSI SQL compatible types for maximum portability
- Single table simplifies management and cleanup
-
-
-
Implement SQL AuthCache
- Create new module:
src/sql_cache/mod.rs - Implement
AuthCachetrait for SQL backend - Use sqlx's compile-time checked queries where possible
- Use ANSI SQL for all queries to ensure cross-database compatibility
- Proper error handling and mapping to
Errortypes
- Create new module:
-
Implement Cache Operations
impl AuthCache for SqlAuthCache { async fn get_code_verifier(&self, challenge_state: &str) -> Result<Option<String>, Error> { // Query: SELECT cache_value FROM oidc_cache // WHERE cache_key = ? AND expires_at > ? // Key format: "cv:{challenge_state}" } async fn set_code_verifier(&self, challenge_state: &str, code_verifier: &str) -> Result<(), Error> { // UPSERT/INSERT OR REPLACE with key "cv:{challenge_state}" } async fn invalidate_code_verifier(&self, challenge_state: &str) -> Result<(), Error> { // DELETE FROM oidc_cache WHERE cache_key = ? } async fn get_auth_session(&self, session_id: &str) -> Result<Option<AuthSession>, Error> { // Query with key "session:{session_id}" } async fn set_auth_session(&self, session_id: &str, session: AuthSession) -> Result<(), Error> { // UPSERT/INSERT OR REPLACE with key "session:{session_id}" } async fn invalidate_auth_session(&self, session_id: &str) -> Result<(), Error> { // DELETE with key "session:{session_id}" } async fn extend_auth_session(&self, session_id: &str, ttl: i64) -> Result<(), Error> { // UPDATE expires_at for existing session } }
-
TTL Management
- Use
expires_atUnix timestamp (seconds since epoch) - Implement background cleanup task to purge expired entries
- Strategy:
- Lazy deletion: Check expiration on read, delete if expired
-
Periodic cleanup: Background task runs every N minutes:
DELETE FROM oidc_cache WHERE expires_at < ?
- Both: Combine for optimal memory management
- Use
-
Cargo Features Configuration
[features] # SQL cache database backends (mutually exclusive in practice) sql-cache-postgres = ["sqlx", "sqlx/postgres", "sqlx/runtime-tokio"] sql-cache-mysql = ["sqlx", "sqlx/mysql", "sqlx/runtime-tokio"] sql-cache-sqlite = ["sqlx", "sqlx/sqlite", "sqlx/runtime-tokio"] # Helper feature to enable all SQL cache backends (for testing) sql-cache-all = ["sql-cache-postgres", "sql-cache-mysql", "sql-cache-sqlite"]
-
Configuration
pub struct SqlCacheConfig { pub connection_string: String, pub max_connections: u32, pub min_connections: u32, pub cleanup_interval_sec: u64, pub table_name: String, // Configurable table name (default: "oidc_cache") pub acquire_timeout_sec: u64, } impl Default for SqlCacheConfig { fn default() -> Self { Self { connection_string: String::new(), // Must be provided max_connections: 20, min_connections: 5, cleanup_interval_sec: 300, // 5 minutes table_name: "oidc_cache".to_string(), acquire_timeout_sec: 30, } } }
- No Redis Dependency: Reduces infrastructure complexity
- Unified Database: Use same database instance as application database
- Multi-Database Support: Choose between PostgreSQL, MySQL, or SQLite
- Cost Effective: Eliminates need for separate Redis infrastructure
- Portable: ANSI SQL ensures code works across all supported databases
- Simple Schema: Easy to understand, maintain, and debug
- Query Flexibility: Can query cache entries with SQL when debugging
- Better Observability: Standard database monitoring tools apply
File Structure:
src/
├── sql_cache/
│ ├── mod.rs # SQL cache implementation
│ ├── schema.rs # Database schema definitions
│ ├── queries.rs # ANSI SQL query strings
│ └── cleanup.rs # Background cleanup task
Key Considerations:
-
ANSI SQL Only: Stick to standard SQL to ensure portability
-
UPSERT Handling: Handle INSERT OR REPLACE / ON CONFLICT differently per database:
#[cfg(feature = "sql-cache-postgres")] const UPSERT_QUERY: &str = "INSERT INTO oidc_cache ... ON CONFLICT (cache_key) DO UPDATE ..."; #[cfg(feature = "sql-cache-mysql")] const UPSERT_QUERY: &str = "INSERT INTO oidc_cache ... ON DUPLICATE KEY UPDATE ..."; #[cfg(feature = "sql-cache-sqlite")] const UPSERT_QUERY: &str = "INSERT OR REPLACE INTO oidc_cache ...";
-
Connection Pooling: Use sqlx's built-in pooling
-
Prepared Statements: sqlx handles this automatically
-
Indexing: Ensure index on
expires_atfor efficient cleanup -
Serialization: Use JSON or similar for
cache_valuefield -
Key Prefixing: Use prefixes to distinguish entry types (
cv:,session:) -
Concurrency: Handle concurrent updates properly
-
Cleanup Strategy: Balance between cleanup frequency and overhead
-
PostgreSQL Optimization: Consider UNLOGGED tables for better performance (optional)
-
Connection Pool Tuning
- Configure pool size based on expected concurrency
- Use connection pool metrics for monitoring
-
Batch Cleanup
- Delete expired entries in batches
- Use LIMIT in cleanup queries to avoid long-running transactions:
DELETE FROM oidc_cache WHERE cache_key IN ( SELECT cache_key FROM oidc_cache WHERE expires_at < ? LIMIT 1000 );
-
Optional PostgreSQL-Specific Optimizations
- Allow UNLOGGED tables via configuration (faster writes, no crash durability)
- Document trade-offs
-
Monitoring
- Track cache hit/miss rates
- Monitor table size and cleanup effectiveness
- Alert on connection pool exhaustion
- Log slow queries
PostgreSQL:
- Can optionally use UNLOGGED tables for performance
- Native JSONB support for cache_value (optional optimization)
- Best performance for high-concurrency scenarios
MySQL/MariaDB:
- Use InnoDB engine
- Consider utf8mb4 charset
- Good general-purpose option
SQLite:
- Perfect for development/testing
- Can be used in production for single-instance deployments
- Consider WAL mode for better concurrency
- File-based, no separate database server needed
- Unit tests for SQL operations
- Integration tests with real database instances (all supported databases)
- TTL and expiration tests
- Cleanup task tests
- Connection pool exhaustion handling
- Performance benchmarks vs Redis
- Cross-database compatibility tests
- Concurrent access tests
- Migration script validation for all databases
None - This will be an optional alternative backend.
Week 1:
- Set up Moka integration and basic wrapper
- Implement
TwoTierAuthCachestruct - Implement basic read-through cache pattern
- Write unit tests
Week 2:
- Implement write-through and invalidation
- Add configuration options
- Integration tests with Redis L2
- Performance benchmarks
- Documentation updates
Week 3:
- Add sqlx dependencies and cargo features
- Design and document simple cache table schema
- Write ANSI SQL query templates
- Create schema migration scripts for all databases
Week 4:
- Implement basic
AuthCachetrait for SQL - Implement CRUD operations with ANSI SQL
- Handle database-specific UPSERT variations
- Connection pool management
- Write unit tests
Week 5:
- Implement cleanup task
- Integration tests with PostgreSQL
- Integration tests with MySQL
- Integration tests with SQLite
- Performance benchmarks vs Redis
- Documentation updates
- Combine Moka L1 with SQL L2 cache
- End-to-end testing of two-tier system (Moka + SQL)
- Performance comparison: Redis vs PostgreSQL vs MySQL vs SQLite
- Cross-database compatibility verification
- Update examples for all database backends
- Migration guide for existing users
- Release preparation
-
Cache Metrics & Observability
- Prometheus metrics for cache hit/miss rates
- Logging of cache operations
- Performance monitoring
- Per-database metrics
-
Distributed Caching
- Optional cache invalidation across multiple instances
- Use database NOTIFY/LISTEN for cache invalidation events (PostgreSQL)
- Polling-based invalidation for other databases
-
Additional L2 Backends
- Memcached support
- In-memory fallback (for testing/development)
- DynamoDB or other cloud-native options
-
Schema Enhancements
- Optional separate tables for code verifiers vs sessions
- Add created_at timestamp for debugging
- Add last_accessed_at for advanced eviction policies
-
Advanced Eviction Policies
- LFU (Least Frequently Used)
- Custom eviction strategies
- Priority-based caching
-
Compression
- Optional compression for large session data in cache_value
- Reduce storage footprint
-
Cache Partitioning
- Separate caches for code verifiers vs sessions
- Different TTL and capacity per cache type
-
Additional SQL Databases
- Microsoft SQL Server
- Oracle (if demand exists)
- CockroachDB
From Single-Tier Redis to Two-Tier (Moka + Redis):
// Before
let cache = Arc::new(redis::AuthCache::new("redis://localhost/", 3600));
// After
use axum_oidc_client::cache::{TwoTierAuthCache, TwoTierCacheConfig};
let redis_cache = Arc::new(redis::AuthCache::new("redis://localhost/", 3600));
let config = TwoTierCacheConfig {
l1_max_capacity: 10_000,
l1_ttl_sec: 3600,
l1_time_to_idle_sec: Some(1800),
enable_l1: true,
};
let cache = Arc::new(TwoTierAuthCache::new(redis_cache, config));From Redis to SQL Cache (PostgreSQL):
# Cargo.toml
[dependencies]
axum-oidc-client = { version = "0.x", features = ["sql-cache-postgres"] }// Before
let cache = Arc::new(redis::AuthCache::new("redis://localhost/", 3600));
// After
use axum_oidc_client::sql_cache::{SqlAuthCache, SqlCacheConfig};
let config = SqlCacheConfig {
connection_string: "postgresql://user:pass@localhost/mydb".to_string(),
max_connections: 20,
min_connections: 5,
cleanup_interval_sec: 300,
table_name: "oidc_cache".to_string(),
..Default::default()
};
let cache = Arc::new(SqlAuthCache::new(config).await?);
// Run migrations to create table
cache.init_schema().await?;Using MySQL:
# Cargo.toml
[dependencies]
axum-oidc-client = { version = "0.x", features = ["sql-cache-mysql"] }let config = SqlCacheConfig {
connection_string: "mysql://user:pass@localhost/mydb".to_string(),
..Default::default()
};
let cache = Arc::new(SqlAuthCache::new(config).await?);
cache.init_schema().await?;Using SQLite (great for development):
# Cargo.toml
[dependencies]
axum-oidc-client = { version = "0.x", features = ["sql-cache-sqlite"] }let config = SqlCacheConfig {
connection_string: "sqlite://cache.db".to_string(),
max_connections: 5, // SQLite has limited concurrency
..Default::default()
};
let cache = Arc::new(SqlAuthCache::new(config).await?);
cache.init_schema().await?;Two-Tier with SQL Cache:
use axum_oidc_client::sql_cache::{SqlAuthCache, SqlCacheConfig};
use axum_oidc_client::cache::{TwoTierAuthCache, TwoTierCacheConfig};
let sql_config = SqlCacheConfig { /* ... */ };
let sql_cache = Arc::new(SqlAuthCache::new(sql_config).await?);
sql_cache.init_schema().await?;
let cache_config = TwoTierCacheConfig { /* ... */ };
let cache = Arc::new(TwoTierAuthCache::new(sql_cache, cache_config));PostgreSQL (sql-cache-postgres):
- ✅ High-concurrency production environments
- ✅ When you already use PostgreSQL for your application
- ✅ Need advanced features (UNLOGGED tables, JSONB)
- ✅ Best overall performance for multi-instance deployments
MySQL/MariaDB (sql-cache-mysql):
- ✅ When you already use MySQL/MariaDB
- ✅ Familiar MySQL ecosystem
- ✅ Good general-purpose option
- ✅ Wide hosting support
SQLite (sql-cache-sqlite):
- ✅ Development and testing
- ✅ Single-instance deployments
- ✅ Embedded applications
- ✅ No separate database server needed
⚠️ Limited concurrency compared to PostgreSQL/MySQL
Based on typical cache workloads:
- Redis - Fastest (in-memory only)
- Moka (L1) + SQL (L2) - Near-Redis speed for cached items
- PostgreSQL - Fast, best for high concurrency
- MySQL - Good performance, widely supported
- SQLite - Good for single instance, file-based
We welcome contributions to help implement these features! Please see our Contributing Guide for details on how to get involved.
- Moka L1 cache integration (Help wanted!)
- SQL cache backend with multi-database support (Help wanted!)
- Performance benchmarking and optimization
For questions or discussions about this roadmap, please:
- Open an issue on GitHub
- Join our community discussions
- Contact the maintainers
Last Updated: 2025