← All posts

How I Built a $2,300/Year RAG System That Rivals $40K OpenAI Solutions

In October 2025, I deployed a production RAG system that would have cost $39,600 annually using OpenAI's APIs. My actual cost? $2,300 per year. That's 94% cost savings while maintaining comparable per

  • ai
  • llm
  • rag
  • architecture
  • optimization
  • aws
  • machine-learning

I sat in front of my terminal on a Tuesday in October 2025, staring at a spreadsheet. $39,600 per year. That was the number staring back at me — what it would cost to run the RAG system I’d just finished prototyping on OpenAI’s APIs. RAG, short for Retrieval Augmented Generation, is how you get an AI to answer questions about your own documents instead of just what it memorized from the internet.

I had a production deadline. I did not have forty grand.

So I rebuilt the whole thing around a local model — Phi-4, a 14-billion-parameter language model that runs entirely on my own GPU, with zero per-token charges — and a graph-based retrieval framework called LightRAG. The result: $2,300 a year. That’s not a typo. Ninety-four percent cheaper, same ballpark accuracy, sub-three-second query latency, running in production on AWS right now, chewing through thousands of documents for real business needs.

Here’s how that happened, what broke along the way, and why the economics of RAG are about to flip.

The spreadsheet that kills projects

The kill shot isn’t technical. It’s the budget meeting.

I’ve been in the room when it happens. Someone on the engineering team builds a genuinely impressive RAG demo — answers complex questions across the whole knowledge base, handles multi-hop reasoning (where answering one question requires connecting facts spread across multiple documents), the works. Everyone’s excited. Then someone asks what it costs to actually run.

For a typical enterprise setup — 50,000 documents, 1,000 queries a day — the numbers land like this on OpenAI’s pricing model:

  • GPT-4 handling document processing and entity extraction: $1,500/month
  • GPT-4 for generating query responses: $800/month
  • Embedding API, the thing that turns text into searchable number vectors: $200/month
  • Managed vector database (Pinecone or similar): $300/month
  • Graph database (Neo4j Aura) for tracking entity relationships: $500/month

Total: $3,300/month. $39,600/year.

And here’s the part nobody talks about: those numbers climb with every document you add and every query users submit. The pricing model is structurally opposed to building a comprehensive knowledge system. You get punished for being thorough.

I watched projects get shelved over this exact math.

The alternative: a $6,000 GPU and a local model

Instead of paying per token, I bought a GPU server outright: $6,000 one time. That’s an NVIDIA RTX 4090 with 24GB of VRAM, 128GB of RAM, a 32-core CPU, and a 1TB NVMe drive. After that, the ongoing costs are electricity and bandwidth — about $190/month, or $2,300/year.

Year one total: $8,300. Break-even against the OpenAI approach: 2.3 months. After that, I’m saving $37,300 annually. Over three years, the total cost of ownership clocks in at $12,900 — an 89% reduction vs. the API route.

But cost is just the least interesting reason to do this. The real payoff is what you can build when you’re not watching a meter.

What I actually built: four databases and a GPU

The system has four storage components. That was not the plan.

When I first opened LightRAG, my instinct was to simplify. “Surely we don’t need PostgreSQL and Neo4j and Redis,” I thought. So I tried running with just PostgreSQL.

It failed.

Tried with just Neo4j.

Failed again.

Each layer turned out to serve a purpose that the others literally cannot fill. Here’s what each one does and why removing it broke things:

PostgreSQL with pgvector is the vector search engine. When a query comes in, it gets converted to an embedding — a list of floating-point numbers representing its meaning in “semantic space.” PostgreSQL finds the document chunks whose embeddings sit closest to the query’s embedding. It also stores all the document content and metadata, and tracks whether each document has been processed yet.

Neo4j holds the knowledge graph — and this is the secret weapon. When someone asks “What security measures protect the patient data in our healthcare platform?”, answering that means following a chain: Healthcare Platform → uses Technology X → protected by Security Measure Y. That’s multi-hop traversal. Neo4j does it natively with graph queries. PostgreSQL would need recursive CTEs (a SQL technique that’s honestly painful for this). A flat vector store can’t do it at all.

Redis sits in front of everything as a cache. If someone asks the same question twice — or a near-identical one — Redis serves the cached answer and Phi-4 never runs. In production, this eliminates about 60% of redundant model calls.

