470 lines
12 KiB
Markdown
470 lines
12 KiB
Markdown
# Performance Configuration
|
|
|
|
Optimize stream performance with batching, compression, indexing, and caching.
|
|
|
|
## Overview
|
|
|
|
Performance configuration tunes stream operations for throughput and latency:
|
|
- **Batch Size** - Events per database query
|
|
- **Compression** - Reduce storage and network I/O
|
|
- **Indexing** - Speed up queries on metadata fields
|
|
- **Caching** - In-memory caching for hot events
|
|
|
|
## Quick Start
|
|
|
|
```csharp
|
|
using Svrnty.CQRS.Events.Abstractions;
|
|
|
|
var configStore = serviceProvider.GetRequiredService<IStreamConfigurationStore>();
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "orders",
|
|
Performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 1000,
|
|
EnableCompression = true,
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "userId", "tenantId" }
|
|
}
|
|
});
|
|
```
|
|
|
|
## Performance Properties
|
|
|
|
```csharp
|
|
public class PerformanceConfiguration
|
|
{
|
|
public int BatchSize { get; set; } // Events per query
|
|
public bool EnableCompression { get; set; } // Compress event data
|
|
public string CompressionAlgorithm { get; set; } // gzip, lz4, zstd
|
|
public bool EnableIndexing { get; set; } // Index metadata
|
|
public List<string> IndexedFields { get; set; } // Fields to index
|
|
public int CacheSize { get; set; } // Cache event count
|
|
public TimeSpan CacheTtl { get; set; } // Cache expiration
|
|
public bool EnableReadAhead { get; set; } // Prefetch batches
|
|
public int ReadAheadBatches { get; set; } // Prefetch count
|
|
}
|
|
```
|
|
|
|
## Batch Size
|
|
|
|
Control database query batch size:
|
|
|
|
```csharp
|
|
// Small batches - lower latency, higher overhead
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 100 // Good for real-time processing
|
|
};
|
|
|
|
// Medium batches - balanced (default)
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 500 // Good general-purpose setting
|
|
};
|
|
|
|
// Large batches - higher throughput, more memory
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 5000 // Good for bulk processing
|
|
};
|
|
```
|
|
|
|
### Batch Size Impact
|
|
|
|
```csharp
|
|
// Small batch - many queries
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 100
|
|
};
|
|
// Process 100k events = 1000 database queries
|
|
|
|
// Large batch - fewer queries
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 10000
|
|
};
|
|
// Process 100k events = 10 database queries
|
|
```
|
|
|
|
## Compression
|
|
|
|
Reduce storage and network I/O:
|
|
|
|
```csharp
|
|
// GZIP - Best compression ratio
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "gzip" // Slower, best ratio
|
|
};
|
|
|
|
// LZ4 - Fastest compression
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "lz4" // Fastest, good ratio
|
|
};
|
|
|
|
// Zstandard - Balanced
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "zstd" // Fast, excellent ratio
|
|
};
|
|
```
|
|
|
|
### Compression Comparison
|
|
|
|
| Algorithm | Speed | Ratio | Best For |
|
|
|-----------|-------|-------|----------|
|
|
| gzip | Slow | High | Cold storage, archives |
|
|
| lz4 | Very Fast | Good | Real-time streams |
|
|
| zstd | Fast | Excellent | General purpose |
|
|
|
|
### When to Use Compression
|
|
|
|
```csharp
|
|
// ✅ Large events - Good candidate
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableCompression = true, // Event size > 1 KB
|
|
CompressionAlgorithm = "lz4"
|
|
};
|
|
|
|
// ❌ Small events - Not worth it
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableCompression = false // Event size < 100 bytes
|
|
};
|
|
```
|
|
|
|
## Indexing
|
|
|
|
Index metadata fields for fast queries:
|
|
|
|
```csharp
|
|
// Index common query fields
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string>
|
|
{
|
|
"userId", // Filter by user
|
|
"tenantId", // Multi-tenant isolation
|
|
"orderId", // Lookup by order
|
|
"eventType" // Filter by event type
|
|
}
|
|
};
|
|
```
|
|
|
|
### Index Selection
|
|
|
|
```csharp
|
|
// ✅ Good - Frequently queried fields
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "userId", "tenantId" }
|
|
};
|
|
|
|
// ❌ Bad - Too many indexes (slows writes)
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string>
|
|
{
|
|
"userId", "tenantId", "orderId", "productId",
|
|
"categoryId", "regionId", "statusCode", "...20 more fields"
|
|
}
|
|
};
|
|
```
|
|
|
|
### Index Impact
|
|
|
|
```csharp
|
|
// Without index
|
|
SELECT * FROM events
|
|
WHERE stream_name = 'orders'
|
|
AND metadata->>'userId' = '12345'
|
|
AND timestamp > NOW() - INTERVAL '7 days';
|
|
// Full table scan: 10 seconds for 10M events
|
|
|
|
// With index
|
|
CREATE INDEX idx_events_user_id ON events ((metadata->>'userId'));
|
|
// Index scan: 50ms for same query
|
|
```
|
|
|
|
## Caching
|
|
|
|
Cache hot events in memory:
|
|
|
|
```csharp
|
|
// Cache last 10,000 events
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
CacheSize = 10000,
|
|
CacheTtl = TimeSpan.FromMinutes(5)
|
|
};
|
|
|
|
// Cache last 100,000 events (high memory)
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
CacheSize = 100000,
|
|
CacheTtl = TimeSpan.FromMinutes(10)
|
|
};
|
|
|
|
// Disable caching
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
CacheSize = 0
|
|
};
|
|
```
|
|
|
|
### Cache Effectiveness
|
|
|
|
```csharp
|
|
// ✅ Good - Read-heavy workload
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
CacheSize = 50000 // Cache hot events
|
|
};
|
|
// Repeated reads from cache: 1ms vs 10ms from DB
|
|
|
|
// ❌ Not useful - Write-heavy workload
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
CacheSize = 0 // No caching for write-heavy streams
|
|
};
|
|
```
|
|
|
|
## Read-Ahead
|
|
|
|
Prefetch batches for sequential reads:
|
|
|
|
```csharp
|
|
// Enable read-ahead for sequential processing
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 1000,
|
|
EnableReadAhead = true,
|
|
ReadAheadBatches = 2 // Prefetch 2 batches ahead
|
|
};
|
|
|
|
// Process events
|
|
await foreach (var @event in eventStore.ReadStreamAsync("orders"))
|
|
{
|
|
// Batches prefetched in background
|
|
await ProcessEventAsync(@event);
|
|
}
|
|
```
|
|
|
|
## Domain-Specific Examples
|
|
|
|
### High-Volume Orders
|
|
|
|
```csharp
|
|
// Optimize for throughput
|
|
var orderPerformance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 5000, // Large batches
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "lz4", // Fast compression
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "userId", "orderId", "tenantId" },
|
|
CacheSize = 100000, // Cache last 100k
|
|
CacheTtl = TimeSpan.FromMinutes(10),
|
|
EnableReadAhead = true,
|
|
ReadAheadBatches = 3 // Aggressive prefetch
|
|
};
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "orders",
|
|
Performance = orderPerformance,
|
|
Tags = new List<string> { "high-volume", "production" }
|
|
});
|
|
```
|
|
|
|
### Real-Time Analytics
|
|
|
|
```csharp
|
|
// Optimize for low latency
|
|
var analyticsPerformance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 100, // Small batches, low latency
|
|
EnableCompression = false, // Skip compression overhead
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "userId", "eventType" },
|
|
CacheSize = 10000, // Moderate cache
|
|
CacheTtl = TimeSpan.FromMinutes(1),
|
|
EnableReadAhead = false // No prefetch needed
|
|
};
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "real-time-analytics",
|
|
Performance = analyticsPerformance
|
|
});
|
|
```
|
|
|
|
### Audit Logs
|
|
|
|
```csharp
|
|
// Optimize for storage
|
|
var auditPerformance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 1000,
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "gzip", // Maximum compression
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "userId", "action", "resourceId" },
|
|
CacheSize = 0, // No caching (rarely re-read)
|
|
EnableReadAhead = false
|
|
};
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "audit-logs",
|
|
Performance = auditPerformance,
|
|
Tags = new List<string> { "compliance", "audit" }
|
|
});
|
|
```
|
|
|
|
### Session Events
|
|
|
|
```csharp
|
|
// Optimize for memory
|
|
var sessionPerformance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = 500,
|
|
EnableCompression = true,
|
|
CompressionAlgorithm = "lz4",
|
|
EnableIndexing = true,
|
|
IndexedFields = new List<string> { "sessionId", "userId" },
|
|
CacheSize = 50000, // Large cache (hot data)
|
|
CacheTtl = TimeSpan.FromMinutes(30),
|
|
EnableReadAhead = true,
|
|
ReadAheadBatches = 2
|
|
};
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "user-sessions",
|
|
Performance = sessionPerformance
|
|
});
|
|
```
|
|
|
|
## Performance Tuning
|
|
|
|
### Measuring Performance
|
|
|
|
```csharp
|
|
// Benchmark different configurations
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
await foreach (var @event in eventStore.ReadStreamAsync("orders"))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
}
|
|
|
|
stopwatch.Stop();
|
|
|
|
_logger.LogInformation(
|
|
"Processed stream in {Duration}ms with batch size {BatchSize}",
|
|
stopwatch.ElapsedMilliseconds,
|
|
batchSize);
|
|
```
|
|
|
|
### A/B Testing Configurations
|
|
|
|
```csharp
|
|
// Test batch size impact
|
|
var configs = new[]
|
|
{
|
|
new { BatchSize = 100, Name = "Small" },
|
|
new { BatchSize = 500, Name = "Medium" },
|
|
new { BatchSize = 2000, Name = "Large" }
|
|
};
|
|
|
|
foreach (var config in configs)
|
|
{
|
|
var performance = new PerformanceConfiguration
|
|
{
|
|
BatchSize = config.BatchSize
|
|
};
|
|
|
|
await configStore.SetConfigurationAsync(new StreamConfiguration
|
|
{
|
|
StreamName = "test-stream",
|
|
Performance = performance
|
|
});
|
|
|
|
var duration = await BenchmarkStreamProcessingAsync("test-stream");
|
|
|
|
_logger.LogInformation(
|
|
"{Name} batch ({Size}): {Duration}ms",
|
|
config.Name,
|
|
config.BatchSize,
|
|
duration);
|
|
}
|
|
```
|
|
|
|
## Monitoring Performance
|
|
|
|
```csharp
|
|
// Track performance metrics
|
|
var metrics = new
|
|
{
|
|
StreamName = "orders",
|
|
BatchSize = config.Performance.BatchSize,
|
|
CompressionEnabled = config.Performance.EnableCompression,
|
|
CacheHitRate = await GetCacheHitRateAsync("orders"),
|
|
AvgQueryTime = await GetAvgQueryTimeAsync("orders"),
|
|
EventsPerSecond = await GetThroughputAsync("orders")
|
|
};
|
|
|
|
_logger.LogInformation(
|
|
"Stream {Stream}: {EventsPerSec} events/sec, {CacheHitRate:F1}% cache hits, {AvgQueryTime}ms avg query",
|
|
metrics.StreamName,
|
|
metrics.EventsPerSecond,
|
|
metrics.CacheHitRate,
|
|
metrics.AvgQueryTime);
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Start with default settings and tune based on metrics
|
|
- Use larger batches for bulk processing
|
|
- Enable compression for large events (> 1 KB)
|
|
- Index fields used in queries
|
|
- Cache hot streams with frequent reads
|
|
- Enable read-ahead for sequential processing
|
|
- Benchmark configuration changes
|
|
- Monitor cache hit rates
|
|
- Use LZ4 for real-time streams
|
|
- Use GZIP for archives
|
|
|
|
### ❌ DON'T
|
|
|
|
- Don't use very large batches (> 10000) - high memory usage
|
|
- Don't compress small events (< 100 bytes)
|
|
- Don't over-index (slows writes)
|
|
- Don't cache cold streams
|
|
- Don't enable read-ahead for random access
|
|
- Don't forget to monitor performance
|
|
- Don't use same config for all streams
|
|
- Don't optimize prematurely
|
|
|
|
## See Also
|
|
|
|
- [Stream Configuration Overview](README.md)
|
|
- [Retention Configuration](retention-config.md)
|
|
- [Lifecycle Configuration](lifecycle-config.md)
|
|
- [Access Control](access-control.md)
|
|
- [Best Practices - Performance](../../best-practices/performance.md)
|