Implementation:Langfuse Langfuse PromptService Cache
| Knowledge Sources | |
|---|---|
| Domains | Prompt Management, Caching, Distributed Systems |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
Concrete tool for Redis-based prompt cache locking, invalidation, and lifecycle management provided by Langfuse.
Description
The PromptService class exposes three public cache management methods -- lockCache, unlockCache, and invalidateCache -- that together implement the lock-invalidate-unlock protocol for safe prompt mutations.
lockCache sets a Redis key with a 30-second TTL that signals to all read operations that the cache is currently being mutated. While the lock is held, getPrompt bypasses the cache entirely and reads from the database.
unlockCache deletes the lock key, re-enabling cache reads. If the delete fails, the error is logged but not re-thrown, relying on the 30-second TTL as a safety net.
invalidateCache uses a key index pattern (a Redis Set that tracks all cache keys for a project) to perform bulk deletion of all cached prompt entries. It also handles legacy key index migration by deleting entries stored under the old per-prompt-name key index format.
Supporting these public methods are several private helpers: isCacheLocked checks for the existence of the lock key, getCachedPrompt reads and deserializes a cached prompt (refreshing its TTL), cachePrompt serializes and stores a prompt result, and the key generation methods (getCacheKey, getCacheKeyPrefix, getKeyIndexKey, getLockKey) construct the appropriate Redis keys.
Usage
These methods are called internally by the createPrompt function (and other prompt mutation operations). The lock is acquired before the database transaction, invalidation happens immediately after locking, the database write occurs, and then the lock is released. Application developers typically do not call these methods directly; they are part of the PromptService infrastructure.
Code Reference
Source Location
- Repository: langfuse
- File: packages/shared/src/server/services/PromptService/index.ts
- Lines: 175-254 (lockCache, unlockCache, invalidateCache) with supporting private methods throughout the class
Signature
// Lock: prevents cache reads during mutation
public async lockCache(
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void>
// Unlock: re-enables cache reads
public async unlockCache(
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void>
// Invalidate: bulk-delete all cached prompts for a project
public async invalidateCache(
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<void>
// Private: check lock status
private async isCacheLocked(
params: Pick<PromptParams, "projectId" | "promptName">,
): Promise<boolean>
// Private: read from cache with TTL refresh
private async getCachedPrompt(
params: PromptParams,
): Promise<PromptResult | null>
// Private: write to cache
private async cachePrompt(
params: PromptParams & { prompt: PromptResult },
): Promise<void>
Import
import { PromptService } from "@langfuse/shared/src/server";
I/O Contract
Inputs (lockCache / unlockCache / invalidateCache)
| Name | Type | Required | Description |
|---|---|---|---|
| projectId | string | Yes | The project ID whose prompt cache should be locked/unlocked/invalidated. |
| promptName | string | Yes | The prompt name. Used by invalidateCache for legacy key index cleanup. The lock itself is scoped to the project level (not per prompt name). |
Outputs (lockCache)
| Name | Type | Description |
|---|---|---|
| void | void | Sets the Redis lock key. If caching is disabled, returns immediately without side effects. Throws on Redis errors (lock acquisition is critical). |
Outputs (unlockCache)
| Name | Type | Description |
|---|---|---|
| void | void | Deletes the Redis lock key. If caching is disabled, returns immediately. Does NOT throw on Redis errors (lock has TTL safety net). |
Outputs (invalidateCache)
| Name | Type | Description |
|---|---|---|
| void | void | Deletes all cached prompt entries for the project from Redis using the key index, plus deletes the key index itself and any legacy-format key index entries. |
Redis Key Patterns
| Key Type | Pattern | Example | Description |
|---|---|---|---|
| Cache Key | prompt:{projectId}:{promptName}:{version|label} | prompt:proj_abc:my-prompt:3 | Stores a serialized PromptResult for a specific version or label. |
| Key Index | prompt_key_index:{projectId} | prompt_key_index:proj_abc | Redis Set tracking all cache keys for bulk deletion. |
| Legacy Key Index | prompt_key_index:{projectId}:{promptName} | prompt_key_index:proj_abc:my-prompt | Old format; cleaned up during invalidation for backwards compatibility. |
| Lock Key | LOCK:prompt:{projectId} | LOCK:prompt:proj_abc | Signals that a mutation is in progress. TTL: 30 seconds. |
Usage Examples
Lock-Invalidate-Unlock During Prompt Creation
import { PromptService } from "@langfuse/shared/src/server";
const promptService = new PromptService(prisma, redis);
// Phase 1: Lock cache to prevent stale reads
await promptService.lockCache({
projectId: "proj_abc123",
promptName: "my-prompt",
});
// Phase 2: Invalidate all cached versions
await promptService.invalidateCache({
projectId: "proj_abc123",
promptName: "my-prompt",
});
// Phase 3: Perform database mutation (transaction)
const [createdPrompt] = await prisma.$transaction([
prisma.prompt.create({ data: { /* ... */ } }),
]);
// Phase 4: Unlock cache to allow fresh reads
await promptService.unlockCache({
projectId: "proj_abc123",
promptName: "my-prompt",
});
Cache Behavior During Lock
// While the lock is held, getPrompt bypasses cache:
const result = await promptService.getPrompt({
projectId: "proj_abc123",
promptName: "my-prompt",
label: "production",
version: undefined,
});
// This reads directly from PostgreSQL, not from Redis.
// After unlock, subsequent calls will repopulate the cache.
PromptService Constructor Configuration
// Caching is enabled when:
// 1. A Redis client is provided (not null)
// 2. The LANGFUSE_CACHE_PROMPT_ENABLED env var is "true"
const promptService = new PromptService(
prisma, // PrismaClient
redis, // Redis | Cluster | null
metricFn, // Optional: (name: string, value?: number) => void
true, // Optional: override cacheEnabled for testing
);
// TTL is configured via LANGFUSE_CACHE_PROMPT_TTL_SECONDS environment variable