Architectural Patterns, CQRS, and Retrieval-Augmented Generation
Prerequisite Knowledge
This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.
Previously Covered in This Subject
- Architectural and Design Patterns � covered in Lecture 4 (�4.8)
- ML Pipeline and Lifecycle � covered in Lectures 1-2 (�1-2.10) and Lecture 3 (�3.3)
- RAG Architecture � covered in Lecture 2 (�2.9.3)
Architectural Patterns, CQRS, and Retrieval-Augmented Generation
5.1 Architectural Patterns — Context and Recap
5.1.1 What Is an Architectural Pattern?
Why do software teams keep reinventing the same wheel? Every decade, a new generation of developers encounters the same scaling, modularity, and integration problems that the previous generation already solved. Architectural patterns exist so you can skip the painful trial-and-error and jump straight to a battle-tested blueprint.
Before diving into specific patterns, we need to anchor on what an architectural pattern actually is. A pattern, in its most general form, is a solution to a recurring problem in a given context. Software architects and designers, drawing on decades of collective experience, have codified numerous architectural patterns as reusable blueprints. When you encounter a problem with a familiar shape, you reach for the pattern that has solved it before — you don't start from scratch.
The textbook (Ch. 8) frames this precisely: "Software architecture is the part of the design process that focuses on those early decisions that are most important for achieving the quality requirements of a system." Architectural patterns are the codified vocabulary for those early decisions. A pattern typically carries five pieces of knowledge: a name (so teams can communicate concisely), the problem it solves, the solution structure, benefits and trade-offs, and known alternatives. When a team says "let's use CQRS here," that single term compresses an enormous amount of shared understanding about the solution shape, what it buys you, and what it costs.
Pattern vs. Architecture Style vs. Design Pattern: These three terms live at different scales. An architecture style (e.g., microservices, event-based, data-flow) describes a broad system-organizing principle. An architectural pattern (e.g., CQRS, pipe and filter) solves a specific recurring problem at the system or subsystem level. A design pattern (e.g., Observer, Strategy) solves a recurring problem at the object or module level. In this lecture we deal with the middle layer — architectural patterns — though the pipe-and-filter pattern also qualifies as an architecture style.
This course sits at the intersection of software architecture and machine learning, so we take established patterns from software engineering and examine how they apply — and what new meaning they acquire — when the system in question is an ML system. The patterns explored in this lecture form a progression: we start with the venerable Pipe and Filter pattern, move to Command Query Responsibility Segregation (CQRS), see how combining these two produces the RAJ architecture, and then touch on monolith and microservice styles as context for where these patterns fit.
Why patterns matter for ML systems specifically. ML systems introduce additional components (data pipelines, model training, inference serving, monitoring) and additional quality requirements (accuracy, reproducibility, fairness, latency) that traditional software does not face. Choosing the right architectural pattern early is critical because, as the textbook warns, "architectural design decisions are fundamental and hard to change later without fully redesigning the system." A mis-chosen pattern for an ML serving system — say, coupling training and inference in one process — can make it nearly impossible to scale reads independently of writes when traffic grows.
Real-world: Twitter's 2011–2012 redesign is the textbook case. The original monolithic Ruby on Rails system could not scale. The redesign explicitly considered four quality goals — latency/cost, reliability, maintainability, and modifiability — and chose a microservice architecture with Scala, a new storage solution, and built-in failover and monitoring. Every major architectural decision was driven by explicit quality goals, not by implementation convenience.
5.1.2 Components and Connectors — The Universal Lens
Every architectural pattern can be understood through a single lens: components and connectors. Every pattern has components — the things that do the work — and connectors — the things that wire them together. What a component is and what a connector is changes from pattern to pattern:
| Architecture Style | Component | Connector |
|---|---|---|
| Monolith | Module (a class, a library) | Function call, in-memory reference |
| Microservices | Service (an independent process) | REST API call, gRPC, message queue |
| Pipe and Filter | Filter (a processing step) | Pipe (data conduit) |
| CQRS | Command handler / Query handler | Event stream, shared data store |
| Event-based | Listener/subscriber | Message bus topic |
The professor's analogy: Think of it as the architectural equivalent of "separate the nouns from the verbs." The components are your nouns — the processing units, the boxes that contain logic. The connectors are your verbs — the pathways that carry data, the communication channels. Once you internalize this component-connector framing, every architectural pattern becomes a variation on a theme rather than a totally new idea.
This framing is the architect's most portable tool. Whether you are looking at a Unix shell pipeline
(ls | grep | sort), a Kubernetes cluster of microservices, or a RAJ system with a vector database,
the first question is always the same: What are the components? What are the connectors? The second
question is: What quality requirements drove this particular arrangement?
Relation to the textbook's architectural views. The textbook (Ch. 8) explains that architects reason about systems through multiple views — a performance view focuses on processes and timing, a modifiability view focuses on modules and interfaces, a security view focuses on trust boundaries. The component-connector lens maps naturally to these views: each view highlights certain components and certain connectors while abstracting away the rest.
Relation to data-flow architectures. The textbook identifies data-flow architectures (also called pipe-and-filter architectures) as one of the common system structures: "The system is organized around data, often in a sequential pipeline, where data produced by one component is used as input by the next component." This is precisely the pattern we examine next in §5.2.
Recap: An architectural pattern is a named, reusable solution to a recurring design problem. The component-connector lens is the universal way to understand any pattern: identify the processing units (nouns) and the communication pathways (verbs). The patterns in this lecture — Pipe and Filter, CQRS, and RAJ — are all compositions of components and connectors optimized for different quality requirements.
5.2 Pipe and Filter Pattern
5.2.1 Definition and Core Concepts
Hook: When Dennis Ritchie and Ken Thompson built Unix at Bell Labs in the 1970s, they invented a mechanism so fundamental that it still underpins every data pipeline, every ML workflow engine, and every LangChain application today — half a century later. That mechanism is the pipe.
The Pipe and Filter pattern has been around since the 1970s — it is one of the oldest and most battle-tested
architectural patterns in computing. The entire Unix operating system is built on it: when you chain commands
with a pipe (command1 | command2 | command3), the output of one command flows directly into the
input of the next. That is the essence of pipe and filter.
In this pattern, a filter is a component that performs some processing — it takes data in, applies a transformation, and produces data out. A pipe is a connector that transports data from one filter to the next. The pipe does no processing of its own; it is purely a conduit. The architecture reads left to right: data originates at a data source, passes through a series of filters connected by pipes, and arrives at a data sink.
Analogy — the factory assembly line. Think of a pipe-and-filter system as a factory assembly line. Each workstation (filter) performs one task — welding, painting, inspecting — and the conveyor belt (pipe) moves the product from one station to the next. The conveyor belt does zero work; it just transports. You can add a new workstation, remove an old one, or rearrange the sequence without redesigning the whole factory. The product (data) flows through, getting transformed at each stop. Where the analogy breaks: a real conveyor belt is rigidly sequential, but pipes support fan-out and fan-in.
Why this pattern endures. Three properties make pipe and filter almost indestructibly useful:
- Modularity. Any filter can be added, removed, or swapped out independently. A pipeline designed for one use case can be reconfigured for another by changing the sequence, inserting new filters, or removing unnecessary ones.
- Replaceable pipes. The pipe itself can be upgraded — you might start with an in-memory variable as your connector and later upgrade to a REST API or a message queue without touching the filter logic.
- Topology flexibility. The pattern is not restricted to a straight line. It supports 1-to-1,
1-to-N (fan-out, like the Unix
teecommand), N-to-1 (fan-in), and arbitrary DAG (directed acyclic graph) topologies. A filter can have multiple replicas running in parallel for throughput.
Scope and assumptions:
- Filters must be independently composable — each filter's output format must match the next filter's input format (the "pipe contract"). If two filters have incompatible data formats, you need an adapter filter between them.
- Data flows in one direction through each pipe; bidirectional communication requires a different pattern (e.g., request-response).
- The pattern does not prescribe implementation granularity — "resize," "color correction," and "noise reduction" can be one filter or three.
Common Pitfall — confusing "filter" with ML filtering. In machine learning, "filtering" often means feature selection, noise removal, or data filtering. In the pipe-and-filter pattern, a "filter" simply means a processing component — any unit that takes data in, does something to it, and produces data out. The name is historical, dating back to 1970s Unix. The input filter's job is to ingest data; the output filter's job is to present results. Neither "filters" in the data-science sense. A data enrichment step (fetching from an additional source) is a perfectly valid filter.
Real-world: This pattern underpins virtually every data pipeline and ML workflow engine in production today — Prefect, Apache Airflow, AWS Step Functions, Kubeflow Pipelines — all of them are, at their core, pipe and filter architectures. The output of one task becomes the input to the next, and each task is an independent processing unit. As the textbook (Ch. 11) puts it: "The typical design of machine-learning pipelines as sequential stages, where each stage receives inputs from the previous stage, easily maps to modular implementations... This style of passing data between subsequent modules corresponds to the traditional data-flow architectural style."
5.2.2 Non-ML Example: Image Processing Pipeline
Worked Example — Image Processing Pipeline
Consider a system that takes an input image, applies a series of transformations, and produces a transformed output image. The data source could be a camera feed, a filesystem, or a network upload. The pipeline flows through five filters:
- Input filter: receives the raw image data from the source.
- Pre-processing filter(s): resize the image, apply color correction, reduce noise. These can be combined into one filter with sub-steps or separated into individual filters — the pattern does not prescribe granularity.
- Feature extraction filter(s): detect edges, identify shapes, recognize objects — whatever the use case demands.
- Filter application: apply blur, convert to grayscale, sharpen edges — again, any combination.
- Output filter: delivers the final image — displaying it to the user, saving it to a file, or uploading it to cloud storage.
Component-connector breakdown:
- Components: each filter (input, pre-processing, feature extraction, application, output)
- Connectors: the pipe between each pair (in the simplest form, an in-memory variable passing a pixel array)
Key insight: You are free to decide whether "resize," "color correction," and "noise reduction" are three sub-steps inside a single filter function, or three separate filters each with its own pipe. The pattern does not constrain your implementation granularity. What matters is that each filter is a self-contained processing unit and the pipe carries data between them.
5.2.3 ML Example: Diabetes Classification Pipeline
Worked Example — Diabetes Classification Pipeline (end-to-end)
The data source is the Pima Indian Diabetes dataset, fetched from a URL. The pipeline processes this data through five filters, each a standalone Python function:
Filter 1 — Data Loading
- Input: URL string
- Processing:
pandas.read_csv(url)— fetches the CSV, loads into a DataFrame - Output: raw DataFrame with all columns, including the outcome label (1 = diabetic, 0 = non-diabetic)
Filter 2 — Data Cleaning
- Input: raw DataFrame
- Processing: missing-value imputation using the median (not mean). Concrete numbers:
- Glucose column: missing values replaced by median = 117
- Blood pressure column: missing values replaced by median = 72
- Output: cleaned DataFrame with no nulls
- Implementation note: median is chosen over mean because it is robust to outliers. The pattern does not care how you clean, only that cleaning is a distinct step with defined input and output.
Filter 3 — Feature Scaling (optional)
- Input: cleaned DataFrame
- Processing:
StandardScalerto normalize features (zero mean, unit variance) - Output: scaled feature matrix
- This filter is optional. For logistic regression on a small dataset, scaling may be unnecessary. You can bypass this filter and connect the cleaning output directly to training. The pattern allows conditional and optional paths.
Filter 4 — Model Training
- Input: scaled (or unscaled) feature matrix
- Processing: 75/25 train-test split → train
LogisticRegressionmodel - Output: trained model + test predictions
- Logistic regression is chosen because this is a binary classification problem (diabetic vs. non-diabetic).
Filter 5 — Output / Evaluation
- Input: trained model + test predictions
- Processing:
accuracy_score(y_test, y_pred)+classification_report(precision, recall, F1 per class) - Output: evaluation metrics — this is the data sink, the final deliverable shown to the user.
What is the pipe? In this Python implementation, the pipe is a variable:
raw_data holds the output of the loading filter and is passed as input to the cleaning filter.
Each filter is a function that takes data in and returns data out. This is the simplest pipe — a variable
carrying a reference.
Scaling up: In a distributed system, the pipe could be a REST call, a gRPC channel, a Kafka topic, or a message queue. The filter logic remains unchanged. This is the power of the pattern — the same filter code works whether the pipe is a local variable or a distributed message broker.
5.2.4 Student Questions and Answers
Q: What is the modularity in pipe and filter? Can a filter be reused in multiple situations?
A: Yes. A cleaning filter written for one CSV file can be reused across multiple projects — as long as the pipe (the input/output contract) remains the same. If the filter expects a DataFrame with certain columns and returns a cleaned DataFrame, any pipeline that can provide that input can plug it in. The textbook (Ch. 11) calls this the benefit of "stable interfaces" — the model (or filter) internals can change without affecting anything outside its interface.
Q: These are sequential filters. What if a filter needs to be skipped — can the output of filter 1 go directly to filter 3?
A: Yes, the pattern allows conditional paths. You can route data around a filter entirely if it is not needed for a particular run. In the diabetes example, scaling is optional — you can wire the cleaning output directly to the training input. The pattern is flexible, not rigidly sequential. Several students asked this — a common misconception is that "pipeline" means "strictly linear."
Q: Is pipe and filter restricted to a monolith architecture, or can it be distributed?
A: It works in both. In a monolith, each filter is a module and the pipe is a function call or in-memory variable. In a microservices architecture, each microservice can be a filter, and the pipe becomes a REST API, gRPC, or an asynchronous message channel. The pattern generalizes across deployment styles.
Q: Would you consider message brokers like Kafka as pipes?
A: By the traditional definition, a pipe only transports data — it does no transformation. Kafka and similar brokers can include routing, filtering, and buffering logic, which makes them "intelligent pipes." The traditional pipe and filter pattern keeps the pipe dumb. But in practice, modern systems often upgrade the pipe to handle reliability, back-pressure, and fan-out. The pattern adapts.
Q: Input and output filters — are they really doing any filtering?
A: No. The term "filter" here simply means a processing step. Do not confuse it with the ML concept of filtering (e.g., feature selection or noise removal). The name is historical, dating back to 1970s Unix. "Filter" means "a component that does some computation." The input filter ingests data; the output filter presents results. Neither "filters" in the data-science sense.
Q: Can we have parallel pipelines — multiple filters running simultaneously?
A: Yes. Even the traditional pattern allows parallelism. A single filter can have multiple
instances running in parallel — think of it as replicas. The next filter downstream must then be capable of
receiving from multiple upstream sources. The Unix tee command — which sends output to both
stdout and a file — is a classic 1-to-N fan-out. The pattern allows 1-to-1, 1-to-N, N-to-1, and arbitrary
graph connections.
Q: Are data pipelines always pipe and filter?
A: Yes. A data pipeline is the canonical example. Whether it is an ETL job, a feature engineering pipeline, or a model training workflow, the output of one step feeds the input of the next — that is the pipe and filter signature. This includes LangChain chains, Prefect flows, and Airflow DAGs.
Q: When a filter enriches data (e.g., fetching from an additional source), is that still a filter?
A: Absolutely. Data augmentation is a processing step — it takes data in, adds information from other sources, and produces enhanced output. That is exactly what a filter does. The name should not mislead you; "filter" means "processing component," not "data removal."
Q: In the Unix world, can the output of one command be piped to multiple other commands?
A: Yes, and the pipe and filter pattern fully supports this. One-to-N, N-to-one, and arbitrary graph topologies are all valid. The traditional sequential pipeline is the simplest topology, not the only one.
5.2.5 Industry Context
The pipe and filter pattern is so fundamental that it appears under different names across the industry: data pipelines, ETL workflows, DAG-based orchestrators, ML pipelines, and even the Unix shell itself.
LangChain as pipe and filter. When you write a LangChain application that chains together a loader, a splitter, an embedder, and a vector store — you are building a pipe and filter pipeline. Each LangChain component is a filter; the chain itself is the pipe. The beauty of this pattern is that you can swap any component: change the embedding model from OpenAI to Sentence Transformers, change the vector store from ChromaDB to Pinecone, and the rest of the pipeline remains intact. This plug-and-play composability is what makes pipe and filter the backbone of modern ML infrastructure.
ML workflow orchestrators. Prefect and Airflow let you define each step as a task and wire
them into a DAG. Each task is a filter. The scheduler and data-passing mechanism together form the pipe. When
you see a Prefect flow with task1 → task2 → task3, you are looking at a pipe and filter
architecture with extra scheduling and retry logic wrapped around it.
Connection to textbook patterns. The textbook (Ch. 8) identifies the Feature Store pattern as a way to "decouple feature creation from model development and serving" — this is pipe and filter thinking applied to feature engineering. The feature store acts as a shared data sink for training pipelines and a shared data source for inference pipelines.
Recap: The pipe and filter pattern decomposes a processing workflow into independent filters connected by dumb pipes. Filters are reusable, swappable, and composable in arbitrary topologies (linear, fan-out, fan-in, DAG). The pattern scales from a Python script passing variables to a distributed Kafka-backed system — the filter logic stays the same, only the pipe changes. Next: what happens when we separate what changes state from what reads state? That is CQRS.
5.3 Command Query Responsibility Segregation (CQRS)
5.3.1 Definition and Origins
Hook: In a typical social-media application, roughly 70–75% of all operations are reads — people scrolling, browsing, consuming content. Only 25–30% are writes — posting, liking, commenting. If your architecture treats reads and writes the same, you are forcing 75% of your traffic through infrastructure optimized for the other 25%. CQRS fixes this mismatch.
CQRS — Command Query Responsibility Segregation — is an architectural pattern that says every operation in a system should be either a command (which changes state) or a query (which returns data), but never both. The responsibility for commanding and the responsibility for querying must be separated.
Origin story. The idea originated with Bertrand Meyer's Command Query Separation (CQS) principle, introduced decades ago in the context of the Eiffel programming language. Meyer's rule was simple: a method should either perform an action (command) or return data (query), not both. Greg Young later elevated this from a method-level principle to an architectural pattern and gave it the name CQRS. Over the last decade and a half, CQRS has become one of the most widely adopted patterns in modern application design.
In HTTP terms, the mapping is natural:
- Commands (change state): POST (create), PUT/PATCH (update), DELETE (delete)
- Queries (return data): GET (read)
Every application you use — YouTube, Instagram, Facebook, Netflix, Swiggy, Zomato — supports these four operations. CQRS says: do not handle the "create/update/delete" logic in the same code path as the "read" logic. Separate them — all the way down to the data layer and infrastructure layer, not just at the API endpoint.
Analogy — restaurant kitchen vs. dining room. A restaurant separates two fundamentally different activities: cooking (commands — changing the state of ingredients into dishes) and serving (queries — delivering dishes to customers). The kitchen is optimized for correctness: proper temperatures, hygiene, ingredient quality. The dining room is optimized for speed and throughput: fast table turnover, concurrent service to many guests. If the kitchen and dining room shared the same space and the same staff doing both jobs, neither would function well. CQRS applies the same logic to software: the write side is your kitchen (optimized for consistency and validation), the read side is your dining room (optimized for speed and scale). Where the analogy breaks: in a restaurant, the kitchen and dining room are physically adjacent; in software, the command and query sides can be on different continents.
Why separate reads from writes? Because they have fundamentally different quality requirements:
| Quality | Command Side (Writes) | Query Side (Reads) |
|---|---|---|
| Primary goal | Correctness, validation, integrity | Speed, availability, throughput |
| Scaling strategy | Vertical (powerful single server) | Horizontal (many replicas, caches) |
| Consistency | Strong — every write immediately visible | Eventual — propagation delay OK |
| Typical ratio | ~25% of operations | ~75% of operations |
| Example infra | Relational DB, transaction logs | CDN, read replicas, Redis cache |
If you handle reads and writes together, you optimize for neither. If you separate them, you can give each side the infrastructure it needs.
5.3.2 Strong Consistency vs. Eventual Consistency
Understanding CQRS requires understanding two kinds of consistency:
Strong consistency means that as soon as a write completes, every subsequent read sees the updated value. This is what traditional relational databases (MySQL, PostgreSQL) provide. In banking, if you transfer 100 rupees to another account, both you and the recipient must see the identical balance immediately — lives and livelihoods depend on it. Strong consistency is typically achieved through vertical scaling: a single powerful server, or a tightly coupled cluster, where all reads and writes go through the same coordination point.
Eventual consistency means that a write is guaranteed to propagate to all nodes eventually, but at any given moment, different readers may see different values. If you post a new Facebook status at 11:10 AM, some of your friends may see it instantly, while others may not see it for a few minutes. The post will reach everyone eventually — but "eventually" can mean seconds to minutes depending on network latency, replication lag, and geographic distance. This is the price of horizontal scaling.
The CDN is CQRS at planetary scale. When Netflix releases a new movie, the original print lives on an origin server — say, in London. Copies are pushed to CDN edge nodes in Bangalore, Chennai, Mumbai, and hundreds of other locations. When you stream that movie in Bangalore, you are accessing the Bangalore read-only copy, not the London origin. The read copy is a cache — it serves blazingly fast because it is geographically close, but it may lag behind the origin by some propagation delay. Since movies rarely change after release, this read-only caching model is perfect for streaming. CDN is, in essence, a CQRS pattern: write once to the origin (command), read from edge caches everywhere (query).
Worked Example — Eventual Consistency in Everyday Apps
| Platform | What is written (command) | What is read (query) | Consistency model |
|---|---|---|---|
| Netflix | Upload movie to origin server | Stream from CDN edge nodes | Eventual — edge nodes propagate over minutes |
| Post a photo | Friends scroll their feed | Eventual — some friends see it instantly, others with delay | |
| Swiggy/Zomato | Restaurant updates menu | Millions of users browse menus | Eventual — menu rarely changes during the day, aggressive caching |
| Banking (NEFT/IMPS) | Transfer 100 ₹ | Check balance | Strong — both accounts update atomically |
| UPI Lite | Offline payment logged locally | Settlement when network returns | Eventual — deliberate, risk-managed exception |
| Couchbase distributed DB | Write a document | Read from any node | Configurable per API call — choose strong or eventual |
The pattern: when reads massively outnumber writes and staleness is tolerable, eventual consistency + CQRS is the right trade-off. When correctness is existential (banking, medical), strong consistency wins even at the cost of scaling limitations.
Q: How does eventual consistency work in banking — do they ever use it?
A: Banking predominantly uses strong consistency through vertical scaling. However, there are narrow exceptions: UPI Lite allows offline payments with deferred settlement, and Visa/Mastercard networks provide fallback limits when the issuing bank is unreachable. These are carefully controlled risk scenarios, not the norm. The professor's rule: if the consequence of inconsistency is financial loss or panic, the system uses strong consistency. If the consequence is a slightly delayed like count on a social media post, eventual consistency is fine.
5.3.3 CQRS Applied to ML Systems
CQRS takes on a richer meaning when applied to machine learning systems, because "command" and "query" map to more than just HTTP verbs.
The Command Side (Write Path) includes everything that changes the state of the ML system:
- Data ingestion — bringing new data into the system
- Data processing and feature engineering — transforming raw data
- Model training — creating or updating a model
- Model evaluation — assessing model quality
- Model deployment — pushing a trained model to production
- Any ML pipeline step orchestrated by workflow engines (Prefect, Airflow, AWS Step Functions, Kubeflow)
These are "state-changing operations" because they produce or modify models, features, or data. The command side may run in a development or staging environment N times before a model is deemed production-ready, but in production, it typically runs on a schedule or is triggered by new data arrival.
The Query Side (Read Path) includes everything that serves predictions or insights without changing system state:
- Prediction API — the inference endpoint that answers "what is the model's output for this input?"
- Metrics API — serving training metrics, evaluation results
- Analytics dashboards — business intelligence on model usage, token consumption, user behavior
- Model monitoring — tracking drift, performance degradation, anomalies
- Any read-optimized cache or data store serving pre-computed results
These are "read operations" because they consume the model's output without altering it. The query side is optimized for low latency, high throughput, and availability — exactly the opposite of the command side, which prioritizes correctness and reproducibility.
Worked Example — Model Deployment with Eventual Consistency
Consider OpenAI releasing a hypothetical ChatGPT 6.0 model at noon on June 1st:
- Noon: Model metadata updates in the central model registry (command-side write).
- Noon + seconds: Inference servers nearest the origin begin serving the new model.
- Noon + minutes: Inference servers in distant regions (Bangalore, São Paulo) still run version 5.5.
- Noon + hours: All inference endpoints synchronize to version 6.0.
This is eventual consistency at work in an ML context. The write (deploying a new model) propagates through a distributed inference fleet over time. The separation of command (training + deployment) from query (inference serving) is what makes this manageable — if training and inference shared the same infrastructure, every model update would cause downtime for users.
Team separation. The command side and query side can be owned by entirely separate teams. The training team runs experiments, produces models, and pushes them to a model registry. The serving team manages prediction APIs, monitors performance, and builds analytics dashboards. They communicate through shared artifacts — the model registry and feature store act as the contract between the two sides. The textbook (Ch. 10) calls this "separating models from business logic" and notes that this separation "can help with planning for mistakes and testing the robustness of the system."
Scope — when CQRS does NOT help:
- Small, single-user tools where reads and writes happen on the same machine — the overhead of separation outweighs the benefit.
- Real-time collaborative systems (Google Docs, multiplayer games) that need strong consistency for every operation — CQRS's eventual consistency model is a poor fit.
- Systems with balanced read/write ratios — CQRS's primary benefit is when reads vastly outnumber writes.
Q: We already separate GET from POST/PUT/DELETE when we build REST APIs. How is CQRS different from standard API separation?
A: API-level separation is only the application layer. CQRS extends the separation to the data layer and the infrastructure layer. You may have separate databases for reads and writes, separate scaling policies, separate caching strategies, and separate teams. The API separation is a starting point; CQRS pushes the separation deeper into the system architecture.
Q: On the command side, the ML pipeline steps happen before deployment. Are those considered command-side operations?
A: Yes, all ML pipeline steps — data ingestion, processing, feature engineering, model training, evaluation — are command-side operations. They change the state of the system by producing new models, updated features, or transformed data.
Q: Why is there an event stream between the command and query sides in the diagram?
A: The event stream is one possible communication mechanism — it is not mandatory. The two sides can communicate synchronously (through the model registry) or asynchronously (through events). The event stream becomes especially useful when the query side needs to react to command-side events — for example, triggering a dashboard refresh when a new model is deployed. The core message of CQRS is the separation; how the two sides communicate is an implementation choice.
Q: Where is the database on the command side? The diagram shows data flowing but no persistent storage.
A: The model registry and feature store at the bottom of the diagram serve as the write-side persistence. Any pipeline step can write to these stores. They are not shown in the sequential flow because they are not sequence-dependent — a training step may write to the model registry, a feature engineering step may write to the feature store, and these writes can happen at any point in the pipeline. The model registry and feature store then become the bridge that the query side reads from.
Q: Is there a timeline synchronization between writes and reads? What if I read data that has not been written yet?
A: In a distributed system with eventual consistency, there will be moments when a read returns stale data. If you deploy a model at noon and query the prediction API at 12:01 PM, you may still hit a server running the old model. This is the nature of eventual consistency. If your use case requires strong consistency — for example, a financial application where every prediction must use the latest model — you must design your infrastructure accordingly. But for most ML serving scenarios, a brief propagation delay is acceptable.
Q: The diagram shows workflow engines generating metrics on the command side and analytics on the query side. How do they relate?
A: Workflow engines like Prefect and Airflow generate execution metrics (task durations, success rates, data volumes) during pipeline runs. These metrics can be sent directly to an event stream and analyzed without waiting for the model to be deployed. So analytics is not exclusively a query-side concern — some analytics begin on the command side. But the bulk of user-facing analytics (prediction volumes, model performance, business KPIs) lives on the query side because that is where the production traffic flows.
5.3.4 Key Insight: CQRS Is a Pattern of Patterns
A real-world application is rarely a single pattern in isolation. It is typically a composition of multiple patterns. The RAJ architecture, which we examine next, is the most instructive example: it is Pipe and Filter plus CQRS. The ingestion pipeline (command side) is a pipe-and-filter pipeline. The inference pipeline (query side) is also a pipe-and-filter pipeline. And the separation between them is CQRS.
Recap: CQRS separates writes (commands — state-changing operations) from reads (queries — state-returning operations) at every layer: code, data, infrastructure, and teams. Strong consistency = every read sees the latest write (banking). Eventual consistency = reads catch up over time (social media, streaming, ML serving). In ML systems, the command side is the training pipeline; the query side is the inference/serving layer. CQRS composes naturally with pipe and filter — which brings us to RAJ.
5.4 Retrieval-Augmented Generation (RAJ)
5.4.1 What RAJ Is and Why It Exists
Hook: Large language models are trained on the internet, but your company's internal documents are not on the internet. When an employee asks "What is our refund policy for enterprise clients?", a general-purpose LLM will confidently fabricate an answer. RAJ exists to prevent exactly this: it forces the LLM to answer only from documents you provide, eliminating hallucination for domain-specific questions.
RAJ — Retrieval-Augmented Generation — is an architecture for building AI applications that answer questions based on a specific set of documents, rather than relying on the LLM's general training data. The name tells the story: you retrieve relevant information from your document store, then use an LLM to augment and generate a natural-language response grounded in that retrieved information.
RAJ sits in the generative AI domain (as opposed to predictive AI or agentic AI). Its killer use case is the enterprise chatbot: you have an internal knowledge base of policy documents, technical manuals, or product specifications, and you want employees to ask natural-language questions and get answers that are accurate, sourced, and restricted to your internal data. The LLM should not hallucinate from its general training data — it should answer only from the documents you provide.
RAJ's core design principle: Separate the knowledge (your documents, stored as vectors) from the reasoning (the LLM, which synthesizes and generates). The knowledge base is built once and updated on your schedule. The reasoning engine (LLM) is stateless — it receives retrieved context plus the question, and generates an answer. This separation is what makes RAJ both controllable and scalable.
The textbook (Ch. 8) formally describes RAJ (under the name RAG) as a design pattern: "Decompose the problem into two steps, search and generation. In the search step, relevant context information is located... The search results are then provided as part of the context in a prompt to the generative model." Benefits: "Enables generating answers about recent or proprietary information without retraining the model. The generative model's answer is focused and grounded in the search result." Costs: "Nontrivial infrastructure and expensive inference cost and additional latency for search and generation."
Real-world: Google Notebook LM is a free tool that implements RAJ. You upload any document (PDF, PPT, CSV, text), and it lets you ask questions grounded in that content. It gives precise answers with source references — exactly the RAJ pattern. The demo shown in class used a university bulletin as input and generated sourced answers. Similarly, a Python chatbot was demonstrated using OpenAI APIs and 16 lecture PDF documents as the knowledge base — any question about the course material was answered accurately with page-level citations, while out-of-domain questions (like "what is the weather in Bangalore?") received a clear "I cannot answer that" response.
5.4.2 The RAJ Pipeline: Command Side (Ingestion / Write Path)
When you feed documents into a RAJ system, the following steps execute — and this is a textbook pipe-and-filter pipeline (see §5.2):
Step 1 — Document Extraction
The input documents (PDFs, Word files, text files, or any supported format) are read and their textual content is extracted. If the source is a multi-page PDF, each page is extracted individually. This is the data source filter in pipe-and-filter terminology.
Step 2 — Chunking
The extracted text is split into chunks. A chunk is a contiguous block of characters — by default, 1,000 characters with a 200-character overlap between consecutive chunks. The overlap ensures that a concept split across a chunk boundary is not lost: the last 200 characters of chunk 1 also appear as the first 200 characters of chunk 2.
Why these numbers? Chunk size (1,000 chars) and overlap (200 chars) are empirically derived defaults:
- Too small (e.g., 200 chars): chunks lose context — a single sentence may not contain enough information to answer a question.
- Too large (e.g., 10,000 chars): the embedding becomes too diffuse to match specific queries — a "bag of everything" vector.
- Overlap prevents boundary loss: without it, a concept that starts at the end of chunk 1 and continues at the start of chunk 2 would be split across two separate retrieval units.
The underlying principle is divide-and-conquer: instead of embedding an entire document as one giant vector (which would lose fine-grained semantic detail), you embed smaller, focused chunks that capture specific ideas.
For domain-specific documents, you may tune these values — a legal contract with dense paragraphs might benefit from larger chunks with more overlap; a FAQ with short Q&A pairs might use smaller chunks. Alternatives to fixed-size character chunking include recursive character splitting (split on paragraph/sentence boundaries), semantic chunking (split where embedding similarity drops), and document-structure-aware chunking (respect headings, sections, tables).
Step 3 — Embedding
Each text chunk is passed through an embedding API, which converts the text into a numerical vector
— a fixed-length array of floating-point numbers. For example, the text "Virat Kohli scored 100 centuries"
becomes a vector like [0.23, -0.41, 0.87, ...].
Analogy — GPS coordinates for meaning. Every location on Earth has a unique GPS coordinate (latitude, longitude). Two nearby locations have similar coordinates. An embedding does the same thing for meaning: every chunk of text gets a vector (a list of numbers), and two chunks with similar meaning get vectors that are close together in vector space. Just as you can find the nearest restaurant by searching GPS coordinates, you can find the most relevant chunk by searching embedding vectors.
The embedding captures the semantic meaning of the text: semantically similar texts produce vectors that are close together in vector space; dissimilar texts produce vectors that are far apart.
Critical constraint: Embeddings from different models are not interchangeable. If you switch embedding models, you must rebuild your entire vector database. This is an important architectural decision with cost and time implications.
Options include OpenAI's text-embedding-3-small (used in the class demo) and
text-embedding-3-large (for larger document corpora), Sentence Transformers, GloVe, FAISS
embeddings, and DPR (Dense Passage Retrieval).
Step 4 — Indexing / Storage
Each chunk, its embedding vector, and its metadata are stored in a vector database (also called a vector store or knowledge base). The metadata is critical: it records the source document name, the page number, and the chunk identifier. This is what enables the system to cite its sources — when a chunk is retrieved during query time, the metadata tells you exactly which document and which page it came from.
| ID | Document (Chunk Text) | Embedding (Vector) | Metadata |
|---|---|---|---|
| chunk_1 | "Text from page 1, paragraph 1..." | [0.23, -0.41, 0.87, ...] | {source: "lecture_5.pdf", page: 1} |
| chunk_2 | "Text from page 1, paragraph 2..." | [0.15, 0.62, -0.33, ...] | {source: "lecture_5.pdf", page: 1} |
Vector database options include ChromaDB (used in the demo, backed by SQLite for persistence), Pinecone, and FAISS (from Facebook/Meta). Most are open-source. Vector databases are optimized for similarity search — given a query vector, they can efficiently find the K nearest neighbor vectors using algorithms like approximate nearest neighbor (ANN) search.
5.4.3 The RAJ Pipeline: Query Side (Inference / Read Path)
When a user asks a question, the following steps execute — again, a pipe-and-filter pipeline:
Step 1 — Query Embedding
The user's question is passed through the same embedding API that was used during ingestion. This produces a query vector in the same vector space as the document chunks. Using the same embedding model is non-negotiable — a vector from model A is meaningless in the vector space of model B. This is why the choice of embedding model is an architectural decision: it locks in both the ingestion and query pipelines.
Step 2 — Semantic Search
The query vector is matched against all chunk vectors in the vector database. This is a semantic search, not a keyword search:
| Search Type | What it matches | Example: query "Dhoni World Cup wins" |
|---|---|---|
| Keyword search | Exact text matches | Matches chunks containing the word "Dhoni" |
| Semantic search | Meaning-based matches | Matches chunks about "India's 2011 World Cup victory" even without the word "Dhoni" |
| Hybrid (best) | Both | Exact name matches surface first, then conceptually related content |
The search returns the top-K most similar chunks, ranked by vector similarity (typically cosine similarity or dot product). The demo used K=5 (retrieve the top 5 chunks), though K=3 would often suffice for smaller document sets. K is a tunable parameter — too low and you miss relevant information; too high and you send irrelevant noise to the LLM, wasting tokens.
Step 3 — Context + LLM Generation
The retrieved chunks (plain text), along with the original user question, are sent to an LLM. The LLM receives two inputs:
- The retrieved chunks — provide the content (the factual material to draw from)
- The user's question — provides the direction (what to extract and how to shape the response)
Both are necessary. Without the chunks, the LLM has no factual basis. Without the question, the LLM has context but no guidance on what to do with it.
The LLM is instructed — via the prompt template — to answer only from the provided context. The template must include an instruction like: "Answer only based on the provided context. If the context does not contain relevant information, state that you cannot answer." Without this guardrail, the LLM may fall back on its training data and hallucinate. With it, the system stays grounded.
Why is the LLM necessary? After semantic search, you have raw text chunks. These chunks might be grammatically imperfect, tangentially relevant, or contain extraneous detail. The user asked "How many World Cup titles did MS Dhoni win?" The retrieved chunk might be a 500-word paragraph about the 2011 World Cup final. The LLM distills this to: "MS Dhoni won 3 ICC World Cup titles." It adapts its verbosity to the question — expand when asked for details, compress when asked for a number.
Step 4 — Response with Citations
The final answer is presented to the user, ideally with source citations pulled from the chunk metadata. The demo showed responses like "Answer from Lecture 10 PDF, page 30." This traceability is what makes RAJ trustworthy for enterprise use — users can verify the source.
Pitfalls in RAJ systems:
- Embedding model lock-in. If you switch embedding models, you must rebuild the entire vector database. Choose carefully.
- Chunking strategy matters. Poor chunking (too small, no overlap, no respect for document structure) directly degrades retrieval quality. Evaluate per use case.
- Prompt template is the guardrail. Without the "answer only from context" instruction, the LLM will hallucinate from its training data. This is the single most common RAJ implementation bug.
- Hybrid search beats pure semantic. For queries involving names, IDs, or codes, pure semantic search retrieves incorrect results for similar-sounding terms. Combine keyword + semantic search for best results.
5.4.4 LangChain: The Glue That Chains It All Together
LangChain is a Python framework that implements the pipe and filter pattern for LLM applications. As the name suggests: "Lang" = language, "Chain" = connecting components. Every step in the RAJ pipeline — document loading, chunking, embedding, vector storage, retrieval, LLM generation — is a LangChain component (a filter). LangChain chains them together so the output of one component flows into the next.
LangChain's most powerful feature is plug-and-play composability. You can:
- Swap the embedding model from OpenAI to Sentence Transformers without changing any other code
- Replace ChromaDB with Pinecone or FAISS
- Change the chunking strategy from character-based to recursive or semantic chunking
- Switch the LLM from GPT-4o-mini to any other model
Each component exposes a standard interface. As long as the new component respects that interface, the chain works. This is pipe and filter at the framework level — each component is an independent filter, and LangChain's chain abstraction is the pipe.
Real-world: In the class demo, the configuration file specified model: gpt-4o-mini,
embedding_model: text-embedding-3-small, chunk_size: 1000,
chunk_overlap: 200, retrieval_k: 5, and vector_store: chromadb. Changing
any one of these parameters is a one-line configuration change — no code refactoring needed.
5.4.5 Demonstration Walkthrough
Two live demonstrations were shown:
Demo 1 — Custom Python Chatbot (without CQRS)
A monolithic chatbot built with OpenAI APIs and 16 lecture PDFs from a cloud-native solutions course. The ingestion and query pipelines ran within the same codebase. When a user asked a question, the system performed chunking → embedding → search → generation in a single execution path. It correctly answered in-domain questions with page-level citations and rejected out-of-domain questions. Limitation: the vector database was rebuilt on every query — this is the monolithic approach that CQRS solves (see §5.5).
Demo 2 — Google Notebook LM
A free Google tool that implements RAJ. A university bulletin PDF was uploaded as the knowledge source. The tool allowed natural-language questions and returned precise, sourced answers. Notebook LM abstracts away the entire pipeline — it handles chunking, embedding, storage, and retrieval automatically. This shows RAJ as a productized service, not just a DIY architecture.
5.4.6 Student Questions and Answers
Q: If the chunk size is not well-defined, a concept could be split across chunk boundaries and the retrieval might miss its full meaning.
A: Correct. This is why the overlap parameter exists — 200 characters of overlap means that a concept straddling the boundary between chunk 1 and chunk 2 will appear in full in at least one of them. Chunk size and overlap are tunable based on the nature of the input documents. The defaults (1000 characters, 200 overlap) work well for typical prose, but dense technical documents may need larger chunks with more overlap. The chunking strategy is not one-size-fits-all; it should be evaluated per use case.
Q: Are white spaces and paragraph breaks counted in the chunk size?
A: No. The NLP pipeline strips extraneous whitespace before chunking. Only printable, meaningful characters are counted. You can inspect the actual chunks to verify this — all major frameworks let you log or print chunk contents.
Q: Does the LLM ever add its own knowledge or go beyond the provided context?
A: Not if the prompt template is properly configured. The template must explicitly instruct the LLM: "Answer only from the provided context. Do not use your training data." With this guardrail, the LLM restricts itself to the retrieved chunks. Without it, the LLM may blend the context with its general knowledge, which defeats the purpose of RAJ. This is why RAJ is the go-to architecture for enterprise applications — it keeps responses grounded in proprietary data and prevents leakage of internal information to external models.
Q: The architecture diagram shows an arrow from the user's question directly to the LLM, bypassing the embedding and search steps. Why?
A: The plain-text question also flows to the LLM so the LLM can tailor its response to what was asked. The retrieved chunks give the LLM content; the original question gives it direction. Both are necessary — chunks without a question is a heap of undirected information; a question without chunks is an unfounded prompt.
Q: Is RAJ only for text, or does it handle images as well?
A: RAJ can handle images, but the pipeline changes. When a PDF contains both text and images (like a presentation slide with diagrams), the extraction, chunking, and embedding steps must be adapted. Multi-modal embeddings exist that can represent both text and images in a shared vector space. This was assigned as homework: research how the RAJ pipeline changes when the input includes images.
Q: What if the input documents are in multiple languages?
A: The embedding model converts text to language-agnostic semantic vectors, so chunks in different languages that discuss the same concept will produce similar vectors. The LLM can read multi-lingual context and produce a response in the user's preferred language. The intelligence for cross-lingual synthesis lives entirely in the LLM; the vector database simply stores and retrieves.
Q: If data sources change constantly (new documents added daily), how does the architecture handle it?
A: This is exactly why CQRS matters in RAJ. The ingestion pipeline (command side) runs independently — triggered by a scheduler, a file-watch event, or a manual process — whenever new documents arrive. It rebuilds or incrementally updates the vector database. The query pipeline (inference side) always reads from the latest vector database state. Without CQRS, every user query would re-run the ingestion pipeline, causing unacceptable latency. With CQRS, writes happen on their own schedule; reads always hit a pre-built, ready-to-search vector store.
Q: How do you evaluate the quality of RAJ responses?
A: Dedicated evaluation frameworks exist. Key metrics: faithfulness (does the answer stick to the provided context?), relevance (does the answer address the question?), and context precision (were the right chunks retrieved?). These will be covered in a later session.
Q: Is RAJ a framework with rigid rules, or a flexible architecture?
A: RAJ is an architecture — a pattern, not a product. The basic architecture (internal documents only) has spawned 6-7 recognized variants. Agentic RAJ, for example, adds tool-using agents that can perform web searches, query APIs, or call external services in addition to searching the internal vector database. The basic architecture is the foundation; the variants add capabilities on top. The assignment will focus on the basic architecture.
Q: Is there a security risk if internal documents are processed through external APIs like OpenAI?
A: Yes. If your documents contain proprietary or sensitive information, sending them to an external embedding API or LLM may violate data residency or confidentiality policies. Solutions: self-hosted embedding models and LLMs, private cloud deployments with contractual data-processing agreements, or on-device models. The RAJ architecture does not require external APIs — it requires embeddings and an LLM, but both can be self-hosted.
Q: When an internal RAJ system also connects to the web for additional references, how do you prevent internal data from leaking out?
A: The system must be designed with clear data boundaries: internal documents are searched in a private vector database, and web search results come from a separate path. The LLM receives both and synthesizes a response, but the internal chunks never leave your infrastructure — only the final synthesized response is shown to the user. Additional guardrails (output filters, PII detection, data-loss-prevention checks) may be needed depending on sensitivity.
Q: How does LangChain relate to the RAJ architecture diagram?
A: LangChain is the implementation layer. Every box in the RAJ diagram — document loader, text splitter (chunker), embedding model, vector store, retriever, LLM — corresponds to a LangChain component. LangChain provides standardized interfaces for each, so you can compose them into a pipeline. LangChain itself follows the pipe and filter pattern: each component is a filter, and the chain connecting them is the pipe.
Q: Does the chunking strategy affect retrieval quality, and are there alternatives to fixed-size character chunking?
A: Yes, significantly. Alternatives include recursive character splitting (split on paragraph/sentence boundaries), semantic chunking (split where embedding similarity drops), and document-structure-aware chunking (respect headings, sections, tables). LangChain supports multiple strategies, and switching between them is a configuration change — another example of plug-and-play composability.
Q: If you change the embedding API, does the entire vector database need to be rebuilt?
A: Yes. Embeddings from different models exist in different vector spaces. A vector produced
by OpenAI's text-embedding-3-small is not comparable to one from Sentence Transformers. If you
switch models, you must re-run the entire ingestion pipeline. This is why the choice of embedding model is an
important architectural decision.
Q: In the context of token consumption, how does RAJ compare to uploading a PDF directly into an LLM chat interface?
A: RAJ is far more token-efficient. Uploading a PDF directly sends the entire document with every query — a 50-page PDF costs 50 pages of tokens per question. RAJ only sends the top-K retrieved chunks (typically 3-5 chunks, a few thousand characters) plus the question. The vector database consumes no tokens — it is independent storage. RAJ trades a one-time embedding cost for dramatically lower per-query costs. For frequently queried documents, RAJ is significantly cheaper.
Q: How does RAJ compare to MCP (Model Context Protocol)?
A: RAJ excels at grounding responses in a fixed document corpus. MCP is better for scenarios where the LLM needs to interact with live tools and services dynamically. Some use cases are tailor-made for RAJ (enterprise document Q&A); others for MCP (agentic workflows with tool use). In token economy, RAJ is generally more efficient because it retrieves only relevant context rather than loading everything.
Q: The architecture diagram shows two parallel paths — one going through embedding and semantic search, and the other going directly from the question to the LLM. Are these two separate execution paths?
A: No, they are not alternatives. Both paths feed into the LLM simultaneously. The retrieval path provides retrieved context (the relevant chunks). The direct path provides the user's intent (what they actually asked). Both are necessary: context without direction is undirected information; direction without context is an unfounded prompt.
Q: In the keyword-vs-semantic-search context, we are seeing hallucination-like behavior where similar names produce incorrect retrievals. What design principles help?
A: Use a hybrid approach: combine keyword search (exact match on names, IDs, codes) with semantic search (meaning-based matching for descriptive queries). Many vector databases support hybrid search natively. A weighted combination often produces the best results. This is a design decision, not a limitation of RAJ — RAJ provides the retrieval architecture; you choose the search strategy.
Recap: RAJ = Retrieve relevant chunks from a vector database + Generate an answer using an LLM grounded in those chunks. The ingestion pipeline (extract → chunk → embed → store) is a pipe-and-filter command side. The query pipeline (embed question → semantic search → LLM generation → cite sources) is a pipe-and-filter query side. LangChain implements this pattern with plug-and-play composability. Next: what happens when we formally separate the ingestion pipeline from the query pipeline using CQRS?
5.5 RAJ with CQRS: The Complete Architecture
5.5.1 The Two-Sided Architecture
Hook: Demo 1 (the monolithic chatbot) rebuilt the entire vector database on every user query. That works for a classroom demo with 16 PDFs — but what happens when you have 10,000 documents and 10,000 users per minute? You need to build once and query many times. That is exactly what CQRS gives you.
When you combine RAJ with CQRS, you get a cleanly separated system where the ingestion pipeline (building the vector database) is the command side and the inference pipeline (answering questions) is the query side.
Command Side (Write Path) — the ingestion pipeline (pipe and filter):
1. Load documents → 2. Extract pages → 3. Chunk text → 4. Generate embeddings → 5. Store in vector database
Query Side (Read Path) — the inference pipeline (pipe and filter):
1. Receive user question → 2. Generate query embedding → 3. Semantic search in vector DB → 4. Retrieve top-K chunks → 5. Send chunks + question to LLM → 6. Return answer with citations
Both sides are independent pipe-and-filter pipelines. The vector database is the shared contract between them — the command side writes to it; the query side reads from it. This mirrors the textbook's (Ch. 10) pattern of separating models from business logic: separating the model "can help with planning for mistakes and testing the robustness of the system."
5.5.2 Why the Separation Matters
Without CQRS (monolithic approach, Demo 1):
Every user query triggers the entire pipeline — chunking, embedding, storage, and search — all in one execution. The vector database is rebuilt on every query even if nothing has changed. This works for demos and small documents but is wasteful at scale.
With CQRS (separated approach):
The vector database is built once and re-queried millions of times. The command side runs on a schedule or is triggered by new document arrival. The query side runs continuously, always reading from the latest pre-built vector store.
The separation buys you five things:
- Build once, query many. You are not re-chunking and re-embedding on every user question.
- Independent updates. New documents can be added to the vector database without interrupting the query service.
- Independent scheduling. The command side runs on a schedule or is triggered by events; the query side runs continuously.
- Team separation. Different teams can own each side, with the vector database as the contract between them.
- Independent scaling. The command side may need GPU compute for embedding generation; the query side needs low-latency serving infrastructure.
Recap: RAJ + CQRS = the complete architecture. The ingestion pipeline (command side) builds the vector database once. The inference pipeline (query side) reads from it millions of times. Both are independent pipe-and-filter pipelines. The vector database is the shared contract. This is the canonical example of pattern composition in ML systems: Pipe and Filter provides the processing structure, CQRS provides the read/write separation.
5.6 Exam Guidance Summary
5.6.1 Assignment and Homework
Exam note — Assignment 1: You will implement several of the architectural patterns covered in class — pipe and filter, CQRS, and RAJ — as a practical coding assignment. The focus is on faithfully implementing the pattern, not on achieving high model accuracy. If your logistic regression gives mediocre accuracy on the diabetes dataset, that is fine — what matters is that your code correctly demonstrates the pipe and filter architecture. Likewise, for RAJ, getting the pipeline right matters more than tuning the model.
Exam note — Homework for next session: Research how the RAJ architecture handles images as input. Specifically: when a PDF contains text plus images (like presentation slides with diagrams), how does the pipeline change? What happens to chunking? What happens to embeddings? What different techniques exist for multi-modal RAJ? The first question in session 6 will be on this topic.
5.6.2 Exam Focus and Study Advice
Exam note — What to expect:
- Understanding the patterns conceptually and being able to explain how they compose (e.g., "RAJ is Pipe and Filter plus CQRS")
- Expect questions that ask you to identify which pattern is being used in a given system description
- Expect questions that ask you to design a simple architecture using these patterns
- Numerical computation is not the emphasis — pattern recognition, architectural reasoning, and the ability to map real-world systems to these patterns are the core skills being assessed
Key pattern relationships to remember:
| Pattern | What it does | One-line summary |
|---|---|---|
| Pipe and Filter | Decomposes processing into independent steps connected by data conduits | The universal data-processing pattern; appears inside almost every other architecture |
| CQRS | Separates writes (commands) from reads (queries) | Essential for read-heavy systems; allows independent scaling and optimization |
| RAJ = Pipe and Filter + CQRS | Builds a document-grounded Q&A system | The canonical example of pattern composition in ML systems |
| Monolith → Microservices | Deployment-style spectrum | Not a pattern vs. anti-pattern choice — each has its place |
Exam note — Study advice: Build a small RAJ pipeline yourself (there are abundant tutorials online). Nothing solidifies these concepts like wiring up a document loader, a chunker, an embedder, a vector store, and an LLM and watching a question flow through the system. The assignment will give you hands-on practice.
5.7 Key Industry Applications
5.7.1 Infrastructure and Platforms
| System / Platform | Pattern Used | How It Maps |
|---|---|---|
| Unix shell pipes | Pipe and Filter | The original implementation. Every | in a shell command is a pipe connecting two filters
(commands). |
| Prefect / Apache Airflow / AWS Step Functions / Kubeflow Pipelines | Pipe and Filter | ML workflow orchestrators that implement the pattern as DAGs of tasks. Each task is a filter; the scheduler and data-passing mechanism form the pipe. |
| LangChain | Pipe and Filter | Python framework for LLM applications — every component (loader, splitter, embedder, vector store, retriever, LLM) is a plug-and-play filter. |
| CDN (Content Delivery Network) | CQRS at planetary scale | Write once to an origin server, read from edge caches worldwide. Used by Netflix, YouTube, and every major streaming platform. |
| Social media (Facebook, Instagram, YouTube) | CQRS (read-heavy) | Users upload content occasionally but scroll/consume constantly. Read paths are aggressively cached; write paths ensure consistency. |
| OpenAI ChatGPT | CQRS | Model deployment follows CQRS — training (command) is separate from inference serving (query). New model versions propagate through inference endpoints with eventual consistency. |
| Google Notebook LM | RAJ | A productized RAJ implementation — upload documents, ask questions, get sourced answers. |
5.7.2 ML-Specific Tools and Domain Examples
| System / Tool | Pattern / Concept | Details |
|---|---|---|
| ChromaDB | RAJ component (vector store) | Backed by SQLite for persistence. Used in the class demo. |
| Pinecone / FAISS (Facebook/Meta) | RAJ component (vector store) | Specialized databases for storing and searching embedding vectors. |
| OpenAI text-embedding-3-small/large | RAJ component (embedding API) | Services that convert text to semantic vectors. Sentence Transformers, GloVe, DPR are alternatives. |
| Banking systems (NEFT, IMPS) | CQRS (strong consistency) | Use vertical scaling for transaction integrity. Exceptions: UPI Lite (offline payments with deferred settlement), Visa/Mastercard fallback limits. |
| Couchbase | CQRS (configurable) | Distributed database offering both strong and eventual consistency, selectable per API call — a rare combination. |
| E-commerce (Flipkart, Myntra), Food delivery (Swiggy, Zomato) | CQRS (read-heavy) | Product listings and menus are write-once, read-many — aggressive read caching. |
| IPL / Hotstar / Disney+ streaming | CQRS + Pipe and Filter | Massive horizontal scaling for live events — millions of concurrent viewers, auto-scaling infrastructure, edge CDN delivery. |
| Azure Foundry / AWS S3 | CQRS (persistence layer) | Model registries and feature stores that act as the contract between command-side training and query-side serving in ML systems. |
SEML Lecture 5 Notes · Architectural Patterns, CQRS, and Retrieval-Augmented Generation
Sections Breakdown
Defines architectural patterns and the component-connector lens
Filters, pipes, topologies, and worked examples
Strong vs eventual consistency, CQRS in ML systems
Ingestion and query pipelines, LangChain, pitfalls
Pattern composition for production ML systems
Assignment, homework, and exam focus
Systems mapped to architectural patterns
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Architectural Patterns — Context and Recap
Must-know: An architectural pattern is a named reusable solution to a recurring problem. The component-connector lens: components = processing units (nouns), connectors = data pathways (verbs). Quality requirements drive architectural choices.
Top pitfall: Confusing architecture style (broad organizing principle like microservices) with architectural pattern (specific solution like CQRS) with design pattern (object-level like Observer).
Self-check: Using the component-connector lens, what are the components and connectors in a Unix shell pipeline?
Connects to: 5.2 Pipe and Filter, 5.3 CQRS, 5.4 RAJ
Pipe and Filter Pattern
Must-know: Pipe and filter: filters = processing components (NOT ML filtering), pipes = dumb data conduits. Supports 1:1, 1:N, N:1, DAG topologies. Every data pipeline (ETL, ML workflow, LangChain chain) is pipe and filter.
Top pitfall: Confusing 'filter' (processing component, 1970s Unix term) with ML filtering (feature selection, noise removal).
Self-check: In the diabetes classification pipeline, what is the 'pipe' between filters 1 and 2? Is feature scaling a mandatory filter?
Connects to: 5.1 Architectural Patterns, 5.3 CQRS, 5.4 RAJ
Command Query Responsibility Segregation (CQRS)
Must-know: CQRS = separate commands (writes) from queries (reads) at data+infra layers, not just API. Strong consistency (banking) vs eventual consistency (social media, streaming). In ML: command side = training pipeline, query side = inference/serving. RAJ = Pipe&Filter + CQRS.
Top pitfall: Thinking API-level GET/POST separation is CQRS — CQRS extends to separate databases, scaling policies, caching, and teams.
Self-check: Is the model registry on the command side or query side? What consistency model does Netflix CDN use?
Connects to: 5.2 Pipe and Filter, 5.4 RAJ, 5.5 RAJ with CQRS
Retrieval-Augmented Generation (RAJ)
Must-know: RAJ = Retrieve + Augment + Generate. Ingestion: extract→chunk(1000chars,200overlap)→embed→vectorDB. Query: embed question→semantic search(top-K)→LLM with context+question→citations. Prompt template guardrail prevents hallucination. Changing embedding model requires full vector DB rebuild.
Top pitfall: Forgetting the 'answer only from context' prompt template instruction — LLM hallucinates from training data. Also: pure semantic search fails on names/codes — use hybrid search.
Self-check: Why must the same embedding model be used for both ingestion and query? What is the role of the overlap parameter in chunking?
Connects to: 5.2 Pipe and Filter, 5.3 CQRS, 5.5 RAJ with CQRS
RAJ with CQRS: The Complete Architecture
Must-know: RAJ + CQRS: ingestion pipeline (command side) builds vector DB once; inference pipeline (query side) reads it millions of times. Vector DB is the shared contract between the two independent pipe-and-filter pipelines.
Top pitfall: Rebuilding the vector database on every query (monolithic approach) — works for demos but wasteful at scale.
Self-check: What serves as the contract between the command side and query side in RAJ with CQRS?
Connects to: 5.2 Pipe and Filter, 5.3 CQRS, 5.4 RAJ
Exam Guidance Summary
Must-know: RAJ = Pipe and Filter + CQRS. Expect pattern identification and architecture design questions. Assignment: implement patterns faithfully, model accuracy is secondary.
Top pitfall: Focusing on model accuracy in the assignment instead of pattern implementation correctness.
Self-check: Write one sentence explaining how Pipe and Filter and CQRS combine to form the RAJ architecture.
Connects to: 5.2 Pipe and Filter, 5.3 CQRS, 5.4 RAJ, 5.5 RAJ with CQRS
Key Industry Applications
Must-know: Know which pattern each major system uses: Unix pipes/Airflow/LangChain = Pipe and Filter; CDNs/social media/streaming = CQRS; Notebook LM = RAJ; vector DBs (ChromaDB, Pinecone, FAISS) = RAJ component.
Top pitfall: Confusing CQRS implementations (CDN, social media) with RAJ implementations (Notebook LM).
Self-check: Which architectural pattern does a CDN implement? Which pattern does LangChain implement?
Connects to: 5.2 Pipe and Filter, 5.3 CQRS, 5.4 RAJ
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.