Skip to content

LEGLINK-857: Improvements to avoid Redis timeouts - #1770

Open
johnbritton wants to merge 23 commits into
devfrom
users/jbritton/redisFixes
Open

LEGLINK-857: Improvements to avoid Redis timeouts#1770
johnbritton wants to merge 23 commits into
devfrom
users/jbritton/redisFixes

Conversation

@johnbritton

@johnbritton johnbritton commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

🛠️ Description of Changes

  1. Parse Redis connection strings to allow options to be set through configuration rather than hardcoded.
  2. Change the distributed semaphore and ResourceCache to use async calls.
  3. Connect to Redis using a connection pool instead of a connection multiplexer to avoid client timeouts.

🧪 Testing Performed

Unit tests and local integration tests.

🧑‍🔬 Unit Testing

  • I have written or updated unit tests to cover my changes
  • Coverage: 56.0%

📓 Documentation Updated

N/A

Summary by CodeRabbit

  • New Features
    • Added configurable Redis connection-pool sizing for distributed locks and resource caching, defaulting to five connections.
    • Added support for Redis connection options in Docker deployments to improve connection handling and DNS resolution.
  • Improvements
    • Cache reads, updates, and deletions now run asynchronously and honor cancellation, improving responsiveness during data acquisition and normalization.
    • Redis-backed caching and distributed locking now use pooled connections.
  • Bug Fixes
    • Improved cache health checks with clearer failure handling and prioritized Redis validation.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec100a6e-6a5f-426f-889b-1f269ca54e33

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Redis integration was migrated to StackExchange.Redis.Extensions with configurable connection pools. Cache reads, writes, and deletes now use asynchronous cancellation-aware APIs, FHIR and normalization callers await them, distributed semaphore acquisition is asynchronous, and related tests and lockfiles were updated.

Changes

Redis foundation

Layer / File(s) Summary
Redis registrations, configuration, and dependencies
DotNet/Shared/..., app-config.yaml, docker-compose.yml, DotNet/*/packages.lock.json
Redis registrations now use pooled StackExchange.Redis.Extensions services, connection strings include additional options, pool-size settings default to 5, and lockfiles reference the Redis extension packages.
Asynchronous cache implementations
DotNet/Shared/Application/Interfaces/IResourceCache.cs, DotNet/Shared/Application/Services/ResourceCache/*
Cache interfaces and ABS, Redis, and hybrid implementations now provide asynchronous operations with cancellation tokens.
Async application callers
DotNet/DataAcquisition.Domain/..., DotNet/Normalization/...
FHIR, location-mapping, reference-resource, and normalization flows await cache operations; semaphore acquisition uses asynchronous APIs.
Validation updates
DotNet/ServiceTests/...
Mocks and assertions were updated for asynchronous cache and semaphore methods, including cancellation-token arguments.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: nvmlantana, mikeatpinnacle

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main Redis timeout-handling improvements.
Description check ✅ Passed The description covers the required changes, testing, unit tests, coverage, and documentation sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch users/jbritton/redisFixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
DotNet/Shared/Application/Health/ResourceCacheHealthCheck.cs (1)

33-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add focused health-check tests for every cache mode.

Cover Redis, ABS, and Hybrid, including Redis failure preventing the Blob check and cancellation/timeout failures. Mock IRedisDatabase and Blob interactions; do not contact external services.

As per path instructions, “If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test,” using Moq for external communication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Shared/Application/Health/ResourceCacheHealthCheck.cs` around lines 33
- 47, Add focused unit tests for ResourceCacheHealthCheck covering Redis, ABS,
and Hybrid modes, with each switch branch exercised. Mock IRedisDatabase and
Blob interactions using Moq, verify Hybrid skips Blob checks when Redis is
unhealthy, and cover cancellation/timeout failure results without contacting
external services.

Source: Path instructions

DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs (1)

57-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover each cache-registration branch with XUnit tests.

Verify Redis resolves RedisResourceCache and IRedisDatabase, ABS resolves without Redis, and Hybrid resolves both keyed implementations. Use Moq/configuration objects without opening Redis or Blob Storage connections.

As per path instructions, “If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test,” and unit tests must contain no network activity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs` around lines
57 - 79, Add XUnit coverage for each `ResourceCacheType` branch in the
registration method: verify Redis resolves `RedisResourceCache` and
`IRedisDatabase`, ABS resolves `ABSResourceCache` without Redis services, and
Hybrid resolves both keyed `RedisResourceCache` and `ABSResourceCache`. Build
the tests with mocked dependencies and configuration objects so service
registration is validated without opening Redis or Blob Storage connections.

Source: Path instructions

DotNet/Shared/Application/Models/Configs/DistributedLockSettings.cs (1)

15-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reject invalid Redis pool sizes at startup.

PoolSize is configuration-bound and passed through unvalidated. Reject <= 0 deterministically, and add focused XUnit coverage for the default, zero, and negative cases without resolving Redis.

Proposed fix
+        if (distributedLockSettings.PoolSize <= 0)
+        {
+            throw new ArgumentOutOfRangeException(
+                nameof(distributedLockSettings.PoolSize),
+                "DistributedLockSettings:PoolSize must be greater than zero.");
+        }
+
         services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(new StackExchange.Redis.Extensions.Core.Configuration.RedisConfiguration

As per path instructions, “Logic that depends on service or interface configuration” should have unit tests, and unit tests must not perform network activity.

Also applies to: 100-104

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Shared/Application/Models/Configs/DistributedLockSettings.cs` at line
15, Validate DistributedLockSettings.PoolSize during configuration/startup and
reject any value less than or equal to zero deterministically before Redis
resolution or connection attempts. Add focused XUnit tests covering the default
value, zero, and negative values, ensuring the tests exercise validation only
and perform no network activity.

Source: Path instructions

DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs (1)

101-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add focused XUnit coverage for the modified cache branches.

Use Moq or narrow wrappers; do not use Redis or Blob network calls in unit tests.

  • DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs#L101-L170: cover connected/unavailable Redis, missing or invalid memory values, threshold fallback, and exceptions.
  • DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs#L46-L65: cover ids-blob present and absent paths.
  • DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs#L116-L121: cover cache-hit and cache-miss reads.
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/FhirApiService.cs#L436-L442: cover cacheable and non-cacheable resource inputs.
  • DotNet/DataAcquisition.Domain/Application/Services/ReferenceResourceService.cs#L537-L546: cover cacheable and non-cacheable cached-reference inputs.

As per path instructions, “If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test,” and “No network activity … should appear in unit tests. Recommend using mocks (via Moq) for any external communication.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs`
around lines 101 - 170, Add focused XUnit tests using Moq or narrow wrappers,
with no Redis, Blob, or other network calls. In
DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs:101-170,
cover connected and unavailable Redis, missing or invalid memory values,
threshold-based ABS/Redis selection, and exception fallback. In
DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs:46-65 and
:116-121, cover ids-blob present/absent and cache-hit/cache-miss reads. In
DotNet/DataAcquisition.Domain/Application/Services/FhirApi/FhirApiService.cs:436-442
and
DotNet/DataAcquisition.Domain/Application/Services/ReferenceResourceService.cs:537-546,
cover cacheable and non-cacheable inputs.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs`:
- Around line 92-95: Update resource cache initialization around
ValidateRedisSettings and the StackExchangeRedisExtensions registration to
reject RedisSettings.PoolSize values less than or equal to zero before
constructing RedisConfiguration, while preserving the default and positive
configured values. Extend the focused XUnit coverage to verify the default pool
size, a positive override, and startup failure for zero and negative values.
- Around line 89-90: Update the Redis configuration setup around
ConfigurationOptions.Parse so redisSettings.Password only overrides the parsed
password when it is explicitly non-null/non-empty; otherwise preserve the
credential embedded in the connection string. Keep the existing configuration
flow and Password assignment behavior for an explicitly configured ResourceCache
Redis password.

In `@DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs`:
- Around line 118-120: Sanitize both the cache-derived arguments, cacheKey and
the GetBlobKey(cacheKey) result, before passing them to _logger.LogWarning in
the ABS blob-not-found path. Preserve the existing warning message and settings
argument, and ensure every logged value is sanitized at the call site.

---

Nitpick comments:
In `@DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs`:
- Around line 57-79: Add XUnit coverage for each `ResourceCacheType` branch in
the registration method: verify Redis resolves `RedisResourceCache` and
`IRedisDatabase`, ABS resolves `ABSResourceCache` without Redis services, and
Hybrid resolves both keyed `RedisResourceCache` and `ABSResourceCache`. Build
the tests with mocked dependencies and configuration objects so service
registration is validated without opening Redis or Blob Storage connections.

In `@DotNet/Shared/Application/Health/ResourceCacheHealthCheck.cs`:
- Around line 33-47: Add focused unit tests for ResourceCacheHealthCheck
covering Redis, ABS, and Hybrid modes, with each switch branch exercised. Mock
IRedisDatabase and Blob interactions using Moq, verify Hybrid skips Blob checks
when Redis is unhealthy, and cover cancellation/timeout failure results without
contacting external services.

In `@DotNet/Shared/Application/Models/Configs/DistributedLockSettings.cs`:
- Line 15: Validate DistributedLockSettings.PoolSize during
configuration/startup and reject any value less than or equal to zero
deterministically before Redis resolution or connection attempts. Add focused
XUnit tests covering the default value, zero, and negative values, ensuring the
tests exercise validation only and perform no network activity.

In `@DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs`:
- Around line 101-170: Add focused XUnit tests using Moq or narrow wrappers,
with no Redis, Blob, or other network calls. In
DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs:101-170,
cover connected and unavailable Redis, missing or invalid memory values,
threshold-based ABS/Redis selection, and exception fallback. In
DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs:46-65 and
:116-121, cover ids-blob present/absent and cache-hit/cache-miss reads. In
DotNet/DataAcquisition.Domain/Application/Services/FhirApi/FhirApiService.cs:436-442
and
DotNet/DataAcquisition.Domain/Application/Services/ReferenceResourceService.cs:537-546,
cover cacheable and non-cacheable inputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0aa6f24a-d267-4183-8bbf-cdce61bc7a97

📥 Commits

Reviewing files that changed from the base of the PR and between 015ca41 and 36aa558.

📒 Files selected for processing (47)
  • DotNet/Account/appsettings.Docker.json
  • DotNet/Account/packages.lock.json
  • DotNet/Admin.BFF/appsettings.Docker.json
  • DotNet/Admin.BFF/packages.lock.json
  • DotNet/Audit/packages.lock.json
  • DotNet/Automation.Link/packages.lock.json
  • DotNet/Automation.UI/packages.lock.json
  • DotNet/Census/packages.lock.json
  • DotNet/DataAcquisition.AcquisitionWorker/appsettings.Docker.json
  • DotNet/DataAcquisition.AcquisitionWorker/packages.lock.json
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/Commands/ReadFhirCommand.cs
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/Commands/SearchFhirCommand.cs
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/FhirApiService.cs
  • DotNet/DataAcquisition.Domain/Application/Services/LocationMappingService.cs
  • DotNet/DataAcquisition.Domain/Application/Services/ReferenceResourceService.cs
  • DotNet/DataAcquisition.Domain/packages.lock.json
  • DotNet/DataAcquisition/appsettings.Docker.json
  • DotNet/DataAcquisition/packages.lock.json
  • DotNet/LinkSdk/packages.lock.json
  • DotNet/Normalization/Listeners/ResourcesAcquiredListener.cs
  • DotNet/Normalization/packages.lock.json
  • DotNet/QueryDispatch/packages.lock.json
  • DotNet/Report/packages.lock.json
  • DotNet/ServiceTests/IntegrationTests/DataAcquisition/AcquisitionProcessingBackgroundServiceTests.cs
  • DotNet/ServiceTests/IntegrationTests/Normalization/NormalizationIntegrationTestFixture.cs
  • DotNet/ServiceTests/UnitTests/DataAcquisition/FhirApiServiceTests.cs
  • DotNet/ServiceTests/UnitTests/DataAcquisition/SearchFhirCommandTests.cs
  • DotNet/ServiceTests/UnitTests/DataAcquisition/Services/LocationMappingServiceTests.cs
  • DotNet/ServiceTests/packages.lock.json
  • DotNet/Shared/Application/Extensions/ExternalServices/RedisCacheExtension.cs
  • DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs
  • DotNet/Shared/Application/Health/ResourceCacheHealthCheck.cs
  • DotNet/Shared/Application/Interfaces/IResourceCache.cs
  • DotNet/Shared/Application/Models/Configs/DistributedLockSettings.cs
  • DotNet/Shared/Application/Models/Configs/ResourceCacheSettings.cs
  • DotNet/Shared/Application/Services/ResourceCache/ABSResourceCache.cs
  • DotNet/Shared/Application/Services/ResourceCache/HybridResourceCache.cs
  • DotNet/Shared/Application/Services/ResourceCache/RedisResourceCache.cs
  • DotNet/Shared/Shared.csproj
  • DotNet/Shared/packages.lock.json
  • DotNet/Submission.Data/packages.lock.json
  • DotNet/Submission/packages.lock.json
  • DotNet/Tenant/packages.lock.json
  • DotNet/Terminology/packages.lock.json
  • Tests/BackendE2ETests/packages.lock.json
  • app-config.yaml
  • docker-compose.yml
👮 Files not reviewed due to content moderation or server errors (9)
  • DotNet/Audit/packages.lock.json
  • DotNet/Automation.Link/packages.lock.json
  • DotNet/Automation.UI/packages.lock.json
  • DotNet/Census/packages.lock.json
  • DotNet/DataAcquisition.AcquisitionWorker/packages.lock.json
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/Commands/ReadFhirCommand.cs
  • DotNet/DataAcquisition.Domain/Application/Services/FhirApi/Commands/SearchFhirCommand.cs
  • DotNet/DataAcquisition.Domain/packages.lock.json
  • DotNet/DataAcquisition/packages.lock.json

Comment thread DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs
Comment thread DotNet/Shared/Application/Extensions/ResourceCacheExtensions.cs
johnbritton and others added 5 commits July 29, 2026 16:00
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Comment thread DotNet/Account/appsettings.Docker.json
@johnbritton
johnbritton requested review from a team as code owners August 5, 2026 14:09

@arianamihailescu arianamihailescu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@MikeAtPinnacle

Copy link
Copy Markdown
Contributor

DataAcquisition and AcquisitionWorker run two different redis configurations — GeneralStartupExtensions.cs:90 (RegisterRedis()) then :102 (AddResourceCache). Normalization runs only the second. So the last one wins.

@MikeAtPinnacle

MikeAtPinnacle commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Did you mean to downgrade redist to 2.8.24 from 2.13.17? All of the package lock files show a downgrade

Comment thread DotNet/Shared/Application/Services/ResourceCache/RedisResourceCache.cs Outdated
Comment thread app-config.yaml Outdated
Add support for using an existing shared Redis connection when registering the resource cache. ResourceCacheExtensions.AddResourceCache now accepts useExistingRedisConnection and uses RegisterOrUseRedisConnection to either validate an existing IRedisDatabase registration or register a new connection. GeneralStartupExtensions passes the flag and conditionally registers Redis. Added unit tests (ResourceCacheExtensionsTests) covering hybrid/redis/abs scenarios and error when existing connection is missing. Removed ResourceCache Redis connection/password from appsettings and docker-compose to avoid duplicate registrations. Updated app-config.yaml and environment config files (app-config.*.json) to clarify Redis usage and narrow some consumer tags.
Bump central package versions in Directory.Packages.props: upgrade StackExchange.Redis to 3.1.11, StackExchange.Redis.Extensions.* to 13.0.1, Microsoft.Extensions.Caching.StackExchangeRedis to 10.0.10, and several Microsoft.Extensions packages (DependencyInjection, Options, Primitives, Telemetry, etc.) to 10.0.10/10.0.5 where applicable. Regenerated packages.lock.json files across projects to reflect the new dependency graph and updated transitive runtime packages. Also add an explicit StackExchange.Redis PackageReference to DotNet/Shared/Shared.csproj so shared projects reference the upgraded Redis client.
Add PooledRedisDistributedSemaphoreProvider that pulls an IRedisDatabase and creates a RedisDistributedSynchronizationProvider per semaphore. Update DI to register the pooled provider. Change RedisResourceCache to depend on IRedisDatabase and obtain Database per operation instead of holding a single IDatabase. Add unit tests verifying pooled Database usage for semaphores and cache deletions. Files: PooledRedisDistributedSemaphoreProvider.cs, DistributedLockSettings.cs (modified), RedisResourceCache.cs (modified), plus two new unit tests.
@johnbritton

Copy link
Copy Markdown
Contributor Author

DataAcquisition and AcquisitionWorker run two different redis configurations — GeneralStartupExtensions.cs:90 (RegisterRedis()) then :102 (AddResourceCache). Normalization runs only the second. So the last one wins.

Updated AddResourceCache so that it can reuse an existing Redis connection. DA and DAW now reuse the existing connection for ResourceCache. Normalization still uses the original ResourceCache connection. This allows us to use different connections for RegisterRedis and AddResourceCache if desired.

Did you mean to downgrade redist to 2.8.24 from 2.13.17? All of the package lock files show a downgrade

No. It was changed to a transitive dependency, so it restored the minimum required version. I made it explicit and updated all the Redis packages to the latest.

@MikeAtPinnacle

Copy link
Copy Markdown
Contributor

Do the connection strings for Redis need to be updated in these files (they are from my previous changes)?

  • Config/app-config.dev.json
  • Config/app-config.qa.json
  • Config/app-config.qa2.json
  • Config/app-config.test.json

Appends Redis client options (abortConnect=false, resolveDns=true, allowAdmin=true) to ConnectionStrings:Redis and ResourceCache:Redis:ConnectionString in dev, qa, qa2, and test app-config JSON files. This enables DNS resolution for managed Redis endpoints, allows admin commands, and prevents early aborts on connection failures.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants