How the Metrics Caching Mechanism Works with TTL in MCP Ambari API

The MCP Ambari API implements a two-tier metrics caching system with TTL (time-to-live) eviction controlled by the AMBARI_METRICS_METADATA_TTL environment variable, using monotonic timestamps and a module-level lock to prevent thundering herds during cache refreshes.

The mcp-ambari-api repository provides a Model Context Protocol server that interfaces with the Ambari Metrics Service (AMS). To minimize expensive HTTP calls to AMS, the library employs a metrics caching mechanism with TTL that stores both per-application metadata entries and complete metric catalogs with configurable freshness guarantees.

Two-Tier Cache Architecture

The system maintains two distinct global caches in src/mcp_ambari_api/functions.py to optimize different access patterns:

Per-Application Metadata Cache

The _METRICS_METADATA_CACHE dictionary stores individual metadata entries for each app-id. Each entry contains a timestamp and an entries list representing the metric names available for that specific application. This cache provides fast lookup when querying metrics for a single service (for example, "namenode" or "datanode").

Dynamic Catalog Cache

The _DYNAMIC_CATALOG_CACHE dictionary stores the complete metric catalog structure, mapping every app-id to its sorted list of metric names. This cache includes both the catalog dictionary and a lookup table for case-insensitive metric name resolution. Both caches share the same TTL configuration.

TTL Configuration and Expiration Logic

The time-to-live (TTL) value is read once at import time from the environment:

AMBARI_METRICS_METADATA_TTL = float(os.environ.get("AMBARI_METRICS_METADATA_TTL", "300"))

Located at approximately lines 84–86 in src/mcp_ambari_api/functions.py, this constant defaults to 300 seconds (5 minutes) but can be overridden via the AMBARI_METRICS_METADATA_TTL environment variable.

When checking cache validity, the code compares the elapsed monotonic time against the stored timestamp:

now = time.monotonic()
cached = _METRICS_METADATA_CACHE.get(cache_key)
if cached and now - cached.get("timestamp", 0) < AMBARI_METRICS_METADATA_TTL:
    return cached.get("entries", [])

If the elapsed time exceeds the TTL, the entry is considered stale. The system then issues a fresh request to AMS and atomically replaces the cache entry with a new timestamp = time.monotonic().

Cache Refresh Flow and Concurrency Control

The ensure_metric_catalog function (lines ~108–158) orchestrates catalog building while preventing the thundering-herd problem. The implementation follows this sequence:

  1. Fast-path check: If use_cache=True and the catalog exists with now - cached_timestamp < TTL, return immediately without acquiring locks.

  2. Lock acquisition: On cache miss, acquire the module-level _catalog_lock to ensure only one coroutine rebuilds the catalog.

  3. Double-check pattern: After acquiring the lock, re-verify the timestamp (another coroutine may have refreshed the cache while waiting).

  4. Metadata fetch: If still expired, call await get_metrics_metadata(None, use_cache=use_cache), which respects the same TTL for individual metadata entries.

  5. Data processing: Filter raw metadata entries, resolve synonyms, drop excluded app-ids, and assemble the final catalog and lookup structures.

  6. Atomic replacement: Update the global _DYNAMIC_CATALOG_CACHE with fresh data and the new timestamp.

Practical Usage Examples

Fetching Metadata for a Single App-id

The get_metrics_metadata function automatically applies TTL checking:

from mcp_ambari_api.functions import get_metrics_metadata

# First call triggers AMS request and caches result for 300s

metadata = await get_metrics_metadata("namenode")

# Subsequent calls within TTL return cached list instantly (no network I/O)

metadata_again = await get_metrics_metadata("namenode")

The cache key is the lower-cased app-id ("namenode"). The timestamp comparison occurs before any HTTP request is issued.

Building the Full Metric Catalog

For complete catalog access with built-in concurrency protection:

from mcp_ambari_api.functions import get_metric_catalog

# Fast path uses catalog cache if fresh

catalog = await get_metric_catalog()  # Returns {app_id: [sorted metric names]}

# When TTL expires, the first coroutine acquires the lock, rebuilds the catalog,

# and waiting coroutines receive the same fresh data upon completion

Customizing TTL at Runtime

Adjust the caching duration without code changes:


# Default 5-minute TTL

export AMBARI_METRICS_METADATA_TTL=300

# Shorter 60-second TTL for rapid testing

export AMBARI_METRICS_METADATA_TTL=60

PYTHONPATH=./src uv run python -m mcp_ambari_api

Summary

  • The MCP Ambari API uses two global caches (_METRICS_METADATA_CACHE and _DYNAMIC_CATALOG_CACHE) defined in src/mcp_ambari_api/functions.py to store AMS metadata.
  • A single environment variable AMBARI_METRICS_METADATA_TTL (default 300s) controls expiration for both caches.
  • Cache freshness is determined by comparing time.monotonic() against stored timestamps.
  • The _catalog_lock prevents multiple coroutines from simultaneously rebuilding expired catalogs (thundering-herd protection).
  • Both get_metrics_metadata() and ensure_metric_catalog() implement double-checking patterns for race-condition safety.

Frequently Asked Questions

What is the default TTL for metrics caching?

The default TTL is 300 seconds (5 minutes). This value is hardcoded as a fallback in src/mcp_ambari_api/functions.py at line 84, where the code reads os.environ.get("AMBARI_METRICS_METADATA_TTL", "300").

How does the cache handle concurrent requests when expired?

When the TTL expires, the first coroutine to reach the cache check acquires the module-level _catalog_lock. Subsequent coroutines block on this lock. Once the first coroutine finishes rebuilding the catalog, waiting coroutines proceed and receive the freshly cached data without triggering additional AMS requests.

Can I disable metrics caching entirely?

While there is no explicit "disable" flag, setting AMBARI_METRICS_METADATA_TTL=0 effectively disables caching by causing every timestamp check to fail the freshness test. Alternatively, pass use_cache=False to get_metrics_metadata() or get_metric_catalog() to bypass the cache for specific calls.

Where is the TTL validation logic located in the source code?

The TTL constant definition resides at approximately lines 84–86 in src/mcp_ambari_api/functions.py. The validation logic for the metadata cache appears around lines 88–106 in the get_metrics_metadata function, while the catalog cache TTL check and lock handling occur around lines 108–158 in the ensure_metric_catalog function.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →