Akavache Documentation

repository·main·Indexed 25 days ago

https://github.com/reactiveui/akavache

An asynchronous, persistent key-value store for native C# desktop and mobile applications built on SQLite3. It provides modular support for serializers like System.Text.Json and offers four cache types: UserAccount, LocalMachine, Secure, and InMemory. Key features include the cache-first pattern via GetOrFetchObject, background refresh with GetAndFetchLatest, and a specialized settings database via Akavache.Settings.

Tokens
62.3K
Snippets
160
Records
189
Agent score
81%

What's inside Akavache

  1. Compare Akavache cache types

    main

    Akavache provides four distinct cache types based on your data's sensitivity, persistence requirements, and sharing needs. Use the following summary to choose the right one:

    Cache TypePurposePersistenceSharingEncryption
    UserAccountUser-specific data✅ PersistentPer-userOptional
    LocalMachineApp-wide data✅ PersistentAll usersOptional
    SecureSensitive data✅ PersistentPer-user✅ Required
    InMemoryTemporary data❌ Memory onlyCurrent session❌ None
  2. Compare Akavache V10 vs V11 Performance

    main

    Akavache V11 introduces significant architectural improvements, particularly for bulk operations and the GetOrFetch pattern. While most read operations and cache type operations remain comparable to V10, V11 offers much faster bulk processing and more predictable memory allocation.

    V11 Performance Advantages

    • Bulk Operations: Over 10x faster than performing individual operations.
    • GetOrFetch Pattern: Scales sub-linearly (e.g., 1.5ms for 10 items vs 45ms for 1000 items).
    • In-Memory Performance: Highly efficient for complex operations.
    • Serialization: Using SystemTextJson matches or exceeds V10 performance.

    V11 Trade-offs

    • Large Sequential Reads: May be up to 8.6% slower in specific scenarios.
    • Initialization: The new builder pattern adds slight complexity to the setup.
    • Dependencies: Uses a more granular package structure.
  3. Features of Akavache.Drawing

    main

    Akavache.Drawing provides specialized image handling capabilities for Akavache, including:

    • Image Loading & Caching: Load images from cache with automatic format detection.
    • URL Image Caching: Download and cache images from URLs with built-in HTTP support.
    • Image Manipulation: Resize, crop, and generate thumbnails with caching.
    • Multiple Format Support: Supports PNG, JPEG, GIF, BMP, WebP, and other common formats.
    • Fallback Support: Automatic fallback to default images when loading fails.
    • Batch Operations: Efficiently load multiple images.
    • Size Detection: Retrieve image dimensions without performing a full load.
    • Advanced Caching: Pattern-based cache clearing and preloading.
    • Cross-Platform: Compatible with all .NET platforms supported by Akavache.
  4. Select the appropriate Cache Type

    main

    Choose a cache type based on the lifecycle and sensitivity of your data:

    Cache TypeRecommended Use Case
    InMemorySession data, frequently accessed, or temporary caching.
    UserAccountUser settings and preferences.
    LocalMachineCached API data or temporary files.
    SecureCredentials and other sensitive data.

    Example of selecting by type:

    // Fast temporary data
    await CacheDatabase.InMemory.InsertObject("session", data);
    
    // Persistent user data
    await CacheDatabase.UserAccount.InsertObject("settings", userSettings);
  5. How GetAllKeysSafe works and when to use it

    main

    The GetAllKeysSafe methods are exception-safe alternatives to GetAllKeys(). While GetAllKeys() will throw an exception and break an observable chain if a storage error occurs, GetAllKeysSafe() catches exceptions, logs them, and returns an empty sequence instead.

    Key benefits:

    • Exception handling: Returns an empty sequence instead of throwing.
    • Null safety: Automatically filters out null or empty keys.
    • Observable chain friendly: Allows reactive pipelines to continue executing even if underlying storage has issues.

    Use GetAllKeysSafe when:

    • Building resilient reactive pipelines.
    • Working with unreliable storage scenarios.
    • You prefer continuation over immediate failure when key enumeration fails.
    // Standard GetAllKeys() - exceptions break the observable chain
    try 
    {
        var keys = await CacheDatabase.UserAccount.GetAllKeys().ToList();
        // Process keys...
    }
    catch (Exception ex)
    {
        // Handle exception outside observable chain
    }
    
    // GetAllKeysSafe() - exceptions are caught and logged, chain continues
    await CacheDatabase.UserAccount.GetAllKeysSafe()
       .Do(key => Console.WriteLine($"Found key: {key}"))
       .Where(key => ShouldProcess(key))
       .ForEach(key => ProcessKey(key));
        // If GetAllKeys() would throw, this continues with empty sequence instead
  6. Understand Akavache cache types

    main

    Akavache provides four distinct cache types, each suited for different data lifecycles:

    • UserAccount: Persistent storage for user settings and preferences that should potentially sync.
    • LocalMachine: Persistent storage for cached data that can be safely deleted by the system.
    • Secure: Encrypted storage for sensitive data like credentials and API keys.
    • InMemory: Temporary storage that does not persist between application sessions.
    // User preferences (persistent)
    await CacheDatabase.UserAccount.InsertObject("user_settings", settings);
    
    // API cache (temporary)
    await CacheDatabase.LocalMachine.InsertObject("api_cache", apiData, DateTimeOffset.Now.AddHours(6));
    
    // Sensitive data (encrypted)
    await CacheDatabase.Secure.SaveLogin("john.doe", "secretPassword", "myapp.com");
    
    // Session data (in-memory only)
    await CacheDatabase.InMemory.InsertObject("current_session", sessionData);
  7. Use BSON for improved performance with large or binary data

    main

    Both System.Text.Json and Newtonsoft.Json offer BSON (Binary JSON) variants. BSON is typically 20-40% faster for large objects and is more efficient at handling byte arrays and binary content.

    When to use BSON:

    • Large objects: More efficient for large data structures.
    • Binary data: Better handling of byte arrays and binary content.
    • Performance critical: Faster serialization/deserialization.
    • Network efficiency: Smaller payload sizes.

    To ensure maximum backward compatibility with existing Akavache data when using BSON, use UseBsonFormat = true in either serializer.

  8. Implement the Write-Behind pattern

    main

    The Write-Behind pattern (also known as Write-Back) prioritizes application performance by updating the cache immediately and deferring the expensive backend update to a later time.

    In this pattern, you use InsertObject to update the cache instantly, then queue the update to be processed by a background service or task to sync with the primary data source.

    public class UserService
    {
        private readonly IBlobCache _cache = CacheDatabase.UserAccount;
        private readonly Queue<PendingUpdate> _pendingUpdates = new();
        
        public async Task UpdateUserAsync(int userId, User updatedUser)
        {
            // Update cache immediately
            var cacheKey = CacheKeys.UserProfile(userId);
            await _cache.InsertObject(cacheKey, updatedUser, TimeSpan.FromMinutes(30));
            
            // Queue backend update for later
            _pendingUpdates.Enqueue(new PendingUpdate { UserId = userId, User = updatedUser });
            
            // Process queue asynchronously (implement background service)
            _ = Task.Run(ProcessPendingUpdates);
        }
    }
  9. Akavache configuration best practices

    main

    Follow these guidelines to ensure stable and performant cache usage:

    1. Initialize early: Set up Akavache before any cache operations are attempted.
    2. Use explicit providers: Always call WithSqliteProvider() before calling WithSqliteDefaults().
    3. One initialization per app: Avoid re-initializing the configuration unless absolutely necessary.
    4. Environment-specific config: Tailor settings (In-Memory vs SQLite) for dev, test, and production.
    5. Validate configuration: Test cache accessibility during the application startup sequence.
    6. Handle initialization errors: Implement graceful error handling for configuration failures.
  10. Performance comparison of serializers in Akavache V11

    main

    When choosing a serializer for Akavache V11, consider the following performance characteristics:

    • System.Text.Json: Generally faster than V10 across all scenarios and recommended for optimal performance.
    • Newtonsoft.Json: Performance is comparable to V10 for small to medium datasets, but it may be slower for very large datasets.

    Migration Recommendation: Start with Newtonsoft.Json to ensure compatibility during your initial migration, then switch to System.Text.Json once the migration is stable to gain performance benefits.

  11. Select the appropriate CacheDatabase type

    main

    Choose the correct CacheDatabase based on the nature of the data you are storing:

    Data TypeRecommended CacheDescription
    SensitiveCacheDatabase.SecureAlways use for authentication tokens or PII.
    TemporaryCacheDatabase.InMemoryUse for transient data that doesn't need to persist across restarts.
    User-SpecificCacheDatabase.UserAccountPersistent data tied to a specific user profile.
    App-WideCacheDatabase.LocalMachineShared persistent data available to all users on the device.

    Decision Logic:

    • If sensitive $\rightarrow$ Secure
    • Else if not persistent $\rightarrow$ InMemory
    • Else if user-specific $\rightarrow$ UserAccount
    • Else $\rightarrow$ LocalMachine
    public class DataService
    {
        // ✅ User-specific data in UserAccount
        public async Task SaveUserPreferences(int userId, UserPreferences prefs)
        {
            await CacheDatabase.UserAccount.InsertObject(
                CacheKeys.UserSettings(userId), prefs);
        }
        
        // ✅ App-wide data in LocalMachine
        public async Task CacheAppConfiguration(AppConfig config)
        {
            await CacheDatabase.LocalMachine.InsertObject("app:config", config);
        }
        
        // ✅ Sensitive data in Secure
        public async Task SaveAuthToken(string token)
        {
            await CacheDatabase.Secure.InsertObject("auth:token", token);
        }
        
        // ✅ Temporary data in InMemory
        public async Task CacheTemporaryCalculation(string key, object result)
        {
            await CacheDatabase.InMemory.InsertObject(key, result, TimeSpan.FromMinutes(30));
        }
    }
  12. Choose between IObservable and async/await patterns

    main

    Akavache supports two primary patterns for handling asynchronous operations. Choosing the right one depends on your complexity requirements:

    • Use async/await when you need simplicity and only require the single, final value from an operation (e.g., a simple data fetch in a UI event handler).
    • Use the IObservable pattern (using operators like .Select(), .Catch(), and .Subscribe()) for complex scenarios, such as:
      • Composing multiple asynchronous steps into a declarative chain.
      • Handling streams of data rather than single values.
      • Using GetAndFetchLatest: This method emits multiple values (first the cached data, then the fresh data from the network). Using await on this method will only receive the first item, whereas the IObservable pattern allows you to react to both the cached and the fresh updates.