Production RAG at Scale: Lessons from Processing 10,000+ Listings Daily A developer building a production RAG pipeline for a job board processing 10,000+ listings daily shares lessons on chunking, embeddings, and cost control. The engineer found that recursive character splitting with overlap outperformed fixed-size and semantic chunking for structured job listings, and that OpenAI's text-embedding-3-small provided better retrieval accuracy than a self-hosted Llama 3.1 model despite higher cost. Batching embedding requests reduced latency without increasing token costs. I spent time debugging a RAG pipeline that looked correct in staging and behaved differently under real load. The job board I built processes thousands of listings every day, scoring each one against candidate profiles using LLM function calls. Getting that pipeline stable and affordable took more than a good vector store choice. It took understanding where the system actually breaks. Here's what I learned about chunking, embeddings, vector stores, cost control, and observability when RAG stops being a demo and starts being a production service. Most tutorials treat chunking as a parameter you tune later. In production, your chunking strategy determines your embedding quality, your retrieval accuracy, and your cost structure from day one. For job listings, I tried three approaches before landing on one that worked: Fixed-size chunks 512 tokens . Simple but terrible for this use case. A job description is a structured document with sections: responsibilities, qualifications, benefits. Fixed chunks split those sections arbitrarily. You lose the semantic boundary between "must have 5 years Python" and "nice to have React experience." Retrieval becomes noisy. Semantic chunking with section headers. Better. I split on markdown headers and paragraph breaks. Each chunk preserved a complete thought. But some sections were too long qualifications could run 800 tokens and others too short benefits might be two lines . Uneven chunk sizes meant wasted embedding calls on tiny sections and missed context on large ones. Recursive character splitting with overlap. This is what stayed. I split on double newlines first, then single newlines, then sentences. Each chunk targets roughly 400 tokens with some overlap. The overlap is critical: it catches the boundary cases where a sentence spans two chunks. For job listings specifically, I added a pre-processing step that normalizes the raw ATS output into a consistent structure before chunking. Greenhouse and Lever both return different formats. Normalizing first means the chunker sees clean input. js async function chunkJobListing text: string : Promise