Take a Break
5:00
Inhale…
Give your mind a break — no phone, no music, just idle time or a quick walk.
MongoDB Architecture, Document Modeling, CRUD Operations, Distributed Consistency, and Multi-Node Configuration
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
- CAP theorem: consistency, availability, and partition tolerance — covered in Lecture 2 and Lecture 3
- SQL vs NoSQL: the four core NoSQL data models and the ACID vs BASE contrast — covered in Lecture 2
- Horizontal (scale-out) scaling and distributed systems — covered in Lecture 2
- Distributed replication and fault tolerance — covered in Lecture 3
- NoSQL distributed storage (HBase) — covered in Lecture 8
This lecture builds a complete picture of MongoDB from the ground up: what it is and how it stores data (9.1), how to install and reach it (9.2), how to create, read, update, and delete documents (9.3), how it stays consistent across many machines (9.4), and how to tune consistency, durability, and latency with read/write concerns for real applications (9.5).
9.1 MongoDB Ecosystem, Data Model, and Architectural Foundations
9.1.1 MongoDB Overview, Etymology, and Storage Paradigm
Hook: You have a relational database that stores every employee record in a fixed table, but your application keeps growing and your product team keeps adding new fields. Why would a company throw away rigid tables and store each record as a free-form document instead? MongoDB exists to answer exactly that question.
What MongoDB Is
MongoDB is an open-source, non-relational (NoSQL) document-oriented database system designed to handle large-scale data processing across distributed clusters. Each of those labels matters:
- Open source: The server software is freely available, and a large community plus a commercial vendor (MongoDB, Inc.) maintain it.
- Non-relational (NoSQL): It does not use the table-row-column model of relational database management systems (RDBMS). "NoSQL" here is best read as "Not only SQL" — MongoDB still has a rich query language, it just is not built around SQL tables and joins.
- Document-oriented: The unit of storage is a document, not a row. A document is a self-contained bundle of key-value pairs.
- Distributed: It is designed from the start to spread data across many machines in a cluster.
The name Mongo derives directly from the word humongous. The founders chose it to signal the core architectural capability: storing, ingesting, and querying high-volume, high-velocity datasets — data so large that a single machine cannot hold or serve it.
Intuition + Analogy: Think of an RDBMS as a printed form with fixed blank spaces — every employee gets the same boxes (name, age, department), and if you want to add "middle name" you have to redesign and reprint the whole form. MongoDB is more like a loose-leaf notebook: each employee's page can carry exactly the fields that employee needs, in any order, and you can add a new field to one page without touching the others. The cost of that flexibility is that the database must do more work to figure out what is on each page — which is why MongoDB documents carry their structure inside themselves (they are "self-describing").
JSON Documents and the BSON Storage Format
Unlike traditional RDBMS that enforce rigid tabular schemas of rows and columns, MongoDB stores data objects as flexible, self-describing JSON (JavaScript Object Notation) documents. A document is self-describing because the field names travel with the values — the data tells the database what it contains, so no external schema definition is needed.
At the physical disk and network transmission layer, MongoDB serializes these JSON objects into a binary-encoded format known as BSON (Binary JSON). BSON extends plain JSON in two important ways:
- Richer data types: BSON adds high-precision types that plain JSON lacks — explicit 64-bit integers, double-precision floating-point numbers, dates, binary data, byte arrays, and timestamps. Plain JSON only natively knows strings, numbers, booleans, arrays, objects, and null; a date or a large integer has no first-class JSON representation.
- Fast binary parsing: Because BSON stores length-prefixed fields in a compact binary layout, the database can skip around inside a document without scanning text character by character. Text JSON must be parsed and re-escaped; BSON is nearly "ready to use" in memory.
This matches the reference book's description: MongoDB does not actually use JSON for storage but BSON (pronounced "Bee-Son") — an open standard used to store complex data structures.
Mapping RDBMS Terms to MongoDB Terms
To map relational database concepts into MongoDB terminology, consider the following structural equivalences:
| RDBMS Concept | MongoDB Concept | Plain Meaning |
|---|---|---|
| Database | Database | A top-level container holding a set of collections. |
| Table | Collection | A grouping of related documents. A collection does not enforce a rigid schema on its members. |
| Row | Document | A single JSON/BSON record made of key-value pairs. |
| Column | Field | A specific key inside a document mapping to a scalar value, an array, or a nested sub-document. |
| Primary Key | _id |
A unique identifier for each document. |
| Join | Embedded documents | Where RDBMS links tables with joins, MongoDB nests related data inside one document. |
These equivalences are the single most examinable vocabulary list in the lecture — the reference book's own comparison table (Database → Database, Table → Collection, Record → Document, Column → Fields/Key-value pairs) matches it exactly.
Worked example — a document inside a collection. A single record in an employees collection is written in JSON as:
{
"_id": 1,
"name": "alpha",
"age": 59,
"department": "Engineering"
}
Walk through the pieces:
_id: 1is the document's primary key — unique inside the collection. (You may also let MongoDB generate one automatically; see 9.3.1.)name,age, anddepartmentare fields — the equivalent of columns.- The whole object is one document — the equivalent of a row.
- On disk, this JSON text is re-encoded into BSON binary before storage.
Sense-check: every term maps one-to-one to the RDBMS vocabulary: one collection ≈ one table, one document ≈ one row, each key ≈ one column, and _id ≈ the primary key.
Native Data Types in MongoDB
MongoDB supports diverse native data types, including:
- String — UTF-8 text, the most commonly used type.
- Integer — 32-bit or 64-bit.
- Double — floating-point (real) numbers.
- Boolean —
true/false. - Array — an ordered list of values under one key (e.g., a list of phone numbers).
- Date — stored in Unix-time format.
- Null — a missing or unknown value.
- ObjectId — the 12-byte auto-generated identifier type for
_id. - Binary data — images, binaries, and other raw bytes.
- Embedded documents — a nested object inside a document (a document within a document).
Q: What is the underlying physical storage format of MongoDB documents, and how does it differ from standard text-based JSON? A: While users interact with MongoDB using plain text JSON for human readability, MongoDB internally serializes and stores all documents on disk in BSON (Binary JSON). BSON provides high-performance binary parsing and supports more data types — such as explicit 64-bit integers, precise timestamps, date objects, and raw binary data — which are missing from standard text-based JSON.
Pitfalls:
- Confusing the query interface with the storage format. You write JSON in the shell, but the server stores BSON. Anything you assume about on-disk JSON text (e.g., "I can open the data file and read it") is wrong.
- Assuming all numbers are equal. BSON distinguishes 32-bit integers, 64-bit integers, and doubles. A value you typed as
1may not match a stored value of1.0in every comparison context — type sensitivity matters (see the_iddiscussion in 9.3.1). - Expecting a schema. Two documents in the same collection may have completely different fields; this is a feature, but it means your application code — not the database — must tolerate missing fields.
Recap + Bridge: MongoDB stores humongous amounts of data as self-describing JSON documents, serialized on disk as BSON, and every relational term (table, row, column) has a direct MongoDB counterpart (collection, document, field). Next we ask a deeper design question: when should those documents nest related data inside themselves, and when should they point to other documents instead?
Real-world domain connection: This is where MongoDB earns its keep in web-scale systems. An e-commerce catalog can store a product with a different attribute set per category (a laptop has ram_gb, a t-shirt has sleeve_length) in one collection without migrating schema — something an RDBMS would need dozens of nullable columns or separate tables for. In analytics pipelines, the self-describing document format also makes MongoDB a natural landing zone for semi-structured JSON logs straight from applications, before the data is transformed into a strict warehouse schema.
---
9.1.2 SQL vs NoSQL Design Philosophy: Normalization vs Query-Centric Data Modeling
The architectural philosophy of schema design in MongoDB fundamentally diverges from classic relational database design. The two worlds answer one question differently: "How do I decide where data lives?"
The RDBMS Answer: Normalize for Integrity
In the RDBMS world, table design prioritizes storage efficiency and data integrity by enforcing Third Normal Form (3NF) normalization. Database architects decompose entities into distinct tables to eliminate redundant data attributes and prevent update anomalies.
Worked example — why an RDBMS splits employees and departments. Suppose a company has 5,000 employees, all in three departments. If each employee row repeated the full department details (name, floor, manager, budget), the same department data would be duplicated ~1,667 times. Consequences of that duplication:
- Update anomaly: Renaming "Engineering" to "Product Engineering" requires updating thousands of rows — miss one and the data becomes inconsistent.
- Insert anomaly: A new department with no employees yet cannot be stored without a dummy employee record.
- Delete anomaly: Deleting the last employee of a department silently destroys the department record too.
To avoid all three, an RDBMS extracts department details into a standalone departments table and links it to employees with a foreign key column (department_id). When an application needs a unified view of employee and department details, the RDBMS engine performs a runtime JOIN operation across the primary–foreign key pair:
\[ \text{Result\_Relation} = \text{Employees} \bowtie_{\text{Employees.dept\_id} = \text{Departments.id}} \text{Departments} \]
Read this as: combine every employee row with the department row whose id equals the employee's dept_id. The result relation contains employee columns plus department columns side by side. The join symbol \(\bowtie\) (bowtie) is the relational algebra notation for combining tables.
Cost of the design: every time the application wants the combined view, the database must execute this join — matching keys, possibly across disks or machines, which is expensive at scale.
The NoSQL Answer: Model for Your Queries
In contrast, NoSQL data modeling in MongoDB is query-centric. Instead of designing data structures to optimize storage normalization, MongoDB data models are designed around the specific read and write access patterns of the application. Developers examine the most frequently executed queries and structure documents so that all data required by a single application workflow resides together within a single collection or document.
Worked example — the same employee data, modeled for the query. If an application frequently queries employee profiles alongside their salary history and home address, MongoDB co-locates salary and address fields within the employee document itself:
{
"_id": 1,
"name": "alpha",
"age": 59,
"department": "Engineering",
"salary_history": [ 62000, 65000, 68000 ],
"address": { "city": "San Jose", "state": "CA" }
}
Now the single most common application workflow — "show me an employee's profile" — is answered by reading one document in one operation. No join, no second lookup. The redundancy that normalization would have removed (department name repeated across employees) is accepted deliberately because the read pattern rewards it.
Sense-check: the RDBMS pays a join cost on every read to save storage; MongoDB pays a storage cost (duplication) to save time on every read. The trade-off is inverted by design.
Comparison: Normalization vs Query-Centric Modeling
| Dimension | RDBMS (3NF) | MongoDB (Query-Centric) |
|---|---|---|
| Primary goal | Storage efficiency + data integrity | Read/write access-pattern efficiency |
| Redundancy | Eliminated by decomposition | Accepted where it serves a hot query |
| Related data access | JOIN at query time |
Pre-joined, co-located documents |
| Schema changes | Costly ALTER TABLE migrations |
Add/remove fields per document freely |
| Best for | Complex transactional reporting, strong consistency | High-volume, high-velocity web workloads |
| Weak spot | Join cost and rigid schema at scale | Duplication management, eventual consistency |
When to pick which: choose the relational model when data integrity and complex cross-entity reporting dominate; choose the document model when your application reads a handful of well-known shapes millions of times per second.
Assumptions & Scope: Query-centric modeling only pays off if the access patterns are actually known and stable. If you design documents around one hot query and the application then starts asking a different question (e.g., "which employees live in San Jose?"), you must scan or restructure the data. The approach assumes: (1) you can enumerate the important queries up front, and (2) redundancy is cheap relative to joins in your deployment. When those assumptions fail — deep ad-hoc analytics, heavy cross-entity aggregation — a relational engine is often the better tool.
Real-world domain connection: In high-scale web applications, optimizing data structures for access patterns rather than 3NF normalization minimizes disk seek latency and network overhead across distributed database nodes. A social feed service, for example, pre-joins "post + author + like-count" into a single read model so that rendering one screen of the feed costs one fast read per item instead of three joined queries per item. This is the same philosophy behind materialized views and denormalized read models in microservice architectures: shape the stored data like the screen you must render.
---
9.1.3 Data Modeling Strategies: Embedding vs Referencing
MongoDB provides two primary techniques for modeling relationships between entities: Embedding (denormalized data models) and Referencing (normalized data models).
Intuition + Analogy: Imagine a wedding invitation. Embedding is writing the full menu on the invitation card itself — everything about the event travels together in one envelope, read once, opened once. Referencing is writing "see our website" on the card — the card is small, but the guest must make a separate trip to the website to learn the details. The first is faster for the guest but bloats the card; the second keeps every card tiny but costs an extra lookup per guest. MongoDB's rule of thumb mirrors this: embed what always travels together, reference what is fetched independently. The analogy breaks where size is absolute — MongoDB has a hard 16 MB ceiling per document, so "fit everything in one envelope" is not always possible.
Embedded Data Models (Denormalization)
Embedding stores related data entities inside a single document as nested sub-documents or arrays of objects.
Worked example — embedding a one-to-one relationship. Consider a user profile requiring home address details:
{
"_id": 101,
"name": "John Doe",
"age": 35,
"address": {
"street": "123 Tech Boulevard",
"city": "San Jose",
"state": "CA",
"zip": "95110"
}
}
The address is a nested sub-document — an object inside the user document. Reading the profile and the address together costs one query; updating the address rewrites one document.
Worked example — embedding a one-to-few relationship. A user's hobbies are a small, bounded list. Store them as an array:
{
"_id": 102,
"name": "Jane Smith",
"hobbies": ["singing", "photography", "swimming"]
}
This is "one-to-few": one user, at most a handful of hobbies. The array is small and unlikely to grow without limit.
Sense-check: both examples show the embedding payoff — a single query returns everything the profile screen needs, and one atomic write updates it all.
Embedding offers key advantages:
- Single-Query Retrieval: The application fetches the user profile and full address in a single database read operation, eliminating join latencies.
- Atomic Writes: Updates to both the user details and address occur atomically within a single document write operation, guaranteeing transactional consistency without multi-document transactions.
Embedding is best suited for One-to-One relationships (such as a user and their physical address) and One-to-Few relationships where the nested array is bounded and small.
Referenced Data Models (Normalization)
Referencing stores related entities in separate collections and links them using unique identifier keys (typically referencing the _id field of the target document).
Worked example — referencing a one-to-many relationship. Consider a two-wheeler product catalog linked to its individual spare parts. A vehicle consists of thousands of distinct parts, so embedding all part details inside a single vehicle document would create an unbounded array. Instead, the vehicle document references the part identifiers:
// Vehicle Collection Document
{
"_id": "V1001",
"model": "TVS Two-Wheeler",
"fuel_capacity_liters": 12,
"parts": ["P9901", "P9902", "P9903"]
}
// Parts Collection Document
{
"_id": "P9901",
"part_name": "Brake Pad",
"manufacturer": "Bosch",
"unit_cost": 45.00
}
The vehicle stores only the identifiers of its parts. To display a full parts list, the application reads the vehicle document, then looks up each referenced part by _id. That is two steps instead of one — the price of keeping the vehicle document small and the part data shareable (the same brake pad can appear in many vehicle models without duplication).
Sense-check: the array ["P9901", "P9902", "P9903"] is bounded by the actual number of parts on one vehicle, but a full vehicle with thousands of parts would make even this array large — which is exactly when the 16 MB rule and the unbounded-growth rule (below) push you toward referencing or back-referencing.
Referencing is essential under the following conditions:
- Unbounded One-to-Many Relationships: When the target array could grow indefinitely (such as a product with thousands of components, or an author with millions of user comments).
- Document Size Constraint: MongoDB enforces a hard limit of 16 Megabytes (16 MB) per individual BSON document. If embedded data risks exceeding 16 MB, entities must be split across collections using references.
- Many-to-Many Relationships: When multiple entities share references to common lookup items (such as students enrolled in multiple courses — each student references the same course documents instead of duplicating them).
Assumptions & Scope: The 16 MB cap is a hard server-enforced ceiling — a single document larger than 16 MB is rejected outright, so it is not a "soft guidance" limit. GridFS (the reference book's binary-storage facility) exists precisely because of this cap: large binaries are split into chunks across documents. Referencing also assumes your application can tolerate the extra round-trips; if a screen needs the referenced data every time, embedding may still be cheaper despite the size, as long as you stay under 16 MB.
Back-Referencing for One-to-Millions Relationships
When an entity has millions of child records (such as a celebrity social media account with tens of millions of posts), storing array references inside the parent user document would exceed the 16 MB document limit. Each reference costs bytes (an ObjectId is 12 bytes); with \(N_{\text{references}}\) stored inside one parent document:
\[ \text{Document Size} \propto N_{\text{references}} \implies N_{\text{references}} > 10^6 \implies \text{Size} > 16 \text{ MB} \]
Even a modest reference of ~20 bytes each would hit 16 MB at roughly \(16 \times 10^6 / 20 = 8 \times 10^5\) references — under a million entries. An influencer with millions of posts simply cannot hold all post references in the user document.
To resolve this, MongoDB employs Back-Referencing (also called "reverse referencing"). Instead of the parent storing references to child documents, each child document contains a back-reference field storing the parent's unique _id:
// Parent User Document (User Collection)
{
"_id": 1,
"username": "influencer_account",
"followers_count": 5000000
}
// Child Post Document (Posts Collection)
{
"_id": 987654321,
"user_id": 1,
"post_text": "Hello world!",
"timestamp": "2026-07-30T10:00:00Z",
"likes": 15400
}
The parent document stays tiny (it holds no post references at all). Each child carries a single back-reference field user_id pointing to the parent. When fetching posts for user _id: 1, the application queries the posts collection filtered by { "user_id": 1 } — a targeted indexed query rather than a bloated parent document.
Worked example — 10 million posts without blowing the limit. An influencer has 10,000,000 posts. Forward referencing would need ~10 million references inside the user document — guaranteed to exceed 16 MB. Back-referencing stores one user_id field per post: 10 million small documents, each comfortably under 16 MB, and the user document holds zero references. Querying "posts by user 1, latest 20" becomes db.posts.find({ "user_id": 1 }).sort({ "_id": -1 }).limit(20) — an indexed range read. Sense-check: the constraint moved from "one document holds everything" to "each document stays small," which is exactly what the 16 MB cap demands.
Q: How does a developer decide between embedding and referencing when modeling data in MongoDB? A: Developers should default to embedded models for maximum read efficiency and atomic writes when entities are retrieved together and arrays remain bounded (one-to-one or one-to-few). Referencing must be chosen when relationships are unbounded (one-to-many or one-to-millions), when embedded data would breach the 16 MB BSON document limit, or when entities are accessed independently across multiple application contexts.
Pitfalls:
- Embedding everything "because it's faster." A nested array that grows without bound silently marches the document toward the 16 MB ceiling, and eventually a write fails or the document becomes a hotspot for every related read.
- Referencing too aggressively. Splitting data that is always fetched together forces two round-trips per screen and doubles latency — the exact cost embedding was meant to avoid.
- Forward-referencing a one-to-millions relationship. Storing millions of references in the parent is the textbook way to exceed 16 MB; flip it to back-referencing.
- Forgetting the query direction. Back-referencing makes "children of X" easy but "parent of this child" (find the influencer of a specific post) a second lookup. Choose the direction that matches your dominant query.
Recap + Bridge: Embed for one-to-one and bounded one-to-few with single-query reads and atomic writes; reference for unbounded one-to-many, many-to-many, and 16 MB pressure; and back-reference for one-to-millions. This decision framework is the heart of MongoDB data modeling — and it returns in section 9.5 when we decide how strongly a write must be confirmed across machines.
Real-world domain connection: This modeling decision appears daily in production. Product catalogs embed variant attributes; order systems reference product and customer documents; social platforms back-reference posts from user documents; and review platforms embed a bounded recent-review list in a product document while archiving older reviews by reference. Getting the embed/reference split right is the difference between a service that renders in milliseconds and one that burns joins on every request.
---
9.1.4 MongoDB Sharding, Architectural Trade-Offs, and Enterprise Scaling Debate
MongoDB handles massive horizontal scaling through a mechanism called Sharding. Sharding distributes datasets across multiple physical database servers (shards), enabling the system to scale storage capacity and throughput linearly beyond the hardware limits of a single machine.
Intuition + Analogy: A single server is like one cashier at a supermarket: eventually the queue grows no matter how fast the cashier works. Sharding is opening more checkout counters — the same customers are split across many cashiers, so total throughput rises with the number of counters. The catch: you must now decide which customer goes to which counter (the shard key) and keep the lines balanced — that coordination is the operational cost the industry debate below is really about.
Horizontal Scaling Mechanics
In a sharded MongoDB cluster:
- Data in a collection is partitioned into logical ranges based on a selected Shard Key — the field used to decide which shard owns each document.
- Individual data chunks are distributed across physical shard nodes. Each shard is itself an independent database; together they constitute one logical database.
- As total data volume grows from gigabytes to terabytes, addition of new hardware nodes triggers automated rebalancing. The sharding architecture automatically redistributes chunks evenly across all cluster nodes.
The reference book highlights the two prime advantages: sharding reduces the amount of data each shard must store and manage, and it reduces the number of operations each shard handles (an insert touches only the shard that owns that key range).
Worked example — the arithmetic of horizontal scaling. If a 1,000 GB collection is hosted on 10 shard nodes, each node stores about 100 GB:
\[ \text{Storage per Node} = \frac{\text{Total Dataset Volume}}{\text{Number of Cluster Nodes}} = \frac{1000 \text{ GB}}{10 \text{ Nodes}} = 100 \text{ GB/node} \]
Adding 90 more nodes (100 total) redistributes the dataset so that each machine holds only 10 GB:
\[ \text{Storage per Node} = \frac{1000 \text{ GB}}{100 \text{ Nodes}} = 10 \text{ GB/node} \]
The per-node storage falls linearly as nodes are added — this is horizontal scaling: you grow by adding machines, not by making one machine bigger (vertical scaling). Sense-check: total storage stayed 1,000 GB; only the distribution changed, so each node's disk pressure (and the read/write traffic it must serve) dropped tenfold.
Industry Debate: Sharding Complexity, Staging vs Single Source of Truth
An in-depth debate highlights real-world industry experiences with MongoDB sharding and its architectural positioning:
- Staging / Interim Data Store Perspective: One perspective argues that MongoDB is primarily used as an interim staging store, cache, or materialized view database rather than a primary single source of truth (SSOT). Early enterprise implementations faced operational challenges managing primary/secondary replica failovers and sharding cluster rebalancing. High-scale platforms (including references to Zoom and Netflix migrating specific workloads) reportedly shifted certain real-world transaction models from MongoDB to dedicated key-value or wide-column stores like Apache Cassandra or AWS DynamoDB due to sharding maintenance complexities.
The technical kernel of this argument is real: sharding adds moving parts — a config server cluster, mongos routing processes, chunk migration, and the risk of hot spots when a poorly chosen shard key routes most traffic to one shard. Operationally, some teams judged that maintenance burden higher than the payoff for their workload and moved to systems where the consistency model is looser (Cassandra's BASE / tunable consistency) in exchange for simpler operations.
- Production SSOT & Distributed Evolution Perspective: Modern enterprise views clarify that MongoDB has evolved significantly into an enterprise-grade production database. MongoDB Atlas (cloud version) and enterprise versions manage sharding, automated failover, and CQRS (Command Query Responsibility Segregation) patterns natively. Nothing prevents MongoDB from serving as a primary transactional database when configured correctly. The staging-only reputation reflects early operational immaturity more than an architectural ceiling.
CQRS (Command Query Responsibility Segregation) is the pattern of separating write models from read models — writes go to one optimized store, reads to another. MongoDB's flexible documents and aggregation framework make it a natural fit for the read side of CQRS.
Pitfalls:
- Choosing a bad shard key. A key with few distinct values (e.g.,
statuswith onlyactive/inactive) creates hot spots — one shard receives nearly all writes. A high-cardinality, evenly distributed key is the target. - Assuming sharding is automatic magic. Chunks rebalance automatically, but capacity planning, key selection, and monitoring remain human responsibilities — this is exactly the operational cost the staging debate warns about.
- Confusing replication with sharding. Replication copies the same data for redundancy; sharding partitions different data for scale. A sharded cluster usually also replicates each shard, but the two mechanisms solve different problems (see 9.4).
Recap + Bridge: Sharding spreads data across machines so per-node storage and traffic shrink as the cluster grows — at the cost of operational complexity that has fueled a real industry debate about when MongoDB should be the system of record versus a staging or read-model store. With the data model and scaling model in place, section 9.2 turns to the practical question: how do you install MongoDB, start the server, and connect to it?
Real-world domain connection: Many modern architectures use MongoDB as a materialized view cache between relational engines and microservice frontends, using MongoDB's flexible aggregation framework to serve complex pre-computed JSON objects directly to UI clients. Meanwhile, MongoDB Atlas manages sharding and failover for teams that want the SSOT model without the ops burden. The choice of deployment pattern — staging cache, read model, or source of truth — is an architecture decision, not a fixed property of the database itself.
---
9.2 MongoDB Installation, Service Management, and Environment Access
Hook: You installed MongoDB, but the server will not start, and the shell says it cannot connect on port 27017. Where do you even begin — and once it is running, what are the three very different ways to talk to it? This section answers both.
9.2.1 Service Management and CLI Diagnostics
MongoDB can be installed on local servers, cloud bare-metal infrastructure, or managed cloud services (such as MongoDB Atlas). On Windows operating systems, MongoDB typically installs as a background system service (MongoDB Server) that the OS starts automatically at boot.
Purpose: The MongoDB server is a daemon process called mongod (short for "Mongo daemon"). It is the process that owns the data files, accepts client connections, and executes queries. Everything else — the shell, Compass, drivers — is a client that connects to mongod. The reference book makes this split explicit: mongod is the database server (like mysqld for MySQL or the Oracle server), while mongo/mongosh is the database client (like mysql or SQL*Plus).
Inputs & Outputs:
- Input: a data directory (via
--dbpath) and a configuration file (mongod.cfg) when present. - Output: a listening server on IP
127.0.0.1port27017, ready to accept client connections.
Diagnostics for Foreground Daemon Execution
If the background Windows service fails to start automatically (often caused by service startup priority delays or unconfigured replica set paths in mongod.cfg), developers run the MongoDB daemon process (mongod) manually from the command-line interface by providing an explicit database storage path:
mongod --dbpath "E:\mongodb_data"
The --dbpath parameter specifies the directory where MongoDB creates and manages its physical BSON data files, storage engines (such as WiredTiger Storage Engine), system logs, and index structures. Running mongod in the foreground with an explicit --dbpath is the standard diagnostic move when the service will not start: you see the error output directly in the console instead of hunting through Windows event logs.
Worked example — starting the server manually. Suppose the Windows service fails with no obvious cause. The diagnostic sequence:
- Create the data directory if it does not exist:
New-Item -ItemType Directory -Force "E:\mongodb_data". - Start the daemon in the foreground:
mongod --dbpath "E:\mongodb_data". - Observe the console output: you should see the WiredTiger storage engine initialize and a line like
waiting for connections on port 27017. - In a second terminal, verify the server is reachable with the client shell:
mongosh. - If the port is already in use, the log reports the conflict — usually another
mongodinstance from the failed service is still holding port 27017.
Sense-check: the same --dbpath directory is the single source of truth for the server's data; point mongod at the wrong or empty directory and the server starts with an empty database, which is a classic cause of "my data vanished" confusion.
By default, the mongod server process binds to IP 127.0.0.1 and listens for client connections on network port 27017.
Pitfalls:
- Forgetting the data directory exists.
--dbpathmust point to an existing (or creatable) directory with write permission; a bad path aborts startup. - Two servers, one port. A leftover service instance and your manual
mongodfight over port 27017; stop the service (Stop-Service MongoDBornet stop MongoDB) before running the foreground daemon. - Assuming the service and the manual run share state. They do share the
dbpath— that is the point — but they are separate processes; changes written while the service runs are only visible after it is stopped and the manual daemon reads the same files.
9.2.2 Access Interfaces: CLI Shell, GUI Compass, and Programmatic Drivers
MongoDB provides three primary interfaces for interacting with database clusters:
- MongoDB CLI Shell (
mongosh/mongo): An interactive JavaScript environment. The shell embeds a full V8 JavaScript engine, allowing developers to execute JavaScript variables, conditional statements (if/else), loops (for/while), and custom functions alongside database commands. Because the shell is a JavaScript interpreter, you can writelet x = 1; for (let i = 0; i < 3; i++) { print(i); }and then immediately rundb.collection.find()in the same session — scripting and database commands share one environment. - MongoDB Compass (GUI): A visual desktop application for inspecting schemas, running visual query filters, analyzing index utilization, and managing document records without writing raw commands. Compass is the "point and click" view of the same data the shell manipulates textually.
- Programmatic Drivers: Language-specific software libraries (such as
pymongofor Python,mongodbnative driver for Node.js, or Java Driver) that enable application code to execute asynchronous read and write operations directly against cluster nodes. Drivers speak the same wire protocol as the shell, so an application's writes are immediately visible to shell queries and vice versa.
Procedural spine — how a query reaches the data.
- Purpose: connect a client (shell, Compass, or driver) to the
mongodserver so commands can be executed. - Inputs: host address and port (default
localhost:27017), a database touse, and the client executable. - Steps:
- Start
mongod(service or foreground) so a listener exists on port 27017. - Launch a client:
mongoshfor the shell, Compass for the GUI, or your application's driver. - Select a database with
use <dbname>; collections inside it are created on demand on first write.
- Trace:
use people→db.employees.insertOne({...})→ MongoDB creates thepeopledatabase andemployeescollection on first document, exactly as the reference book describes ("a collection is created on demand"). - When to use which: shell for scripts and administration; Compass for schema inspection and debugging; drivers for production application code.
Database Import Tools
MongoDB provides standalone CLI utility executables for bulk data ingestion and export:
mongoimport: Imports external datasets formatted as CSV, TSV, or JSON into a MongoDB collection.mongoexport(its counterpart): Exports collections back out to these formats — useful for backups and data exchange.
Worked example — bulk-importing employee records from CSV. Suppose employees.csv has a header row name,age,department followed by data rows. Import it into the people collection of the employees database:
mongoimport --host localhost:27017 --db employees --collection people --type csv --headerline --file "employees.csv"
Breaking the flags down:
--host localhost:27017— where themongodserver is listening.--db employees— target database.--collection people— target collection.--type csv— input format.--headerline— treat the first line as field names (sonamebecomes a field key rather than data).--file "employees.csv"— the source file.
After import, the same data is queryable from the shell: use employees; db.people.find().count() returns the number of imported rows. Sense-check: mongoimport does the equivalent of hundreds of insertOne calls in one bulk pass, which is far faster for loading datasets at scale.
Pitfalls:
- Forgetting
--headerlinefor CSV. Without it, the first data row is treated as field names and the actual header text is lost or mismatched. - Importing into the wrong database/collection.
--dband--collectionare easy to mis-type; verify withshow dbs/show collectionsafterwards. - Expecting the shell to be plain SQL.
mongoshis JavaScript — if a command "does nothing," a missing;or alet-scoping issue is often the culprit.
Recap + Bridge: MongoDB runs as the mongod daemon on port 27017; you reach it through the JavaScript shell, the Compass GUI, or language drivers, and you load bulk data with mongoimport. With a running server and a way in, section 9.3 covers the actual work: creating, reading, updating, and deleting documents.
Real-world domain connection: In real deployments, service management differs by environment — systemd units on Linux, Windows services here, and fully managed instances in MongoDB Atlas that remove daemon management entirely. The foreground mongod --dbpath diagnostic remains the universal debugging move across all of them, and mongoimport is the standard tool for loading initial datasets (historical exports, CSVs from partner systems, test fixtures) before applications start writing.
---
9.3 MongoDB CRUD Operations and Query Mechanics
Hook: In an RDBMS you write INSERT INTO ..., SELECT ... WHERE ..., UPDATE ... SET ..., and DELETE FROM .... Every one of those four operations has a MongoDB equivalent — but the syntax is JavaScript objects, and a few traps (like passing an array to insertOne) will silently change what your query does. This section walks each operation with real commands.
CRUD stands for Create, Read, Update, Delete — the four fundamental operations on stored data. The reference book's mapping: creation uses insert/update/save; reading uses find; updates use update with \$set; deletion uses remove.
9.3.1 Document Creation: insertOne() vs insertMany()
MongoDB provides explicit methods for inserting documents into a target collection.
Procedural spine — inserting documents.
- Purpose: add one or many documents to a collection.
- Inputs: a document (or array of documents) with key-value fields; an optional
_id. - Output: an acknowledgement object reporting success and the inserted
_id. - When to use which:
insertOne()for a single record;insertMany()for a batch in one round-trip;insert()(the reference book's older form) for general inserts.
Single Document Insertion (insertOne)
The insertOne() method adds a single JSON document to a collection. The target collection is automatically created if it does not already exist.
use people;
db.employees.insertOne({
"_id": 1,
"name": "alpha",
"age": 59
});
Upon execution, MongoDB returns a JSON acknowledgement:
{ "acknowledged": true, "insertedId": 1 }
The acknowledged: true field means the server confirmed the write (at the default write concern w: 1 — see 9.5), and insertedId echoes back the _id that was stored, so the application knows the exact key to retrieve the document later.
Multiple Document Insertion (insertMany)
The insertMany() method accepts an array of JSON objects to insert multiple documents in a single round-trip database call:
db.alphabets.insertMany([
{ "_id": 1, "letter": "ALPHA", "english": "A" },
{ "_id": 2, "letter": "BETA", "english": "B" },
{ "_id": 3, "letter": "GAMMA", "english": "C" }
]);
One round-trip means the client sends all three documents in a single network message and the server acknowledges them together — much faster than three separate insertOne calls, and the reason bulk loading tools like mongoimport exist.
Worked example — batch insert and verify. Insert the three alphabets above, then verify:
db.alphabets.countDocuments();
// 3
db.alphabets.find().sort({ "_id": 1 });
// { "_id": 1, "letter": "ALPHA", "english": "A" }
// { "_id": 2, "letter": "BETA", "english": "B" }
// { "_id": 3, "letter": "GAMMA", "english": "C" }
Sense-check: all three documents are stored independently, each with its own _id — this is the intended outcome of insertMany, and it is exactly what the array-embedding trap below gets wrong.
Array Embedding vs Multi-Document Trap
A critical syntactic distinction exists when calling insertOne() with an array argument versus insertMany().
If a developer passes an array of objects to insertOne():
// WARNING: Array embedding trap
db.people.insertOne([
{ "name": "alpha" },
{ "name": "beta" }
]);
MongoDB treats the entire array as a single document containing a nested array, resulting in a single document insertion (countDocuments() returns 1). To insert separate individual documents, insertMany() must be called.
Q: Why does this happen — and what does the stored document actually look like? A: insertOne is defined to insert one document. You handed it one argument, an array, so MongoDB stores that array as the value of an anonymous field inside one document. The stored record is effectively:
{
"_id": ObjectId("..."),
"0": { "name": "alpha" },
"1": { "name": "beta" }
}
One document, two array slots, countDocuments() returns 1. The method name is the contract: "one" inserts one document; "many" inserts many. This is a genuine student-triggered correction moment in the lecture — the fix is simply to use insertMany for multiple independent records.
Primary Key (_id) Constraints and Type Sensitivity
Every document stored in MongoDB MUST contain a unique _id field.
- Auto-Generation: If an inserted document omits the
_idfield, MongoDB automatically generates a 12-byte BSONObjectId(combining timestamp, machine identifier, process ID, and incrementing counter).
The 12 bytes break down as: 4 bytes of timestamp, 3 bytes of machine identifier, 2 bytes of process ID, and 3 bytes of an incrementing counter. This composite layout guarantees uniqueness across processes and machines without any central coordinator — essential in a distributed cluster where two servers must never generate the same _id. The reference book's figure shows exactly this: Timestamp | MachineID | ProcessID | Counter.
- User-Defined
_id: Developers can supply custom_idvalues (such as integer employee IDs or string UUIDs). However, duplicate_idvalues within the same collection trigger a duplicate key error (E11000 duplicate key error collection).
- Data Type Sensitivity: MongoDB treats
_idvalues of different BSON data types as distinct. For example, string"_id": "1"and integer"_id": 1are treated as unique values and can co-exist within the same collection.
Q: What happens if two documents are inserted into the same collection with "_id": 1 (integer) and "_id": "1" (string)? A: Both insertions succeed without error because MongoDB data type matching is strict. Integer 1 and String "1" have distinct BSON type codes, so MongoDB treats them as unique primary key values.
Worked example — type-sensitive keys in action.
db.items.insertOne({ "_id": 1, "label": "integer one" }); // ok
db.items.insertOne({ "_id": "1", "label": "string one" }); // ok, different BSON type
db.items.insertOne({ "_id": 1, "label": "duplicate" }); // E11000 duplicate key error
After the first two inserts, db.items.countDocuments() returns 2. The third insert fails because integer 1 already exists. Sense-check: uniqueness is defined per (value, type) pair, not per displayed value — 1 and "1" look similar on screen but are different keys to the database.
Pitfalls:
- Passing an array to
insertOne()expecting multiple documents — you get one document with an embedded array (the array-embedding trap above). - Assuming
_idis always auto-generated. If your application supplies_idvalues, it owns the responsibility for uniqueness; a duplicate silently (well, loudly with E11000) breaks the insert. - Assuming numeric equality across types. Querying
{ "_id": 1 }will not match a document whose_idis the string"1"— type matching is strict in both inserts and queries.
9.3.2 Document Retrieval: find(), Projections, and Cursors
The find() method retrieves documents matching specified filter criteria. It is the MongoDB equivalent of SELECT.
Basic Query Syntax
To fetch all documents in a collection:
db.employees.find();
This is the equivalent of SELECT * FROM employees. In the CLI shell, find() returns a cursor that automatically iterates and displays the first 20 matching documents. Typing it continues cursor iteration for the next 20 documents. A cursor is a pointer into the result set — the database does not ship all matching documents at once; it hands the shell a cursor that pulls results in batches (20 at a time), which keeps memory use bounded even for huge result sets.
Filter Predicates (WHERE Clause Equivalents)
To filter documents by field criteria, pass a query document as the first argument to find():
// Select employees where age equals 40
db.employees.find({ "age": 40 });
// Select alphabets where _id is greater than 1 (\$gt operator)
db.alphabets.find({ "_id": { "\$gt": 1 } });
The query document { "age": 40 } means "match documents whose age field equals 40" — the SQL equivalent is WHERE age = 40. Comparison operators extend this: \$gt (greater than), \$gte (greater than or equal), \$lt (less than), \$lte (less than or equal), \$ne (not equal). The reference book shows the same pattern with {StudRollNo: 'S101'} for equality and {Grade: 'VII', Hobbies: 'Ice Hockey'} for combined conditions (implicit AND), plus \$or for OR conditions.
Field Projections (SELECT Clause Equivalents)
The second argument to find() specifies projection fields, controlling which document keys are returned:
// Project only 'name' and 'age', suppressing '_id'
db.employees.find(
{ "age": { "\$gte": 30 } },
{ "name": 1, "age": 1, "_id": 0 }
);
The projection document uses 1 to include a field and 0 to exclude it. Here: include name and age, exclude _id. This is the MongoDB equivalent of SELECT name, age FROM employees WHERE age >= 30 — returning only the needed fields reduces network payload and client parsing cost.
Worked example — filter + projection in one query. Given employees with ages 25, 31, 40, 45:
db.employees.find(
{ "age": { "\$gte": 30 } },
{ "name": 1, "age": 1, "_id": 0 }
);
// { "name": "beta", "age": 31 }
// { "name": "gamma", "age": 40 }
// { "name": "delta", "age": 45 }
The 25-year-old is filtered out by the predicate; _id is suppressed by the projection. Sense-check: only the fields named with 1 appear, and only documents passing the filter come back.
Document Counting
To count matching records efficiently without transferring document payloads:
db.employees.countDocuments({ "gender": "male" });
countDocuments returns a single number — the count of documents matching the filter — without moving the documents themselves across the wire. It answers "how many?" without the cost of "show me all of them."
Pitfalls:
- Forgetting the projection suppresses
_idby default? Actually the opposite:_idis included by default; you must explicitly set"_id": 0to remove it. - Using
findwhen you only need a count. Transferring full documents just to count them wastes bandwidth — usecountDocuments. - Assuming
find()returns an array. It returns a cursor; in the shell it auto-prints, but in application code you must iterate it (.toArray()in drivers).
9.3.3 Document Modification: update(), updateOne(), and updateMany()
MongoDB provides methods for modifying existing documents within a collection.
Syntax Structure
An update operation requires two primary arguments:
- Filter Predicate: Identifies target documents to modify.
- Update Operator Document: Specifies exact modifications using atomic update operators (such as
\$set,\$inc,\$unset).
db.collection.update( <filter>, <update_doc> );
This is the MongoDB equivalent of UPDATE ... SET ... WHERE ...: the first argument is the WHERE, the second is the SET.
Modifying Specific Fields with \$set
To update specific fields without overwriting the entire document, use the \$set operator:
db.alphabets.update(
{ "_id": 1 },
{ "\$set": { "letter": "alpha", "english": "A" } }
);
\$set (set) assigns the given values to the named fields, leaving every other field untouched. The reference book shows the same pattern: db.Students.update({StudRollNo:'S101'}, {\$set:{Hobbies:'Ice Hockey'}}) — updating only Hobbies.
If \$set is omitted and a plain JSON object is passed as the second argument, MongoDB replaces the entire existing document with the new object, wiping out all unmentioned fields.
Pitfalls — the replacement trap: The single most common destructive mistake in MongoDB updates is writing
db.alphabets.update({ "_id": 1 }, { "letter": "alpha", "english": "A" });
without \$set. Because the second argument is a plain object, MongoDB replaces the whole document: any fields not listed (e.g., pronunciation, created_at) are permanently deleted. Always ask: "do I want to patch fields (\$set) or replace the document (plain object)?" The reference book's term for the intended behavior is updating information in-place — MongoDB updates the data where it sits without moving it, keeping indexes unaltered.
Granular Control: updateOne() vs updateMany()
To protect against accidental global modifications caused by broad filter matches:
updateOne(): Modifies only the first document that matches the filter criteria, even if multiple documents satisfy the predicate.
// Modifies only one matching record
db.alphabets.updateOne(
{ "_id": { "\$gt": 1 } },
{ "\$set": { "letter": "corrected_alpha" } }
);
updateMany(): Modifies all documents that satisfy the filter predicate.
// Modifies all matching records
db.alphabets.updateMany(
{ "_id": { "\$gt": 1 } },
{ "\$set": { "letter": "corrected_alpha" } }
);
Worked example — one vs many, same filter. Collection alphabets contains _id 1, 2, 3. The filter { "_id": { "\$gt": 1 } } matches documents 2 and 3.
updateOnewith that filter updates only document 2 (the first match in natural order) — document 3 keeps its oldletter.updateManywith the same filter updates both documents 2 and 3.
Sense-check: the filter is identical; only the scope of the update differs. updateOne exists so a broad filter cannot accidentally rewrite a thousand documents.
Exam note: Know the four-way split precisely: insertOne vs insertMany (creation), updateOne vs updateMany (modification), \$set vs plain-document replacement, and the \$lookup join syntax below. These are the most commonly tested shell operations in this lecture.
9.3.4 Document Deletion: remove()
To delete documents matching a filter predicate:
db.people.remove({ "_id": 1 });
This is the MongoDB equivalent of DELETE FROM people WHERE _id = 1. Passing an empty document {} to remove() deletes all documents inside the target collection — the equivalent of DELETE FROM people without a WHERE clause, which is why the reference book pairs db.Students.remove({StudRollNo:'S101'}) (one row) with db.Students.remove() (everything).
Pitfall: remove({}) with an empty filter is irreversible within the shell — it deletes every document in the collection. Treat it like DROP TABLE or a bare DELETE with no WHERE: double-check the filter before pressing enter.
9.3.5 Aggregation Pipelines and Relational Lookup Joins (\$lookup)
Starting with MongoDB version 3.2, MongoDB introduced the \$lookup aggregation pipeline stage, enabling relational left outer joins across separate collections.
An aggregation pipeline is a sequence of stages, each transforming a stream of documents and passing the result to the next stage. \$lookup is the stage that performs the join.
Consider two collections: orders and products. To join order items with product metadata:
db.orders.aggregate([
{
"\$lookup": {
"from": "products", // Target collection to join
"localField": "product_id", // Field in the orders collection
"foreignField": "_id", // Matching field in the products collection
"as": "product_details" // Output array field name
}
}
]);
This aggregation pipeline matches orders.product_id against products._id and embeds matching product objects into a nested array named product_details.
Worked example — a concrete join. orders contains:
{ "_id": 1001, "product_id": "P9901", "qty": 2 }
{ "_id": 1002, "product_id": "P9902", "qty": 1 }
products contains:
{ "_id": "P9901", "part_name": "Brake Pad", "unit_cost": 45.00 }
{ "_id": "P9902", "part_name": "Headlight", "unit_cost": 30.00 }
Running the \$lookup above produces:
{ "_id": 1001, "product_id": "P9901", "qty": 2,
"product_details": [ { "_id": "P9901", "part_name": "Brake Pad", "unit_cost": 45.00 } ] }
{ "_id": 1002, "product_id": "P9902", "qty": 1,
"product_details": [ { "_id": "P9902", "part_name": "Headlight", "unit_cost": 30.00 } ] }
Note two things: the joined product is delivered inside an array (product_details), and it is a left outer join — an order with no matching product would still appear, with an empty product_details array. Sense-check: localField (order's product_id) was matched against foreignField (product's _id), and the result was nested under the name given to as.
Pitfalls:
- Expecting a flat result.
\$lookupalways outputs an array (asnames an array field), not a merged flat object — unlike SQL's row-level join. - Reversing localField and foreignField.
localFieldis in the input collection (the oneaggregateruns on);foreignFieldis in thefromcollection. Swapping them silently yields empty joins. - Using joins to paper over bad modeling. In section 9.1 we chose embedding to avoid joins; a
\$lookupon every query is a signal the data model may need revisiting.
Real-world domain connection: The CRUD vocabulary here is the same one used by every driver — pymongo's insert_one/find/update_one/delete_one mirror these shell methods one-for-one. \$lookup powers denormalized read models in microservices, and the insertOne/insertMany distinction directly informs bulk-ingestion pipelines for log and sensor data (batch writes via insertMany, single control records via insertOne).
---
9.4 Distributed Consistency, CAP Theorem, and Replication Mechanics
Hook: What happens when a server accepts a write, crashes before telling anyone, and then comes back online hours later — with a value nobody else has ever seen? If the system just keeps that value, some clients have seen one history and others a different one. MongoDB's answer involves electing a new leader, rolling back the orphaned write, and saving it to a .rollback file. This section is the story of that recovery.
9.4.1 Replica Sets and Node Roles
A MongoDB Replica Set is a cluster of mongod processes that maintain identical datasets, ensuring high availability and fault tolerance.
Intuition + Analogy: A replica set is a team with one designated leader. All instructions (writes) go to the leader, who writes them into a logbook (the Oplog) and sends copies to the rest of the team. If the leader collapses, the team members notice within seconds (they ping each other every 2 seconds), vote, and promote a new leader. When the old leader recovers, it must accept that the team has moved on without it — anything it wrote alone is discarded (rolled back). The analogy breaks where a human team might argue; the election is an automated, majority-based vote.
A standard replica set consists of:
- Primary Node: The single active node that receives all write operations from client applications. The primary records write actions in its transaction log (Oplog / Operations Log). The reference book confirms the design: "every write request from the client is directed to the primary. The primary logs all write requests into its Oplog."
- Secondary Nodes: Replicas that continuously replicate the primary's Oplog and apply write operations to their local datasets asynchronously or synchronously.
The primary is the only node that accepts writes — this single-writer discipline is what keeps the dataset consistent, because there is never more than one authority for "what happened."
Core Secondary Responsibilities
Secondary nodes perform two vital system functions:
- Oplog Data Replication: Fetching Oplog entries from the primary (or other secondaries) to keep local datasets synchronized.
- Heartbeat & Automated Leader Election: Exchanging 2-second heartbeat ping signals across cluster nodes. If the primary node fails or becomes unreachable, secondaries initiate a Raft-like consensus election to elect a new primary node.
How the election works (Raft-like consensus). The 2-second heartbeat is a liveness signal: every node expects to hear from the primary within roughly that interval. When the heartbeats stop:
- Secondaries detect the primary's silence.
- They start an election — each eligible secondary votes, and a candidate needs a majority of votes to become primary.
- The winner steps up as the new primary; the remaining nodes become its secondaries.
Majority-based voting is the key detail: with 3 nodes you need 2 votes, with 5 nodes you need 3. This is the same majority rule that reappears in section 9.5 as w: majority — it is the mechanism that guarantees "no two primaries can ever both believe they are primary" (split-brain prevention). Because a minority partition can never elect a leader by itself, only one side of a network split can ever accept writes.
9.4.2 Failure Scenarios and Rollback File Mechanics
When network partitions or primary node crashes occur in a replica set, un-replicated write operations can cause data divergence.
Step-by-Step Failure and Rollback Progression
Consider a 4-node replica set consisting of 1 Primary (Node A) and 3 Secondaries (Nodes B, C, D):
Worked example — the full failure, election, and rollback sequence.
Step 1 — Initial State: All 4 nodes are synchronized at state sequence 99.
\[ \text{State} = \{A: 99, B: 99, C: 99, D: 99\} \]
Every node has applied every write up to sequence number 99, so their datasets are identical.
Step 2 — Un-Replicated Write to Primary: A client submits a write operation (value = 100) to Primary Node A. Node A writes 100 to its local Oplog. Before Node A can replicate 100 to any secondary node, a network partition isolates Node A.
\[ \text{State} = \{A: 100, B: 99, C: 99, D: 99\} \]
Node A is now ahead of everyone else by exactly one write — and nobody else knows about it.
Step 3 — Leader Election: Secondaries B, C, and D detect the primary's absence via missed heartbeats. They hold an election and promote Node B as the new Primary. (Nodes B, C, D together are a majority — 3 of 4 — so the election is valid and Node A, cut off, cannot object.)
Step 4 — Subsequent Writes on New Primary: Clients execute new write operations (value = 101) to the new Primary Node B. Node B successfully replicates 101 to Secondaries C and D.
\[ \text{State} = \{A: 100, B: 101, C: 101, D: 101\} \]
The cluster majority now has history ... 99 → 101, while isolated Node A has history ... 99 → 100.
Step 5 — Old Primary Re-joins Cluster: The network partition heals, and isolated Node A re-joins the cluster as a Secondary. Node A inspects the current primary (Node B) Oplog and discovers a timeline divergence at sequence 99:
- Node A Oplog contains sequence 100.
- Cluster Majority Oplog contains sequence 101.
The two histories diverge exactly at sequence 99: A's next entry (100) is not present in the majority's Oplog, and the majority's next entry (101) is not in A's Oplog. One of them must yield.
Sense-check: the divergence point is the last common sequence number (99). Everything A wrote after that point — just the single write value = 100 — was never acknowledged by any other node, so it cannot be part of the cluster's agreed history.
Rollback File Execution (.rollback)
To restore cluster data consistency, MongoDB forces Node A to execute a Rollback:
- Node A identifies all local write operations that occurred after sequence 99 (value
100). - Node A removes value
100from its active database storage engine. - Node A writes the discarded write operations into a local Rollback File (stored in the
dbpathunderrollback/directory as BSON files). - Node A fetches Oplog entries starting from sequence 99 from Primary Node B, applying sequence
101to catch up with the cluster.
Q: Why save the discarded writes to a file instead of deleting them outright? A: Because value = 100 was a legitimate write that a client sent and the old primary accepted. It is uncommitted (never acknowledged by a majority), but it may still matter to whoever issued it. Writing it to a .rollback BSON file preserves the data so an administrator can inspect and manually re-apply it if needed. The file lives inside the dbpath under a rollback/ directory. From the cluster's perspective the write is gone; from the administrator's perspective it is recoverable with effort.
Q: What happens to un-replicated write operations when a crashed primary node re-joins a MongoDB cluster? A: The re-joining node detects Oplog divergence against the current cluster majority. To maintain consistency, MongoDB rolls back the un-replicated writes by removing them from active database storage and saving them to local
.rollbackBSON files inside the database path directory. These writes are permanently removed from cluster access unless manually extracted from the rollback files by an administrator.
Pitfalls:
- Believing
w: 1writes are safe. A write acknowledged only by the primary can be rolled back if the primary crashes before replication — the exact scenario in this trace. Only majority-committed writes are rollback-proof (see 9.5). - Assuming the old primary keeps its data. On re-join, Node A's un-replicated write is removed from active storage, not merged. The
.rollbackfile is the only copy. - Confusing rollback with data loss of committed writes. Committed writes (acknowledged by a majority) are never rolled back — rollback only ever discards writes that no majority ever saw.
Recap + Bridge: A replica set has one primary that logs writes to its Oplog and secondaries that replicate them; when the primary fails, a majority election promotes a new one, and any writes the old primary made alone are rolled back into .rollback files on re-join. The "majority" idea that governs elections is the same "majority" that governs write durability in section 9.5.
9.4.3 CAP Theorem Classification: CP Default vs AP Tuning
Under the CAP Theorem (Consistency, Availability, Partition Tolerance), distributed databases must choose between Consistency and Availability during network partitions.
The theorem says a distributed system can guarantee at most two of three properties during a partition:
- C — Consistency: every read returns the most recent write (all nodes agree on one history).
- A — Availability: every request receives a response (even if that response is stale).
- P — Partition Tolerance: the system keeps operating despite network partitions between nodes.
Because partitions will happen in real networks, P is effectively mandatory — so the real choice is C vs A during a partition.
By default, MongoDB is classified as a CP Database (Consistency and Partition Tolerance):
- All write and read operations route through the single Primary node.
- During primary failover elections (which take several seconds), the cluster temporarily rejects write operations, sacrificing availability to prevent inconsistent data reads.
In the failure trace above, notice what the cluster did during the partition: secondaries did not keep serving writes immediately — they first held an election (several seconds) and only then accepted writes from the new primary. That refusal to answer during the election is the "sacrificed availability" of CP.
However, MongoDB allows developers to tune client configurations to switch operationally from a CP model to an AP Model (Availability and Partition Tolerance). By directing read requests to Secondary nodes (readPreference: secondary), applications maintain read availability even during primary outages, accepting eventual consistency.
Assumptions & Scope: "CP by default" describes default routing, not a hard law. With readPreference: secondary, reads keep working from secondaries during a primary outage (AP-like reads), but those reads may be stale — you cannot have both "always fresh" and "always available" during a partition. The reference book makes the same trade explicit for the NoSQL family: Cassandra explicitly chooses AP (it "adheres to the Availability and Partition Tolerance properties of the CAP theorem" with BASE — Basically Available, Soft state, Eventual consistency), while MongoDB's default is the consistency-first side. Neither is "better"; each workload picks its corner.
Pitfalls:
- Treating CAP as "pick two always." P is effectively forced in distributed deployments; the practical question is only C vs A during partitions.
- Assuming MongoDB is always AP or always CP. It is CP by default and can be tuned toward AP via read preference — the classification is configurable behavior, not a fixed identity.
- Equating "eventual consistency" with "wrong data." Eventually consistent reads are stale for a bounded window, not permanently incorrect — which is why applications like public review feeds (9.5) can tolerate it.
Real-world domain connection: Replica-set failover is the mechanism that keeps production databases available when a machine dies mid-shift: MongoDB Atlas automates these elections, and the rollback-file mechanism is why DBAs can recover a lost write after a failover. The CAP trade-off shows up in every distributed datastore decision — Cassandra and DynamoDB sit on the AP side with tunable consistency, MongoDB defaults to CP, and architects pick based on whether stale reads or write refusals hurt their product more.
---
9.5 Read and Write Concerns, Read Preferences, and Architectural Configurations
Hook: A video site can lose a playback bookmark; a ticket system absolutely cannot lose a seat reservation. Both run on MongoDB — yet they need opposite settings. This section gives you the knobs that turn "MongoDB is CP by default" into "MongoDB behaves exactly how my product needs it to behave."
MongoDB provides a precise parameter framework for configuring durability, consistency, and latency across database operations. Three knobs matter:
- Write Concern — how strongly the server must confirm a write before the client gets success.
- Read Concern — how fresh (and rollback-proof) the data a read returns must be.
- Read Preference — which node (primary or secondary) serves a read.
9.5.1 Write Concern Parameters (w, j, wtimeout)
Write Concern controls the level of acknowledgement requested from MongoDB before a write operation returns success to the client application.
Write Concern is specified as w: <value>, j: <boolean>, wtimeout: <number>:
w— how many nodes must acknowledge the write.j— whether the write must be flushed to the on-disk journal before acknowledging.wtimeout— how many milliseconds to wait for those acknowledgements before giving up.
Write Concern Levels (w)
w: 0(Unacknowledged Write): The client sends the write request but does not wait for database acknowledgement. Write operations execute with minimum latency but zero error reporting or durability guarantees. If the write fails (bad key, disk error), the client never learns.w: 1(Acknowledged by Primary): The default setting. The write operation returns success as soon as the Primary node writes the data to its local memory/Oplog. If the Primary crashes before replicating to secondaries, the write may be lost during rollback — this is precisely thevalue = 100write from section 9.4's trace.w: majority(Majority Acknowledged): The write operation returns success only after a strict majority of replica set nodes acknowledge writing the data:
\[ N_{\text{ack}} \ge \left\lfloor \frac{N_{\text{total}}}{2} \right\rfloor + 1 \]
For a 3-node cluster, at least 2 nodes must acknowledge. Writes committed under w: majority are guaranteed never to be rolled back.
Worked example — computing the majority threshold. With \(N_{\text{total}}\) nodes in the replica set:
\[ \begin{aligned} N_{\text{total}} = 3 &: \quad N_{\text{ack}} \ge \left\lfloor \frac{3}{2} \right\rfloor + 1 = 1 + 1 = 2 \\ N_{\text{total}} = 4 &: \quad N_{\text{ack}} \ge \left\lfloor \frac{4}{2} \right\rfloor + 1 = 2 + 1 = 3 \\ N_{\text{total}} = 5 &: \quad N_{\text{ack}} \ge \left\lfloor \frac{5}{2} \right\rfloor + 1 = 2 + 1 = 3 \\ N_{\text{total}} = 7 &: \quad N_{\text{ack}} \ge \left\lfloor \frac{7}{2} \right\rfloor + 1 = 3 + 1 = 4 \end{aligned} \]
Notice the pattern: majority is "more than half," computed as \(\lfloor N/2 \rfloor + 1\). For 3 nodes that is 2; for 4 nodes it is 3; for 5 nodes it is 3; for 7 nodes it is 4. The floor function \(\lfloor x \rfloor\) (floor of \(x\)) rounds down to the nearest integer.
Why this prevents rollback: if a write is stored on a strict majority of nodes, no future election can produce a new primary history without that write — any majority set overlaps the nodes holding it, and overlap is guaranteed because two majorities of the same set always share at least one node. Sense-check: a write on 3 of 4 nodes cannot be rolled back, because any 3-node majority that could form later must include at least one of those 3 holders.
Journaling Acknowledgment (j)
j: true(Journal Acknowledged): Ensures that the node has written the operation to disk-backed journal logs before returning success, protecting against data loss during sudden power outages.
The distinction: w: 1 means "the primary has it in memory," j: true means "the primary has it on durable disk." A power failure wipes memory; a journal write survives. Combining w: majority, j: true means "a majority of nodes have it on disk" — the strongest practical durability MongoDB offers.
Write Timeout (wtimeout)
Prevents client write requests from blocking indefinitely if replica nodes fail to acknowledge within a specified millisecond threshold. Without wtimeout, a client waiting for w: majority could hang forever if a node is down; with it, the write fails fast and the application can decide what to do.
Pitfalls:
- Treating
w: 1as durable. It is only "acknowledged by primary memory" — a crash before replication rolls the write back (section 9.4). - Using
w: 0for anything you care about. Zero error reporting means a failed write looks like success; it is only for fire-and-forget telemetry. - Forgetting
j: truefor crash survival. Majority in memory is still lost on a full cluster power failure; disk journaling is what survives a power cut.
9.5.2 Read Concern Parameters
Read Concern controls the level of isolation and consistency of data read from the cluster.
local: Returns the node's most recent data. Does not check whether data has been committed to a majority of nodes. Fast, but risks reading data that might later be rolled back (dirty read).available: Similar tolocal, returning data from the target node without majority validation. (Default for queries against secondaries in sharded clusters).majority: Returns data that has been acknowledged by a strict majority of replica set nodes. Guarantees that read data cannot be rolled back.linearizable: The strongest read concern. The Primary node blocks the read operation and verifies its primary status with a majority of cluster nodes before returning data. Guarantees that the query reflects all majority-committed writes completed prior to the read.snapshot: Used in multi-document transactions to provide point-in-time snapshot isolation.
Intuition — a spectrum of "how sure am I?" Read concern is a freshness-and-safety dial. local/available say "give me whatever this node has right now — fastest, possibly stale or even uncommitted." majority says "only give me data a majority has agreed on." linearizable says "pause, confirm I am still the real primary with a majority, then give me the freshest committed data" — the strongest guarantee, at the cost of a blocking round of checks on every read.
Assumptions & Scope: majority read concern requires the majority of nodes to have the data available; during a partition that can delay reads. linearizable incurs real latency (a majority round-trip per read) and is only justified where stale reads are genuinely harmful — financial confirmations, not news feeds. snapshot only applies inside multi-document transactions; it is meaningless for a single find() outside one.
9.5.3 Read Preference Modes
Read Preference determines how client applications route read queries across replica set nodes.
primary: Default mode. All reads route exclusively to the Primary node. Guarantees strict read consistency.primaryPreferred: Reads route to the Primary. If the Primary is unreachable, reads fall back to Secondary nodes.secondary: Reads route exclusively to Secondary nodes. Offloads read bandwidth from the Primary, but reads may reflect stale data.secondaryPreferred: Reads route to Secondaries. If no secondaries are available, reads fall back to the Primary.nearest: Reads route to whichever node exhibits the lowest network latency (measured via periodic ping checks), regardless of whether it is Primary or Secondary.
Intuition + Analogy: Read preference is the "which door do I walk through?" knob. primary = always the front door (one entry point, always freshest). secondary = always the side doors (many doors, spread the crowd, but the merchandise may lag by a moment). nearest = whichever door is closest to you right now (lowest latency). The reference book notes clients "usually read from the primary" but "can also specify a read preference that will direct the read operations to the secondary" — exactly this knob.
Pitfalls:
- Expecting
secondaryreads to be fresh. Secondaries replicate asynchronously; asecondaryread can be stale by the replication lag. - Using
nearestfor consistency-critical reads. Lowest latency is not the same as freshest —nearestmay hand you a lagging node. - Forgetting read preference only affects reads. Writes always go to the primary, regardless of read preference.
9.5.4 Multi-Level Configuration Hierarchy
MongoDB permits configuring Read Concern, Write Concern, and Read Preference at five distinct granularity levels:
- Cluster Level: Global defaults applied across all cluster connections.
- Client/Connection Level: Set inside application database connection strings.
- Database Level: Configured on specific database handles.
- Collection Level: Applied to specific collection objects.
- Operation Level: Overridden on individual
.find()or.insertOne()queries.
The levels nest: a setting configured lower (more specific) overrides the higher (more general) default. In practice, teams set sane cluster defaults, then tighten or loosen per operation where a specific query needs it — most applications never touch more than the operation level for the handful of hot paths.
9.5.5 Real-World Case Studies and Architectural System Design Matrices
The lecture examined four distinct real-world application scenarios, demonstrating how business requirements dictate specific database configurations.
Q: How do author vs public reader read concerns differ when a user publishes a new blog review? A: When an author submits a review and immediately clicks "View My Post", the query must use
readPreference: primaryandreadConcern: linearizableto guarantee self-consistency so the author immediately sees their own post. Conversely, external readers browsing the blog usereadPreference: secondaryandreadConcern: localto allow high read scalability on secondaries, accepting short replication delays.
Q: Why must the author use linearizable while readers use local? A: The author just issued a write and immediately reads it back. If that read hits a lagging secondary, the author sees their own post missing — an alarming, trust-destroying bug that often triggers duplicate submissions. readPreference: primary + readConcern: linearizable guarantees the author's read reflects their just-committed write. External readers have no such expectation: they do not know when a review was written, so a 30-second replication delay is imperceptible, and routing them to secondaries (secondary + local) scales read capacity across many nodes. The same application deliberately uses two different configurations for two different user roles.
Case Study 1: Video Streaming Service (Playback Timestamp Bookmarking)
- Scenario: A video streaming application (such as YouTube) periodically records playback progress timestamps (e.g., every 10 seconds) so users can resume watching from their last location.
- Business Analysis: Playback bookmarking is non-life-critical. High write throughput from millions of concurrent streams is required. If a server crash loses a 10-second playback update, resuming 5 to 10 seconds earlier is completely acceptable to end-users.
- Architectural Configuration:
- Write Concern:
w: 1(Best-effort fast writes to Primary; low write latency prioritized over strict durability). - Read Preference:
nearest(Fetch last-known timestamp from lowest-latency node for instant application startup). - Read Concern:
available/local(Read in-memory data quickly without majority checks).
Why: the cost of losing a bookmark is "resume slightly earlier" — negligible. So the system optimizes for write throughput (w: 1) and read latency (nearest, local) and accepts that a crashed write might lose a few seconds of progress.
Case Study 2: Blogging Platform & User Review Publishing
- Scenario: A user submits a hotel review on a platform (such as Airbnb or Play Store) and immediately clicks "View My Published Review".
- Business Analysis: Dual-perspective requirements exist:
- Author Perspective: When the author submits and immediately clicks view, seeing their own review is critical. Failing to display their freshly submitted post causes immediate user alarm and duplicate submissions.
- External Reader Perspective: External users browsing reviews do not know the exact minute a review was written. A 30-second replication delay before external readers see the review is imperceptible and acceptable.
- Architectural Configuration:
- Write Concern:
w: majority(Author review write must be committed to cluster majority to guarantee durability). - Author Read Configuration:
readPreference: primary,readConcern: linearizable(Author reads wait for write confirmation on Primary to ensure instant self-consistency). - External Reader Configuration:
readPreference: secondary,readConcern: local(External public reads route to Secondaries for high read scalability).
Why: the author's write must be durable (w: majority) and immediately visible to them (primary + linearizable), while the much larger volume of public reads must scale (secondary + local). Two roles, two configurations, one system.
Case Study 3: Taxi Rental Service Real-Time Location Dispatch
- Scenario: GPS location updates emitted every 2 seconds by active taxi drivers to match nearby passengers.
- Business Analysis: Driver location streams represent ephemeral, high-frequency time-series data. Stale locations are quickly rendered obsolete by newer pings. High availability and low write latency are key.
- Architectural Configuration:
- Write Concern:
w: 1orw: 0(Fast ingestion without blocking on secondaries). - Read Preference:
nearest(Route passenger location lookups to nearest cluster node). - Read Concern:
local(Serve fresh local node coordinates).
Why: a location that is 2 seconds old is already outdated — losing one ping costs nothing because the next ping replaces it. The system prioritizes write speed (w: 1/w: 0) and read latency (nearest, local).
Case Study 4: Circus Ticket Reservation Engine
- Scenario: Booking seats for a live circus show with strict seat inventory limits.
- Business Analysis: Financial transaction requiring absolute prevention of double-booking. Financial correctness overrides latency concerns.
- Architectural Configuration:
- Write Concern:
w: majority,j: true(Disk-backed majority durability for seat reservations). - Read Preference:
primary(Prevent reading uncommitted or stale seat reservations). - Read Concern:
linearizable(Ensure zero read-staleness before issuing ticket confirmation).
Why: two customers must never receive the same seat. Every reservation read must see every committed reservation (linearizable, primary) and every write must survive any failure (w: majority, j: true) — latency is irrelevant compared to correctness.
System Design Configuration Matrix
| Application Use Case | Write Concern (w) |
Read Preference | Read Concern | Primary Trade-Off |
|---|---|---|---|---|
| Video Playback Progress | w: 1 |
nearest |
available / local |
High Throughput & Low Latency over Strict Durability |
| Author Review Publish | w: majority |
primary |
linearizable |
Strict Durability & Immediate Consistency over Latency |
| Public Review Browsing | w: majority |
secondary |
local |
High Read Availability & Scalability over Freshness |
| Taxi Location Dispatch | w: 1 |
nearest |
local |
Real-Time Latency over Transactional Consistency |
| Circus Ticket Booking | w: majority, j: true |
primary |
linearizable |
Absolute Consistency & Durability over Latency |
Exam note: For scenario questions, classify the workload first: (1) Can a lost write be tolerated? If yes → w: 1 or w: 0; if no → w: majority (add j: true for financial/crash-survival). (2) Does the reader need its own write instantly? If yes → primary + linearizable; if no → secondary/nearest + local. (3) Is lowest latency the goal? If yes → nearest. This three-question recipe reproduces all four case studies.
Pitfalls:
- Copying one configuration everywhere. The four case studies differ precisely because their failure costs differ; a one-size-fits-all config either wastes throughput or risks correctness.
- Applying the author's config to public readers (or vice versa) — the review case shows one system correctly using both extremes for different roles.
- Forgetting the
j: truedistinction. "Majority acknowledged" is about node count; "journaled" is about disk durability. Financial writes need both (w: majority, j: true).
Real-world domain connection: These are the exact trade-offs engineers tune in production: streaming services batch high-frequency telemetry with w: 1; review and content platforms split author paths (linearizable) from public reads (secondary/local); ride-hailing ingests ephemeral GPS at w: 0–w: 1; and ticketing, payments, and inventory systems run the strictest settings because a single double-booked seat or oversold ticket is a direct business loss. Knowing which dial to turn for which product is a core distributed-systems design skill — the same reasoning transfers directly to other NoSQL systems' consistency knobs (e.g., Cassandra's tunable consistency levels).
---
Exam Guidance Summary
Exam note: Students preparing for assessments should focus on the following key core concepts:
- MongoDB Data Terminology Mapping: Memorize relational-to-MongoDB equivalences: Database \(\rightarrow\) Database, Table \(\rightarrow\) Collection, Row \(\rightarrow\) Document, Column \(\rightarrow\) Field. Be ready to write the mapping table from memory (9.1.1).
- Schema Modeling Decisions: Explain when to choose Embedding versus Referencing. Be ready to evaluate document size limits (16 MB BSON cap), one-to-many vs one-to-millions relationships, and back-referencing models. Expect a scenario asking "should this relationship be embedded or referenced — and why?" (9.1.3).
- Primary Key
_idMechanics: Understand auto-generation of BSONObjectId, uniqueness enforcement, and strict data-type matching (integer1vs string"1"). A favorite trap question: both_id: 1and_id: "1"can coexist — explain why (9.3.1). - CRUD Shell Syntax & Operator Mechanics:
- Single vs batch operations:
insertOne()vsinsertMany(),updateOne()vsupdateMany(). - Update operator syntax:
\$setoperator vs plain document replacement (the replacement trap). - Relational joins in MongoDB: Aggregation pipeline
\$lookupsyntax — namelocalField,foreignField,from, andas(9.3).
- Distributed Replica Set & Rollback Mechanics: Step-by-step trace of Oplog divergence when a Primary node crashes after receiving un-replicated writes, how secondaries elect a new primary, and how the old primary writes un-replicated data to
.rollbackfiles upon re-joining. Practice the 4-node state-sequence trace (9.4). - Consistency & Durability Configurations: Define and configure Write Concern (
w: 0,w: 1,w: majority), Read Concern (local,majority,linearizable), and Read Preference (primary,secondary,nearest). Be prepared to solve scenario-based system design questions by selecting optimal parameter configurations — reproduce the four case studies (video bookmarking, review publishing, taxi dispatch, circus booking) and the majority-threshold formula (9.5).
Recommended revision order: lock the vocabulary mapping first (it anchors everything), then the embed/reference decision rules, then CRUD syntax, then the failover trace, and finally the concern/preference matrix with the case studies.
---
Key Industry Applications
Real-world: Practical deployment patterns highlight MongoDB's role across modern software architectures:
- E-Commerce Product Catalogs: Utilizing embedded sub-documents and arrays to store variable product attributes, reviews, and categories without complex relational schema migrations. Each product category carries its own field set inside one collection — the query-centric modeling payoff from 9.1.2 applied directly.
- Log Aggregation & Ephemeral Analytics: Ingesting high-velocity log lines and sensor telemetry using
w: 1orw: 0write concerns for low-latency writes — the taxi-dispatch pattern from 9.5.3 applied to observability data that becomes obsolete within seconds. - Microservices Materialized Views: Serving pre-aggregated BSON data documents directly to REST/GraphQL APIs, eliminating multi-table SQL joins at runtime — MongoDB as the read-side cache between relational engines and UI clients (the staging/materialized-view role from 9.1.4).
- Content Management Platforms: Managing user reviews, blogs, and media metadata using hybrid read concerns (
linearizablefor content creators,local/secondaryfor public traffic) — the author-vs-reader split from 9.5.5 applied at platform scale.
The common thread: every one of these patterns is a direct consequence of a decision from this lecture — embed vs reference (9.1), query-centric modeling (9.1.2), and the concern/preference matrix (9.5). When you see a production MongoDB deployment, you can now reverse-engineer why it stores data the way it does and which consistency settings its engineers chose.
---
BDA Lecture 9 notes
Sections Breakdown
MongoDB is a document-oriented NoSQL database storing self-describing JSON documents serialized on disk as BSON; data modeling is query-centric, choosing between embedding, referencing, and back-referencing based on relationship cardinality and the 16 MB document cap, with sharding providing horizontal scale.
MongoDB runs as the mongod daemon (default port 27017) managed as a Windows service or run in the foreground with an explicit --dbpath; clients reach it via the JavaScript mongosh shell, Compass GUI, or programmatic drivers, and bulk data enters through mongoimport.
CRUD in MongoDB: insertOne/insertMany for creation (array passed to insertOne becomes one embedded-array document), find with filters/projections/cursors for reads, update/updateOne/updateMany with $set vs full-document replacement for writes, remove for deletes, and $lookup aggregation for left-outer joins.
A replica set has one primary logging writes to its Oplog and secondaries replicating via 2-second heartbeats; on primary failure a majority election promotes a new primary, and un-replicated writes made by the old primary are rolled back into .rollback BSON files on re-join; MongoDB is CP by default and tunable toward AP via read preference.
Write concern (w: 0/1/majority, j, wtimeout) sets durability of writes; read concern (local/available/majority/linearizable/snapshot) sets freshness and rollback-safety of reads; read preference (primary/secondary/nearest) routes reads across nodes; four real-world case studies show how business failure costs dictate the exact configuration matrix.
Exam focus for Lecture 9: RDBMS-to-MongoDB terminology mapping, embed vs reference vs back-reference modeling with the 16 MB cap, _id type-sensitivity (integer 1 vs string "1"), CRUD shell syntax (insertOne/insertMany, updateOne/updateMany, $set, $lookup), the Oplog divergence/rollback trace, and the write/read concern plus read preference matrix for scenario questions.
MongoDB's practical deployment patterns: e-commerce product catalogs with embedded variable attributes, low-latency log/sensor ingestion with w:1 or w:0, microservices materialized views serving pre-aggregated BSON to APIs, and content platforms using hybrid read concerns (linearizable for creators, local/secondary for public traffic).
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.
9.1 MongoDB Ecosystem, Data Model, and Architectural Foundations
Must-know: RDBMS-to-MongoDB terminology mapping (table->collection, row->document, column->field); embedding for bounded one-to-few with atomic single-query reads, referencing for unbounded/many-to-many/16 MB pressure, back-referencing for one-to-millions.
\[ \text{Storage per Node} = \frac{\text{Total Dataset Volume}}{\text{Number of Cluster Nodes}} \]
⚠️ Top pitfall: Embedding an unbounded array marches the document toward the 16 MB BSON cap; forward-referencing a one-to-millions relationship blows the limit — flip to back-referencing.
Self-check: Why can integer _id 1 and string _id "1" coexist in one collection?
Connects to: 9.3, 9.4
9.2 MongoDB Installation, Service Management, and Environment Access
Must-know: mongod is the server daemon (default 127.0.0.1:27017); --dbpath sets the data directory; the three access interfaces are the JS shell, Compass GUI, and drivers; mongoimport loads CSV/TSV/JSON with --headerline.
⚠️ Top pitfall: A leftover service instance and a manual mongod fight over port 27017; pointing --dbpath at the wrong directory starts an empty database.
Self-check: What flag tells mongoimport to treat the first CSV line as field names?
Connects to: 9.3
9.3 MongoDB CRUD Operations and Query Mechanics
Must-know: insertOne inserts one document; insertMany inserts many; an array passed to insertOne becomes ONE document with an embedded array. updateOne patches one match, updateMany patches all; $set patches fields while a plain object replaces the whole document. Integer _id 1 and string _id "1" coexist because BSON type matching is strict.
⚠️ Top pitfall: Passing an array to insertOne (silently creates one embedded-array document) and omitting $set in update (silently replaces and wipes the whole document).
Self-check: Why do integer _id 1 and string _id "1" both insert without a duplicate-key error?
Connects to: 9.1, 9.5
9.4 Distributed Consistency, CAP Theorem, and Replication Mechanics
Must-know: Trace the 4-node failover: all at 99, primary A accepts un-replicated write 100, partition isolates A, B/C/D elect B primary, B commits and replicates 101, A re-joins, detects divergence at 99, rolls back 100 into a .rollback BSON file in dbpath, then syncs 101.
\[ \text{State} = \{A: 100, B: 101, C: 101, D: 101\} \]
⚠️ Top pitfall: Believing a w:1-acknowledged write is safe — if the primary crashes before replication it is rolled back; only majority-committed writes are rollback-proof.
Self-check: Where does MongoDB save an un-replicated write from a crashed primary that re-joins the cluster?
Connects to: 9.5
9.5 Read and Write Concerns, Read Preferences, and Architectural Configurations
Must-know: w: majority threshold is floor(N_total/2)+1 (3-node cluster needs 2 acknowledgements); writes committed under majority are never rolled back. For scenario questions: tolerate lost writes -> w:1/w:0; must not lose -> w:majority (+j:true for financial); author needs own write instantly -> primary+linearizable; public readers -> secondary+local; lowest latency -> nearest.
\[ N_{\text{ack}} \ge \left\lfloor \frac{N_{\text{total}}}{2} \right\rfloor + 1 \]
⚠️ Top pitfall: Treating w:1 as durable (it is primary-memory only and can be rolled back) and forgetting j:true for crash/power-failure survival; using nearest for consistency-critical reads.
Self-check: For a 5-node replica set, how many nodes must acknowledge a write for w: majority?
Connects to: 9.4
Exam Guidance Summary
Must-know: Memorize the terminology mapping, the embed/reference decision rules, CRUD operator split (insertOne/insertMany, updateOne/updateMany, $set vs replacement, $lookup), the 4-node failover and rollback trace, and the concern/preference configuration matrix with the four case studies.
\[ N_{\text{ack}} \ge \left\lfloor \frac{N_{\text{total}}}{2} \right\rfloor + 1 \]
⚠️ Top pitfall: Mixing up the four-way CRUD split (insertOne vs insertMany; updateOne vs updateMany) or confusing w:1 durability with majority durability in scenario answers.
Self-check: What is the majority write-concern threshold for a 3-node replica set, and why can integer _id 1 and string _id "1" coexist?
Connects to: 9.1, 9.3, 9.4, 9.5
Key Industry Applications
Must-know: Each industry pattern maps to a lecture decision: embedded variable attributes (9.1 embed), w:1/w:0 log ingestion (9.5 taxi pattern), materialized-view read models (9.1.4), and hybrid linearizable/local concerns for creator vs public traffic (9.5.5 author-vs-reader).
⚠️ Top pitfall: Assuming one global configuration fits all workloads — real platforms split author reads (linearizable) from public reads (secondary/local) inside one system.
Self-check: Which lecture decision does the e-commerce product-catalog pattern illustrate?
Connects to: 9.1, 9.5