Skip to content

In-Memory Ban Status Caching #344

Description

@SagiEv

In-Memory Ban Status Caching

Implement a high-performance in-memory cache (Caffeine) to eliminate the PostgreSQL query currently happening on every authenticated API request in BannedUserFilter.

Design Choices / Fallback (Answering your question)

What happens if the system reboots and the cache is cleared?
We use a Read-Through Cache pattern via Spring's @Cacheable.

  1. When the system boots, the in-memory Caffeine cache is completely empty.
  2. When a banned user (or any user) makes their very first API request post-reboot, BannedUserFilter checks the cache.
  3. Because it's a cache miss, Spring automatically executes the underlying database query (UserRepository), retrieves the current status, and stores it in the Caffeine cache.
  4. For all subsequent requests from that user, the system reads from the lightning-fast memory cache.
    Conclusion: No banned users are "lost" on reboot. The system gracefully falls back to the database precisely once per user.

Proposed Changes

Configuration

[MODIFY] CacheConfig.java

  • Define a new constant: public static final String CACHE_USER_BAN_STATUS = "userBanStatus";
  • Register the cache in the cacheManager() bean with a reasonable configuration (e.g., maximum size 10,000 users, expire after write 2 hours).

Application Layer

[MODIFY] UserService.java

  • Add a new method public boolean isUserBanned(String username) that queries the database.
  • Annotate isUserBanned with @Cacheable(value = CacheConfig.CACHE_USER_BAN_STATUS, key = "#username").
  • Annotate the existing banUser(String username) method with @CacheEvict(value = CacheConfig.CACHE_USER_BAN_STATUS, key = "#username") to wipe the cache when a ban happens.
  • Annotate the existing unbanUser(String username) method with @CacheEvict(value = CacheConfig.CACHE_USER_BAN_STATUS, key = "#username").

Infrastructure / Security Layer

[MODIFY] BannedUserFilter.java

  • Change the dependency from UserRepository to UserService.
  • Replace the live database fetch userRepository.findByUsername(username) with a call to userService.isUserBanned(username).

Verification Plan

Automated Tests

  • Run npm run test:e2e:stories (already running) and standard backend tests.
  • Verify BannedUserFilterTest (if it exists) passes with mocked cache/service.

Manual Verification

  • Log in as a normal user. Verify the system works.
  • Admin bans the user. Verify the user is immediately blocked (403).
  • Restart the backend server.
  • The banned user attempts an API call. Verify they are still blocked (403), confirming the cache-miss fallback works perfectly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions