Skip to content

feat(metadata): introduce MetadataStore abstraction layer#1981

Open
NJX-njx wants to merge 1 commit intovllm-project:mainfrom
NJX-njx:feat/1642-metadata-store-abstraction
Open

feat(metadata): introduce MetadataStore abstraction layer#1981
NJX-njx wants to merge 1 commit intovllm-project:mainfrom
NJX-njx:feat/1642-metadata-store-abstraction

Conversation

@NJX-njx
Copy link

@NJX-njx NJX-njx commented Mar 4, 2026

Summary

Introduces a MetadataStore abstract base class to decouple the metadata service from direct Redis client usage, improving testability and enabling future backend flexibility.

Changes

New: store.py - MetadataStore abstraction

  • MetadataStore ABC with get/set/exists/delete/ping/close interface
  • RedisMetadataStore implementation wrapping
    edis.asyncio.Redis
  • Backward-compatible .client property for migration period

Modified: �pp.py - Lifespan management

  • Creates RedisMetadataStore instead of raw
    edis.Redis
  • Exposes both �pp.state.metadata_store (new) and �pp.state.redis_client (backward compat via .client)
  • Health check uses metadata_store.ping() with fallback to
    edis_client.ping()
  • Shutdown closes via metadata_store.close()

Modified: users.py - CRUD operations

  • Imports and uses MetadataStore interface instead of
    edis.asyncio
  • _get_metadata_store()\ replaces _get_redis_client()\
  • All CRUD operations use store methods directly

Modified: est_users_api.py - Tests

  • \MockMetadataStore\ (in-memory dict-backed) replaces \AsyncMock()\ Redis mocking
  • Tests use real store behavior instead of mock return value configuration
  • Cleaner, more maintainable test setup

Benefits

  • Testability: Tests use \MockMetadataStore\ with real behavior instead of fragile mocks
  • Extensibility: Easy to swap in etcd, DynamoDB, or other backends by implementing \MetadataStore\
  • Separation of concerns: Business logic decoupled from storage implementation
  • Backward compatibility: Existing code using
    edis_client\ continues to work via .client\ property

Closes #1642

Add MetadataStore abstract base class and RedisMetadataStore implementation
to decouple metadata operations from the Redis client directly.

Changes:
- Add store.py with MetadataStore ABC defining get/set/exists/delete/ping/close
- Add RedisMetadataStore wrapping redis.asyncio.Redis with backward compat .client property
- Update app.py lifespan to create RedisMetadataStore, expose both metadata_store and redis_client
- Update users.py to use MetadataStore interface instead of raw redis.Redis
- Update tests to use MockMetadataStore (in-memory dict-backed implementation)

This enables future backend flexibility (e.g., etcd, in-memory) and
simplifies testing without Redis mocking.

Closes vllm-project#1642
Copilot AI review requested due to automatic review settings March 4, 2026 11:09
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural improvement by abstracting the metadata storage layer. By creating a MetadataStore interface and a RedisMetadataStore implementation, the system gains enhanced testability, allowing for easier mocking and isolation of business logic. It also paves the way for greater extensibility, enabling future integration with different backend technologies like etcd or DynamoDB without extensive code changes. The design ensures backward compatibility, facilitating a smooth transition for existing components.

Highlights

  • MetadataStore Abstraction Introduced: A new abstract base class, MetadataStore, has been added to decouple metadata service operations from direct Redis client usage. This includes an initial RedisMetadataStore implementation.
  • Application Lifespan Management Updated: The app.py module now initializes and manages the RedisMetadataStore during startup and shutdown, ensuring proper resource handling. Readiness checks prioritize the new store while maintaining backward compatibility.
  • User CRUD Operations Refactored: The users.py API endpoints have been updated to utilize the MetadataStore interface for all create, read, update, and delete operations, replacing direct calls to the Redis client.
  • Improved Testability: Unit tests for the user API (test_users_api.py) now use an in-memory MockMetadataStore instead of AsyncMock for Redis, leading to more robust and maintainable tests.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • python/aibrix/aibrix/metadata/api/v1/users.py
    • Removed direct import of redis.asyncio.
    • Imported the new MetadataStore.
    • Renamed the internal helper function _get_redis_client to _get_metadata_store and updated its return type annotation.
    • Modified _gen_key docstring to refer to 'storage key' instead of 'Redis key'.
    • Updated all CRUD operations (create_user, read_user, update_user, delete_user) to interact with the MetadataStore interface methods (exists, set, get, delete) instead of the raw Redis client.
  • python/aibrix/aibrix/metadata/app.py
    • Imported RedisMetadataStore.
    • Updated the readiness_check endpoint to first ping app.state.metadata_store and fall back to app.state.redis_client if the new store is not present.
    • Modified the lifespan startup event to initialize RedisMetadataStore and assign it to app.state.metadata_store.
    • Ensured backward compatibility by assigning the underlying Redis client from metadata_store.client to app.state.redis_client.
    • Updated logging messages to reflect the initialization of the 'Metadata store'.
    • Modified the lifespan shutdown event to close app.state.metadata_store first, with a fallback to app.state.redis_client.aclose().
  • python/aibrix/aibrix/metadata/store.py
    • Added a new file defining the MetadataStore abstract base class.
    • Implemented abstract methods: get, set, exists, delete, ping, and close.
    • Added RedisMetadataStore as a concrete implementation of MetadataStore, wrapping redis.asyncio.Redis.
    • Included a client property in RedisMetadataStore to expose the underlying Redis client for backward compatibility during migration.
  • python/aibrix/tests/metadata/test_users_api.py
    • Removed the import of unittest.mock.AsyncMock.
    • Imported the new MetadataStore.
    • Added a MockMetadataStore class, an in-memory dictionary-backed implementation of MetadataStore, for testing purposes.
    • Replaced the mock_redis pytest fixture with a mock_store fixture that provides an instance of MockMetadataStore.
    • Updated the app_with_redis fixture to app_with_store, configuring the FastAPI app to use mock_store as its metadata_store.
    • Modified all user CRUD test cases to directly interact with and assert against the mock_store's internal state, removing reliance on AsyncMock return value configurations.
    • Updated the test class docstring to reflect MetadataStore-backed user management.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a MetadataStore abstraction to decouple the metadata service from direct Redis client usage, improving testability and enabling future backend flexibility (per #1642).

Changes:

  • Added MetadataStore ABC and RedisMetadataStore implementation.
  • Updated metadata app startup/shutdown and readiness checks to use metadata_store (with redis_client kept for backward compatibility).
  • Updated Users CRUD API and its tests to use the MetadataStore interface and an in-memory mock store.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
python/aibrix/aibrix/metadata/store.py Adds MetadataStore interface and Redis-backed implementation (plus .client migration shim).
python/aibrix/aibrix/metadata/app.py Initializes/tears down the new store and updates /readyz health logic.
python/aibrix/aibrix/metadata/api/v1/users.py Migrates user CRUD endpoints from direct Redis calls to MetadataStore.
python/aibrix/tests/metadata/test_users_api.py Replaces Redis AsyncMock usage with an in-memory MockMetadataStore.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +94 to +104
# Initialize metadata store (abstraction over Redis)
metadata_store = RedisMetadataStore(
host=envs.STORAGE_REDIS_HOST or "localhost",
port=envs.STORAGE_REDIS_PORT,
db=envs.STORAGE_REDIS_DB,
password=envs.STORAGE_REDIS_PASSWORD,
decode_responses=False,
)
app.state.metadata_store = metadata_store
# Backward compatibility: expose underlying Redis client for components
# that haven't migrated to the MetadataStore interface yet
app.state.redis_client = metadata_store.client
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

lifespan() unconditionally creates and assigns a new RedisMetadataStore to app.state.metadata_store (and resets app.state.redis_client). This makes it hard to inject a mock/in-memory store for tests or alternative bootstrapping code, because any pre-set metadata_store will be overwritten when lifespan runs. Consider initializing the store only if it isn't already present (or allowing an override via args/env), so callers/tests can provide their own store instance.

Copilot uses AI. Check for mistakes.
async def ping(self) -> bool:
try:
return await self._client.ping()
except Exception:
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

RedisMetadataStore.ping() swallows all exceptions and returns False without logging. This makes failures harder to diagnose and (combined with callers that don't check the return value) can silently mask outages. Consider either (a) logging the exception and returning False, with callers explicitly checking the result, or (b) letting exceptions propagate and treating failures via exception handling.

Suggested change
except Exception:
except Exception:
logger.exception("Redis metadata store ping failed")

Copilot uses AI. Check for mistakes.
- User Pydantic model validation
- User CRUD API: Redis-backed user management with rate limiting
- User CRUD API: MetadataStore-backed user management with rate limiting
- MetadataStore abstraction unit tests
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The module docstring claims "MetadataStore abstraction unit tests", but this file only defines a MockMetadataStore helper and exercises the Users API. Consider updating the stated test coverage to match what’s actually covered here (or add dedicated unit tests for MetadataStore implementations elsewhere).

Suggested change
- MetadataStore abstraction unit tests

Copilot uses AI. Check for mistakes.
Comment on lines 115 to 130
@@ -95,14 +124,14 @@ def app_with_redis(self, mock_redis):
e2e_test=False,
)
app = build_app(args)
app.state.redis_client = mock_redis
app.state.metadata_store = mock_store
# Also set redis_client for backward compatibility (via .client if needed)
app.state.redis_client = None
return app
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

app_with_store sets app.state.metadata_store = mock_store, but build_app() attaches the lifespan handler which currently overwrites app.state.metadata_store on startup. If the test client triggers lifespan events, the real RedisMetadataStore will replace this mock and the tests may unexpectedly hit a real Redis instance. Consider adjusting lifespan() to respect a pre-configured metadata_store, or disabling lifespan/startup in these tests so the injected mock is actually used.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to 54
await request.app.state.metadata_store.ping()
# Backward compatibility: check redis_client if metadata_store not set
elif hasattr(request.app.state, "redis_client"):
await request.app.state.redis_client.ping()
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

readiness_check awaits metadata_store.ping() but ignores its boolean result. With the current RedisMetadataStore.ping() implementation returning False on failure (instead of raising), /readyz can incorrectly report "ready" even when the backend is unhealthy. Consider checking the returned value and returning 503 when it is falsy (or change ping() to raise on failure and keep this handler exception-based).

Suggested change
await request.app.state.metadata_store.ping()
# Backward compatibility: check redis_client if metadata_store not set
elif hasattr(request.app.state, "redis_client"):
await request.app.state.redis_client.ping()
ping_ok = await request.app.state.metadata_store.ping()
if not ping_ok:
logger.error("Metadata store ping returned a falsy result.")
return JSONResponse(
content={"status": "not ready", "error": "metadata store unavailable"},
status_code=503,
)
# Backward compatibility: check redis_client if metadata_store not set
elif hasattr(request.app.state, "redis_client"):
ping_ok = await request.app.state.redis_client.ping()
if not ping_ok:
logger.error("Redis client ping returned a falsy result.")
return JSONResponse(
content={"status": "not ready", "error": "redis client unavailable"},
status_code=503,
)

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a MetadataStore abstraction layer, successfully decoupling the metadata service from direct Redis client usage. While this architectural improvement is well-implemented across users.py and app.py, and significantly enhances the test suite in test_users_api.py with a robust MockMetadataStore, a critical security vulnerability has been identified. The newly introduced or modified User CRUD API endpoints in users.py lack essential access control checks. This oversight allows for unauthorized viewing and modification of user metadata, which could be exploited to bypass rate limits or disrupt service for other users. It is strongly recommended to implement authentication and authorization before deploying these changes to a production environment.

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.

[metadata] provide better abstraction for redis client

2 participants