MP Observability
Event-driven observability for LMCache's multiprocess (MP) mode, built on OpenTelemetry.
For metrics, see METRICS.md.
For event metadata contracts, see
EVENTS.md.
For design rationale, see
event-bus.md.
For the trace recording subsystem (lmcache trace), see
trace.md.
Architecture
Producers (L1Manager, StorageManager, MPCacheServer)
│
│ event_bus.publish(Event(...))
▼
EventBus (async queue + drain thread)
│
├──► L1MetricsSubscriber → OTel counter.add(...)
├──► SMMetricsSubscriber → OTel counter.add(...)
├──► EventBusSelfMetricsSubscriber → OTel observable gauges/counters
│ (bus health: queue depth, drain
│ lag, drops, subscriber exceptions)
├──► L1LoggingSubscriber → logger.debug(...)
├──► SMLoggingSubscriber → logger.debug(...)
├──► MPServerLoggingSubscriber → logger.debug(...)
└──► MPServerTracingSubscriber → OTel span start/end
OTel SDK (configured at startup)
│
├──► OTLP push (production) → OTel collector → Prometheus / Grafana / etc.
└──► Prometheus pull (dev/debug) → /metrics on configured port
Configuration
All observability behaviour is controlled by ObservabilityConfig
(defined in config.py). When running the LMCache MP mode server from the
CLI, pass the flags below; when embedding programmatically, construct an
ObservabilityConfig directly.
CLI flags
| Flag | Default | Description |
|---|---|---|
--disable-observability |
off | Disable the EventBus entirely. No events are published or consumed. |
--disable-metrics |
off | Skip registering metrics subscribers (OTel counters). |
--disable-logging |
off | Skip registering logging subscribers. |
--enable-tracing |
off | Register tracing subscribers (OTel spans). Disabled by default. Requires --otlp-endpoint. |
--event-bus-queue-size N |
10000 |
Maximum number of events in the EventBus queue before tail-drop. |
--otlp-endpoint URL |
(none) | OTLP gRPC endpoint (e.g. http://localhost:4317). When set, metrics and traces are pushed to an OTel collector. When unset, metrics fall back to Prometheus pull mode. |
--prometheus-port PORT |
9090 |
Port for the Prometheus /metrics endpoint. Only used when --otlp-endpoint is not set. |
ObservabilityConfig fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
True |
Master switch for the EventBus. |
max_queue_size |
int |
10000 |
Maximum events in the EventBus queue before tail-drop. |
metrics_enabled |
bool |
True |
Register metrics subscribers (OTel counters / histograms). |
logging_enabled |
bool |
True |
Register logging subscribers. |
tracing_enabled |
bool |
False |
Register tracing subscribers (OTel spans). |
otlp_endpoint |
str | None |
None |
OTLP gRPC endpoint. When set, metrics and traces are pushed. When None, metrics use Prometheus pull fallback. |
prometheus_port |
int |
9090 |
Port for the Prometheus /metrics endpoint (pull fallback only). |
service_instance_id |
str | None |
None |
OTel Resource attribute service.instance.id, attached to every metric and span. No CLI flag: run_cache_server projects the MP server's --instance-id onto it. None (standalone callers only) falls back to a random UUID v4 at init_observability time; an explicit value is preserved. |
Metrics export modes
otlp_endpoint |
Mode | How to query |
|---|---|---|
http://host:4317 |
OTLP push | Query the OTel collector's Prometheus exporter |
None |
Prometheus pull fallback | curl http://localhost:<prometheus-port>/metrics |
Note: OTel counters only appear on
/metricsafter the first increment. If you see only Python runtime metrics, trigger a store/retrieve first.
Tracing
Tracing is opt-in (--enable-tracing). When enabled, MPServerTracingSubscriber
creates OTel spans from MP server START/END event pairs (store, retrieve,
lookup/prefetch). Trace export requires an OTLP endpoint — there is no local
fallback. --enable-tracing requires --otlp-endpoint; the server will
raise a ValueError at startup if the endpoint is missing.
How to Add a New Event and Subscriber
Step 1 — Define the event type
Add a new member to EventType in event.py:
class EventType(Enum):
# ... existing events ...
# My new component events
MY_COMPONENT_OPERATION = "my_component.operation"
Step 2 — Publish the event from the producer
In your component (e.g., a manager class), publish to the EventBus:
from lmcache.v1.mp_observability.event import Event, EventType
from lmcache.v1.mp_observability.event_bus import get_event_bus
class MyComponent:
def __init__(self):
self._event_bus = get_event_bus()
def do_operation(self, keys):
# ... business logic ...
self._event_bus.publish(Event(
event_type=EventType.MY_COMPONENT_OPERATION,
metadata={"keys": keys},
))
Step 3 — Create a subscriber
Create a file under the appropriate subscribers/ subdirectory:
subscribers/metrics/for OTel counters / histogramssubscribers/logging/for debug log outputsubscribers/tracing/for OTel spans
Example metrics subscriber (subscribers/metrics/my_component.py):
from opentelemetry import metrics
from lmcache.v1.mp_observability.event import Event, EventType
from lmcache.v1.mp_observability.event_bus import EventCallback, EventSubscriber
class MyComponentMetricsSubscriber(EventSubscriber):
def __init__(self):
meter = metrics.get_meter("lmcache.my_component")
self._op_counter = meter.create_counter(
"lmcache_mp.my_component_operations",
description="Total operations on my component",
)
def get_subscriptions(self) -> dict[EventType, EventCallback]:
return {
EventType.MY_COMPONENT_OPERATION: self._on_operation,
}
def _on_operation(self, event: Event) -> None:
self._op_counter.add(len(event.metadata["keys"]))
Step 4 — Export from __init__.py
Add the subscriber to the corresponding __init__.py so it can be
imported from the package:
# subscribers/metrics/__init__.py
from lmcache.v1.mp_observability.subscribers.metrics.my_component import (
MyComponentMetricsSubscriber,
)
Step 5 — Register the subscriber at startup
In the server startup function (e.g., run_cache_server() in server.py),
register conditionally based on ObservabilityConfig:
if obs_config.metrics_enabled:
from lmcache.v1.mp_observability.subscribers.metrics import (
MyComponentMetricsSubscriber,
)
bus.register_subscriber(MyComponentMetricsSubscriber())
Step 6 — Document the metadata contract
Add a row to the metadata contracts table in EVENTS.md so subscribers can rely on the schema:
| `MY_COMPONENT_OPERATION` | `keys` | `list[ObjectKey]` |
Design rules
| Rule | Reason |
|---|---|
Create meters and counters in __init__(), not at module level |
MeterProvider must be set before get_meter() is called. Module-level calls happen at import time, before setup. |
Prefix OTel metric names with lmcache_mp. |
Keeps the MP namespace separate from lmcache. (the single-process engine namespace). |
Use metadata: dict[str, Any] for event payloads |
Flexible, no coupling between producers and subscribers. See metadata contracts in EVENTS.md. |
| Separate metrics, logging, and tracing subscribers | Single responsibility. Can enable/disable independently via config. |
Store self._event_bus = get_event_bus() in __init__ |
Avoids calling the singleton getter on every publish. |