Phi-4 via Ollama is the local language model. 14 billion parameters, meaning it has 14 billion numerical weights that determine its behavior — enough capacity for complex entity extraction and answer synthesis. It runs entirely on my infrastructure. Every query costs exactly nothing at the margin.

graph TB
    subgraph Application["Application Layer"]
        User[User Queries]
        API[LightRAG API<br/>FastAPI Service]
    end

    subgraph Processing["Processing Engine"]
        Pipeline[Document Pipeline]
        QueryEngine[Query Engine<br/>Multi-hop Reasoning]
    end

    subgraph Storage["Storage Architecture"]
        PG[(PostgreSQL<br/>pgvector<br/>Vector Search)]
        Neo[(Neo4j<br/>Knowledge Graph<br/>Entity Relationships)]
        Redis[(Redis<br/>Response Cache<br/>Processing Queue)]
        Phi4[Phi-4 SLM<br/>14B Parameters<br/>128K Context Window]
    end

    User --> API
    API --> Pipeline
    API --> QueryEngine
    Pipeline --> PG
    Pipeline --> Neo
    Pipeline --> Phi4
    QueryEngine --> PG
    QueryEngine --> Neo
    QueryEngine --> Redis
    QueryEngine --> Phi4

    style User fill:#e1f5ff
    style API fill:#b3e5fc
    style Pipeline fill:#81d4fa
    style QueryEngine fill:#81d4fa
    style PG fill:#4db6ac
    style Neo fill:#4db6ac
    style Redis fill:#4db6ac
    style Phi4 fill:#ffb74d

How graph RAG actually works (and why it beats naive RAG)

Standard RAG is simple: take the user’s question, find chunks of text that look semantically similar, feed those chunks to a model, and ask it to synthesize an answer. This works fine for “What’s our refund policy?” It falls apart for anything that requires connecting dots across documents.

LightRAG does something smarter. During indexing, Phi-4 reads each document and pulls out entities — people, technologies, concepts, organizations — along with the relationships between them. Those go into Neo4j as a graph: nodes are entities, edges are relationships, and both get deduplicated and normalized so “Kubernetes” and “k8s” don’t become two separate nodes.

When a query arrives, the system combines two signals: vector similarity (finding text that means similar things) and graph traversal (following relationship chains through the knowledge graph). The retrieved context includes both relevant text chunks and the related entities with their connections.

Here’s a concrete example:

“What technologies does our platform use and what security protocols protect them?”

Naive RAG returns chunks that mention technologies and chunks that mention security, but can’t stitch them together. You get two separate lists.

Graph RAG identifies “platform” as an entity, traverses its relationships to find connected technology entities, then traverses from those technology entities to security protocol entities. The response shows explicit connections — this technology is protected by that protocol — because the graph encoded those relationships during indexing.

The difference in answer quality is not subtle. Graph RAG produces structured, traceable answers that naive RAG simply cannot match.

What broke in production (so you don’t have to)

Four things, specifically.

1. The context window was too small, and the errors were useless

LightRAG needs at least a 32K context window — meaning the model must be able to “see” up to 32,000 tokens of text at once — for entity extraction to work reliably. Ollama models default to 8K.

The failure mode was infuriating: cryptic “context length exceeded” errors during document processing. No indication that the fix was a one-line configuration change.

ollama pull phi4
ollama show --modelfile phi4 > Modelfile
echo "PARAMETER num_ctx 32768" >> Modelfile
ollama create -f Modelfile phi4-32k
ollama run phi4-32k

Or at the API level:

llm_config = {
    "model": "phi4",
    "options": {
        "num_ctx": 32768,
        "num_gpu": 1
    }
}

If your entity extraction is failing, check this first. It will save you hours.

2. The GPU ran out of memory on large documents

Phi-4 14B needs roughly 18GB of VRAM for inference — the process of running the model to produce output. I was running it with quantization, which compresses the model weights to trade a tiny accuracy hit for a big memory reduction. But batch too many documents at once and you’ll still hit out-of-memory errors.

What worked: 4-bit or 8-bit quantization (the quality loss is negligible for entity extraction), conservative batch sizes calibrated to available VRAM, and nvidia-smi monitoring to catch memory pressure before it killed the process. I also wired in a fallback to CPU inference — painfully slow, but it means the pipeline doesn’t die when the GPU is saturated.

