Auto-Reload System
Overview
The Auto-Reload System in DDAP automatically detects changes to your database schema and reloads the GraphQL schema without requiring application restarts.
Why Auto-Reload is Useful:
- Zero-downtime schema updates
- Faster development cycles
- Reduced deployment complexity
- Continuous availability during schema updates
Quick Start
builder.Services.AddDdap(options =>
{
options.ConnectionString = "Server=localhost;Database=MyDb;...";
options.AutoReload = new AutoReloadOptions
{
Enabled = true,
IdleTimeout = TimeSpan.FromMinutes(5)
};
});
Configuration Options
public class AutoReloadOptions
{
public bool Enabled { get; set; } = false;
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
public ReloadStrategy Strategy { get; set; } = ReloadStrategy.InvalidateAndRebuild;
public ReloadBehavior Behavior { get; set; } = ReloadBehavior.ServeOldSchema;
public ChangeDetection ChangeDetection { get; set; } = ChangeDetection.CheckHash;
public Func<IServiceProvider, Task<bool>>? OnBeforeReloadAsync { get; set; }
public Func<IServiceProvider, Task>? OnAfterReloadAsync { get; set; }
public Func<IServiceProvider, Exception, Task>? OnReloadErrorAsync { get; set; }
}
Reload Strategies
InvalidateAndRebuild
Complete schema rebuild from scratch - most thorough but slowest.
Strategy = ReloadStrategy.InvalidateAndRebuild
Trade-offs:
- ✅ Handles all schema changes reliably
- ❌ Slowest (1-5 seconds)
HotReload
Incremental updates without full rebuild - fastest but may not handle all changes.
Strategy = ReloadStrategy.HotReload
Trade-offs:
- ✅ Fastest (100-500ms)
- ❌ Cannot handle breaking changes or renames
RestartExecutor
Restart GraphQL executor - balanced approach.
Strategy = ReloadStrategy.RestartExecutor
Trade-offs:
- ✅ Moderate speed (500ms-2s)
- ✅ Handles most changes
Reload Behaviors
ServeOldSchema
Continue serving requests with old schema during reload - zero downtime.
Behavior = ReloadBehavior.ServeOldSchema
Trade-offs:
- ✅ Zero downtime
- ❌ 2x memory during reload
BlockRequests
Reject new requests during reload - returns HTTP 503.
Behavior = ReloadBehavior.BlockRequests
Trade-offs:
- ✅ Lower memory usage
- ❌ Requests fail during reload
QueueRequests
Queue requests during reload and process after - balanced approach.
Behavior = ReloadBehavior.QueueRequests
Trade-offs:
- ✅ No failed requests
- ❌ Increased latency during reload
Change Detection
AlwaysReload
Always reload after idle timeout without checking for changes.
ChangeDetection = ChangeDetection.AlwaysReload
Trade-offs:
- ✅ Simple and predictable
- ❌ Wastes resources on unnecessary reloads
CheckHash
Compare schema hash to detect changes - reliable and database-agnostic.
ChangeDetection = ChangeDetection.CheckHash
Trade-offs:
- ✅ Catches all schema changes
- ❌ Higher overhead than timestamps
Hash includes: Tables, columns, types, keys, indexes, constraints
CheckTimestamps
Check database object modification timestamps - fastest method.
ChangeDetection = ChangeDetection.CheckTimestamps
Trade-offs:
- ✅ Fastest detection
- ❌ Database-specific, may miss some changes
Database Support:
- SQL Server:
sys.objects.modify_date - PostgreSQL:
pg_class.relfilenode - MySQL:
information_schema.tables.update_time
Lifecycle Hooks
Execute custom logic at specific points in the reload process.
OnBeforeReloadAsync
Invoked before reload starts. Return true to proceed, false to cancel.
OnBeforeReloadAsync = async (sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Schema reload starting");
// Check if reload should proceed
var cache = sp.GetService<IMemoryCache>();
if (cache?.Get("BlockReload") is true)
return false; // Cancel reload
return true; // Proceed
}
OnAfterReloadAsync
Invoked after reload completes successfully.
OnAfterReloadAsync = async (sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Schema reload completed");
// Warm up caches with new schema
var repository = sp.GetRequiredService<IEntityRepository>();
await repository.WarmCacheAsync();
}
OnReloadErrorAsync
Invoked when reload fails.
OnReloadErrorAsync = async (sp, exception) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogError(exception, "Schema reload failed");
// Send alert
var alerting = sp.GetService<IAlertingService>();
await alerting?.SendAlertAsync("Schema Reload Failed", exception.Message);
}
Events
Subscribe to reload events for monitoring and diagnostics.
builder.Services.AddSingleton<IReloadEventHandler, MyReloadEventHandler>();
public class MyReloadEventHandler : IReloadEventHandler
{
public Task OnReloadStartedAsync(ReloadStartedEvent evt)
{
// Log reload start
return Task.CompletedTask;
}
public Task OnReloadCompletedAsync(ReloadCompletedEvent evt)
{
// Log duration and entity count
return Task.CompletedTask;
}
public Task OnReloadFailedAsync(ReloadFailedEvent evt)
{
// Log error
return Task.CompletedTask;
}
}
Complete Example
Production-ready configuration:
builder.Services.AddDdap(options =>
{
options.ConnectionString = connectionString;
options.AutoReload = new AutoReloadOptions
{
Enabled = true,
IdleTimeout = TimeSpan.FromMinutes(3),
Strategy = ReloadStrategy.InvalidateAndRebuild,
Behavior = ReloadBehavior.ServeOldSchema,
ChangeDetection = ChangeDetection.CheckHash,
OnBeforeReloadAsync = async (sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Preparing for schema reload");
// Check if blocked by configuration
var config = sp.GetRequiredService<IConfiguration>();
if (config.GetValue<bool>("BlockAutoReload"))
return false;
return true;
},
OnAfterReloadAsync = async (sp) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Schema reload completed");
// Clear distributed cache
var cache = sp.GetService<IDistributedCache>();
await cache?.RemoveAsync("schema-metadata");
// Warm up new schema
var repository = sp.GetRequiredService<IEntityRepository>();
await repository.PreloadMetadataAsync();
},
OnReloadErrorAsync = async (sp, exception) =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
logger.LogError(exception, "Schema reload failed");
// Send alert in production
if (builder.Environment.IsProduction())
{
var alerting = sp.GetService<IAlertingService>();
await alerting?.SendAlertAsync(
"DDAP Schema Reload Failed",
exception.Message
);
}
}
};
});
Best Practices
1. Environment-Specific Configuration
if (builder.Environment.IsDevelopment())
{
options.AutoReload = new AutoReloadOptions
{
Strategy = ReloadStrategy.HotReload,
IdleTimeout = TimeSpan.FromSeconds(30)
};
}
else
{
options.AutoReload = new AutoReloadOptions
{
Strategy = ReloadStrategy.InvalidateAndRebuild,
IdleTimeout = TimeSpan.FromMinutes(5)
};
}
2. Appropriate Idle Timeouts
- Development: 30s-1m (fast feedback)
- Staging: 2-3m (balance)
- Production: 5-10m (stability)
3. Implement Monitoring Track reload frequency, duration, and failure rate using event handlers.
4. Consider Database Size For large schemas (1000+ tables):
- Use
CheckTimestampsinstead ofCheckHash - Increase
IdleTimeoutto reduce check frequency - Consider
HotReloadfor minor changes
5. Test Reload Scenarios Test adding/removing tables, changing columns, and heavy traffic during reload.
Troubleshooting
Schema Not Reloading
Problem: Database changes not reflected in API.
Solutions:
- Verify
Enabled = true - Check idle timeout has passed
- Test with
ChangeDetection.AlwaysReload - Check logs for reload errors
- Ensure database permissions for metadata queries
Frequent Unnecessary Reloads
Problem: Schema reloads without changes.
Solutions:
- Switch to
CheckHashorCheckTimestamps - Increase
IdleTimeout - Investigate what's triggering changes
High Memory Usage
Problem: Memory spikes during reload.
Solutions:
- Use
BlockRequestsinstead ofServeOldSchema - Switch to
HotReloadstrategy - Increase
IdleTimeoutto reduce frequency
Requests Failing During Reload
Problem: 503 errors with BlockRequests.
Solutions:
- Switch to
ServeOldSchemafor zero downtime - Use
QueueRequeststo buffer requests - Implement client retry logic
Reload Takes Too Long
Problem: Unacceptable reload duration.
Solutions:
- Use faster strategy (
HotReload>RestartExecutor>InvalidateAndRebuild) - Filter tables to reduce schema size
- Optimize database metadata queries
Performance Considerations
Memory Usage
| Behavior | Memory Usage | Notes |
|---|---|---|
ServeOldSchema |
~2x during reload | Two complete schemas in memory |
QueueRequests |
1x + queue | Queue size depends on request rate |
BlockRequests |
1x | Minimal overhead |
Reload Duration
| Strategy | Typical Duration | Scales With |
|---|---|---|
HotReload |
100-500ms | Number of changes |
RestartExecutor |
500ms-2s | Total tables |
InvalidateAndRebuild |
1-5s | Total tables + columns |
Change Detection Overhead
| Method | Check Time | Database Load |
|---|---|---|
AlwaysReload |
0ms | None |
CheckHash |
50-500ms | Reads all metadata |
CheckTimestamps |
10-50ms | Reads system tables only |
Recommendations
Small Schemas (<100 tables):
- Use
InvalidateAndRebuild+CheckHash - Impact is minimal, reliability is worth it
Medium Schemas (100-500 tables):
- Use
RestartExecutor+CheckHash - Balance between speed and reliability
Large Schemas (500+ tables):
- Use
HotReload+CheckTimestamps - Prioritize performance
- Test thoroughly for edge cases
For more information, see: