Translation API caching: cache keys, Redis, invalidation and cost measurement
Design isolated translation cache keys, implement cache-aside with Redis, control concurrent misses and measure request savings separately from character savings.

Product descriptions, help articles and interface copy often repeat. Translating them again during every build or job rerun consumes character credits, adds latency and creates more output to review.
A useful translation cache reuses a successful result under the same conditions. Those conditions include the tenant, language pair, engine, terminology, context and processing rules. Similar-looking text is not necessarily the same request, and a high request hit rate does not imply an equally large bill reduction.
This article describes an application-side implementation. Redis, request coalescing and revision fields must be integrated in your service; they are not additional public TranslateHub parameters. Use the API documentation for the supported request contract.
1. Choose content worth caching
Start with stable, repeatedly used text whose permitted reuse is clear: interface labels, public product descriptions and help-center paragraphs. For one-off messages or context-dependent conversation fragments, first decide whether retention and reuse are appropriate.
Keep a cache separate from the published translation store. A cache accelerates repeated computation and can expire or be evicted. Reviewed translations belong in the content system with their source revision and review state. Evicting a Redis key should not remove the only approved copy of a published page.
A page CDN cache is another layer: it reuses complete HTTP responses, while a translation cache avoids repeated API work. For indexable pages, reuse translations during generation and serve complete HTML; see translation performance and Core Web Vitals.
2. Include every condition that changes the result
A key containing only source text and target language can mix tenants, engines or terminology revisions. Use a fixed-order structure that represents the translation identity.
| Condition | Risk when omitted |
|---|---|
| Tenant or controlled sharing scope | Reusing one customer’s output for another |
| Source and target language | Mixing automatic detection with explicit language choices |
| Exact source text | Merging similar sentences with different meanings |
| Engine and configuration revision | Returning old results after a provider change |
| Terminology and policy revision | Reusing superseded terminology or processing rules |
| Format and context fingerprint | Mixing plain text, markup or different conversational contexts |
Do not silently remove punctuation, collapse whitespace or change case. Those differences can matter in code, model numbers and UI labels. If normalization is intentional, define it first and use the normalized text both for identity and for the actual translation input.
The following fields are internal metadata. engineRevision can identify your adapter configuration when an upstream model revision is unavailable. Use an explicit none for absent terminology or context so that absence is distinguishable from a forgotten field.
import { createHmac } from 'node:crypto';
export function translationCacheKey(input, secret) {
if (!secret) throw new Error('Missing cache fingerprint secret');
const identity = [
input.tenantId,
input.sourceLang,
input.targetLang,
input.engineId,
input.engineRevision,
input.glossaryRevision,
input.format,
input.contextFingerprint,
input.policyRevision,
input.text,
];
if (identity.some((value) => typeof value !== 'string' || !value.length)) {
throw new TypeError('Cache identity fields must be non-empty strings');
}
const digest = createHmac('sha256', secret).update(JSON.stringify(identity)).digest('hex');
return `tr:v1:${digest}`;
}HMAC keeps source text out of key names and makes guessing short inputs harder without the secret. It does not encrypt cached values. Protect stored translations with access and retention controls, and keep the fingerprint secret server-side. Rotating it produces a new key space.
3. Implement cache-aside with Redis
Read first, translate on a miss, then store successful output with an expiration. Redis SET can write the value and expiration together, avoiding an interruption between a write and a separate expiry command.
This server-side example uses node-redis’s sendCommand. Generate key with the preceding function; expectedEngine must match that identity, and translate is an asynchronous function bound to the selected engine.
const inFlight = new Map();
function readEntry(raw, expectedEngine) {
if (raw === null) return null;
try {
const entry = JSON.parse(raw);
if (
entry?.schema === 1 &&
entry.engine === expectedEngine &&
typeof entry.text === 'string' &&
entry.text.length > 0
) {
return entry;
}
} catch {
// A malformed value is a miss, not a successful translation.
}
return null;
}
export async function cachedTranslation(redis, key, expectedEngine, translate) {
const cached = readEntry(await redis.get(key), expectedEngine);
if (cached) return { ...cached, cache: 'hit' };
const existing = inFlight.get(key);
if (existing) return { ...(await existing), cache: 'coalesced' };
const work = (async () => {
const result = await translate();
if (
typeof result.text !== 'string' ||
!result.text.length ||
typeof result.engine !== 'string' ||
!result.engine.length
) {
throw new Error('Invalid translation result');
}
const entry = { schema: 1, text: result.text, engine: result.engine };
if (entry.engine === expectedEngine) {
const ttl = 3600 + Math.floor(Math.random() * 600);
try {
await redis.sendCommand(['SET', key, JSON.stringify(entry), 'EX', String(ttl)]);
} catch {
console.warn('Translation cache write failed');
}
}
return entry;
})();
inFlight.set(key, work);
try {
return { ...(await work), cache: 'miss' };
} finally {
if (inFlight.get(key) === work) inFlight.delete(key);
}
}The function distinguishes direct hits, in-process coalescing and new translation work. Rejections propagate and finally removes the pending task. A cache write failure produces a warning without discarding a usable translation. A cache read failure stops the request rather than forwarding all traffic upstream.
The roughly one-hour TTL is an example setting. If translation falls back to another engine, the result is returned but not stored under the expected engine’s key. Cache it separately under its actual identity if appropriate; the multi-engine architecture guide explains routing and fallback boundaries.
4. Control concurrent misses separately
Ten simultaneous requests can all miss an expired key before the first result is written. A cache that looks healthy afterward does not erase the redundant provider calls. The example’s Map coalesces work only within one process, not across containers or instances.
For cross-instance coordination, use a short-lived ownership mechanism. Redis’s cache-aside example demonstrates SET NX PX and an ownership check before releasing a lock. Other requests can briefly wait for the result, within their own deadlines.
Do not blindly delete a lock that may have expired and been acquired by another caller. Compare an unpredictable owner token and delete atomically. Lease expiration also does not stop the original worker, so this coordination reduces duplication without guaranteeing exactly-once processing or billing.
Always bound upstream concurrency. During a Redis outage, either return a recoverable failure or permit a controlled amount of bypass traffic. An unrestricted fallback can turn a cache outage into a provider overload or spending spike.
5. Use revisions for invalidation, with TTL as a limit
Changing source text already produces a new exact key. When terminology, context construction or processing rules change, advance their revisions so new requests use a new identity. Remove old keys according to retention needs or let their TTL expire.
Revision changes also handle an invalidation race: an old translation may finish after a deletion and write stale output back. New callers using the new revision will not read that old key.
TTL bounds retention; it does not establish correctness. A reviewed correction belongs in an approved translation revision or store, not merely a longer-lived cache value. If output must stop being used immediately, define invalidation for every reading layer and replica involved.
6. Keep approximate reuse out of ordinary cache hits
“Refunds are available” and “Refunds are not available” are similar strings with opposite meanings. A short phrase can also refer to different things in different contexts. For conditions, amounts, identifiers and negation, exact identity is a safer basis for automatic reuse than semantic similarity.
A translation memory can suggest approximate matches for review, provided it shows the source, provenance and permitted scope. Approval belongs to a separate workflow rather than silently treating a similarity threshold as an identical request.
7. Measure savings using character-weighted metrics
Request hit rate measures reused requests; character hit rate measures the source characters represented by those requests. Under character billing, the latter is usually closer to the variable-cost effect.
The following is a hypothetical calculation, not customer data or a product promise. Each miss makes one provider call; retries and concurrent coalescing are excluded.
| Request type | Requests | Source characters each | Cache result | Total characters |
|---|---|---|---|---|
| Repeated short copy | 8,000 | 100 | All hits | 800,000 |
| Unique long text | 2,000 | 1,600 | All misses | 3,200,000 |
| Total | 10,000 | — | — | 4,000,000 |
The request hit rate is 8,000 / 10,000 = 80%. Characters avoided are 800,000 / 4,000,000 = 20%. If the variable price is P per million source characters, the avoided submission corresponds to 0.8 × P before considering provider metering, retries, plans and cache operating costs.
Track direct hit rate, coalesced waiters, submitted characters, actual provider attempts, P95 latency and stale-output incidents together. Reconcile character estimates with reported usage rather than using one hit-rate percentage as a billing claim.
8. Verify behavior before rollout
- Repeat an identical request within one tenant and verify that the second call avoids the provider.
- Change tenant, language, engine, terminology revision, context or source text and verify a separate identity.
- Send simultaneous identical requests in one process, then test the multi-instance case separately.
- Inject provider failures, empty output and invalid formatting; verify no successful cache entry is written and later retry still works.
- Inject Redis read and write failures; check the error policy, alerts and upstream concurrency cap.
- Change a revision while an old task is running and verify that new callers do not read its eventual write.
Validate placeholders, numbers and markup before writing application results. Reuse amplifies the value of correct output, but also repeats undetected mistakes; keep a regression set using the quality evaluation framework.
9. Common implementation questions
Will caching always remove 80% of calls?
No. Repetition, key granularity, expiration and access patterns determine the result. The 80% above is an illustrative calculation, not a measured saving for your workload.
Can identical text be shared across customers?
Only within an explicitly permitted sharing scope. Tenant isolation is a useful default; genuinely shareable public material can use a separately governed namespace.
Does installing Redis automatically enable translation caching?
No. The application still defines identity, lookup, writes, expiry, failure behavior and validation. Redis supplies storage operations; your workflow decides when a translation can be reused.