3. Query latency went from 2 seconds to 15+ after indexing 10,000 documents

The out-of-the-box database configs are tuned for development, not production. After 10,000 documents, my queries slowed to a crawl.

The fix was database tuning — memory allocation, index configuration, query planning hints. I’ll walk through the exact knobs in the Tech section below. After tuning: 3-5x improvement. Back to sub-3-second queries.

4. Documents fail in production. Constantly.

Network timeouts during ingestion. Corrupted PDFs. Phi-4 occasionally hallucinates invalid JSON during entity extraction, and the pipeline chokes. You need retry logic from day one.

LightRAG has built-in document status tracking — Pending → Processing → Completed/Failed — and I leaned on it hard. The web UI includes a “Retry Failed Documents” button with adaptive polling: 2 seconds during active processing, stretching to 30 seconds during idle periods. Every failure gets logged with the specific error, so you can pattern-match systemic issues vs. one-off corruption.

the mechanism — why this storage stack works give me the detail

The four-database layout isn’t aesthetic — each layer maps to a fundamentally different data structure that would be slow or impossible in the others.

PostgreSQL + pgvector stores float32 embedding vectors and uses an IVFFlat or HNSW index for approximate nearest-neighbor search. HNSW is the right default: it trades a larger index footprint for dramatically better recall at low ef_search values. Create it once after bulk ingestion (building on live data is much slower):

-- Enable the extension, then create an HNSW index on your embeddings column
CREATE EXTENSION IF NOT EXISTS vector;
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);
-- At query time, tune recall vs. speed:
SET hnsw.ef_search = 40;

Neo4j stores entity→relationship→entity triples extracted by Phi-4 during ingestion. The graph traversal that makes multi-hop reasoning fast is Cypher’s MATCH (a)-[:RELATES_TO*1..3]->(b) pattern — depth-bounded BFS that would require recursive CTEs in SQL and a full graph scan in a flat vector store.

Redis sits in front of both. LightRAG hashes the query + mode (local/global/hybrid) as a cache key. A 60 % cache-hit rate on repeated or near-identical queries means 60 % of your Phi-4 inference cycles never run.

The tuning knobs that actually move the needle (for a machine with 64 GB RAM and NVMe storage):

# postgresql.conf
shared_buffers      = 8GB     # ~12 % of RAM; OS page cache handles the rest
effective_cache_size = 24GB   # planner hint, not actual allocation
work_mem            = 256MB   # per sort/hash op — high because pgvector sorts a lot
random_page_cost    = 1.1     # NVMe is nearly sequential; default 4.0 kills index use

# neo4j.conf
dbms.memory.heap.max_size=16G
dbms.memory.pagecache.size=20G   # page cache > heap for graph-heavy workloads
dbms.jvm.additional=-XX:+UseG1GC # G1 keeps GC pauses predictable under sustained load

Quick smoke test: after indexing 1,000 documents, run EXPLAIN (ANALYZE, BUFFERS) on a similarity query and confirm Index Scan using … hnsw appears — if you see Seq Scan the index wasn’t built or ef_search is set below the index’s m value.

AWS deployment (and the real monthly bill)

I run production on AWS with each component mapped to a managed or self-managed service:

Component          Local Development      Production AWS
─────────────────  ────────────────────   ──────────────────────
PostgreSQL         Docker container       RDS PostgreSQL
Neo4j              Docker container       EC2 + EBS (r6i.2xlarge)
Redis              Docker container       ElastiCache Redis
Phi-4 Inference    Ollama on GPU         EC2 G5 instance
LightRAG App       Docker container       EC2 or ECS Fargate
Document Storage   Local filesystem       S3 + EFS

The monthly AWS breakdown, running 24/7:

  • RDS PostgreSQL with Multi-AZ: $560
  • EC2 r6i.2xlarge for Neo4j (Reserved Instance): $300
  • ElastiCache Redis HA cluster: $280
  • EC2 G5.2xlarge for Phi-4 (Reserved Instance): $650
  • ECS Fargate for LightRAG application: $180
  • Supporting infrastructure (ALB, S3, CloudWatch): $250

Total: $2,220/month ($26,640/year).

Still 33% cheaper than OpenAI’s API approach, and you get things the API route can’t offer: no rate limits (you won’t get throttled when the model says you’ve sent too many tokens in a minute), complete data sovereignty (your documents never leave your infrastructure), customizable entity extraction, and fixed costs regardless of query volume.

The on-premise route: maximum savings

If you can deploy on your own hardware, the numbers get even starker:

  • GPU server (RTX 4090, 24GB VRAM, 128GB RAM, 32-core CPU, 1TB NVMe): $6,000 one-time
  • Annual electricity (24/7, 500W average at $0.12/kWh): $525
  • Bandwidth: $600
  • Maintenance and spares: $175
  • Yearly opex: $1,300

Break-even vs. OpenAI: 1.8 months. Three-year total: $9,900 — a 75% reduction.

Phi-4 vs. GPT-4: the actual numbers

I benchmarked the system on a 500-document technical corpus with 50 evaluation questions spanning simple factual lookups to complex multi-hop reasoning:

MetricPhi-4 + LightRAGGPT-4 API + Standard RAG
Answer accuracy87%92%
Multi-hop reasoning83%89%
Average query latency2.8 seconds1.2 seconds
Cost per 1,000 queries$0 (after capex)$12-18
Rate limitsNone10,000 TPM
Data sovereigntyCompleteNone

A few things worth sitting with:

  1. Phi-4 hits 87% of GPT-4’s accuracy with zero marginal cost per query. For most enterprise use cases, a 5% accuracy gap is a rounding error compared to the budget implications.

  2. Graph RAG dramatically outperforms naive RAG for both models — multi-hop reasoning improves by 40% or more. The architecture matters more than the model.

  3. Phi-4’s latency is slower (2.8s vs. 1.2s), but when you factor in API network overhead and rate-limit queuing, the gap narrows in practice.

  4. The cost advantage at scale is overwhelming. You cannot argue with free-at-the-margin.

Who should do this (and who shouldn’t)

I’m not a zealot about self-hosting. There are cases where paying OpenAI is the right call.

Go local if you:

  • Handle sensitive or regulated data — HIPAA, GDPR, trade secrets, anything you legally cannot send to a third-party API
  • Need predictable costs at scale — if you’re doing 10,000+ documents and 1,000+ daily queries, the API bill gets real fast
  • Want to customize — domain-specific entity extraction, custom graph schemas, specialized retrieval strategies
  • Have someone comfortable managing PostgreSQL, Neo4j, and GPU infrastructure
  • Can invest 2-4 weeks in initial setup and hardening

Stick with the APIs if you:

  • Need to ship tomorrow — can’t spare the setup time
  • Have no DevOps bandwidth — nobody on the team who wants to own infrastructure
  • Process non-sensitive public data
  • Prefer variable per-use pricing over fixed infrastructure costs
  • Require vendor SLAs with guaranteed uptime and support contracts

For enterprises with serious document volumes and regulatory requirements, self-hosting makes overwhelming sense. For a startup prototyping on public data, pay the API bill and focus on product.

Deployment roadmap: local → AWS → production

Week 1: Prove it locally

git clone https://github.com/HKUDS/LightRAG.git
cd LightRAG
cp env.example .env
# Edit .env with secure passwords
docker compose up -d

Verify at http://localhost:9621/webui/. Upload 10-50 test documents. Run queries in all three modes: local, global, hybrid. You need a GPU with 24GB+ VRAM, or accept slower CPU-only inference. Budget 500GB+ of free disk.

Week 2: AWS pilot

Deploy RDS PostgreSQL with pgvector. Launch an EC2 r6i.2xlarge for Neo4j. Configure an ElastiCache Redis cluster. Spin up an EC2 G5.2xlarge with Ollama and Phi-4. Wire it all together inside a VPC with private subnets for the databases, an Application Load Balancer for the LightRAG app, and CloudWatch for logging and monitoring.

Then run 1,000 representative documents through it, fire 100 evaluation queries, and measure latency, accuracy, and actual costs against your acceptance criteria.

Weeks 3-4: Harden for production

Security: encryption at rest on all databases. AWS Secrets Manager for credentials — never hardcode them. API authentication via keys or JWT. Rate limiting so no single client can saturate the system. Audit logging for compliance.

Reliability: RDS Multi-AZ for high availability. Automated backups for PostgreSQL, Neo4j, and Redis. Health checks with auto-recovery. Test your disaster recovery procedure before you need it.

