Production Caching Strategy¶
Caching architecture for the Cogniverse multi-agent video search system. Two independent caching systems exist:
- Pipeline artifact cache (
cogniverse_core.common.cache) — durable, disk/S3-backed cache for video processing outputs (keyframes, transcripts, descriptions, segment frames), keyed by tenant/schema profile. - Tenant instance cache (
cogniverse_foundation.caching.TenantLRUCache) — bounded in-memory LRU cache for per-tenant singleton objects (agent instances, memory managers, backend clients, registry instances), used to prevent unbounded per-tenant memory growth in long-running processes.
Overview¶
The pipeline artifact cache provides a unified interface for caching across ingestion components with:
- Pluggable backends: Extensible via
CacheBackendABC - Multi-tenant isolation: Key prefixing with profile/schema_name
- Priority-based tiers: Multiple backends with configurable priority
- Pipeline integration: Specialized caching for video processing artifacts
The tenant instance cache provides bounded, thread-safe, per-tenant object caching with:
- Capacity-bounded LRU eviction: Oldest-used tenant is evicted when a configurable capacity is exceeded
- Atomic get-or-build:
get_or_setresolves or builds a per-tenant instance under a lock so concurrent callers share one instance - Eviction callbacks: An optional
on_evicthook releases native resources (Vespa clients, gRPC channels) when a tenant is evicted
Architecture¶
flowchart TD
PAC["<span style='color:#000'><b>PipelineArtifactCache</b><br/>Video Processing</span>"] --> CM["<span style='color:#000'><b>CacheManager</b><br/>Multi-Backend Orchestration</span>"]
CM --> SFS["<span style='color:#000'><b>StructuredFilesystemBackend</b><br/>Local Storage (priority 0)</span>"]
CM --> S3["<span style='color:#000'><b>S3CacheBackend</b><br/>S3/MinIO (priority 1)</span>"]
TLC["<span style='color:#000'><b>TenantLRUCache</b><br/>In-Process Instance Cache</span>"] --> AGT["<span style='color:#000'>Agent instances<br/>(text_analysis_agent)</span>"]
TLC --> MEM["<span style='color:#000'>Mem0MemoryManager<br/>per-tenant singletons</span>"]
TLC --> BR["<span style='color:#000'>BackendRegistry<br/>per-(backend,tenant) clients</span>"]
TLC --> EPR["<span style='color:#000'>EntryPointRegistry<br/>per-tenant provider instances</span>"]
style PAC fill:#ce93d8,stroke:#7b1fa2,color:#000
style CM fill:#ce93d8,stroke:#7b1fa2,color:#000
style SFS fill:#a5d6a7,stroke:#388e3c,color:#000
style S3 fill:#81d4fa,stroke:#0288d1,color:#000
style TLC fill:#ffcc80,stroke:#ef6c00,color:#000
style AGT fill:#b0bec5,stroke:#546e7a,color:#000
style MEM fill:#b0bec5,stroke:#546e7a,color:#000
style BR fill:#b0bec5,stroke:#546e7a,color:#000
style EPR fill:#b0bec5,stroke:#546e7a,color:#000 Cache Backend Interface¶
The actual interface from cogniverse_core.common.cache.base:
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Tuple
class CacheBackend(ABC):
"""Abstract base class for cache backends"""
@abstractmethod
async def get(self, key: str) -> Optional[Any]:
"""Retrieve value from cache"""
pass
@abstractmethod
async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
"""Store value in cache with optional TTL in seconds"""
pass
@abstractmethod
async def delete(self, key: str) -> bool:
"""Delete key from cache"""
pass
@abstractmethod
async def exists(self, key: str) -> bool:
"""Check if key exists in cache"""
pass
@abstractmethod
async def clear(self, pattern: Optional[str] = None) -> int:
"""Clear cache entries matching pattern"""
pass
@abstractmethod
async def get_stats(self) -> Dict[str, Any]:
"""Get cache statistics"""
pass
# Optional methods
async def get_metadata(self, key: str) -> Optional[Dict[str, Any]]:
"""Get metadata for a key (optional)"""
return None
async def list_keys(
self, pattern: Optional[str] = None, include_metadata: bool = False
) -> List[Tuple[str, Optional[Dict[str, Any]]]]:
"""List keys matching pattern (optional)"""
return []
async def cleanup_expired(self) -> int:
"""Clean up expired entries (optional)"""
return 0
Cache Manager¶
Manages multiple cache backends with priority-based tier promotion:
from cogniverse_core.common.cache.base import CacheManager, CacheConfig
# Configuration
cache_config = CacheConfig(
backends=[
{
"backend_type": "structured_filesystem",
"priority": 0,
"enabled": True,
"base_path": "/var/cache/cogniverse",
"enable_ttl": True,
"cleanup_on_startup": True
}
],
default_ttl=604800, # 7 days
enable_compression=True,
serialization_format="pickle"
)
# Initialize
manager = CacheManager(cache_config)
# Usage
await manager.set("my_key", data, ttl=3600)
value = await manager.get("my_key")
await manager.delete("my_key")
stats = await manager.get_stats()
Key Features: - Priority-based backend selection (lower number = higher priority) - Automatic tier promotion on cache hits - Aggregated statistics across all backends
CacheConfig.enable_stats gates statistics collection: when False, CacheManager stops incrementing its hit/miss/set/delete counters and get_stats() returns {"enabled": False}. CacheConfig.enable_compression and serialization_format are both propagated to each backend that does not override them (see _initialize_backends) — a backend's own CONFIG_CLASS field (e.g. StructuredFilesystemConfig.enable_compression / .serialization_format) is what it actually reads at runtime, and wins over the manager-level value when the backend's own config dict sets it explicitly. enable_compression controls whether _serialize gzip-compresses the payload; serialization_format selects pickle/json/msgpack encoding.
Multi-Tenant Isolation¶
Tenant isolation is achieved through key prefixing using the schema name:
from cogniverse_core.common.cache.pipeline_cache import PipelineArtifactCache
# Profile is set to tenant-specific schema name
cache = PipelineArtifactCache(
cache_manager=manager,
ttl=604800,
profile="video_frames_acme" # Tenant-specific schema name
)
# Keys are automatically prefixed with the profile and a SHA-256 digest
# of the canonicalized video path (see Key Structure below):
# "video_frames_acme:video:a3f2e9d8c7b6a5f4:keyframes:..."
Key Structure:
The video segment is not the human-readable video name — _generate_video_key canonicalizes the video path to a URI (file://<absolute> for bare paths) and SHA-256 hashes it, keeping the first 16 hex characters. Artifact parameters are appended sorted alphabetically by keyword (sorted(kwargs.items())), not in call order:
{profile}:video:{sha256_digest_16hex}:{artifact_type}[:param=value ...]
Examples:
video_frames_acme:video:a3f2e9d8c7b6a5f4:keyframes:max_frames=3000:strategy=similarity:threshold=0.999
video_frames_acme:video:a3f2e9d8c7b6a5f4:transcript:lang=auto:model=base
document_content_startup:video:7c1b9e4f0a2d8863:transcript:lang=auto:model=base
Pipeline Artifact Cache¶
Specialized caching for video processing results:
from cogniverse_core.common.cache.pipeline_cache import PipelineArtifactCache
cache = PipelineArtifactCache(
cache_manager=manager,
ttl=604800, # 7 days
profile=schema_name # Tenant isolation
)
# Cache keyframes
await cache.set_keyframes(
video_path="/path/to/video.mp4",
keyframes_metadata=keyframes_data,
keyframe_images=None, # Optional
strategy="similarity",
threshold=0.999,
max_frames=3000
)
# Retrieve cached keyframes (with default parameters)
cached = await cache.get_keyframes(
video_path="/path/to/video.mp4",
strategy="similarity",
threshold=0.999,
max_frames=3000
)
# Cache transcript
await cache.set_transcript(
video_path="/path/to/video.mp4",
transcript_data=transcript_data,
model_size="base",
language=None
)
# Cache VLM frame descriptions
await cache.set_descriptions(
video_path="/path/to/video.mp4",
descriptions_data=descriptions_data,
model_name="Qwen/Qwen2-VL-2B-Instruct",
batch_size=500
)
descriptions = await cache.get_descriptions(
video_path="/path/to/video.mp4",
model_name="Qwen/Qwen2-VL-2B-Instruct",
batch_size=500
)
# Cache temporal segment frames (e.g. for chunked/segment-based profiles)
await cache.set_segment_frames(
video_path="/path/to/video.mp4",
segment_id=0,
start_time=0.0,
end_time=6.0,
frames=frame_list,
timestamps=timestamp_list,
sampling_fps=2.0,
max_frames=12
)
segment = await cache.get_segment_frames(
video_path="/path/to/video.mp4",
segment_id=0,
start_time=0.0,
end_time=6.0
)
# Invalidate all cache entries for a video
await cache.invalidate_video(video_path="/path/to/video.mp4")
PipelineArtifactCache supports four artifact types end to end: keyframes, audio transcripts, frame descriptions, and temporal segment frames, each fetched with its own getter. Full per-method parameter documentation lives in docs/modules/cache.md.
Structured Filesystem Backend¶
The default backend implementation using local filesystem:
from cogniverse_core.common.cache.backends.structured_filesystem import (
StructuredFilesystemBackend,
StructuredFilesystemConfig
)
config = StructuredFilesystemConfig(
backend_type="structured_filesystem",
base_path="/var/cache/cogniverse", # Override default (~/.cache/cogniverse/pipeline)
serialization_format="pickle",
enabled=True,
priority=0,
enable_ttl=True,
cleanup_on_startup=True
)
backend = StructuredFilesystemBackend(config)
# Metadata stored alongside cached files
# Supports pattern-based listing and cleanup
# TTL enforcement and cleanup on startup
S3 Cache Backend¶
The S3/MinIO backend stores artifacts remotely and is registered under the key "s3":
from cogniverse_core.common.cache.backends.s3 import S3CacheBackend, S3CacheBackendConfig
config = S3CacheBackendConfig(
backend_type="s3",
# endpoint/access_key/secret_key default to MINIO_* env vars when None
endpoint=None, # falls back to MINIO_ENDPOINT
access_key=None, # falls back to MINIO_ACCESS_KEY
secret_key=None, # falls back to MINIO_SECRET_KEY
bucket="cogniverse-pipeline-cache",
key_prefix="pipeline/",
priority=1,
enabled=True,
)
backend = S3CacheBackend(config)
# Uses asyncio.to_thread for async I/O; envelope metadata stored in S3
# object Metadata["cache-meta"] as JSON.
To use S3 as a second tier alongside the local filesystem:
pipeline_cache:
enabled: true
default_ttl: 604800
enable_compression: true
serialization_format: pickle
backends:
- backend_type: structured_filesystem
priority: 0
enabled: true
base_path: /var/cache/cogniverse
enable_ttl: true
cleanup_on_startup: true
- backend_type: s3
priority: 1
enabled: true
# endpoint, access_key, secret_key read from MINIO_* env vars
bucket: cogniverse-pipeline-cache
key_prefix: pipeline/
Usage in Ingestion Pipeline¶
from cogniverse_runtime.ingestion.pipeline import VideoIngestionPipeline
from cogniverse_foundation.config.utils import create_default_config_manager
# Initialize config manager with cache configuration
config_manager = create_default_config_manager()
# Pipeline automatically initializes cache from config_manager
pipeline = VideoIngestionPipeline(
tenant_id="my_tenant",
config_manager=config_manager,
schema_name="video_frames_acme"
)
# Cache configuration is read from config_manager's app_config:
# {
# "pipeline_cache": {
# "enabled": True,
# "backends": [
# {
# "backend_type": "structured_filesystem",
# "base_path": "/var/cache/cogniverse",
# "priority": 0,
# "enabled": True,
# "enable_ttl": True,
# "cleanup_on_startup": True
# }
# ],
# "default_ttl": 604800,
# "enable_compression": True,
# "serialization_format": "pickle"
# }
# }
# Cache is used automatically during processing:
# - Keyframes cached after extraction
# - Transcriptions cached after audio processing
# - Frame descriptions cached after VLM inference
# - Segment frames cached for chunked/segment-based profiles
#
# Note: embeddings themselves are not cached by PipelineArtifactCache —
# embedding generation is not part of this cache's supported artifact set.
Configuration¶
pipeline_cache:
enabled: true
default_ttl: 604800 # 7 days
enable_compression: true
serialization_format: pickle
backends:
- backend_type: structured_filesystem
priority: 0
enabled: true
base_path: /var/cache/cogniverse
enable_ttl: true
cleanup_on_startup: true
Cache Statistics¶
stats = await manager.get_stats()
# Returns:
{
"manager": {
"hits": 1234,
"misses": 56,
"sets": 789,
"deletes": 12,
"hit_rate": 0.956,
"size_bytes": 52428800,
"total_files": 456
},
"backends": {
"StructuredFilesystemBackend": {
"hits": 0,
"misses": 0,
"sets": 0,
"deletes": 0,
"evictions": 0,
"size_bytes": 52428800,
"total_files": 456,
"metadata_files": 456
}
}
}
Best Practices¶
- Use profile for tenant isolation: Always set
profileto tenant-specific schema name - Set appropriate TTLs: Match TTL to data freshness requirements
- Monitor hit rates: Low hit rates may indicate cache sizing issues
- Enable TTL enforcement: Set
enable_ttl: trueandcleanup_on_startup: trueto prevent disk exhaustion - Choose serialization and compression deliberately:
serialization_format(picklefor speed,jsonfor human-readable debugging,msgpackfor compact binary) andenable_compression(gzip) both default from the top-levelCacheConfigbut can be overridden per backend — set them directly on a backend's config dict when one tier needs different tradeoffs than the rest - Bound tenant instance caches: For
TenantLRUCacheusages, sizecapacityto the expected concurrent-tenant count; passon_evictwhenever the cached object holds a native resource (Vespa client, gRPC channel) that needs explicit cleanup
Backend Registry¶
The CacheBackendRegistry dispatches on backend_type using each backend's CONFIG_CLASS class variable:
from cogniverse_core.common.cache import CacheBackendRegistry
registry = CacheBackendRegistry()
# Registered backend types:
# "structured_filesystem" → StructuredFilesystemBackend (CONFIG_CLASS = StructuredFilesystemConfig)
# "s3" → S3CacheBackend (CONFIG_CLASS = S3CacheBackendConfig)
backend = registry.create({"backend_type": "s3", "bucket": "my-bucket"})
Extra keys in the config dict are filtered to only the fields declared in CONFIG_CLASS, preventing TypeError on unexpected keys.
Tenant Instance Cache¶
Separate from the pipeline artifact cache, TenantLRUCache (cogniverse_foundation.caching) is a generic, thread-safe, capacity-bounded LRU cache keyed by tenant_id. It has no TTL and no backends — it exists so long-running, multi-tenant processes don't accumulate one instance per tenant forever (Mem0 memory managers, compiled DSPy modules, Vespa backend clients all hold native resources).
from cogniverse_foundation.caching import TenantLRUCache
cache: TenantLRUCache[MyPerTenantObject] = TenantLRUCache(
capacity=16,
on_evict=lambda tenant_id, instance: instance.close(), # optional cleanup
)
# Atomic get-or-build: concurrent callers share one instance per tenant
instance = cache.get_or_set("tenant_acme", lambda: MyPerTenantObject(tenant_id="tenant_acme"))
# Direct access
cache.set("tenant_acme", instance)
cached = cache.get("tenant_acme") # None if absent, moves entry to MRU
cache.pop("tenant_acme") # remove without triggering on_evict
list(cache.keys()) # tenant ids, LRU order
list(cache.values()) # snapshot of cached instances
cache.clear() # evicts everything, invoking on_evict per entry
capacity must be >= 1 (raises ValueError otherwise). When set/get_or_set pushes the cache over capacity, the least-recently-used entry is evicted and on_evict(key, value) is invoked (exceptions from on_evict are logged and swallowed, not propagated).
Live usages (all class-level TenantLRUCache instances, one per tenant-scoped singleton):
| Class | Location | Caches |
|---|---|---|
TextAnalysisAgent._agent_instances | libs/agents/cogniverse_agents/text_analysis_agent.py | Per-tenant agent instance (compiled DSPy module + LM config) |
Mem0MemoryManager._instances | libs/core/cogniverse_core/memory/manager.py | Per-tenant memory manager singleton |
BackendRegistry._backend_instances | libs/core/cogniverse_core/registries/backend_registry.py | Per-(backend_name, tenant) search/ingestion backend client |
EntryPointRegistry._instances | libs/foundation/cogniverse_foundation/registry/entry_point_registry.py | Per-tenant instance for each entry-point-registry subclass (e.g. telemetry providers) |
Key Locations¶
libs/core/cogniverse_core/common/cache/base.py-CacheBackendABC,CacheManager,BackendConfiglibs/core/cogniverse_core/common/cache/registry.py-CacheBackendRegistry(CONFIG_CLASS dispatch)libs/core/cogniverse_core/common/cache/pipeline_cache.py-PipelineArtifactCachelibs/core/cogniverse_core/common/cache/backends/structured_filesystem.py-StructuredFilesystemBackendlibs/core/cogniverse_core/common/cache/backends/s3.py-S3CacheBackendlibs/runtime/cogniverse_runtime/ingestion/pipeline.py- Pipeline integrationlibs/foundation/cogniverse_foundation/caching/tenant_lru.py-TenantLRUCache