Skip to content

refactor: async Redis connection with retry/deduplication - #2767

Open
Pyper9219 wants to merge 1 commit into
evolution-foundation:mainfrom
Pyper9219:main
Open

Pyper9219 wants to merge 1 commit into
evolution-foundation:mainfrom
Pyper9219:main

Conversation

@Pyper9219

@Pyper9219 Pyper9219 commented Oct 25, 2026 •

Copy link
Copy Markdown
  • Make getConnection async with a connecting promise to avoid duplicate simultaneous connections
  • Add ensureClient() in RedisCache with null-guards
  • Remove unused err parameter in error handler (fixes pre-commit ESLint)
  • Expose redis and postgres ports in docker-compose
  • Set POSTGRES_DB=evolution_api

📋 Description

🔗 Related Issue

Closes #(issue_number)

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

What

Make Redis client resilient for serverless / external DB setups (Vercel + NeonDB)

  • src/cache/rediscache.client.ts: async connection with retry + deduplication, fix ESLint err shadowing
  • src/cache/rediscache.ts: add ensureClient() null-guards to prevent crashes on cold start
  • docker-compose.yaml: expose Redis/Postgres ports + set POSTGRES_DB for local dev parity with Neon external DB

Why

When deploying with external Postgres (NeonDB) and Redis Cloud on Vercel, the cache client can be null during cold starts. This prevents crashes and retries connection.

Checks

  • tsc --noEmit passed
  • tsup build succeeded
  • eslint --ext .ts src passed
  • working tree clean, commit a6f5d70

Summary by Sourcery

Harden Redis connectivity for cold starts and external database deployments while improving local Docker database access.

Bug Fixes:

  • Prevent Redis cache operations from crashing when the client is unavailable during startup or connection failures.
  • Ensure Redis connection failures can be retried after a failed attempt.

Enhancements:

  • Make Redis connection establishment asynchronous and deduplicate concurrent connection attempts.
  • Expose Redis and PostgreSQL ports for local development and standardize the PostgreSQL database name.

- Make getConnection async with a connecting promise to avoid duplicate simultaneous connections
- Add ensureClient() in RedisCache with null-guards
- Remove unused err parameter in error handler (fixes pre-commit ESLint)
- Expose redis and postgres ports in docker-compose
- Set POSTGRES_DB=evolution_api
@sourcery-ai

sourcery-ai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR refactors Redis initialization into an awaited, deduplicated connection flow and adds lazy null-safe access throughout RedisCache, preventing cold-start crashes when Redis is temporarily unavailable. Docker Compose is also updated to expose Redis/PostgreSQL ports and use the expected PostgreSQL database name.

Sequence diagram for deduplicated Redis connection

sequenceDiagram
    participant Cache as RedisCache
    participant Redis as redisClient
    participant Client as RedisClient

    Cache->>Redis: getConnection()
    alt connection already ready
        Redis-->>Cache: return client
    else connection in progress
        Redis-->>Cache: return connecting promise
    else no connection
        Redis->>Redis: _connect()
        Redis->>Client: createClient()
        Redis->>Client: connect()
        Client-->>Redis: ready
        Redis-->>Cache: return client
    end
Loading

File-Level Changes

Change Details Files
Make Redis connection establishment asynchronous and deduplicate concurrent connection attempts.
  • Convert connection retrieval to return a promise and await the Redis client connection.
  • Track an in-flight connection promise so simultaneous callers share one attempt.
  • Reset connection state on ready, error, end, and connection exceptions.
src/cache/rediscache.client.ts
Make cache operations resilient when Redis is unavailable during startup or reconnects.
  • Add lazy ensureClient initialization with per-cache in-flight promise deduplication.
  • Guard all key, hash, mutation, scan, and deletion operations against a null client.
  • Return safe fallback values when connection establishment fails.
src/cache/rediscache.ts
Align local Docker service configuration with externally managed database/cache environments.
  • Publish Redis and PostgreSQL ports to the host.
  • Set the local PostgreSQL database name to evolution_api.
docker-compose.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="docker-compose.yaml" line_range="48" />
<code_context>
         aliases:
           - evolution-redis
+    ports:
+      - "6379:6379"
     expose:
       - "6379"
</code_context>
<issue_to_address>
**Unauthenticated Redis is network-exposed**

When the Compose host is reachable from an untrusted network, the published Redis port accepts connections on host interfaces, and Redis starts without authentication, so remote clients can read, alter, or delete cache data.

Remove the public Redis port mapping or bind it to localhost, and configure authentication if Redis must be reachable remotely.

Also at `docker-compose.yaml:70-71`.
</issue_to_address>

### Comment 2
<location path="docker-compose.yaml" line_range="71" />
<code_context>
     volumes:
       - postgres_data:/var/lib/postgresql/data
+    ports:
+      - "5432:5432"
     networks:
       - evolution-net
</code_context>
<issue_to_address>
**PostgreSQL is reachable off-host**

When the Compose host is reachable from an untrusted network and the configured database password is weak or compromised, the `5432:5432` mapping binds PostgreSQL on every host interface, allowing remote clients to send login attempts directly to the database and, with a weak or compromised password, access application data using the configured database role.

Bind the PostgreSQL port to localhost for local development or restrict it to a trusted private network instead of publishing it on all host interfaces.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and publishing Redis and PostgreSQL ports makes both services reachable through the host’s network interfaces, potentially exposing cached data and database contents or allowing writes wherever those interfaces are reachable. Reverting closes the ports, but cannot undo data access or changes that already occurred.

Blocking findings: docker-compose.yaml:48, docker-compose.yaml:71


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread docker-compose.yaml
aliases:
- evolution-redis
ports:
- "6379:6379"

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.

🔴 Critical · Unauthenticated Redis is network-exposed

When the Compose host is reachable from an untrusted network, the published Redis port accepts connections on host interfaces, and Redis starts without authentication, so remote clients can read, alter, or delete cache data.

Remove the public Redis port mapping or bind it to localhost, and configure authentication if Redis must be reachable remotely.

Also at docker-compose.yaml:70-71.

Prompt for AI agents
In `docker-compose.yaml` at line 48:

**Unauthenticated Redis is network-exposed**

When the Compose host is reachable from an untrusted network, the published Redis port accepts connections on host interfaces, and Redis starts without authentication, so remote clients can read, alter, or delete cache data.

Remove the public Redis port mapping or bind it to localhost, and configure authentication if Redis must be reachable remotely.

Also at `docker-compose.yaml:70-71`.

From AGENTS.md L316:

- **Secure defaults** for all configurations

Comment thread docker-compose.yaml
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"

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.

🟡 Medium · PostgreSQL is reachable off-host

When the Compose host is reachable from an untrusted network and the configured database password is weak or compromised, the 5432:5432 mapping binds PostgreSQL on every host interface, allowing remote clients to send login attempts directly to the database and, with a weak or compromised password, access application data using the configured database role.

Bind the PostgreSQL port to localhost for local development or restrict it to a trusted private network instead of publishing it on all host interfaces.

Prompt for AI agents
In `docker-compose.yaml` at line 71:

**PostgreSQL is reachable off-host**

When the Compose host is reachable from an untrusted network and the configured database password is weak or compromised, the `5432:5432` mapping binds PostgreSQL on every host interface, allowing remote clients to send login attempts directly to the database and, with a weak or compromised password, access application data using the configured database role.

Bind the PostgreSQL port to localhost for local development or restrict it to a trusted private network instead of publishing it on all host interfaces.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant