DS DevShelfHub Projects · AI tools
Articles / Taking RAG to Production: Redis Semantic Caching, BetterDB Observability, and Claude MCP

AI Engineering

Production RAG: Redis Semantic Caching, BetterDB, and Claude MCP

By DevShelfHub

A practical blueprint for shipping a RAG pipeline to production — semantic caching with Redis/Valkey, agent memory, TTL discipline, observability with BetterDB, and natural-language ops via Claude MCP.

Production RAG: Redis Semantic Caching, BetterDB, and Claude MCP

Introduction

Building a RAG application is the easy part. You chunk a PDF, push embeddings into a vector store, wire up a retrieval call, and ship a tidy notebook demo. The moment that pipeline meets real users, however, everything that looked clean in development starts misbehaving—latency creeps up, the same questions get re-computed thousands of times, LLM bills bloat, and nobody has any visibility into what the system is actually doing.

This article walks through a practical blueprint for taking a RAG pipeline to production using Redis (or Valkey) for semantic caching, agent memory, and rate limiting, and BetterDB as the AI-native observability layer on top of Redis. We’ll also hook the whole stack to Claude via MCP so you can debug your cache, anomalies, and memory in plain English.

📚 Table of contents

  • Why a vanilla RAG pipeline breaks in production
  • The production architecture at a glance
  • Redis and Valkey: the in-memory backbone
  • Semantic caching: how it actually works
  • TTL: the most overlooked knob in RAG
  • Agent memory and session storage
  • BetterDB: observability built for Redis-backed AI
  • Project walkthrough: cloning, env setup, Docker Compose
  • Running the ingest and query APIs
  • Hooking BetterDB to Claude with MCP
  • Best practices for production RAG
  • Common mistakes & pro tips
  • Frequently asked questions

💥 Why a vanilla RAG pipeline breaks in production

A typical RAG demo has two endpoints: /ingest and /query. Ingest takes a PDF, chunks it, embeds it, and writes the vectors. Query takes a user question, embeds it, runs cosine similarity, and asks an LLM to ground its answer on the retrieved chunks. Clean. Linear. Easy to demo.

The problems start the day you hand the URL to anyone other than yourself:

  • Repeated questions hit the LLM every single time. “What is your refund policy?” gets asked thousands of times a day and costs you thousands of times.
  • Latency is unpredictable. First-time queries take 1–3 seconds; you have no way to make repeats faster.
  • You can’t see what the system is doing. No memory graphs, no slow query logs, no anomaly detection, no audit trail.
  • Rate limits and abuse are unmanaged. One automated client can drain your OpenAI quota in minutes.
  • Agent memory has nowhere to live. Multi-turn sessions either get lost on restart or pile up in a database that wasn’t designed for them.

👉 Production RAG is less about better embeddings and more about caching, observability, and discipline around what you don’t recompute.

🧭 The production architecture at a glance

The architecture we’re building keeps the RAG flow intact but slots in two new pieces around it.

🔁 RAG pipeline

  • /ingest—chunk, embed, store vectors
  • /query—embed, retrieve, generate
  • LangChain orchestrates the chain

🧠 Redis / Valkey

  • Vector store for ingested chunks
  • Semantic cache for query answers
  • Rate-limit counters
  • Agent memory and session keys

🔭 BetterDB

  • Live observability over Redis
  • Key analytics, TTL inspection
  • Anomaly detection, slow logs
  • AI agent + MCP for plain-English queries

🤝 Claude (MCP)

  • Asks BetterDB questions in English
  • “Show anomalies in the last 3 hours”
  • “What’s the memory breakdown by namespace?”
  • Acts as the on-call analyst for your cache

Conceptually it’s a stack: RAG → Redis/Valkey → BetterDB → Claude. Every layer adds a capability that the previous one couldn’t do on its own.

🗄️ Redis and Valkey: the in-memory backbone

Redis (REmote DIctionary Server) is an open-source, in-memory data structure store used as a cache, a message broker, and a lightweight database. Valkey is the fully open-source fork of Redis that emerged after the licensing change—wire-compatible, drop-in, and maintained by the Linux Foundation. For the purposes of this project, anywhere you see “Redis” you can mentally substitute “Valkey” and vice versa.

Inside a RAG stack, Redis/Valkey ends up doing four jobs at once:

  • Vector storage. Each chunk’s embedding lives as a hash key under a namespace like rag:doc:<id>.
  • Semantic cache. Answers to similar questions are cached under embedding-derived keys with a TTL.
  • Agent memory. LangChain session histories are persisted as keys, so users get continuity across requests.
  • Rate limiting. Simple counters with an expiry per user/IP keep abusive clients out.

You can run it locally with a single docker compose up -d, use a managed service like Upstash, or self-host with persistence enabled—the rest of the stack is identical.

⚡ Semantic caching: how it actually works

A regular cache uses string equality. “What is RAG?” and “Explain retrieval augmented generation” are two different strings, so a regular cache misses both. A semantic cache compares queries by meaning, not text—both questions hash to nearby points in embedding space, so the second one hits.

🧪 The flow

  1. User sends a query.
  2. Embed the query into a vector.
  3. Search the semantic cache for a stored vector within a similarity threshold (e.g. cosine ≥ 0.95).
  4. If hit: return the cached answer—no LLM call, no retrieval, latency drops from seconds to ~10 ms.
  5. If miss: run the full RAG pipeline, then write the answer back to the cache with a TTL.

The numbers in the demo are honest about the win: a fresh query took roughly 1.2 seconds, while a repeat (or a paraphrase) came back in around 220 milliseconds with cache_hit=true. That’s a 5–6x latency improvement and the LLM didn’t bill you for the second call.

👉 Pick your similarity threshold carefully. Too loose, and unrelated questions get the same answer. Too tight, and you barely cache anything. 0.93–0.97 is a reasonable starting band for most domains.

⏱️ TTL: the most overlooked knob in RAG

Every key written to Redis can carry a Time To Live—a duration after which the key is automatically deleted. Set TTL well and your cache stays fresh. Set it poorly and you either serve stale answers forever or pay for the LLM on every request.

🟢 Reasonable TTLs

  • Static documentation Q&A: 1–24 hours
  • Volatile / news-driven answers: 5–15 minutes
  • Agent memory per session: 15–60 minutes idle
  • Rate-limit counters: 60 seconds

🔴 Smells

  • Keys with no TTL at all (silent memory bloat)
  • TTL longer than your content actually stays valid
  • Identical TTLs for cache and memory—they have different needs
  • No metric for “cache stale rate”

In BetterDB you can see the TTL distribution of every key namespace at a glance—how many are expiring soon, how many will live forever, and how the average TTL has drifted over time. That single view catches a category of bugs you’d otherwise never notice.

🧠 Agent memory and session storage

Multi-turn conversations require state. The cleanest pattern is to push session history into Redis under a key shaped like session:<user_id>:<chat_id>, with a sliding TTL that resets each time the user sends another message. LangChain ships first-class Redis-backed memory classes that do exactly this, so the integration is two or three lines of code.

📦 What you end up with in Redis

  • rag:doc:*—chunked, embedded document vectors
  • cache:semantic:*—cached LLM answers keyed by embedding similarity
  • session:*—conversational memory per user/session
  • ratelimit:*—short-TTL counters for abuse control

Three logical namespaces, one data store, one operational story. That consolidation is the real productivity win—you don’t need a separate cache, separate session DB, and separate vector store with three different SLAs to babysit.

🔭 BetterDB: observability built for Redis-backed AI

Once Redis is doing four jobs, the next question is “how do I see what’s actually happening in there?” That’s where BetterDB sits—a purpose-built observability platform for Redis and Valkey, with first-class support for AI workloads. Think of it as Datadog, but laser-focused on Redis and aware of patterns like RAG, semantic cache, agent memory, and MCP traffic.

📊 Key analytics

Breakdowns by namespace, key count, memory used, hit rate, and TTL distribution. You can spot a runaway namespace before it eats your RAM.

🐢 Slow queries

Captures Redis’s SLOWLOG with rich context—ideal when one batch ingest is silently starving the rest of your latency budget.

🚨 Anomaly detection

Flags sudden spikes in memory, key counts, or connection churn. Useful when an agent loop forgets its TTL and starts leaking session keys.

📝 Audit trail

Every operation, every change, every connection logged. Critical when you need to explain to security what touched what, and when.

Crucially, BetterDB also ships an AI agent and an MCP server—which means you can drive the whole dashboard from natural language inside Claude. We’ll come back to that.

🧰 Project walkthrough: cloning, env setup, Docker Compose

Time to wire it together. The full project is a deliberately simple FastAPI service with /ingest and /query endpoints, a Redis/Valkey container, and a BetterDB agent that connects out to the BetterDB cloud (or self-hosted).

1️⃣ Clone and create a virtualenv

git clone <repo-url> betterdb-redis
cd betterdb-redis
uv venv
source .venv/bin/activate
uv pip install -e .

2️⃣ Configure the .env

OPENAI_API_KEY=sk-...
REDIS_URL=redis://localhost:6379
BETTERDB_URL=wss://your-workspace.betterdb.io
BETTERDB_TOKEN=<generated-in-betterdb-settings>

Both the BetterDB URL and the MCP token come from the BetterDB UI under Settings → MCP tokens. Generate one per environment.

3️⃣ Bring up Valkey locally with Docker Compose

docker compose up -d
docker compose ps

The compose file pulls the official Valkey image, names the container, and exposes port 6379. Verify it’s healthy before moving on.

4️⃣ Start the BetterDB agent

docker run -d \
  --name betterdb-agent \
  -e BETTERDB_URL=wss://your-workspace.betterdb.io \
  -e BETTERDB_TOKEN=<your-token> \
  betterdb/agent:latest

The agent connects out to BetterDB, registers your local Valkey, and starts streaming telemetry. You should see “agent connected” in both the container logs and the BetterDB dashboard.

🛠️ Running the ingest and query APIs

With Valkey and BetterDB connected, boot the FastAPI app:

uvicorn main:app --reload --port 8000

Open http://localhost:8000/docs for the automatic Swagger UI. You’ll see three endpoints:

  • POST /ingest—upload a PDF; the service chunks, embeds, and stores it. Response includes the chunk count.
  • POST /query—submit a question; returns the answer, latency, and a cache_hit flag.
  • GET /stats—quick stats about the cache and the index.

Run an ingest, then run two queries: ask the same question twice. The first response will report cache_hit=false with multi-second latency, the second will report cache_hit=true with sub-300 ms latency. Open BetterDB and click Trigger collection—you’ll see the new keys, their TTLs, and the namespace breakdown update live.

🧩 Hooking BetterDB to Claude with MCP

The Model Context Protocol (MCP) is Anthropic’s open standard for letting tools expose themselves to AI assistants. BetterDB ships an MCP server—point Claude at it once and you can drive your cache analytics in natural language.

⚙️ Configure Claude Desktop

Open Claude Desktop → Settings → Developer → Edit Config. Add the BetterDB MCP server entry under mcpServers:

{
  "mcpServers": {
    "betterdb": {
      "command": "npx",
      "args": ["-y", "@betterdb/mcp-server"],
      "env": {
        "BETTERDB_URL": "wss://your-workspace.betterdb.io",
        "BETTERDB_TOKEN": "<your-mcp-token>"
      }
    }
  }
}

Restart Claude. The BetterDB tools now show up in the developer panel.

💬 Useful prompts to try

  • “Show me the memory breakdown by namespace.”
  • “Are there any anomalies detected in the past three hours?”
  • “Which semantic cache keys have unusually long TTLs?”
  • “Summarize slow log entries from the last hour.”
  • “How many session keys do I have, grouped by user, and what’s expiring soon?”

Claude calls the BetterDB MCP tools under the hood, fetches the live data, and answers in plain English—often with patterns you would’ve missed on a dashboard, like “two distinct anomaly clusters separated by 16 hours.” That kind of pattern recognition is why MCP-backed dashboards are so valuable for on-call.

✅ Best practices for production RAG

🧹 Namespace every key

Prefix keys with rag:, cache:, session:, ratelimit:. Future-you needs the namespaces to debug, expire, and flush selectively.

📐 Set TTLs deliberately

Decide a default TTL per namespace and document it. Treat “no TTL” as a code-review red flag.

📈 Watch the hit rate

A semantic cache that hits less than ~20% is barely paying for itself. Tune your similarity threshold and your TTL based on what BetterDB shows.

