惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
NISL@THU
NISL@THU
T
Tor Project blog
T
Tenable Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Privacy International News Feed
Cyberwarzone
Cyberwarzone
Project Zero
Project Zero
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
O
OpenAI News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Hugging Face - Blog
Hugging Face - Blog
V2EX - 技术
V2EX - 技术
H
Help Net Security
C
Check Point Blog
T
The Exploit Database - CXSecurity.com
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
U
Unit 42
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
D
Docker
I
InfoQ
Schneier on Security
Schneier on Security
K
Kaspersky official blog
罗磊的独立博客
Cisco Talos Blog
Cisco Talos Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Security Affairs
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
雷峰网
雷峰网
G
GRAHAM CLULEY
Cloudbric
Cloudbric
IT之家
IT之家
Stack Overflow Blog
Stack Overflow Blog
AI
AI
L
LINUX DO - 热门话题
云风的 BLOG
云风的 BLOG
博客园_首页
T
Threat Research - Cisco Blogs
S
Secure Thoughts
有赞技术团队
有赞技术团队

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Building a Multi-Tenant AI Document Platform on AWS (Part 1: Architecture)
Josh Blair · 2026-05-21 · via DEV Community

Building a Multi-Tenant AI Document Platform on AWS (Part 1: Architecture)

How I designed a production-ready RAG system from scratch using AWS-native services — and kept the monthly bill under $20.


Every senior full-stack role I see right now lists "AI experience" somewhere in the requirements. The problem is that most AI portfolio projects look the same: a Next.js frontend, a call to the OpenAI API, maybe a LangChain wrapper. They're fine demos, but they don't show anything about how you'd actually build and operate a system at scale.

I wanted to build something that would hold up in a technical interview with an AWS architect — not just "I built a chatbot." So I built Sift: a multi-tenant RAG (Retrieval-Augmented Generation) document platform, fully serverless, built on AWS-native services, with real data isolation between tenants, an async document processing pipeline, and a total monthly cost of about $10–15 in a live demo environment.

This is the first post in a six-part series. Here I'll walk through the overall architecture and explain why I made each service choice. Future posts will dig into specific areas: auth and multi-tenancy, the Step Functions pipeline, the RAG implementation, the React frontend, and the CI/CD pipeline.


What Sift Does

Users upload documents — PDF, DOCX, CSV, or plain text. Sift processes them asynchronously through a six-stage pipeline, generating text embeddings and storing them in a vector database. Users then ask natural language questions, and Sift retrieves the most relevant chunks from their documents and feeds them to a Claude model to generate a grounded answer with numbered citations linking back to the source text.

It's a legitimate enterprise use case: internal knowledge bases, contract review, research assistants. The architecture reflects that — it's not a demo that would fall apart the moment a second organization tried to use it.


The Full Architecture

Here's how data flows through the system:

Sift architecture diagram

Let's go through each choice.


Why Each Service?

Aurora Serverless v2 with pgvector

The go-to recommendation for vector storage right now is a managed vector database — Pinecone, Weaviate, OpenSearch with k-NN. I deliberately chose not to use any of them.

Sift already needs a relational database for its operational data: tenants, users, documents, processing status. Aurora PostgreSQL Serverless v2 handles all of that and the vector search with the pgvector extension. That's one less service to operate, one less connection to secure, one less cost line item.

pgvector supports cosine similarity search, IvfFlat indexes for approximate nearest-neighbor search at scale, and runs directly in the Postgres query planner — which means I can join vector search results with relational data in a single query. When a user asks a question, I can filter to only that tenant's documents before the similarity search, not after. That's a meaningful security and efficiency win.

The Serverless v2 auto-pause feature means the cluster scales to zero ACUs (Aurora Capacity Units) during idle periods. For a portfolio demo that doesn't have 24/7 traffic, that alone cuts the database cost from ~$50–70/month for a minimum-size provisioned Aurora instance to roughly $7/month in actual usage.

The tradeoff: the first query after an idle period has a cold-start delay while the cluster resumes — typically 5–15 seconds. That's acceptable for a demo, and for production you'd set a non-zero minimum ACU to keep it warm.

Step Functions Express Workflows

When I first sketched the pipeline, the obvious choice was SQS queues between Lambda stages — it's the standard event-driven pattern and I've used it plenty of times. I chose Step Functions Express instead, and it was the right call for this project.

The visibility argument is simple: when I open the AWS console and click into a Step Functions execution, I can see exactly which stage a document is in, what the input and output were at each step, and precisely where it failed if something went wrong. With SQS, you're inferring pipeline state from DLQ message counts and CloudWatch metrics. That's fine in production where you've built dashboards for it — it's friction in a portfolio project where the goal is demonstrating the architecture clearly.

Step Functions also handles retries and error catching declaratively in the state machine definition. Instead of writing retry logic in each Lambda, I configure it once in the YAML:

Retry:
  - ErrorEquals: [States.ALL]
    IntervalSeconds: 2
    MaxAttempts: 3
    BackoffRate: 2

Enter fullscreen mode Exit fullscreen mode

Transient errors — throttling, network blips — get retried automatically. Unrecoverable failures route to the MarkFailed state, which updates the document status and preserves the error for display in the UI.

Express Workflows (vs. Standard Workflows) are priced per execution and duration rather than per state transition. For a document pipeline that completes in under 5 minutes, this is significantly cheaper. The tradeoff is that Express Workflows have a maximum duration of 5 minutes and don't support activities or sync patterns that Standard Workflows do — neither matters here.

Amazon Bedrock

I used Bedrock for both embedding generation (Titan Embed v2, 1024 dimensions) and the chat completion (Claude Haiku 4.5).

The straightforward reason is that it keeps everything inside the AWS trust boundary. No data leaves my VPC to a third-party API. Authentication is IAM, not an API key stored in a secret. There's no separate vendor account to manage, no risk of data being used for model training, and the latency is lower because it's in-region.

The more practical reason: on an AWS-focused résumé, "I used Bedrock" is worth more than "I used OpenAI." It demonstrates familiarity with Bedrock's APIs, model catalog, and IAM integration patterns that you'd actually use in enterprise AWS environments.

Titan Embed v2 produces 1024-dimensional vectors with normalized outputs — which means cosine similarity and dot product give equivalent results, simplifying the pgvector query. Claude Haiku 4.5 handles the metadata extraction (generating a document summary and extracting topics from text chunks) and the final chat response generation. Haiku is fast and cheap for the metadata step; the quality is more than adequate for structured extraction tasks.

Amazon Cognito

Auth is one of those areas where building your own is almost always the wrong call. Cognito gives you a managed user pool with email/password auth, JWT issuance, and a hosted UI for free at demo scale.

The interesting piece is the Pre-Token Generation Lambda trigger. When Cognito issues a JWT, it calls a Lambda function that can add custom claims to the token before it's signed. I use this to inject a tenantId claim — the tenant the user belongs to — directly into the token.

That tenantId flows downstream: API Gateway validates the JWT and rejects unauthenticated requests; my Lambda functions extract the claim from the validated token context; and the database uses it to enforce Row-Level Security. The tenant identity is cryptographically bound to the token — a user can't forge a different tenantId without Cognito's private signing key.

AWS SAM over CDK

CDK is a better abstraction for large teams and complex infrastructure. SAM is CloudFormation with a thinner layer of syntactic sugar, which means the output is standard CloudFormation YAML that any AWS engineer can read without knowing TypeScript or Python CDK constructs.

For portfolio purposes, that's actually the right choice. When I'm walking through this in an interview, I want to point directly at template.yaml and explain the AWS::Serverless::StateMachine resource, the IAM policy, the event source mapping — without the interviewer needing to know what aws_cdk.aws_stepfunctions compiles to under the hood.


Multi-Tenancy: Three Layers of Isolation

This is where a lot of "multi-tenant" demos fall short — they add a tenantId column to their tables and filter on it in application code. That works until a bug in one query path leaks cross-tenant data.

Sift uses three independent isolation layers:

Layer 1 — Cognito custom claim. The tenantId is in the signed JWT. No application code can inject or modify it.

Layer 2 — S3 key prefix. Every uploaded document is stored at {tenantId}/{documentId}/{filename}. Tenant A can't construct a presigned URL for tenant B's key without knowing the exact key path — and they'd also need to be an IAM principal with S3 access, which they're not.

Layer 3 — PostgreSQL Row-Level Security. Before any database query executes, the Lambda sets a session variable:

SELECT set_config('app.current_tenant_id', $1, false)

Enter fullscreen mode Exit fullscreen mode

The RLS policies on every table enforce this automatically at the database level:

CREATE POLICY tenant_isolation ON documents
  USING (tenant_id = current_setting('app.current_tenant_id')::UUID);

Enter fullscreen mode Exit fullscreen mode

Even if application code contained a bug that omitted a WHERE tenant_id = ? clause, the database would still return only the current tenant's rows. The isolation is enforced below the application layer.

One important gotcha: the database application user must not have BYPASSRLS privileges. Postgres superusers bypass RLS by default. If your Lambda connects as a superuser, your RLS policies are decoration.


Cost Breakdown

One thing I wanted to demonstrate with this project is that you can run a legitimate, production-architected system for almost nothing at low volume:

Service Monthly Cost
Aurora Serverless v2 (auto-pause, demo traffic) ~$7
Amazon Bedrock (embeddings + chat, light usage) ~$2–5
Lambda, API Gateway, S3, CloudFront Free tier
Cognito (under 50k MAU) Free tier
Total ~$10–15

The variable cost scales with Bedrock usage — each embedding call and each chat completion. At real production volume you'd be looking at real Bedrock costs, and you'd want to add CloudFront caching for static assets and tune Lambda memory settings. But the architectural pattern holds: Aurora Serverless v2 auto-pause means you're not paying for compute when nobody is using the system.


AWS Well-Architected Alignment

Since this is a portfolio project for AWS architecture roles, I mapped the design against the six pillars explicitly:

  • Operational Excellence — SAM + GitHub Actions with OIDC: the entire infrastructure is code, deployments are automated, there are no manual steps
  • Security — OIDC federation (zero stored credentials), Row-Level Security, Cognito JWT, no Lambda with public S3 access
  • Reliability — Step Functions retry logic, EventBridge decoupling between S3 upload and pipeline execution, Aurora Multi-AZ in production config
  • Performance Efficiency — Aurora Serverless v2 scales with demand, Map state in Step Functions parallelizes embedding generation
  • Cost Optimization — Aurora auto-pause, Bedrock pay-per-token, Lambda and CloudFront at free tier scale
  • Sustainability — Serverless by default: compute resources allocated only when actively processing

What's Next

This series covers each major subsystem in depth:

  • Part 2: Multi-tenant auth — how the Cognito Pre-Token Lambda works, the RLS policy setup, and the C# tenant context middleware
  • Part 3: The Step Functions pipeline — state machine design, the Map state for parallel embedding, error handling
  • Part 4: RAG and vector search — chunking strategy, pgvector queries, and how citations are generated
  • Part 5: The React frontend — polling patterns, Amplify auth integration, the upload flow
  • Part 6: CI/CD with GitHub Actions and OIDC — zero-secret deployments to AWS

The live demo is running at sift.bonefishsoftware.com — log in with the shared Acme Corp credentials from the README and try uploading a PDF.

The code is at github.com/joshblair/sift.


Part of the Sift series: building a production-ready multi-tenant RAG platform on AWS.