PostgreSQL, SQL, MongoDB, and DynamoDB — core concepts explained in depth.
MongoDB Search and MongoDB Vector Search (formerly Atlas Search / Atlas Vector Search) are Lucene-backed full-text and ANN/HNSW semantic search served by a separate mongot process synchronized via change streams — eventually consistent, memory-bound by the vector index size, and since MongoDB 8.2 available self-managed rather than only on Atlas, though with a materially narrower feature set than the managed platform.
An aggregation pipeline is a bash-style chain of stages over a stream of documents, and the skill is knowing which stages shrink the stream ($match, $group), which grow it ($unwind), and which merely reshape it ($project, $sort) — then ordering them so field-path expressions, accumulators, and a well-labeled $group _id produce the right number instead of a plausible one.
Query MongoDB with find — projections, $ conditionals, $in/$or/$not, and the type-specific traps around null, arrays, and embedded documents — then drive the resulting cursor with limit, skip, and sort without falling into large-skip pagination.
How MERGE conditionally updates, deletes, or inserts rows in one atomic statement across matched/unmatched source rows, why PostgreSQL had no MERGE at all until version 15 (2022) — two years after this book's 2nd edition — and why MySQL's INSERT ... ON DUPLICATE KEY UPDATE can insert-or-update but never delete in the same statement.
How UNION ALL vertically stacks rowsets and UNION does the same while deduplicating (at the cost of a sort), why column count and type compatibility rules are identical across PostgreSQL, MySQL, and SQL Server, and how MySQL 8.0.31 (2022) closed its long-standing INTERSECT/EXCEPT gap — while SQL Server still has no ALL variant for either.
How to sort NULLs to a specific position independent of the non-NULL order, and how to sort by a key that depends on another column's value — and why the book's claim that PostgreSQL needs the same workaround as MySQL/SQL Server for NULL sorting is simply wrong (PostgreSQL has had native NULLS FIRST/LAST since 2008).
How IS NULL/IS NOT NULL and COALESCE handle missing data portably across databases, how LIKE's % and _ wildcards match partial text, and the case-sensitivity and indexing gotchas (leading vs trailing wildcards, pg_trgm) that differ across PostgreSQL, MySQL, and SQL Server.
How PostgreSQL, MySQL, and SQL Server each cap the number of rows a query returns (LIMIT, TOP, and the ANSI FETCH FIRST standard MySQL doesn't support), how to randomize the sample before capping it, and why TABLESAMPLE beats ORDER BY RANDOM() for large tables.
Removing rows identified by their relationship to a second table — the IN/EXISTS subquery form, the NOT IN null trap on the inverse case, and the three incompatible vendor join-DELETE syntaxes.
How to arbitrarily keep one row per duplicate group and delete the rest with MIN(id) NOT IN, why MySQL still rejects a DELETE subquery that reads its own target table, and how ROW_NUMBER() over a CTE offers a more portable — though not identically-syntaxed — alternative across PostgreSQL, MySQL, and SQL Server.
How to return unmatched rows from both sides of a join at once — departments with no employees and employees with no department, simultaneously — using FULL OUTER JOIN where it's supported, or a UNION of a LEFT and RIGHT outer join where it isn't (MySQL, and Oracle's proprietary (+) syntax).
How to correctly sum a column across every row of a table when a second, joined table only has matching rows for some of them — outer join instead of inner join, and let the CASE expression treat a NULL match as zero instead of skipping the row.
Why joining before aggregating can silently inflate a SUM by counting a duplicated row more than once, and how to fix it with SUM(DISTINCT ...), pre-aggregating in a subquery before the join, or a window function on DB2/Oracle/SQL Server.
How a missing or incomplete join condition silently multiplies row counts instead of erroring out, why the resulting row count is always the product of the input cardinalities, and why explicit ANSI JOIN syntax catches the mistake at parse time where old-style comma joins let it run.
Use a symmetric set difference (EXCEPT/MINUS, or a correlated-subquery fallback) to prove two tables or views hold exactly the same rows, duplicates included.
How LEFT JOIN + IS NULL (an anti-join) finds rows in one table with no match in another, how the same LEFT JOIN mechanism — or a scalar subquery in the SELECT list — adds optional data to an already-correct query without dropping rows, and why MySQL and PostgreSQL both recognize the anti-join pattern and stop scanning early instead of evaluating it as a naive join-then-filter.
Use LEAD and LAG to read or shift a neighboring row's value, and RANK, DENSE_RANK, and ROW_NUMBER to assign ordinal positions with three different tie-handling rules.
Produce subtotal and grand-total rows alongside detail rows in a single query — ROLLUP for a hierarchy, CUBE or GROUPING SETS for every combination of grouping columns — and use GROUPING() to tell a manufactured subtotal NULL apart from a real one, with MySQL Server still limited to ROLLUP alone.
Splitting a result set into fixed-size buckets with ROW_NUMBER and CEILING versus a fixed number of buckets with NTILE, then rendering the distribution as a horizontal or vertical text histogram.
A whole family of "which rows, and how many" problems past a plain LIMIT — paginating through results, sampling every Nth row, handling ties in a top-N query, and finding the highest/lowest value per group.
Compare a row to its neighbor inside an ordered partition with LEAD/LAG, and carry the last known value forward across NULL gaps with LAST_VALUE or a counted-group FIRST_VALUE — plus the real portability gaps in offset expressions and IGNORE NULLS.
Find contiguous runs in a sparse numeric sequence, collapse each run to its start and end, and generate the dense sequence that reveals what's missing.
Computing the mode, the median, and a median-absolute-deviation outlier score in SQL, and why PostgreSQL, MySQL, and SQL Server each need a different query to do it.
One window-function shape — SUM/AVG OVER with an ORDER BY and a frame — solves running totals, running products, moving averages, and resettable accumulators, including why a running product has no native aggregate and why the default RANGE frame is a tie-related footgun.
Computing several different partition-level aggregates side by side in one query, and moving windows framed symmetrically or by value range rather than by trailing row count.
Reason about dates as intervals rather than points: measure the gap to the next row with LEAD, fill holes in a date sequence with a generated calendar, and detect colliding date ranges with a self-join condition or PostgreSQL's native range types.
Finding every occurrence of a weekday in a year and the first/last occurrence in a month, and navigating the incompatible weekday-numbering conventions across PostgreSQL, MySQL, and SQL Server.
Manufacturing date rows that no table stores — a full calendar grid, or a fiscal quarter's start and end dates — with generate_series, recursive CTEs, and SQL Server 2022's numeric-only GENERATE_SERIES.
Decomposing a date into leap-year status, days-in-year, individual units of time, and month boundaries — and where EOMONTH, LAST_DAY, and DATETRUNC have replaced the manual arithmetic.
Counting working days between two dates and tallying weekday occurrences across a year — generating one row per day with generate_series, recursive CTEs, or a numbers table, and avoiding SQL Server's session-dependent @@DATEFIRST weekday numbering.
Adding intervals to dates and measuring the distance between two dates in days, months, years, or sub-day units — and why SQL Server counts calendar boundaries where PostgreSQL and MySQL truncate elapsed units.
Clean stray characters out of a string, split mixed alphanumeric data into its numeric and character parts, and prove a text value is castable — the book's TRANSLATE/REPLACE trick versus today's regex and type-driven validators.
Counting how many times a character or substring occurs in a value, and finding rows that do not match an expected pattern — the portable LENGTH/REPLACE and TRANSLATE techniques, and the native regex functions each vendor has added since.
Collapsing rows into a comma-separated string with STRING_AGG/GROUP_CONCAT, splitting a delimited string back into rows for an IN-list or join, and pulling out the nth token — with the per-vendor differences that still matter.
Float and double can't represent most decimal fractions exactly because they're base-2, so 0.1 + 0.2 !== 0.3 and money totals silently drift — the fix is NUMERIC/DECIMAL, integer minor units, or BigDecimal, not rounding harder at every step.
Express a self-referencing parent-child hierarchy with a fixed chain of self-joins — one join per level — and recognize where the chain stops scaling and a recursive CTE takes over.
Walk a self-referencing table to arbitrary depth with a recursive CTE — full tree views, all descendants of a given node, and leaf/branch/root classification — plus the vendor recursion-depth limits that decide when it breaks.
Reshape grouped rows into columns with the portable CASE-inside-aggregate technique, and see where SQL Server's PIVOT operator and PostgreSQL's crosstab() actually help.
Collapse several same-typed columns into one value column plus a label column — the inverse of pivoting — using UNION ALL, a single-scan LATERAL + VALUES list, or SQL Server's native UNPIVOT operator.
Covers CouchDB's multi-master replication model (no designated primary, any node accepts writes directly, with true bidirectional sync built from two unidirectional replication jobs), ad hoc vs. continuous replication and the _replicator database for durable jobs, how replication conflicts are detected and surfaced via a deterministic winner algorithm and the _conflicts field, and the Changes API's polling/long-polling/continuous/eventsource feed types with filter functions and _selector filtering, plus a book-vs-today check confirming CouchDB 3.x clustering maturity and PouchDB's active status as Apache PouchDB.
CouchDB has no ad-hoc query language for anything beyond simple key lookups: every non-trivial query is a pre-defined map/reduce view, written in JavaScript with emit(), saved into a design document, and materialized incrementally into a B-tree index as documents change. Since CouchDB 2.0 (2016), Mango's declarative _find queries cover simple lookups without hand-written JavaScript, but they are themselves built on the same MapReduce view infrastructure and still can't replace views for aggregation or fan-out patterns.
CouchDB exposes documents exclusively over HTTP/REST — no wire protocol, no binary driver, every operation is a curl-shaped request — and replaces locking with revision-based MVCC: a stale _rev gets a 409 on a single node, but across replicas both concurrent writes succeed and the resulting conflict is detected and merged by the application, never by the database.
A time series collection is a MongoDB 5.0 collection type that turns one-document-per-reading into compressed columnar buckets keyed by an immutable metaField plus a rounded time window, buying real storage and query wins for append-only measurement data at the cost of a long and specific list of restrictions: metaField-only updates, no unique or text indexes, no change streams, no writes in transactions, and a metaField choice you cannot change later.
MongoDB schema design starts from the queries rather than the entities, so the chapter's method is to establish constraints, access patterns, relation types, and cardinality first, then reach for a named pattern (Bucket, Subset, Outlier, Extended Reference) and settle the embed-or-reference question per field — with an honest closing section on the joins MongoDB was never designed to do.
MongoDB's write path is insertOne/insertMany, deleteOne/deleteMany/drop, and updateOne/updateMany/replaceOne — and the decisions that actually bite are replacement vs. $-operators, ordered vs. unordered batches, and whether upsert quietly turns a failed match into a brand-new document.
MongoDB swaps rows and tables for documents and dynamically-schemaed collections, using embedded documents and arrays where a relational model would JOIN, and a 12-byte ObjectId (4-byte timestamp, 5 random bytes, 3-byte counter) to mint unique _id values with no cross-server coordination.
Beyond the general-purpose B-tree: 2dsphere geospatial indexes for location queries, text indexes for basic keyword search (now explicitly superseded by MongoDB Search in production), TTL indexes for automatic expiry, capped collections for fixed-size insertion-ordered queues, and GridFS for files over the 16 MB document limit — each with its own sharp edges the book calls out and current docs sharpen further.
The shard key decides which shard every document lands on, and the chapter's three distribution shapes explain the consequences: an ascending key funnels every insert into a single max chunk, a hashed key spreads writes evenly by range-partitioning a 64-bit hash space but can never serve a targeted range query, and a location-based key plus zones buys placement control at the cost of the balancer's help — with cardinality capping how far any of them can ever be split.
Every MongoDB index is a real WiredTiger B-tree on disk — which is why a compound index serves any prefix of its keys and nothing else, why equality-sort-range key order swings the book's example query from 4,325 ms to 37 ms, and why every index you add is a tax on every write.
How Neo4j's separate indexing service (key-value and full-text/Lucene node indexes) and its native path-finding primitives make lookups and shortest-path queries first-class, not client-side code — traced through the book's Kevin Bacon shortestPath() exercise, a from-scratch Dijkstra weighted-graph trace, and how both the legacy REST indexing API and the single dijkstra option have since given way to schema-backed CREATE INDEX/CREATE FULLTEXT INDEX and the much larger Graph Data Science algorithm library.
How Neo4j's property graph model — nodes with labels and properties, relationships with a type, direction, and properties of their own — differs fundamentally from relational and document models, why it is 'whiteboard friendly', and Cypher's core CRUD vocabulary (CREATE, MATCH, WHERE, RETURN, MERGE, DELETE/DETACH DELETE) for building and querying a graph.
How an HBase cluster actually holds a table together underneath the API: regions as contiguous, non-overlapping row-key ranges served by RegionServers, HMaster handling region assignment and failover (not the read/write path itself), HDFS as the durable storage layer every RegionServer writes StoreFiles and WALs through, ZooKeeper coordinating master election and the hbase:meta bootstrap location, and hbase:meta itself as an ordinary, splittable table that maps row keys to owning regions and servers — plus how MapReduce parallelizes batch jobs one map task per region via TableInputFormat/TableOutputFormat, with book-vs-today notes on size-aware split policies, the rise of Spark-on-HBase for new analytics work, and confirmation that HBase remains actively released and maintained.
HBase's sparse, distributed, sorted multidimensional map (row key, column family, column qualifier, timestamp/version to a cell value), CRUD and table administration through the HBase shell (create, put, get, scan, disable, alter, enable), and an explicit comparison to Cassandra's wide-column model: shared Bigtable-paper lineage and column-family vocabulary, but master-coordinated writes over HDFS and ZooKeeper with row-level strong consistency versus Cassandra's leaderless, tunable-consistency architecture, plus a book-vs-today check on HBase's current maintenance status and shifting market position against Cassandra/ScyllaDB and managed Bigtable/DynamoDB.
The book documents Neo4j's now-removed master-slave HA model, where slaves accepted writes and synced back to an elected master; verified against current Neo4j docs, this walks through what replaced it — Raft-based Causal Clustering with core/read-replica servers, later renamed to per-database primary/secondary roles in Neo4j 5, plus the routing, bookmark, and licensing changes that came with it.
How an application driver actually connects to and uses a replica set: seed lists and DNS seedlist connection strings, driver-side primary discovery and failover behavior, write concern (w: 1, w: majority, custom getLastErrorModes rules) as the tool that prevents seeing a false success on a write about to be rolled back, and the five read preference modes that trade consistency for availability or latency.
How a MongoDB replica set actually fails over: the primary/secondary/oplog model, majority-based elections and quorum design across data centers, rollback mechanics after a failover, and the priority/hidden/votes/arbiter knobs used to shape which member becomes primary.
A short addendum with no book source, verified live against current AWS documentation: four genuinely additive DynamoDB changes since 2020 not covered elsewhere in this category — the cost-optimized Standard-IA table class (Dec 2021), resource-based policies for simplified cross-account access (Mar 2024), zero-ETL integrations into Redshift and OpenSearch (2023-2024), and opt-in Multi-Region Strong Consistency for Global Tables (GA June 2025) — each with its real mechanical constraints, not just its marketing framing.
The orienting case for DynamoDB before going deep: a fully-managed key-value-and-wide-column store built for infinite scaling with no performance degradation, an HTTP/IAM connection model that fits serverless compute, and workload-based pricing — driven historically by hyperscale (the 2007 Dynamo paper) and hyper-ephemeral compute (Lambda) — contrasted honestly against relational databases, MongoDB, and Cassandra, including the trade-offs DeBrie doesn't soften: no joins ever, schema enforcement moves to your application, and permanent AWS lock-in.
DynamoDB has no unique-constraint or auto-increment feature outside the primary key, so uniqueness on a second attribute needs a marker item plus TransactWriteItems, and sequential IDs need an atomic-counter UpdateItem followed by a PutItem — and pagination is cursor-based via LastEvaluatedKey/ExclusiveStartKey rather than SQL's OFFSET/LIMIT, a distinction that still holds in AWS's current docs, though the TransactWriteItems action limit has since grown from 25 to 100.
DynamoDB orders every Query result by the sort key's B-tree order — ascending by default, reversed with ScanIndexForward=False — so sorting strategy is entirely a data-modeling problem: use immutable attributes in the primary key and push changing sort attributes into a GSI, zero-pad numbers embedded in string sort keys so lexicographic UTF-8 comparison matches numeric order, use KSUIDs for IDs that must be both unique and chronologically sortable, and position parent items relative to their children to control which end of an item collection a Query lands on.
DynamoDB filtering is cheapest when it happens via the key condition, not after the read — composite sort keys concatenate a low-cardinality attribute (like an enum status) with a high-cardinality one (like a date) into a single sort key so one Query filters on both, sparse indexes exclude non-matching items at write time so the index itself is the filter, and FilterExpression is a last resort since it is applied after items are read, meaning you pay for all of the items that get filtered out; as of November 2025 AWS added native multi-attribute composite keys to GSIs, which the book's manual-concatenation technique still works identically alongside but no longer strictly requires.
Once an access-pattern-first entity-relationship model is done, DeBrie gives six concrete rules for turning it into working code: keep indexing attributes (PK/SK) separate from application attributes, implement the data model only at the boundary of the application, never reuse an attribute across multiple indexes, tag every item with a Type attribute, write small CLI scripts to debug access patterns, and only for the largest tables, shorten attribute names to save storage.
Cassandra data modeling runs conceptual model to application queries to logical model to physical model to a sizing pass, producing one denormalized table per query rather than one normalized table per entity — worked end to end on the book's hotel/reservation example, from Q1-Q9 through partition-size and disk-size arithmetic to the final CQL schema.
Single-table design packs every entity type into one DynamoDB table so related items share a partition key and can be pre-joined into an item collection readable in a single Query — the only substitute DynamoDB offers for joins — but the book is equally explicit about its three costs and the two situations (fast-evolving greenfield apps, and GraphQL backends) where those costs win.
DynamoDB data modeling runs in the opposite direction from relational design — enumerate every access pattern before designing a key, since there are no joins to fall back on — and the book's five-step process (understand the app, ERD, access patterns, primary key, then secondary indexes) is now essentially AWS's own official guidance.
Alex DeBrie's full GitHub data model — nine entity types, 24 access patterns, one table — shows the adjacency list and GSI overloading surviving contact with a real design: three separate GSIs each dedicated to a different relationship, the same Repo item overloading GSI2 with two different meanings depending on whether it's a fork, and Users-to-Organizations split so each many-to-many direction gets a different strategy instead of one pattern forced onto both.
Many-to-many is DynamoDB's hardest shape because you want to query both directions with no linking table to join through, and the book's four strategies — shallow duplication, adjacency list, materialized graph, and normalization with multiple requests — form a gradient ordered by how much mutable relationship data each can tolerate.
With no joins available, a parent/child relationship in DynamoDB is modeled by picking one of five strategies per access pattern — embed the children in a complex attribute, duplicate the parent's data onto each child, pre-join them in one item collection behind a composite primary key, rebuild that collection in a secondary index, or smash a deep hierarchy into a composite sort key — each gated by a specific question the book makes you answer.
DynamoDB's whole API is written in five expression types (KeyCondition, Filter, Projection, Condition, Update) glued together by a #name/:value placeholder syntax that exists because attribute values are typed and DynamoDB refuses to parse them out of a string — and the book's sharpest opinion in this pair of chapters, "I would not recommend using an ODM in DynamoDB," because a relational-style ORM hides the exact access-pattern decision that makes a Query cheap in the first place.
DynamoDB's API splits cleanly into three cost tiers — item-based actions (GetItem/PutItem/UpdateItem/DeleteItem, O(1), one item) — Query (hash lookup plus B-tree seek within one item collection, the operation single-table design is built around) — and Scan (a full-table walk the book sums up in three words: 'don't use Scans') — with PartiQL added later as a SQL-shaped syntax over the same three tiers, not a new one.
How to turn data volume, replication factor, and topology into an actual deployment plan: choosing SimpleStrategy vs NetworkTopologyStrategy for rack- and DC-aware replica placement, deriving node count from the book's storage formula and 1 TB/node guideline, and the hardware and network decisions (CPU/RAM, SSD vs HDD, JBOD vs RAID, avoiding load balancers) that back each node.
Cassandra's fifty-word elevator pitch unpacked as a high-level orienting map before any single mechanism: distributed and decentralized peer-to-peer architecture with no master, elastic scalability, high availability, tuneable consistency previewed against the CAP theorem, the row-oriented (not column-oriented) wide column data model, the Facebook-Dynamo-Bigtable origin story, and the book's own checklist for whether Cassandra fits a project at all.
Why relational databases hit a wall at web scale, what Brewer's CAP theorem actually forces you to give up during a network partition, and why Cassandra's shared-nothing, AP-by-default architecture with tunable per-query consistency is a deliberate answer to that trade-off rather than a limitation.
The on-disk machinery underneath Cassandra's write path: how a memtable accumulates writes in memory and flushes to an immutable SSTable, why SSTables must then be merged through compaction (SizeTiered/Leveled/TimeWindow, each a different workload trade-off), how Bloom filters let a read skip an SSTable without touching disk, and how tombstones and gc_grace_seconds interact with compaction to age out deletes — plus a book-vs-today note on Cassandra 5.0's opt-in Unified Compaction Strategy.
How a Cassandra cluster agrees on membership and data placement without a coordinator: the gossip protocol and Phi Accrual Failure Detector for cluster state, snitches for topology-aware routing, the token ring and consistent hashing for deterministic partition ownership, and virtual nodes for balanced rebalancing.
On a read, the coordinator asks the fastest replica for the row and every other replica for only a digest of it, resolves any disagreement by picking the cell with the latest timestamp (last write wins, with a lexicographic tiebreak), and then transparently read-repairs the replicas that answered with obsolete data as part of that same request — a mechanism that is opportunistic by design, is now configured by the Cassandra 4.0 read_repair table option rather than the book's removed read_repair_chance, and never replaces scheduled anti-entropy repair.
A Cassandra write is fast because the coordinator only waits for the commit-log-then-memtable step on each replica before acknowledging — the SSTable flush happens after the client is already told success — and lightweight transactions trade that speed for linearizability by running a full four-round-trip Paxos negotiation (prepare/promise, read/results, propose/accept, commit/ack) scoped to a single partition.
Cassandra separates the replication factor, set once per keyspace by SimpleStrategy or the production-recommended NetworkTopologyStrategy, from the consistency level chosen per query by the client, so a coordinator node forwards each read or write to the replicas that own the partition and returns as soon as ONE, QUORUM (floor(RF/2 + 1)), or ALL of them answer — with R + W > RF the formula for strong consistency, and hinted handoff plus repair covering the replicas that did not answer.
Explains Cassandra 5.0's native vector search: the VECTOR<type, dimension> CQL type that stores an embedding as a normal fixed-length column value, and the ORDER BY ... ANN OF ... query that finds its approximate nearest neighbors. Shows that vector search is not a separate subsystem but Storage-Attached Indexing (SAI) applied to vectors — same CREATE INDEX ... USING 'sai' syntax, same per-SSTable attachment and query-planner behavior as SAI's text and numeric indexes, but backed by a JVector graph (a close cousin of HNSW, inspired by DiskANN) instead of a trie or k-d tree.
Cassandra's original built-in secondary index (2i) and materialized views, the two mechanisms for querying on a non-partition-key column before Storage-Attached Indexing existed: 2i's cluster-wide fan-out and hard cardinality/tombstone limits, SASI's partial per-SSTable fix, and materialized views' automatic-denormalization trade against write-path cost and the book's own admitted immaturity — the exact gap the sibling SAI concept closes.
Explains Storage-Attached Indexing (SAI), Cassandra 5.0's storage-engine-integrated secondary index: what it fixes about the older built-in secondary index (2i) and materialized views (cardinality cliffs, tombstone failures, per-column storage cost, denormalization consistency risk), how per-SSTable index attachment works mechanically, CQL syntax, and its newer text-analysis and vector-search (ANN) angles, plus where its limits still push you back toward query-first table design.
Redis in Action's memory-reduction chapter covers three techniques: short-structure encodings (ziplist/intset, whose Redis 7.0 rename to listpack is covered in the Core Data Types concept), sharding one logical HASH or SET across many keyed shards, and packing fixed-width records into Strings with GETRANGE/SETRANGE/GETBIT/SETBIT — the sharded-HASH pattern is still official Redis guidance today, while the sharded-SET-for-unique-counting use case has been substantially superseded by HyperLogLog's fixed ~12KB approximate count.
Redis Pub/Sub is a live broadcast built on SUBSCRIBE/PUBLISH/PSUBSCRIBE with no storage layer underneath: a message published to a channel with zero subscribers is lost forever, at-most-once, with no queue, no persistence, and no replay for a reconnecting client, and a subscribed connection is dedicated to receiving pushed messages until every channel and pattern is unsubscribed (RESP2) or RESP3 lifts that restriction; Redis 7.0's Sharded Pub/Sub (SSUBSCRIBE/SPUBLISH) scales the broadcast itself across a cluster by hashing shard channels to the same 16384 hash slots as keys, without changing any of those delivery guarantees.
MULTI/EXEC gives Redis a serialized, uninterrupted run of a queued command sequence, not a SQL-style rollback-on-error transaction — a command that fails during EXEC (e.g. LPOP against a string) does not abort the ones after it, so the app, not Redis, has to decide up front (via DISCARD) whether to run at all; WATCH layers optimistic-locking check-and-set on top, aborting EXEC if a watched key changed since the watch, while pipelines solve an entirely different problem — batching commands to cut network round trips, with no atomicity guarantee of their own — and the two are orthogonal: a pipeline can run without MULTI/EXEC, and MULTI/EXEC is itself always sent as a pipeline under the hood.
Redis Streams (5.0, 2018) are a persisted, append-only log with time-ordered entry IDs, read via XADD/XRANGE/XREAD — but the real payoff is consumer groups (XGROUP, XREADGROUP, XACK, XPENDING, XCLAIM/XAUTOCLAIM), which give multiple cooperating consumers per-message acknowledgment and crash recovery via a Pending Entries List, an at-least-once guarantee neither Pub/Sub's fire-and-forget fan-out nor a plain List-based queue provides natively.
Sets, Sorted Sets, Bitmaps, and HyperLogLog are four distinct data structures, not just more commands: Sets give O(1) unique membership with real set algebra (SINTER/SUNION/SDIFF); Sorted Sets keep a skip-list ordering by score that doubles as both a ranked leaderboard and a range index; Bitmaps repurpose a String as one bit per integer ID for compact boolean analytics; and HyperLogLog trades exactness for a fixed ~12 KB cardinality estimate regardless of scale.
Redis Strings, Lists, and Hashes are three distinct structures, not variations on key-value: Strings hold bytes/integers/floats with atomic SET/GET/INCR and a 512 MB cap; Lists are true linked lists giving O(1) push/pop at either end via LPUSH/RPUSH/LPOP/RPOP/LRANGE (O(N) for anything else) and power queues via BRPOP/RPOPLPUSH; Hashes map String fields to String values via HSET/HGET/HGETALL/HINCRBY, collapsing related fields (an Instagram case study cut 21 GB to roughly 5 GB moving from per-field String keys to Hashes) — and since Redis 7.0 the compact internal encoding both books call ziplist is renamed listpack.
Redis in Action builds two ad hoc task-queue patterns straight from core data types: a FIFO queue on a LIST (RPUSH/BLPOP, with multiple lists giving cheap priority lanes) and a delayed/scheduled queue on a ZSET scored by execution timestamp, manually polled since ZSETs have no blocking pop. Both remain structurally current per Redis's own job-queue docs, but the plain BLPOP consumer this book section uses has no crash recovery for a worker that dies mid-job — the gap Streams' consumer groups (and production libraries like Sidekiq, BullMQ, and Redisson) close natively.
The Redis patterns that show up in nearly every production web app: a HASH plus a scored ZSET for login/session and shopping-cart storage, cache-aside with SET...EX for rendered pages and a per-row refresh schedule for database rows that can't tolerate a flat TTL, and INCR/sorted sets for view counters, time-series stats, and rate limiting — including how the modern sliding-window-log pattern extends the book's fixed-window counters.
Cassandra's opt-in, pluggable security model: PasswordAuthenticator and the default cassandra/cassandra superuser for authentication, CassandraAuthorizer and role-based GRANT/REVOKE for authorization, and TLS via server_encryption_options and client_encryption_options for node-to-node and client-to-node encryption in transit — plus why SSTable-level encryption at rest still isn't in open-source Cassandra even at 5.0.
MongoDB's older, broader security layer beneath field-level encryption: SCRAM and x.509 certificate authentication (with a full CA-to-cluster tutorial from the book), role-based authorization from built-in roles like readWrite and dbOwner up to root, and TLS for encryption in transit — plus why Atlas enforces TLS by default while self-managed MongoDB still ships with it off.
Redis's only pre-2020 security tools were a single shared requirepass password, rename-command obscurity, network-level firewalling, and stunnel-wrapped TLS since Redis had none natively — this concept covers why that was inadequate and walks through the real fix: Redis 6.0's ACL system (named users, per-command/category rules, per-key-pattern and per-channel access, selectors) and built-in TLS (tls-port, mutual-TLS-by-default, cluster/replication encryption) that replaced both gaps.
Redis shipped under permissive BSD 3-Clause from 2009 through 2023, then Redis Inc. relicensed it to dual RSALv2/SSPLv1 in March 2024 over cloud providers monetizing Redis without contributing back, prompting the Linux Foundation to launch the BSD-licensed, multi-vendor-governed Valkey fork (backed by AWS, Google Cloud, and Oracle) just eight days later; in May 2025 Redis Inc. added the OSI-approved AGPLv3 as a third licensing option for Redis 8, leaving today's Redis-vs-Valkey choice a genuine trade-off between Redis's first-to-ship features and Valkey's permissive license and multi-vendor governance.
Both source books teach ad hoc Lua scripting via EVAL/EVALSHA and a hand-rolled SCRIPT LOAD cache-miss wrapper because that was the only scripting Redis had in 2013-2015; Redis Functions (Redis 7.0, 2022) replaces it with named, versioned libraries loaded via FUNCTION LOAD and invoked with FCALL/FCALL_RO, which Redis itself persists to the AOF and replicates instead of leaving script durability to each client application.
Redis 8 (May 2025) folded five formerly-separate, separately-licensed Redis Stack modules directly into core Redis for free: native JSON documents, full-text/vector search via the Redis Query Engine (FT.CREATE/FT.SEARCH), Time Series (TS.*), and five probabilistic structures beyond HyperLogLog (Bloom, Cuckoo, Count-min sketch, Top-K, t-digest) — plus a brand-new native Vector Sets data type (VADD/VSIM) for embedding similarity search, Redis's answer to the same AI/RAG need MongoDB addresses with Atlas Vector Search.
Goes past the one-line summary in redis-partitioning-and-cluster-fundamentals to trace Sentinel's real mechanism: quorum only detects a failed master (SDOWN then ODOWN), a separate majority vote among all Sentinel processes authorizes the failover, and directives like down-after-milliseconds, failover-timeout, and parallel-syncs govern each stage of that sequence — plus the Sentinel-aware client discovery pattern, and current Redis docs' own positioning of Sentinel as high availability specifically for non-clustered Redis, since Redis Cluster provides its own built-in failover once sharding is already in play.
Redis started with no native distribution story, so the book walks the client-side partitioning schemes people used instead — range, hash, presharding, consistent hashing, and hash tagging — before showing the two purpose-built systems that replaced them: Redis Cluster, which shards data across 16384 hash slots via HASH_SLOT = CRC16(key) mod 16384 (with {tag} hash tags forcing related keys into the same slot for multi-key ops), and Redis Sentinel, which handles quorum-based automatic failover on an unsharded master/replica pair without distributing any data.
Sizing a connection pool from RAM/CPU/disk first principles, routing writes/reads through HAProxy frontend-backend pairs with leastconn balancing, and multiplexing many clients onto few real PostgreSQL connections with PgBouncer's session/transaction/statement pool_mode trade-offs.
OS-level sysctl and /sys tuning that keeps PostgreSQL online under stress rather than just faster — byte-based vm.dirty_background_bytes/vm.dirty_bytes to avoid a huge emergency write flush, vm.swappiness=1 to keep backends out of swap, and disabling Transparent Huge Pages, whose khugepaged defragmentation can stall PostgreSQL for tens of seconds — a recommendation current PostgreSQL documentation now states explicitly.
Why a plain CREATE INDEX locks a table against every write for the whole build, how CREATE INDEX CONCURRENTLY trades a slower, two-scan build for zero write blocking, its hard restrictions (no transaction block, one build per table, OLTP lock-wait pileups), and how today's official docs cover a real gap in the book's recipe: what to do when a concurrent build fails and leaves an INVALID index behind, plus REINDEX CONCURRENTLY — already available in the book's own target version, PostgreSQL 12, but never mentioned in the recipe.
Why a cold OS/shared-buffer cache after a crash or restart can make a technically 'up' PostgreSQL server unusably slow, how to snapshot and reload the most active tables and indexes with pgFincore or pg_prewarm, and how pg_prewarm's autoprewarm background worker — already available well before PostgreSQL 12 — automates the entire snapshot/restore recipe without a hand-built active_snap table.
How the pg_settings.context column tells you whether changing a setting needs a full postmaster restart, a SIGHUP reload, or a superuser/user-level SET, how IS DISTINCT FROM safely lists settings changed from their boot_val default, and how PostgreSQL 15's GRANT SET/ALTER SYSTEM ON PARAMETER now lets admins delegate specific superuser-context settings to non-superuser roles without a full superuser grant.
How to set a defensible starting postgresql.conf for a highly available server — connection/memory sizing, WAL and checkpoint tuning, replication readiness, planner cost estimates, and logging — and which of the book's specific settings were renamed, removed, or became the default since 2020 (wal_level's hot_standby→replica, wal_keep_segments→wal_keep_size, checkpoint_completion_target's default now 0.9).
The tuning levers that sit on top of Cassandra's LSM-tree storage engine: key/row/chunk/counter caches and when each one actually helps, compaction throughput throttling and concurrency for when SSTable backlogs build up, and JVM heap/GC settings, with a book-vs-today check confirming Cassandra 5.0 now ships G1GC as the default (not the book's CMS) since CMS was removed entirely in JDK 14.
Covers the operator-run maintenance that keeps a Cassandra cluster healthy: anti-entropy repair via nodetool repair (full vs incremental, sequential vs parallel, primary-range and subrange repair, Merkle trees and overstreaming), the node lifecycle (adding nodes and data centers, diagnosing and replacing failed nodes, decommission/removenode/assassinate in order of preference), and backup/restore via snapshots and incremental backups (hard-linked SSTables, sstableloader, schema exclusion caveat).
How to actually look inside a running Cassandra node: the JMX/MBean layer (StorageServiceMBean, CompactionManagerMBean, GossiperMBean) that exposes internal state, the nodetool commands built on it (status, info, tpstats, compactionstats, tablestats), and the virtual tables feature (system_views, system_virtual_schema) that lets you query that same state with plain CQL — plus Dropwizard metrics, log configuration, and full query logging.
What to actually watch on a running MongoDB deployment (mongostat/mongotop/serverStatus, memory and page faults, working set sizing, replication lag and oplog length, WiredTiger's ticketing system as lock-percentage's successor) and how to get data out safely (mongodump/mongorestore vs filesystem/volume snapshots, plus the sharded-cluster caveats), with book-vs-today notes on Atlas displacing Ops Manager as the default path and Cloud Manager's 2024 deprecation of older-version support.
A push-based monitoring pipeline where Telegraf polls PostgreSQL (including arbitrary custom SQL via the extensible plugin) and pushes to InfluxDB for storage, with Grafana rendering dashboards — replication slot lag, XID wraparound age, and session-state counts as the metrics worth collecting.
Reading live connection state via pg_stat_activity, finding high-frequency-not-just-slow queries via pg_stat_statements, and tracing blocker-to-blocked chains with pg_locks + pg_blocking_pids() — plus the predefined pg_monitor role that replaces the book's manual SECURITY DEFINER wrapper functions.
The escalation path for evicting a misbehaving PostgreSQL client — from querying pg_stat_activity for long-running or idle-in-transaction sessions, through the gentler pg_cancel_backend() and the forceful pg_terminate_backend(), down to a network-level tcpkill when the client refuses to acknowledge termination — and how PostgreSQL 14's optional timeout argument on pg_terminate_backend() now lets the function itself wait for confirmation instead of requiring a manual re-check query.
How to rank tables and indexes by size (pg_total_relation_size), by write/read activity (pg_stat_user_tables, pg_stat_user_indexes), and by bloat (the pgstattuple extension) — and how PostgreSQL 16's last_seq_scan/last_idx_scan columns solve the book's own complaint that activity counters have no associated timestamp.
Cross-server queries via postgres_fdw (CREATE SERVER, user mappings, foreign tables, IMPORT FOREIGN SCHEMA), building a hand-rolled sharding scheme with a bit-packed unique-ID generator, and why Citus is the more pragmatic path to real horizontal sharding today.
Patroni's DCS-based architecture (etcd/ZooKeeper/Consul for consensus, HAProxy for routing, every node running the same reconciliation loop) for fully automated PostgreSQL failover, patronictl switchover for zero-downtime maintenance, and why Kubernetes operators (CloudNativePG, Zalando postgres-operator) have become a common deployment path today.
How repmgr layers cluster metadata, one-command standby cloning, witness-based quorum, and repmgrd-driven automatic failover on top of native streaming replication — SSH/sudo trust requirements, promote_command/follow_command, and how it compares to Patroni for new deployments today.
How Barman (centralized fleet backup via SSH/streaming, near-zero-RPO capability) and pgBackRest (self-contained, compressed, incremental/differential backups) go beyond pg_basebackup with catalogs, retention policies, and PITR — plus native cloud storage support and the PostgreSQL 15 removal of exclusive backup mode.
Row-level replication built into PostgreSQL since v10 — CREATE PUBLICATION/CREATE SUBSCRIPTION with no third-party tools, why DDL and sequences don't replicate, the replica-identity requirement for UPDATE/DELETE, and what PostgreSQL 15-19 added (row/column filters, conflict logging, pg_createsubscriber).
How PostgreSQL builds log shipping, streaming replication, hot standby, replication slots, and synchronous replication all on top of the WAL — pg_basebackup/pg_receivewal, FIRST vs ANY synchronous_standby_names, hot_standby_feedback, and slot-based WAL retention safety valves.
Why silent CPU/RAM corruption can poison a synchronous primary and standby nearly simultaneously, how archiving WAL to a tertiary server with a deliberate hour-long cron/mtime delay buys time to detect it before it spreads, why the book's own recovery_min_apply_delay example doesn't actually delay by an hour, and why data checksums — not the book's vague 'monitors' — are the real detection mechanism this recipe depends on.
How to patch PostgreSQL on both nodes of a primary/replica pair without ever taking the database fully offline, by upgrading the idle replica first, cutting over to it as the new primary, then rebuilding the old primary as a fresh replica — plus why pg_rewind, already available at the book's own PostgreSQL 12 target, replaces the recipe's wasteful full pg_basebackup re-copy of the demoted node.
How to move a PostgreSQL database to new hardware with minimal downtime by building a streaming replica with pg_basebackup, waiting for it to catch up, then promoting it to primary — plus two gaps in the book's own PostgreSQL 12-era recipe: pg_stat_replication's sent_location/replay_location columns were already renamed to sent_lsn/replay_lsn back in PostgreSQL 10, and pg_basebackup's -R flag already automated the manual standby.signal/primary_conninfo setup the recipe walks through by hand.
How to translate 'who uses the database and what will they tolerate' into a concrete uptime target expressed in nines, and how the book's ad-hoc SLA checklist maps to today's formal SLI/SLO/SLA/error-budget vocabulary and published managed-Postgres SLA benchmarks (AWS RDS, Google Cloud SQL).
How multi-master PostgreSQL trades the failover/quorum problem for a write-conflict-avoidance problem, why mesh topology overhead grows as C = N*(N-1), and how the 2020 book's generic 'proprietary software' framing maps to today's EDB Postgres Distributed and the newly open-source pgEdge/Spock.
How fencing guarantees a demoted or isolated PostgreSQL primary genuinely can't accept writes anymore — STONITH (remote power-off) and SMITH (self-power-off on isolation) — and how Patroni's built-in watchdog now automates the SMITH half via a Linux softdog/hardware watchdog device instead of a hand-rolled isolation check.
How witness-node voting rules avoid tied elections during automated failover, and how connection indirection (DNS, VIP, connection multiplexer, or load balancer) keeps applications from needing reconfiguration when the primary changes — today typically HAProxy polling Patroni's own REST health-check API.
How to derive how many PostgreSQL nodes a highly available cluster needs (backup, replicas, witness) and where to place them across data centers, and how Patroni's DCS-based quorum has replaced the manually placed witness node.
How Recovery Point Objective (data-loss tolerance) and Recovery Time Objective (downtime tolerance) drive PostgreSQL architecture decisions, and how synchronous replication's quorum commit turns an RPO target into an enforceable guarantee.
Migrating to Cassandra is a re-modeling exercise, not a table-for-table port: the book's direct-translation patterns (entities to tables, join tables to mapped tables or collapsed UDTs, loose types to UDTs) still have to pass through the query-first methodology, plus the strangler pattern for adapting the application and dual-write/CDC for zero-downtime cutover. Integrating outward, Kafka Connect's DataStax sink connector streams events into Cassandra (with CDC-out remaining the harder, still-partially-solved direction), and the spark-cassandra-connector runs data-local analytics over Cassandra tables without a separate ETL pipeline — plus what changed since 2022 now that the Spark connector has moved to Apache governance and Debezium has largely closed the CDC-out gap the book left open.
Building a real Cassandra client with the Java driver: a single long-lived CqlSession built with the fluent builder, why PreparedStatement beats SimpleStatement for more than convenience (one-time preparation, token-aware routing, injection protection), the QueryBuilder and annotation-driven object mapper as higher-level alternatives, and async execution with CompletionStage — plus what changed since the book's 4.0-era coverage now that the driver is Apache-governed under org.apache.cassandra on the 4.19.x line.
Cassandra's data model built bottom-up from column to row to partition to table to keyspace to cluster, with the composite primary key (partition key plus clustering columns) that decides both node placement and on-disk order, per-column timestamps and TTL, and the full CQL type system from numeric and textual types through uuid/timeuuid, counters, the three collections, tuples, and frozen user-defined types.