This comprehensive guide covers performance analysis, benchmarks, best practices, and optimization techniques for Akavache V11 vs V10.
- Bulk Operations: 10x+ faster than individual operations
- GetOrFetch Pattern: Sub-linear scaling (1.5ms to 45ms for 100x data)
- Memory Consistency: More predictable allocation patterns
- In-Memory Performance: 122ms for 1000 complex operations
- Architecture: Modern builder pattern with better error handling
- Cache Type Operations: All persistent caches within 2% of each other
- Read Operations: Generally comparable with V10
- Object Serialization: SystemTextJson matches or exceeds V10 performance
- Memory Usage: Similar allocation patterns across versions
- Large Sequential Reads: Up to 8.6% slower in some cases
- Initialization Overhead: Builder pattern adds slight complexity
- Package Dependencies: More granular package structure
| Operation | Small (10) | Medium (100) | Large (1000) |
|---|---|---|---|
| GetOrFetch | 1.5ms | 15ms | 45ms |
| Bulk Operations | 3.3ms | 4.5ms | 18ms |
| In-Memory | 2.4ms | 19ms | 123ms |
| Cache Types | ~27ms | ~255ms | ~2,600ms |
| Factor | V10 to V11 Migration Recommended |
|---|---|
| New Projects | Always |
| Performance Critical | Yes (with SystemTextJson) |
| Legacy Data Compatibility | Yes (with Newtonsoft BSON) |
| Large Sequential Reads | Evaluate (8.6% slower) |
| Developer Experience | Highly Recommended |
Akavache V11 delivers architectural improvements with comparable performance. The new features (multiple serializers, cross-compatibility, modern patterns) provide significant value with minimal performance impact.
Recommendation: Upgrade to V11 for all new projects and consider migration for existing projects that would benefit from the architectural improvements.
This report provides a detailed performance comparison between Akavache V10.2.41 and Akavache V11.0 (current development version), covering all major functionality areas and use cases.
Akavache V11.0 represents a significant architectural advancement over V10.2.41, introducing:
- New builder pattern for initialization
- Multiple serializer support (System.Text.Json, Newtonsoft.Json, BSON variants)
- Cross-serializer compatibility
- Enhanced modularity with separate packages
- Improved settings management
The performance analysis shows comparable or improved performance across most operations, with V11's architectural benefits providing significant developer experience improvements.
- Framework: .NET 9.0
- OS: Windows 11 (10.0.26100.4770/24H2/2024Update/HudsonValley)
- Benchmarking Tool: BenchmarkDotNet v0.15.2
- Runtime: .NET 9.0.8 (9.0.825.36511), X64 RyuJIT AVX2
- Test Data: Complex objects with Guid, string, int, and DateTimeOffset properties
// Legacy initialization
BlobCache.ApplicationName = "MyApp";
// Ready to use: BlobCache.UserAccount, BlobCache.LocalMachine, etc.- Single serialization approach
- Traditional singleton pattern
- Monolithic package structure
// Modern builder pattern
CacheDatabase.Serializer = new SystemJsonSerializer();
CacheDatabase.Initialize(builder =>
builder.WithApplicationName("MyApp")
.WithSqliteDefaults());
// Ready to use: CacheDatabase.UserAccount, CacheDatabase.LocalMachine, etc.- Multiple serializer options
- Explicit configuration
- Modular package design
- Enhanced type safety
Based on comprehensive benchmarks across different operation types and data sizes:
| Operation | Mean Time | Memory Allocation | Notes |
|---|---|---|---|
| GetOrFetchObject | 1.476 ms | 167.47 KB | Fast object retrieval with fallback |
| InMemoryOperations | 2.402 ms | 227.2 KB | Excellent in-memory performance |
| BulkOperations | 3.261 ms | 66.08 KB | Efficient bulk processing |
| UserAccount/LocalMachine/Secure | ~27 ms | ~227 KB | Consistent across cache types |
| Operation | Mean Time | Memory Allocation | Performance Notes |
|---|---|---|---|
| GetOrFetchObject | 15.179 ms | 1,427.63 KB | Linear scaling |
| InMemoryOperations | 18.796 ms | 2,025.13 KB | Good scalability |
| BulkOperations | 4.504 ms | 243.73 KB | Bulk advantage clear |
| InsertWithExpiration | 245.723 ms | 671.27 KB | Expiration overhead minimal |
| Cache Type Operations | ~255 ms | ~2,000 KB | Consistent performance |
| Operation | Mean Time | Memory Allocation | Scalability |
|---|---|---|---|
| GetOrFetchObject | 45.034 ms | 14,053.05 KB | Sub-linear scaling |
| InMemoryOperations | 122.826 ms | 19,740.02 KB | Excellent for large datasets |
| BulkOperations | 17.992 ms | 2,015.54 KB | Best scaling performance |
| Cache Types | ~2,600 ms | ~19,800 KB | Predictable large-scale performance |
| InvalidateObjects | 7,141.040 ms | 17,071.66 KB | Expected invalidation cost |
-
Bulk Operations Excel:
- 1000 items processed in just 17.992 ms
- Minimal memory overhead (2,015.54 KB)
- 10x+ faster than individual operations
-
GetOrFetch Pattern Optimization:
- Highly optimized for cache-miss scenarios
- Scales sub-linearly: 10 items (1.5ms) → 1000 items (45ms)
- Excellent for real-world usage patterns
-
Memory Efficiency:
- Consistent memory usage across cache types
- Predictable allocation patterns
- No memory leaks or excessive overhead
-
In-Memory Performance:
- 122ms for 1000 complex object operations
- Ideal for session data and temporary caching
Based on earlier comparative runs:
- V11 shows 1.8-3.4% faster read performance for smaller datasets
- More consistent performance with lower standard deviations
- Bulk reads perform equivalently between versions
- Sequential writes: Comparable performance
- Bulk writes: V11 shows significant advantages with new bulk API
- Object serialization: SystemTextJson in V11 outperforms legacy serialization
- Generally equivalent or better memory efficiency in V11
- More predictable allocation patterns
- Garbage collection performance improved
- ~260ms for 100 complex objects (serialization + storage + retrieval)
- 2,029.27 KB memory allocation
- Best choice for new applications
V11 can read data written by different serializers:
// Write with one serializer
CacheDatabase.Serializer = new NewtonsoftBsonSerializer();
await cache.InsertObject("key", data);
// Read with another serializer
CacheDatabase.Serializer = new SystemJsonSerializer();
var retrieved = await cache.GetObject<MyData>("key"); // Still works!All cache types show remarkably consistent performance in V11:
| Cache Type | 100 Items | 1000 Items | Use Case |
|---|---|---|---|
| UserAccount | 253.847 ms | 2,603.138 ms | User settings, preferences |
| LocalMachine | 257.036 ms | 2,642.941 ms | Cached API data, temporary files |
| Secure | 255.278 ms | 2,620.867 ms | Credentials, sensitive data |
| InMemory | 18.796 ms | 122.826 ms | Session data, frequently accessed |
Key Insight: Persistent cache types perform within 2% of each other, showing excellent architecture consistency.
- 45.034 ms for 1000 operations
- Ideal for API data caching with fallback
- Efficient handling of cache misses
- 3,552.625 ms for 1000 complex operations
- Includes: Insert → Read → Update → Verify cycle
- Demonstrates excellent real-world performance
- 2,604.595 ms for 1000 items with expiration
- Minimal overhead for time-based invalidation
- Essential for API caching scenarios
- < 5% performance difference for most operations
- Significant improvements in consistency
- Better error handling and recovery
- Initialization pattern requires code changes
- Must explicitly choose serializer
- Package structure requires dependency updates
- Future-proof architecture
- Better performance monitoring
- Enhanced debugging capabilities
- Serializer flexibility
// For new applications (fastest)
CacheDatabase.Serializer = new SystemJsonSerializer();
// For V10 compatibility (most compatible)
CacheDatabase.Serializer = new NewtonsoftBsonSerializer();// Instead of multiple individual inserts
await cache.InsertObjects(largeDataSet); // Much faster!// Fast temporary data
await CacheDatabase.InMemory.InsertObject("session", data);
// Persistent user data
await CacheDatabase.UserAccount.InsertObject("settings", userSettings);var data = await cache.GetOrFetchObject("api_data",
() => httpClient.GetFromJsonAsync<ApiData>(url));- Similar or better performance
- Modern, maintainable architecture
- Future-proof design
- Enhanced developer experience
- You need multiple serializer support
- You want better error handling
- You require cross-serializer compatibility
- Performance is not the primary concern (impact minimal)
- Large sequential read workloads (8.6% slower in some cases)
- Legacy data requiring specific serialization
- Applications with extremely tight performance budgets
- Use System.Text.Json serializer
- Leverage bulk operations wherever possible
- Consider InMemory cache for frequently accessed data
- Implement proper expiration strategies
Akavache V11.0 successfully delivers on its architectural improvements while maintaining excellent performance characteristics. The comprehensive benchmarks show:
- Performance : Generally equal or better than V10
- Consistency : Much more predictable performance patterns
- Flexibility : Multiple serializer options for optimization
- Architecture : Modern, maintainable, and extensible design
Bottom Line: V11 is a compelling upgrade that provides architectural benefits without significant performance regression. The slight trade-offs in specific scenarios are more than offset by the improved developer experience, better error handling, and future-proof design.
Performance data collected using BenchmarkDotNet v0.15.2 on .NET 9.0. Results represent typical performance and may vary based on hardware configuration, data characteristics, and usage patterns.
// ✅ ALWAYS use System.Text.Json for optimal V11 performance
// This is faster than V10 across all scenarios and significantly faster than V11 Newtonsoft
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<SystemJsonSerializer>(builder =>
builder.WithApplicationName("MyApp")
.WithSqliteProvider()
.WithSqliteDefaults());
// ⚠️ For V10 compatibility with large datasets, consider Newtonsoft BSON
// (Only if you need V10 format compatibility - otherwise use System.Text.Json)
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<NewtonsoftBsonSerializer>(builder =>
builder.WithApplicationName("MyApp")
.WithSqliteProvider()
.WithSqliteDefaults());// ✅ Use batch operations for multiple items
await CacheDatabase.UserAccount.InsertObjects(manyItems);
// ✅ Bulk retrieval
var keys = new[] { "key1", "key2", "key3" };
var results = await CacheDatabase.UserAccount.GetObjects<MyData>(keys).ToList();
// ❌ Avoid individual operations in loops
foreach (var item in items)
{
await CacheDatabase.UserAccount.InsertObject($"key_{item.Id}", item); // Slow
}// ✅ Set reasonable expiration for cached data
await CacheDatabase.LocalMachine.InsertObject("temp_key", data, 30.Minutes().FromNow());
// ✅ Use different expiration policies by data type
await CacheDatabase.LocalMachine.InsertObject("api_cache", apiData, 1.Hours().FromNow());
await CacheDatabase.LocalMachine.InsertObject("image_cache", imageBytes, 1.Days().FromNow());
// ✅ Don't expire user settings (unless necessary)
await CacheDatabase.UserAccount.InsertObject("user_preferences", prefs); // No expiration// ✅ Use InMemory cache for frequently accessed data
await CacheDatabase.InMemory.InsertObject("hot_data", frequentData);
// ✅ Use LocalMachine for cacheable data
await CacheDatabase.LocalMachine.InsertObject("api_response", apiData);
// ✅ Use UserAccount for persistent user data
await CacheDatabase.UserAccount.InsertObject("user_settings", settings);
// ✅ Use Secure for sensitive data
await CacheDatabase.Secure.InsertObject("api_key", apiKey);// ✅ Use specific types instead of object when possible
await CacheDatabase.UserAccount.GetObject<SpecificType>("key"); // Good
// ❌ Avoid generic object retrieval when type is known
await CacheDatabase.UserAccount.Get("key", typeof(SpecificType)); // Slower
// ✅ Avoid storing very large objects
// Instead, break them into smaller chunks or use compression
// ✅ Use compression for large data sets
var compressedData = await CompressAsync(largeData);
await CacheDatabase.LocalMachine.Insert("compressed_key", compressedData);// ✅ Use GetOrFetch for cache-miss scenarios
var data = await CacheDatabase.LocalMachine.GetOrFetchObject("api_data",
() => httpClient.GetFromJsonAsync<ApiData>(url));
// ✅ Set reasonable expiration for fetched data
var weatherData = await CacheDatabase.LocalMachine.GetOrFetchObject("weather",
() => weatherApi.GetCurrentWeather(),
DateTimeOffset.Now.AddMinutes(30));// ✅ Initialize once at app startup
public class App
{
static App()
{
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<SystemJsonSerializer>(builder =>
builder.WithApplicationName("MyApp")
.WithSqliteProvider()
.WithSqliteDefaults());
}
}
// ❌ Don't initialize multiple times// ✅ Always shutdown Akavache properly
public override void OnExit(ExitEventArgs e)
{
CacheDatabase.Shutdown().Wait();
base.OnExit(e);
}// ✅ Use in-memory cache for unit tests
[SetUp]
public void Setup()
{
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<SystemJsonSerializer>(builder =>
builder.WithApplicationName("TestApp")
.WithInMemoryDefaults());
}
[TearDown]
public void TearDown()
{
CacheDatabase.Shutdown().Wait();
}Benchmark reproduction requires Windows hosts. Linux/macOS are not supported due to Windows-specific projects and dependencies used in the benchmark harnesses.
- .NET 9.0 SDK
- Windows operating system
- PowerShell 5.0+ (for automation script)
# From the solution root directory
.\src\RunCompatTest.ps1# V11 benchmarks (current)
cd src
dotnet run -c Release -p Akavache.Benchmarks/Akavache.Benchmarks.csproj
# V10 comparison benchmarks
dotnet run -c Release -p Akavache.Benchmarks.V10/Akavache.Benchmarks.V10.csprojImportant Notes:
- Results vary by hardware configuration and system load
- Benchmarks are indicative, not absolute measurements
- Large benchmark runs can take 10-30 minutes to complete
- Some benchmark projects use BenchmarkDotNet which requires Windows-specific optimizations
- Large Databases with Newtonsoft.Json: V10 outperforms V11 when using legacy Newtonsoft serialization with very large datasets
- Sequential Read Performance: Up to 8.6% slower than V10 specifically when using the legacy Newtonsoft.Json serializer (System.Text.Json does not have this limitation and performs better than V10)
- Package Dependencies: More granular package structure may require careful workload management
- Linux/macOS Build: Benchmark projects and compatibility tests require Windows due to platform-specific dependencies
For optimal V11 performance:
- Always use System.Text.Json serializer - faster than V10 across all scenarios
- Use bulk operations for multiple items - 10x+ performance improvement
- Choose appropriate cache types - InMemory for hot data, LocalMachine for API caches
- Set reasonable expiration times - balance between freshness and performance
- Initialize once at startup - avoid repeated initialization overhead
- Proper shutdown - ensure data consistency and resource cleanup
V11 delivers architectural improvements with excellent performance when following these best practices. // This is faster than V10 across all scenarios .WithAkavacheCacheDatabase()
//
### 2. Use Batch Operations
```csharp
// ❌ Slow - Individual operations
foreach (var item in items)
{
await cache.InsertObject($"key_{item.Id}", item);
}
// ✅ Fast - Batch operation (10x+ faster)
var keyValuePairs = items.ToDictionary(item => $"key_{item.Id}", item => item);
await cache.InsertObjects(keyValuePairs);
// ✅ InMemory for frequently accessed data
await CacheDatabase.InMemory.InsertObject("hot_data", frequentData);
// ✅ UserAccount for user-specific persistent data
await CacheDatabase.UserAccount.InsertObject("user_prefs", userPreferences);
// ✅ LocalMachine for shared application data
await CacheDatabase.LocalMachine.InsertObject("app_config", configuration);// ✅ Short expiration for frequently changing data
await cache.InsertObject("stock_price", price, TimeSpan.FromMinutes(1));
// ✅ Long expiration for stable reference data
await cache.InsertObject("country_list", countries, TimeSpan.FromDays(30));
// ✅ No expiration for settings and configuration
await cache.InsertObject("user_settings", settings);// ✅ Fast - Specific type
await cache.GetObject<SpecificType>("key");
// ❌ Slower - Generic object
await cache.Get("key", typeof(SpecificType));// ❌ Large objects slow down serialization
public class HeavyObject
{
public byte[] LargeByteArray { get; set; } // Multiple MB
public List<ComplexNestedObject> Items { get; set; } // Thousands of items
}
// ✅ Break large objects into smaller chunks
public class OptimizedObject
{
public string Id { get; set; }
public string DataReference { get; set; } // Reference to chunked data
}
// Store large data separately in chunks
await cache.InsertObject("data_chunk_1", chunk1);
await cache.InsertObject("data_chunk_2", chunk2);// ❌ Expensive - Re-serializes entire object
var data = await cache.GetObject<LargeObject>("key");
await cache.InsertObject("key", data, newExpiration);
// ✅ Efficient - Only updates metadata (up to 250x faster)
await cache.UpdateExpiration("key", newExpiration);- Large objects - BSON is more efficient for complex data structures
- Binary data - Better handling of byte arrays and binary content
- Performance critical - BSON serialization is typically 20-40% faster for large objects
// System.Text.Json BSON (recommended)
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<SystemJsonBsonSerializer>(builder => /* ... */);
// Newtonsoft.Json BSON (for V10 compatibility)
AppBuilder.CreateSplatBuilder()
.WithAkavacheCacheDatabase<NewtonsoftBsonSerializer>(builder => /* ... */);public class CacheMemoryMonitor
{
public static void LogMemoryUsage(string operation)
{
var beforeGC = GC.GetTotalMemory(false);
GC.Collect();
var afterGC = GC.GetTotalMemory(true);
Console.WriteLine($"{operation}: {beforeGC:N0} -> {afterGC:N0} bytes");
}
}// Periodically clean up expired entries
await cache.Vacuum();
// Clear specific data types when no longer needed
await cache.InvalidateAllObjects<TemporaryData>();
// Clear InMemory cache during memory pressure
if (MemoryPressure.IsHigh())
{
await CacheDatabase.InMemory.InvalidateAll();
}public class SmartCacheWrapper<T> where T : class
{
private readonly WeakReference<T> _weakRef = new(null);
private readonly string _cacheKey;
private readonly IBlobCache _cache;
public async Task<T> GetObject()
{
if (_weakRef.TryGetTarget(out var cached))
return cached;
var data = await _cache.GetObject<T>(_cacheKey);
_weakRef.SetTarget(data);
return data;
}
}public class CacheMetrics
{
private long _hits = 0;
private long _misses = 0;
public double HitRate => _hits / (double)(_hits + _misses);
public async Task<T> GetWithMetrics<T>(string key)
{
try
{
var result = await cache.GetObject<T>(key);
Interlocked.Increment(ref _hits);
return result;
}
catch (KeyNotFoundException)
{
Interlocked.Increment(ref _misses);
throw;
}
}
}public class PerformanceTracker
{
public static async Task<T> TimeOperation<T>(string operationName, Func<Task<T>> operation)
{
var stopwatch = Stopwatch.StartNew();
try
{
return await operation();
}
finally
{
stopwatch.Stop();
Console.WriteLine($"{operationName}: {stopwatch.ElapsedMilliseconds}ms");
}
}
}
// Usage
var data = await PerformanceTracker.TimeOperation("Cache Get",
() => cache.GetObject<MyData>("key"));public class CacheSizeMonitor
{
public static long GetDatabaseSize(string dbPath)
{
if (File.Exists(dbPath))
return new FileInfo(dbPath).Length;
return 0;
}
public static void LogCacheSizes()
{
var basePath = CacheDatabase.GetCacheDirectory();
Console.WriteLine($"UserAccount: {GetDatabaseSize(Path.Combine(basePath, "userAccount.db")):N0} bytes");
Console.WriteLine($"LocalMachine: {GetDatabaseSize(Path.Combine(basePath, "localMachine.db")):N0} bytes");
Console.WriteLine($"Secure: {GetDatabaseSize(Path.Combine(basePath, "secret.db")):N0} bytes");
}
}[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
public class CacheBenchmarks
{
private IBlobCache _cache;
private TestData[] _testData;
[GlobalSetup]
public void Setup()
{
_cache = new InMemoryBlobCache(new SystemJsonSerializer());
_testData = GenerateTestData(1000);
}
[Benchmark]
public async Task InsertObjects()
{
var dict = _testData.ToDictionary(d => d.Id.ToString(), d => d);
await _cache.InsertObjects(dict);
}
[Benchmark]
public async Task GetObjects()
{
var keys = _testData.Select(d => d.Id.ToString()).ToArray();
var results = await _cache.GetObjects<TestData>(keys);
}
}# Install BenchmarkDotNet
dotnet add package BenchmarkDotNet
# Run benchmarks
dotnet run -c Release --project YourBenchmarkProject- Issue: V10 outperforms V11 when using legacy Newtonsoft serialization with very large datasets
- Solution: Use System.Text.Json for better performance
- Issue: Up to 8.6% slower than V10 specifically when using Newtonsoft.Json serializer
- Solution: System.Text.Json does not have this limitation and performs better than V10
- Issue: Performance varies between platforms due to different SQLite implementations
- Solution: Test on target platforms and optimize accordingly
For comprehensive performance analysis:
- 📊 Performance Summary - Quick comparison and migration decision matrix
- 📈 Comprehensive Benchmark Report - Detailed performance analysis and recommendations
Benchmark reproduction requires Windows hosts. Linux/macOS are not supported due to Windows-specific dependencies.
- .NET 9.0 SDK
- Windows operating system
- PowerShell 5.0+ (for automation script)
# V11 benchmarks (current)
cd src
dotnet run -c Release -p Akavache.Benchmarks/Akavache.Benchmarks.csproj
# V10 comparison benchmarks
dotnet run -c Release -p Akavache.Benchmarks.V10/Akavache.Benchmarks.V10.csproj# From the solution root directory
.\src\RunCompatTest.ps1- Use System.Text.Json for optimal performance in V11
- Batch operations when working with multiple items
- Choose appropriate cache types based on access patterns
- Set reasonable expiration times to balance freshness and performance
- Monitor cache hit rates and adjust strategies accordingly
- Use UpdateExpiration instead of re-inserting data when extending lifetime
- Break large objects into smaller, manageable chunks
- Clear unnecessary data regularly to maintain performance
- Monitor memory usage and implement pressure relief mechanisms
- Test on target platforms as performance characteristics vary