-
Notifications
You must be signed in to change notification settings - Fork 0
PLAT-02: Distributed caching strategy and cache-invalidation semantics #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
ab10cb3
Add ADR-0023: distributed caching strategy with cache-aside pattern
Chris0Jeky c631674
Add ICacheService interface in Application layer
Chris0Jeky e5afd4c
Add cache service implementations with DI wiring
Chris0Jeky 1f817f3
Apply cache-aside pattern to BoardService hot read paths
Chris0Jeky 33a1d2c
Add cache service and cache-aside behavior tests
Chris0Jeky bee66f0
Update STATUS.md and IMPLEMENTATION_MASTERPLAN.md for cache delivery
Chris0Jeky 9473f35
Fix self-review findings: log level, dispose, unused field
Chris0Jeky 8d3bfc1
Remove board detail caching to prevent stale data from sibling servic…
Chris0Jeky 91c76c6
Replace Lazy<ConnectionMultiplexer> with reconnection-capable pattern
Chris0Jeky 7ae17ce
Add capacity-capped eviction and defensive timer callback to InMemory…
Chris0Jeky e98c2b6
Remove unused Microsoft.Extensions.Caching.Memory package reference
Chris0Jeky bddb47b
Log warning for unknown cache provider configuration values
Chris0Jeky 88b4a27
Update BoardServiceCacheTests for board-detail caching removal and fi…
Chris0Jeky 2e73840
Update ADR-0023 and docs to reflect review-driven design changes
Chris0Jeky bebb2c0
Merge remote-tracking branch 'origin/main' into feature/distributed-c…
claude c4b1f83
Guard proposal decisions with EF concurrency
Chris0Jeky 705e411
Fix cache service review comments from PR #805
Chris0Jeky 60835d3
Merge main to resolve conflicts
Chris0Jeky 9f6c7e7
Merge branch 'feature/sso-oidc-mfa-policy' of https://github.com/Chri…
Chris0Jeky 5940167
Resolve merge conflicts with main for distributed cache PR
Chris0Jeky 208c657
Fix MfaCredentialTests and resolve merge conflicts with main
Chris0Jeky 573f512
Resolve merge conflicts with main after SSO/OIDC/MFA merge
Chris0Jeky File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
backend/src/Taskdeck.Application/Interfaces/ICacheService.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| namespace Taskdeck.Application.Interfaces; | ||
|
|
||
| /// <summary> | ||
| /// Generic cache abstraction for cache-aside pattern. | ||
| /// Implementations must degrade safely: cache failures never throw exceptions | ||
| /// to callers and do not affect data correctness. | ||
| /// </summary> | ||
| public interface ICacheService | ||
| { | ||
| /// <summary> | ||
| /// Attempts to retrieve a cached value. Returns null on miss or error. | ||
| /// </summary> | ||
| /// <typeparam name="T">The cached value type (must be JSON-serializable).</typeparam> | ||
| /// <param name="key">The cache key (will be prefixed automatically).</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>The cached value, or null if not found or on error.</returns> | ||
| Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class; | ||
|
|
||
| /// <summary> | ||
| /// Stores a value in the cache with the specified TTL. | ||
| /// Silently swallows errors — caller is never affected by cache write failures. | ||
| /// </summary> | ||
| /// <typeparam name="T">The value type (must be JSON-serializable).</typeparam> | ||
| /// <param name="key">The cache key (will be prefixed automatically).</param> | ||
| /// <param name="value">The value to cache.</param> | ||
| /// <param name="ttl">Time-to-live for the cached entry.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| Task SetAsync<T>(string key, T value, TimeSpan ttl, CancellationToken cancellationToken = default) where T : class; | ||
|
|
||
| /// <summary> | ||
| /// Removes a cached entry. Silently swallows errors. | ||
| /// </summary> | ||
| /// <param name="key">The cache key to invalidate.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| Task RemoveAsync(string key, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Removes all cached entries matching the specified prefix pattern. | ||
| /// Used for bulk invalidation (e.g., all board list caches for a user). | ||
| /// Silently swallows errors. | ||
| /// </summary> | ||
| /// <param name="keyPrefix">The key prefix to match for removal.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| Task RemoveByPrefixAsync(string keyPrefix, CancellationToken cancellationToken = default); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| namespace Taskdeck.Application.Services; | ||
|
|
||
| /// <summary> | ||
| /// Centralized cache key definitions for the cache-aside pattern. | ||
| /// Keys are structured as "resource:scope:id" and prefixed by the | ||
| /// cache service with the global key prefix (e.g., "td:"). | ||
| /// </summary> | ||
| public static class CacheKeys | ||
| { | ||
| // NOTE: BoardDetail is intentionally NOT cached. BoardDetailDto includes columns | ||
| // with card counts, and ColumnService/CardService mutate that data without cache | ||
| // awareness. Caching board detail would serve stale column/card information. | ||
|
|
||
| /// <summary> | ||
| /// Cache key for a user's board list (default, non-filtered, non-archived). | ||
| /// Format: boards:user:{userId} | ||
| /// </summary> | ||
| public static string BoardListForUser(Guid userId) => $"boards:user:{userId}"; | ||
| } |
29 changes: 29 additions & 0 deletions
29
backend/src/Taskdeck.Application/Services/CacheSettings.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| namespace Taskdeck.Application.Services; | ||
|
|
||
| /// <summary> | ||
| /// Configuration for the distributed caching layer. | ||
| /// Bound from appsettings.json "Cache" section. | ||
| /// </summary> | ||
| public sealed class CacheSettings | ||
| { | ||
| /// <summary> | ||
| /// Cache provider: "Redis", "InMemory", or "None". | ||
| /// Defaults to "InMemory" for local-first usage. | ||
| /// </summary> | ||
| public string Provider { get; set; } = "InMemory"; | ||
|
|
||
| /// <summary> | ||
| /// Redis connection string. Only used when Provider is "Redis". | ||
| /// </summary> | ||
| public string? RedisConnectionString { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Global key prefix to avoid collisions in shared Redis instances. | ||
| /// </summary> | ||
| public string KeyPrefix { get; set; } = "td"; | ||
|
|
||
| /// <summary> | ||
| /// Default TTL in seconds for board list cache entries. | ||
| /// </summary> | ||
| public int BoardListTtlSeconds { get; set; } = 60; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The invalidation logic only clears the board list cache for the owner. If a board is shared with other users, their cached lists will remain stale until the TTL expires. This discrepancy with ADR-0023 (which specifies invalidating all accessible users' caches) could lead to data inconsistency in multi-user scenarios. Consider using
RemoveByPrefixAsyncto invalidate all user board lists or updating the design document to reflect this limitation.