Monitoring: CloudWatch dashboards for query latency, throughput, error rates, and GPU utilization. Alerts on failures, latency spikes, and resource exhaustion. Distributed tracing across query paths so you can see exactly where the time goes.

Performance: Tune database memory settings. Configure connection pooling. Redis query caching. Neo4j indexes on common traversal patterns.

Beyond the spreadsheet

Three-year totals, for the decision-maker who needs to see them lined up:

OpenAI API: $118,800 Self-hosted AWS: $79,920 (33% savings) Self-hosted on-premise: $12,900 (89% savings)

The spreadsheet matters. But the things it doesn’t capture matter more:

Data sovereignty. Your documents never leave your infrastructure. For healthcare, finance, and legal — that eliminates entire categories of compliance overhead and legal exposure.

No ceiling on experimentation. When every query costs money, you ration experiments. When they’re free at the margin, you run A/B tests, try weird retrieval strategies, and iterate without asking permission from the budget.

Fixed costs at any scale. Usage spikes during a product launch don’t trigger an awkward conversation with finance. Batch processing 100,000 documents overnight doesn’t produce a surprise bill. The infrastructure cost is the infrastructure cost.

No throttling. OpenAI caps you at a certain number of tokens per minute. When you control the model, there is no token-per-minute limit. You process at whatever rate your GPU can sustain.

What I’m building next

Multi-modal document understanding. Right now LightRAG only handles text, but real enterprise documents are full of architecture diagrams, database schemas, screenshots, and performance charts. I’m experimenting with vision models — specifically LLaVA — to extract structured information from images before feeding it into the RAG pipeline. Early results on technical diagrams are promising.

Natural language to graph queries. Currently every question goes through the RAG retrieval pipeline. I want to also enable direct graph queries: “Show me every microservice that depends on the authentication service” → auto-generated Cypher query → interactive graph visualization. This exposes the full power of the knowledge graph for exploratory work, not just Q&A.

Continuous learning from feedback. LightRAG doesn’t learn from usage patterns. I’m building thumbs up/down on answers to surface weak areas, user corrections for entity relationships, an automated fine-tuning pipeline, and an A/B testing framework for prompt optimization. The goal: the knowledge base gets better every time someone uses it.

What six months in production taught me

  1. Local models are ready. Phi-4 handles complex RAG tasks that a year ago required GPT-4. The performance gap is closing fast.

  2. Architecture beats model. Graph RAG dramatically outperforms naive RAG regardless of which model you plug in. Multi-hop reasoning and entity relationships produce answers that vector similarity alone cannot.

  3. The cost difference is not incremental — it’s category-defining. 67-94% savings turns comprehensive knowledge systems from “nice to have” into “obviously yes.”

  4. Data sovereignty isn’t theoretical. For regulated enterprises, keeping everything in-house removes entire risk categories. That’s worth real money.

  5. The setup time is the main barrier — 2-4 weeks to production. Ongoing maintenance is 2-4 hours a month.

  6. 87% of GPT-4 accuracy on a $6,000 box is a trade most enterprises should take.

Start here

  1. Prove the concept locally with 1,000 documents before committing to production infrastructure.
  2. Benchmark against your current solution or document set — don’t trust my numbers, measure yours.
  3. Build your cost model from actual document counts and query volumes, not my averages.
  4. Choose AWS vs. on-premise based on compliance requirements, not cost alone.
  5. Index one domain at a time. Expand iteratively. Don’t boil the ocean.

The era of prohibitively expensive RAG is ending. A $6,000 GPU, a local model, graph-based retrieval, and some careful database tuning gets you a production-grade system at sustainable cost.


Have you experimented with running LLMs locally for search or retrieval? What’s keeping you on the API side? I’d like to hear what’s working and what’s hard.

If this was useful:

  • Send it to your engineering team — or your finance team
  • Try LightRAG with Phi-4 on a small document corpus
  • Reach out if you’re planning a production deployment and want to trade notes

I’m working on follow-up posts about fine-tuning Phi-4 for domain-specific entity extraction, optimizing Neo4j query performance on large graphs, and building production monitoring for self-hosted RAG. Which of those would you actually read? Let me know.


Jon Roosevelt is an AI architect specializing in healthcare and enterprise systems. He builds production ML systems that balance performance, cost, and data sovereignty — making powerful AI accessible through pragmatic infrastructure choices.