<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhijeet Pandey</title>
    <description>The latest articles on DEV Community by Abhijeet Pandey (@abhijeet284).</description>
    <link>https://dev.to/abhijeet284</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4104599%2F20595a6e-4ee9-4bc9-99e3-932258a39228.png</url>
      <title>DEV Community: Abhijeet Pandey</title>
      <link>https://dev.to/abhijeet284</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhijeet284"/>
    <language>en</language>
    <item>
      <title>My RAG App OOM-Crashed on a 512MB Free Tier — So I Rebuilt It, Then Broke It Five More Ways on AWS on Purpose</title>
      <dc:creator>Abhijeet Pandey</dc:creator>
      <pubDate>Tue, 01 Sep 2026 15:06:36 +0000</pubDate>
      <link>https://dev.to/abhijeet284/my-rag-app-oom-crashed-on-a-512mb-free-tier-so-i-rebuilt-it-then-broke-it-five-more-ways-on-aws-5dpn</link>
      <guid>https://dev.to/abhijeet284/my-rag-app-oom-crashed-on-a-512mb-free-tier-so-i-rebuilt-it-then-broke-it-five-more-ways-on-aws-5dpn</guid>
      <description>&lt;p&gt;&lt;code&gt;torch&lt;/code&gt; was quietly eating my entire memory budget, and the only symptom was a service that said "live" while every real request just hung and restarted. That was bug #1. It wasn't the last one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;Document Assistant is a RAG (Retrieval-Augmented Generation) app: upload a PDF, ask it questions in plain language, and get an answer streamed back token-by-token that's actually grounded in that document — not a hallucinated guess from a model that's never seen it. It's the standard pattern for Q&amp;amp;A over private documents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/0Abhijeet/Document-assistant" rel="noopener noreferrer"&gt;https://github.com/0Abhijeet/Document-assistant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://document-assistant-okwx.onrender.com" rel="noopener noreferrer"&gt;https://document-assistant-okwx.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture, and why each piece is there
&lt;/h2&gt;

&lt;p&gt;The first version was a single-file FastAPI app: FAISS for vectors saved to local disk, Ollama running a local model, no database, no tests, no deployment story. It looked like a working prototype and had a real correctness bug baked in — FAISS's local index got wiped and rebuilt on every upload, so only one document could exist in the system at any time. Ollama also assumes a persistent local model process, which doesn't fly on any stateless container host.&lt;/p&gt;

&lt;p&gt;The rebuild:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser (chat UI) → FastAPI (Render)
                         │
                         ▼
                  ingest.py: PyPDFLoader → RecursiveCharacterSplitter → fastembed (384-dim)
                         │
                         ▼
                  Postgres + pgvector (Neon): documents / chunks / query_logs
                         │
                         ▼
                  retrieve.py: cosine similarity search
                         │
                         ▼
                  generate.py: Groq API (streamed)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Postgres + pgvector instead of a dedicated vector DB.&lt;/strong&gt; One database holds document metadata, chunk embeddings, and query logs — simpler than running a second stateful system just for vectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neon for hosting.&lt;/strong&gt; Free tier is permanent rather than a time-boxed trial, pgvector works out of the box, and there's none of Cloud SQL's VPC/networking overhead for what's a portfolio project at this stage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groq instead of Ollama.&lt;/strong&gt; Free tier, no credit card, an OpenAI-compatible client that's close to a drop-in replacement, and rate limits generous enough (30 req/min, 14,400/day) not to matter for a demo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fastembed instead of sentence-transformers.&lt;/strong&gt; This was a decision I made &lt;em&gt;after&lt;/em&gt; a production crash, not before — more on that below. Same 384-dimension output, so no schema changes were needed to switch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alembic migrations&lt;/strong&gt;, with the first migration also enabling the &lt;code&gt;pgvector&lt;/code&gt; extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pytest against a real Postgres instance, not SQLite.&lt;/strong&gt; pgvector's &lt;code&gt;Vector&lt;/code&gt; column type has no SQLite equivalent — you either test against the real thing or you're not really testing the schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions CI&lt;/strong&gt; that spins up a throwaway &lt;code&gt;pgvector/pgvector:pg16&lt;/code&gt; service container and runs the full suite on every push.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ingestion is additive by design — uploading a new document never deletes existing ones. That's a direct fix for the FAISS bug, and it's backed by a dedicated regression test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part: four bugs that actually taught me something
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The production OOM crash-loop
&lt;/h3&gt;

&lt;p&gt;The app deployed to Render and briefly reported "live," but every real request either hung indefinitely or the instance silently restarted. Render's free tier caps memory at 512MB, and &lt;code&gt;sentence-transformers&lt;/code&gt; pulls in &lt;code&gt;torch&lt;/code&gt;, which alone can approach that ceiling. I found this by reading Render's "Instance failed" event log, which showed the repeated silent restarts — the app logs themselves gave no useful signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; swap &lt;code&gt;sentence-transformers&lt;/code&gt; for &lt;code&gt;fastembed&lt;/code&gt;, which runs on ONNX instead of torch and produces the same 384-dimension vectors at a fraction of the memory footprint. No schema migration required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a dependency you picked for convenience can quietly become your memory budget. Check what it drags in, not just what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The container that was "running" against the wrong database
&lt;/h3&gt;

&lt;p&gt;Later, on AWS, I hit a version of this bug that's arguably worse: the container started with no errors and looked completely healthy — but it was talking to a throwaway local Postgres container instead of the external database I intended (first Neon, then RDS).&lt;/p&gt;

&lt;p&gt;Two things were compounding. &lt;code&gt;DATABASE_URL&lt;/code&gt; was hardcoded inline in &lt;code&gt;docker-compose.yml&lt;/code&gt; under &lt;code&gt;app.environment&lt;/code&gt;, which takes precedence over the same key set in &lt;code&gt;.env&lt;/code&gt;. Separately, &lt;code&gt;depends_on: db: condition: service_healthy&lt;/code&gt; was auto-starting the local &lt;code&gt;db&lt;/code&gt; service regardless of which service I actually asked &lt;code&gt;docker compose up&lt;/code&gt; to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; changed the compose file to reference &lt;code&gt;${DATABASE_URL}&lt;/code&gt; instead of a hardcoded value, and used &lt;code&gt;--no-deps&lt;/code&gt; to stop the local &lt;code&gt;db&lt;/code&gt; service from auto-starting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; "container running" and "healthy" are not proof it's talking to the backend you think it is. Verify against the actual data source, not process status.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. RDS "Connection timed out" — with both instances in the same VPC
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;psql&lt;/code&gt; hung and failed even though RDS showed "Available" and both instances lived in the same VPC. The RDS security group's inbound rule was IP-based (briefly, and wrongly, widened to &lt;code&gt;0.0.0.0/0&lt;/code&gt; as a shortcut) instead of referencing the EC2 security group directly. EC2's traffic to RDS originates from its security-group identity inside the VPC, not from any external IP — so an IP-based rule can never match it, no matter which IP you put there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; deleted the IP-based rule and added a new one with the source set to the EC2 security group itself, selected from autocomplete rather than typed as an IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; SG-to-SG referencing and IP-based rules solve different problems and aren't interchangeable on the same rule. Also worth saying plainly: briefly opening a &lt;em&gt;database's&lt;/em&gt; inbound rule to the whole internet to "just get it working" is a meaningfully worse mistake than doing the same on SSH — it's direct data exposure, not just a login prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;DATABASE_URL=DATABASE_URL=postgresql://...&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;After the RDS cutover, the app crashed with "Could not parse SQLAlchemy URL." The &lt;code&gt;.env&lt;/code&gt; line had silently become &lt;code&gt;DATABASE_URL=DATABASE_URL=postgresql://...&lt;/code&gt; — the key got typed twice while editing in &lt;code&gt;nano&lt;/code&gt;. SQLAlchemy was trying to parse a string that literally started with &lt;code&gt;DATABASE_URL=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; rewrite the line as a single clean assignment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; a malformed &lt;code&gt;.env&lt;/code&gt; line fails silently at the shell level and only shows up once the app actually tries to use the value. &lt;code&gt;cat&lt;/code&gt; the file back and read it after every edit, before restarting anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AWS at all, if Render already works
&lt;/h2&gt;

&lt;p&gt;The AWS deployment isn't a second production environment — Render remains the permanent, always-on demo. The AWS side exists specifically to close a resume/JD gap: RDS, VPC security groups, and IAM are named directly in target job descriptions, and the networking work involved (like the SG-to-SG fix above) is itself the transferable skill, not just a keyword to have on a resume.&lt;/p&gt;

&lt;p&gt;The setup: EC2 (&lt;code&gt;t2.micro&lt;/code&gt;, Ubuntu 22.04) running the app in Docker with a &lt;code&gt;restart: unless-stopped&lt;/code&gt; policy, verified against a real instance reboot with no manual intervention; RDS PostgreSQL (&lt;code&gt;db.t3.micro&lt;/code&gt;, PostgreSQL 16.x) with &lt;code&gt;pgvector&lt;/code&gt; enabled, migrated from Neon via &lt;code&gt;pg_dump&lt;/code&gt; / &lt;code&gt;pg_restore --no-owner --no-privileges&lt;/code&gt;; a 2GB swap file on the EC2 instance since 1GB of RAM is tight for the embedding model; and an Elastic IP so the demo URL survives stop/start cycles. IAM used a dedicated console user instead of root, with MFA on root and a small cost budget with an alert threshold.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5mtmglsejlks6675pud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5mtmglsejlks6675pud.png" alt=" " width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I deliberately scoped out Nginx/HTTPS termination for this deployment — it demonstrates general reverse-proxy skills, not AWS-specific ones, and doesn't address the actual goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limitations — stated plainly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Thread-pool concurrency, not true async.&lt;/strong&gt; FastAPI runs blocking &lt;code&gt;def&lt;/code&gt; routes in a worker thread pool automatically, which avoids freezing the event loop but doesn't scale to heavy concurrent load. Fine for a demo, not for production traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No authentication.&lt;/strong&gt; Anyone with the URL can upload or query documents. Fine for a portfolio demo, not for real private data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment is manual&lt;/strong&gt;, not part of CI — a deliberate scope cut, not an oversight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts&lt;/strong&gt; on Render's free tier: the service spins down after inactivity, and the first request after idle time takes 30–60s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ivfflat index (&lt;code&gt;lists = 100&lt;/code&gt;)&lt;/strong&gt; is a reasonable default that hasn't been benchmarked against real data volume.&lt;/li&gt;
&lt;li&gt;On the AWS side, the SSH security group rule was temporarily widened to &lt;code&gt;0.0.0.0/0&lt;/code&gt; to unblock EC2 Instance Connect — an open item to be scoped back to "My IP" before the exercise is considered closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd do at scale
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Move to true async DB and embedding calls (&lt;code&gt;asyncpg&lt;/code&gt;, async-compatible inference) instead of thread-pool concurrency&lt;/li&gt;
&lt;li&gt;Add authentication and per-user document scoping&lt;/li&gt;
&lt;li&gt;Automate deployment in CI instead of the current manual step&lt;/li&gt;
&lt;li&gt;Benchmark and tune ivfflat against real data volume, or evaluate HNSW indexing&lt;/li&gt;
&lt;li&gt;Add rate limiting on upload/query endpoints&lt;/li&gt;
&lt;li&gt;Add structured logging/observability beyond the current &lt;code&gt;query_logs&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;On the AWS side: close the SSH rule back to "My IP," then tear down EC2/RDS/the Elastic IP once documentation is captured, so nothing keeps running unattended against the free-tier credit&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;If you're evaluating RAG architecture decisions, hit something similar with Docker Compose environment precedence, or want to talk through the AWS networking side — I'd like to hear from you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/0Abhijeet/Document-assistant" rel="noopener noreferrer"&gt;https://github.com/0Abhijeet/Document-assistant&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://document-assistant-okwx.onrender.com" rel="noopener noreferrer"&gt;https://document-assistant-okwx.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;About me: I'm Abhijeeth, an AI/ML engineer based in Chennai. I build and ship production RAG/LLM systems — FastAPI, pgvector, Docker, AWS. Open to full-time AI/ML Engineer roles. github.com/0Abhijeet&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  rag #aws #postgres #python
&lt;/h1&gt;

</description>
    </item>
  </channel>
</rss>