🪪 Per-tenant rate limits

Always rate-limit before you hit the LLM. A short-TTL Redis counter per API key is enough to keep one bad actor from torching your budget.

🧪 Add “cache poison” tests

Write tests that mutate the source PDF and assert the cache invalidates. Stale answers are one of the silent killers of RAG trust.

🔐 Don’t cache PII verbatim

Hash anything user-specific before it lands in a cache key, and avoid caching answers that contain regulated data unless you have a clear policy.

Common mistakes & pro tips

❌ Common mistakes

  • Treating Redis as “just a cache” and ignoring memory limits
  • Caching LLM answers without versioning the underlying index
  • Using one giant similarity threshold for every query type
  • Storing entire chat histories without compaction—memory leaks within days
  • Skipping observability until something breaks in production
  • Hard-coding TTLs in business logic instead of config

✅ Pro tips

  • Run BetterDB locally first—it teaches you what “normal” looks like
  • Bake the MCP token into a dev-only file; rotate it before going to prod
  • Use Claude+MCP for incident triage—it’s shockingly good at it
  • Embed the cache key with the same model you embed documents with
  • Surface cache_hit in your API responses; ops will thank you
  • Periodically dump key counts to a metrics backend for trend lines

Conclusion

A RAG demo is a hundred lines of Python. A production RAG service is the same hundred lines plus the discipline of caching, observability, memory, and rate limiting. Redis and Valkey give you the in-memory backbone to do all four cleanly. BetterDB gives you the eyes to see what’s actually happening. Claude with MCP turns that visibility into a conversation instead of a chore.

Build the pipeline, but treat the layer around it as a first-class part of the architecture. The teams that ship reliable AI products are not the ones with the fanciest retrievers—they’re the ones who can answer “what is the cache doing right now?” in under a minute.

Related reading: RunPod serverless RAG pipeline guide — NVIDIA DGX Spark hands-on

Taking RAG to Production: Redis Semantic Caching, BetterDB Observability, and Claude MCP FAQ

What’s the difference between Redis and Valkey?

Valkey is a fully open-source fork of Redis maintained by the Linux Foundation after Redis Labs moved to a source-available license. The wire protocol and commands are compatible, so anything written for Redis works against Valkey. For most users it’s purely a licensing/governance choice.

Do I need a dedicated vector database?

Not at this scale. Redis/Valkey with vector indexes (or RedisVL/RedisJSON) handles tens of millions of vectors comfortably. Reach for a dedicated vector DB only when you hit billion-scale corpora, multi-tenant isolation requirements, or specific recall needs that demand more advanced indexing.

How is semantic caching different from prompt caching?

Prompt caching (e.g. Anthropic’s native prompt cache) avoids re-tokenizing the same prefix. Semantic caching avoids calling the LLM at all for similar questions. They’re complementary: prompt caching reduces per-call cost; semantic caching reduces the number of calls.

What if my answers change frequently?

Lower the TTL and use a tighter similarity threshold. You can also key the cache by a content version (e.g. include the document hash in the cache key) so that re-ingesting a doc invalidates its cached answers automatically.

Can I run BetterDB fully on-prem?

Yes. The BetterDB agent can be self-hosted via Docker or npm, and there are deployment options that keep telemetry inside your VPC. Check the official docs for the latest enterprise deployment patterns.

Is MCP only for Claude?

MCP is an open protocol, and a growing number of clients (Claude Desktop, Cursor, Continue, custom agents) support it. Once BetterDB’s MCP server is configured, any compliant client can call its tools—Claude is just the most polished experience today.

How do I size Redis for a RAG workload?

Start with two numbers: total embedded chunks × embedding dimension × bytes per float, plus your expected cache footprint (cached answers + memory + rate-limit keys). Add 30% headroom and pick a managed plan or VM that matches. BetterDB’s memory dashboards make resizing a data-driven decision.

Can I use this with LangGraph or other agent frameworks?

Yes. The pattern is framework-agnostic: any agent runtime can write session keys to Redis, look up semantic cache hits before calling the LLM, and emit metrics for BetterDB. LangChain and LangGraph both ship Redis checkpoint/memory adapters that drop right in.