Document Ingestion Pipeline

An ingestion pipeline turns documents into vectors a RAG system can search. It has five stages: extract, clean, chunk, embed and store, and the engineering is in surviving failures and counting what each stage loses. Scanned files that skip OCR vanish at stage one, and nothing downstream says so.

Overview

An ingestion pipeline turns documents into vectors a RAG system can search.

In this guide
  1. What the pipeline does
  2. Stage 1: extract the text
  3. Stage 2: clean the text
  4. Stage 3: chunk by tokens
  5. Stage 4: embed in batches
  6. Stage 5: store with idempotent upserts
  7. The orchestrator
  8. Estimate losses and time
  9. Design choices, briefly
  10. Things to try with Document Ingestion Pipeline
  11. What to remember about Document Ingestion Pipeline
  12. Questions people ask about Document Ingestion Pipeline
  13. Document Ingestion Pipeline: recap in one screen

Parameters

Visualisation

—

Readout

What to watch

  • Turn OCR off and watch scanned files drop out at the first stage.
  • The slowest stage is marked red; add workers and the time falls.
  • Fewer embedding retries lose a few documents at stage four.

Document Ingestion Pipeline

Turn ten thousand PDFs into searchable vectors with a five-stage pipeline that handles OCR, cleaning, chunking, embedding and storage, and loses as little as possible on the way.

What the pipeline does

An ingestion pipeline is the foundation of any RAG system. Before a model can answer questions about your documents, those documents must be broken apart, turned into vectors and stored in an index. The pipeline does that work from end to end.

It has five stages. Extraction turns PDF pages into raw text, with an OCR fallback for scanned pages. Cleaning removes Unicode quirks, repeated headers and footers, and extra whitespace. Chunking cuts the clean text into pieces of a set size, with overlap so that no fact falls through a boundary. Embedding turns each chunk into a vector. Storage writes the vectors to a database, with metadata that traces each one back to its source.

For ten thousand PDFs the design favours throughput and fault tolerance over speed on a single file. Each stage reads from a queue or folder and writes to the next.

The hard part is not any one stage. It is the orchestration. At ten thousand files, failures are certain. A scanned PDF can crash the OCR engine. An API call can time out mid-batch. The pipeline has to survive them without losing work. Checkpoints let it resume. Batching keeps it fast. Idempotent writes mean that running it twice updates records instead of duplicating them.

Every choice here affects retrieval later, in chunking strategies, embedding models and HNSW indexing.

Stage 1: extract the text

PDF is a presentation format, not a data format. Text may be stored as characters, as glyph references, or as a picture of a page in a scan. That makes extraction the messiest stage.

pymupdf, imported as fitz, is a common default. It is written in C, copes with malformed and encrypted files, and extracts fast. Compare it with alternatives on your own files before you commit. pdfplumber is the better pick when you need tables.

For scanned pages, fall back to OCR. A simple rule works: if a page yields under about 50 characters of text but holds images, render it to an image and run an OCR engine such as Tesseract. Its documentation suggests about 300 DPI. Lower resolutions lose accuracy, and higher ones multiply the pixels for little gain.

example_01.pyNumPy
Output

Stage 2: clean the text

Raw extracted text is noisy. Page numbers, headers, footers and ligatures all get in the way of later steps. Cleaning has a fixed order, because each step relies on the one before.

  1. Normalise Unicode so an accent is one character, not two.
  2. Replace ligatures such as fi and fl with plain letters. Normalising to NFKC does this too, but it changes other characters as well.
  3. Remove lines that repeat on three or more pages. They are almost always headers or footers.
  4. Collapse extra blank lines and runs of spaces.
example_02.pyNumPy
Output

Stage 3: chunk by tokens

Chunking is the bridge between documents and the embedding model. The goal is a piece small enough to carry one focused idea and large enough to stand on its own. Measure size in tokens, not characters, because the embedding model and the language model both have token limits.

Character counts hide variation. A 2,000-character chunk may be 300 tokens in plain English and 800 in code. Use the model's own tokenizer, such as tiktoken for OpenAI models.

Overlap protects facts that straddle a boundary. A common setting is 50 tokens of overlap on 400-token chunks. That stores about 12% more text and recovers facts that would be cut. For how to choose both numbers, see chunk size optimization.

example_03.pyNumPy
Output

Stage 4: embed in batches

Embedding turns each chunk into a vector. Batching matters a great deal: one chunk per call leaves a GPU almost idle, so pass many chunks at once. See batch embedding generation for batch sizes and memory.

Normalise the vectors to unit length. Then cosine similarity equals the dot product, and you can use the cheaper dot-product metric in the database and get the same ranking.

example_04.pyNumPy
Output

Stage 5: store with idempotent upserts

The last stage writes the vectors to a database such as Qdrant, Pinecone, Weaviate or pgvector. Use an upsert, not an insert. An upsert writes a record if the ID is new and replaces it if the ID exists, so running the pipeline twice cannot create duplicates. That only works if the ID is deterministic: derive it from the document and chunk position, not from a random number.

Write in batches of a few hundred points per call, and test what your database prefers. Create indexes on fields you filter by, such as doc_id. Store metadata with each point: chunk ID, document ID, chunk text, chunk index, token count and source path. Without it, a retrieved vector cannot be traced back to anything.

example_05.pyNumPy
Output

The orchestrator

The orchestrator ties the stages together, and it needs two properties. Use parallelism where it helps, and checkpoint so a crash does not lose progress.

PDF parsing is CPU-bound, so spread it over several processes with ProcessPoolExecutor. Threads would not help because of the GIL. Embedding is already batched on the GPU. Storage waits on the network and benefits from larger batches.

For a batch job, a checkpoint file listing finished documents is the simplest design. On restart, load it, drop finished documents and continue. Update it after each stored batch, so a crash costs at most one batch. For a pipeline that runs all the time, use a job queue such as Celery instead. The page on embedding resilience and checkpointing builds that in detail.

Estimate losses and time

Before the first big run, model the funnel. Every stage loses some documents and costs some time. The rates below are placeholders. Replace them with numbers measured on a sample of your own files.

example_06.pyNumPy
Output

Change ocr_on to False and run it again. The extract stage then loses most scanned files, and nothing downstream reports it. That silent loss is the main reason to count documents at every stage.

Design choices, briefly

Why tokens, not characters? Models have token limits, and a token count is a predictable unit of cost.

Why ProcessPoolExecutor? Parsing is CPU-bound and the GIL stops threads from running it in parallel. For network work, use threads or asyncio.

Why normalise vectors? Unit vectors let you use the faster dot product with identical ranking.

Why a file checkpoint? It adds no dependency and is enough for a batch job.

Things to try with Document Ingestion Pipeline

  1. Turn off OCR with 20% of files scanned. Most scanned documents drop out at stage one.
  2. Raise Scanned share to 60 with OCR on. Extraction becomes the slowest stage.
  3. Lower Embedding retries to 0. A few documents are lost at the embed stage.
  4. Raise Workers to 16. The wall-clock time falls in proportion.

What to remember about Document Ingestion Pipeline

Ingestion is five stages: extract, clean, chunk, embed, store. Use OCR for scans, count tokens, make IDs deterministic and writes idempotent, and checkpoint so a crash costs one batch. Count documents at every stage, because silent loss at the first one is invisible later.

Questions people ask about Document Ingestion Pipeline

How long does it take to ingest 10,000 PDFs? It depends on how many pages need OCR and on your hardware. Run a sample of 100 files, time each stage and scale up.

What happens after a crash? The checkpoint lists finished documents, so a restart skips them and continues with the rest.

Which PDF library should I use? pymupdf is a common default. Use pdfplumber for tables, and test on your own files.

How much overlap? About 10 to 20% of the chunk size. For 400 tokens, that is 40 to 80.

Can I skip cleaning? Not safely. Without it, headers pollute the embeddings and encoding differences create near-duplicates.

How do I choose a chunk size? Start at 256 to 512 tokens and measure recall on a test set.

Document Ingestion Pipeline: recap in one screen

  • Five stages: extract, clean, chunk, embed, store.
  • OCR scanned pages, or they vanish at stage one.
  • Chunk by tokens, with 10 to 20% overlap.
  • Use deterministic IDs and upserts so a re-run cannot duplicate.
  • Checkpoint progress, and count documents at every stage.

Recall check

0 of 3

Say the answer out loud before you reveal it — recalling it is what makes it stick, and rereading it is not.

  1. Without scrolling back — what is the one-line takeaway from this module?

  2. What does this module say about “What the pipeline does”?

  3. What does this module say about “Stage 1: extract the text”?

Cheat sheet

Document Ingestion Pipeline

Walk a five-stage ingestion pipeline from PDF extraction and OCR to cleaning, chunking, embedding and idempotent upserts, and model what each stage loses.

GEN AI · vizlearn.in/gen_ai/document_ingestion_pipeline.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.

Teaching a course with this? Individual teachers can use it freely; for a whole department or programme, see institutional licences.