Take a Break
5:00
Inhale…
Give your mind a break — no phone, no music, just idle time or a quick walk.
Apache Hive Bucketing Resolution, Relational Joins, Subquery Pipelines, Apache ZooKeeper, and Apache HBase
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
- Hive bucketing: concept, hash-modulo formula, and CLUSTERED BY DDL — covered in Lecture 7
- Hive architecture, metastore management, and table DDL/DML operations — covered in Lecture 7
- The MapReduce programming model and key-value join mechanics — covered in Lecture 4
- The four core NoSQL data models and the ACID vs BASE contrast — covered in Lecture 2
- HDFS NameNode metadata (fsimage and editlog) and the Hadoop ecosystem — covered in Lecture 3
8.1 Hive Bucketing Data Types and Precision Resolution
8.1.1 Floating-Point Precision Defects in Bucketing Keys
Why should a tiny detail like a column's data type change where your data lands? Two rows with weights 2 and 6 — numbers that divide into 3 buckets with different remainders — ended up sitting in the same physical bucket file. The culprit was not the data, but the column's data type. This section shows why FLOAT bucketing keys quietly misplace rows, and how one schema change fixes the whole table.
In Apache Hive, bucketing organizes table data into discrete physical files based on the hash value of a designated bucketing column. Think of buckets as labeled drawers: every record gets a drawer number computed from its bucketing-column value. The hashing algorithm applies a modulo operation over the total number of defined buckets \(N\):
\[ \text{bucket\_id} = \text{hash}(\text{column\_value}) \pmod{N} \]
where:
- \(\text{hash}(\cdot)\) maps the column value to a large integer,
- \(N\) is the number of buckets defined in the table,
- the result is an index in the range \(0, 1, \dots, N-1\), meaning one of the \(N\) bucket files on HDFS.
For an integer column, the hash of the value is the value itself, so for an INT column the formula effectively becomes \(\text{bucket\_id} = \text{column\_value} \pmod{N}\). That is the clean case. The trouble starts when the bucketing column is a floating-point type.
Bucketing = hashing + modulo. The bucket file that stores a row is chosen by computing \(\text{bucket\_id} = \text{hash}(\text{column\_value}) \pmod{N}\). For an INT column this reduces to \(\text{value} \pmod{N}\), which is exact. For a FLOAT or DOUBLE column the same formula runs on a binary floating-point value, and that is where rows start landing in unexpected files.
A significant edge-case anomaly occurs when floating-point data types (FLOAT or DOUBLE) are selected as bucketing keys. In a previous session, a table named clustered_metals was created with a weight column specified as a FLOAT data type and partitioned into \(N = 3\) buckets.
When floating-point values undergo modulo arithmetic, IEEE 754 floating-point representations introduce internal binary rounding artifacts. For instance, an integer weight of \(3\) stored as a float may be represented internally as \(2.9999999\dots\). Evaluating \(2.9999999 \pmod 3\) produces a remainder near \(2.999\dots\), placing the record into bucket index 2 instead of bucket index 0.
A note on the exact mechanism. Strictly, IEEE 754 can store the number 3.0 exactly. The deeper cause of the misplacement is that Hive hashes a FLOAT value through its raw 32-bit IEEE 754 bit pattern rather than through its numeric value. The modulo then runs on that huge bit pattern, so the result looks unrelated to the remainder you would compute by hand. Either way the conclusion holds: floats make unreliable bucketing keys, and converting the column to INT restores exact placement.
During raw bucket file inspection, this precision artifact caused records with incompatible integer remainders to cluster into identical physical files. For example, records with weights \(2\) (\(2 \pmod 3 = 2\)) and \(6\) (\(6 \pmod 3 = 0\)) were improperly co-located inside bucket file 2, whereas weights \(4\) and \(7\) (\(4 \pmod 3 = 1\), \(7 \pmod 3 = 1\)) resided correctly in bucket file 1.
The fix: change the data type.
To resolve this precision defect, the table schema must be redefined to convert the bucketing column weight from FLOAT to INT:
CREATE TABLE clustered_metals (
shop_id INT,
metal STRING,
short_form STRING,
weight INT
)
CLUSTERED BY (weight) INTO 3 BUCKETS;
INSERT INTO TABLE clustered_metals
SELECT shop_id, metal, short_form, weight
FROM metals_temp;
After updating the column data type to INT and re-ingesting raw records from metals_temp, Hive distributes the records into three clean HDFS files inside the table directory.
Worked example — where each weight lands with INT bucketing. Suppose the table holds eight sample records with weights 1 through 8 and \(N = 3\) buckets. With an INT column, \(\text{bucket\_id} = \text{weight} \pmod 3\):
| Weight \(w\) | \(w \pmod 3\) | Bucket file |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 0 | 0 |
| 4 | 1 | 1 |
| 5 | 2 | 2 |
| 6 | 0 | 0 |
| 7 | 1 | 1 |
| 8 | 2 | 2 |
So the three HDFS files contain:
- Bucket File 0 (
weight mod 3 == 0): rows with weights \(3, 6\), - Bucket File 1 (
weight mod 3 == 1): rows with weights \(1, 4, 7\), - Bucket File 2 (
weight mod 3 == 2): rows with weights \(2, 5, 8\).
Sense-check: every weight lands in exactly one file, each file holds weights with the same remainder, and all three remainders \(\{0, 1, 2\}\) appear. Before the fix, weight 6 had been drifting into bucket file 2 because of the float precision artifact, and weight 3 as well (since \(2.9999999 \pmod 3 \approx 2.999\)). After the INT fix, \(6 \pmod 3 = 0\) and \(3 \pmod 3 = 0\), so both return to bucket file 0 where they belong.
Q: Why did weights 2 and 6 end up in the same bucket file during floating-point bucketing? A: When weight was defined as a FLOAT, internal floating-point representation errors (such as storing 3 as \(2.9999999\)) altered the output of the modulo operation \(\text{weight} \pmod 3\). This caused precision-adjusted remainders to map to unexpected bucket indices. Switching the column type from FLOAT to INT ensures exact integer division and places weights 2 (\(2 \pmod 3 = 2\)) and 6 (\(6 \pmod 3 = 0\)) into their correct respective bucket files.
Assumptions & scope: Bucketing by a column behaves cleanly only when the bucketing key is an exact type. INT, BIGINT, and STRING produce stable, predictable hashes. FLOAT and DOUBLE produce the artifacts described here because the hash runs on the binary bit pattern. If a value is truly fractional (for example a weight of 2.5 grams), converting to INT is not the right fix — the bucket key should be a naturally integer-valued attribute such as an ID or a count, or the value should be scaled into an integer (for example grams to milligrams) before bucketing.
8.1.2 Query Optimization and Full Table Scan Avoidance
Bucketing provides direct performance advantages by avoiding full table scans during filtered query execution. When a query contains a WHERE clause on a bucketed column, Hive calculates the target bucket index prior to reading data from storage.
Consider a query filtering records by weight:
SELECT * FROM clustered_metals WHERE weight = 1;
Instead of scanning all underlying HDFS data blocks, Hive evaluates \(1 \pmod 3 = 1\). The execution engine reads only bucket file 1 and completely skips bucket files 0 and 2.
Worked example — skipping buckets at query time. With the eight-row clustered_metals table bucketed into 3 files:
- The query asks for
WHERE weight = 1. - Hive computes \(\text{bucket\_id} = 1 \pmod 3 = 1\).
- The engine opens only the HDFS file for bucket 1 (weights \(1, 4, 7\)) and reads it.
- Bucket files 0 and 2 are never opened.
Files read: 1 of 3. Files skipped: 2 of 3. Sense-check: weight 1 lives in bucket 1 from the distribution table above, so the plan finds it without touching the other files.
Pitfalls:
- Small-data illusion: With 8 sample records the difference is invisible — reading any file takes milliseconds. The payoff appears only at scale, where skipping two out of three multi-gigabyte files is real saved disk I/O.
- Bucketing is not partitioning: Partitioning splits data into directories by a coarse column (such as
metaltype); bucketing splits a partition into files by a high-cardinality column (such asshop_idorweight). They can be combined on one table without restriction. - Over-optimization backfires: Millions of tiny bucket files inflate the NameNode's memory footprint, because HDFS keeps one metadata entry per block. Excessive partitions and buckets can slow the cluster down more than they speed queries up.
Real-world: While bucketing 8 sample records across weights 1 through 8 shows negligible performance differences, bucketing becomes essential when processing multi-terabyte datasets containing high-cardinality columns (such as user IDs, phone numbers, or transaction timestamps). At enterprise scale, skipping unneeded bucket files reduces disk I/O and query execution time significantly. Enterprise data warehouses combine partitioning and bucketing over ORC/Parquet files to accelerate ad-hoc queries on petabyte-scale data lakes, skipping unneeded HDFS blocks during WHERE evaluation.
Recap + bridge: Bucketing assigns each row to a file via \(\text{hash}(\text{value}) \pmod N\). On a FLOAT key, binary rounding artifacts scatter rows into wrong files; converting the key to INT restores exact placement and lets WHERE queries read only the one relevant bucket file. Buckets give cheap lookup on a single table — but combining data across tables is a join, which is exactly what comes next.
8.2 Relational Joins in Apache Hive
8.2.1 Relational Normalization and MapReduce Join Execution Mechanics
How does a JOIN that feels instant in SQL actually run across a cluster? When you type JOIN ... ON, the engine first builds every possible pairing of rows — even the pairs you will throw away. On a 3-row table and a 4-row table that is 12 pairs; on two 1-billion-row tables it is a number too large to write out. This section shows the two-step relational machinery underneath, then how Hive expresses the same operation in HQL.
Database normalization decomposes flat tables into distinct entities to eliminate redundant data storage and prevent update anomalies. For example, maintaining employee names and salaries across separate salary and names tables prevents duplicating employee details when salary updates occur:
salarytable:id,salary_amount(e.g.,1 -> 10000,2 -> 10500,3 -> 15000)namestable:id,employee_name(e.g.,1 -> John,2 -> Clark,3 -> Mark,4 -> Alex)
Why normalize at all? Normalization (splitting one flat table into separate entity tables) removes redundancy: the same employee's name is stored once, not repeated on every salary row. The cost is that answering a question like "who earns 10000?" now requires reading two tables and matching rows on the shared id key. A relational join is exactly that matching step — it merges records across common keys to rebuild the combined view.
When business logic requires a combined view of normalized tables, a relational join merges records across common keys. Under the hood, distributed join processing in MapReduce follows a two-step relational transformation:
Step 1 — Cartesian Cross Product. The execution engine pairs every record from Table A with every record from Table B. For a table with \(M\) rows and a table with \(N\) rows, the cross product generates \(M \times N\) intermediate tuples:
\[ |T_A \times T_B| = |T_A| \times |T_B| = 3 \times 4 = 12 \text{ intermediate tuples} \]
where \(|T_A|\) and \(|T_B|\) are the row counts of the two tables, and \(\times\) denotes the Cartesian product. The raw intermediate output pairs every salary entry with every name entry.
Step 2 — Predicate Verification and Filtering. The join operator evaluates the equality condition:
\[ \text{TableA.id} == \text{TableB.id} \]
Tuples failing this predicate match are discarded. Only tuples where TableA.id equals TableB.id are retained in the final output relation.
Worked example — building and filtering the cross product. Take salary (3 rows) as \(T_A\) and names (4 rows) as \(T_B\). The cross product has \(|T_A| \times |T_B| = 3 \times 4 = 12\) tuples:
| # | salary tuple | names tuple | id match? | kept? |
|---|---|---|---|---|
| 1 | (1, 10000) | (1, John) | 1 == 1 ✓ | kept |
| 2 | (1, 10000) | (2, Clark) | 1 == 2 ✗ | dropped |
| 3 | (1, 10000) | (3, Mark) | 1 == 3 ✗ | dropped |
| 4 | (1, 10000) | (4, Alex) | 1 == 4 ✗ | dropped |
| 5 | (2, 10500) | (1, John) | 2 == 1 ✗ | dropped |
| 6 | (2, 10500) | (2, Clark) | 2 == 2 ✓ | kept |
| 7 | (2, 10500) | (3, Mark) | 2 == 3 ✗ | dropped |
| 8 | (2, 10500) | (4, Alex) | 2 == 4 ✗ | dropped |
| 9 | (3, 15000) | (1, John) | 3 == 1 ✗ | dropped |
| 10 | (3, 15000) | (2, Clark) | 3 == 2 ✗ | dropped |
| 11 | (3, 15000) | (3, Mark) | 3 == 3 ✓ | kept |
| 12 | (3, 15000) | (4, Alex) | 3 == 4 ✗ | dropped |
The 12 raw pairs shrink to 3 surviving rows: (1, 10000, John), (2, 10500, Clark), (3, 15000, Mark). Final output relation: 3 rows. Sense-check: the inner join keeps exactly the ids present in both tables — Alex (id 4) has no salary row, so Alex disappears from the result.
Assumptions & scope: The two-step view (cross product, then filter) is the relational definition of a join. Real engines never materialize the full cross product for large tables — they use hash joins, merge joins, or (in Hive) map-side joins to avoid it. The definition stays correct, though: an inner join's output is always the subset of \(T_A \times T_B\) whose rows satisfy the equality predicate. Hive only supports equijoins — the join predicate must use equality (=), not <, >, or BETWEEN.
Exam note: Students are expected to implement distributed join algorithms manually in Java MapReduce for course assignments, handling key-value emission in Mappers and cross-table matching in Reducers. The two-step relational model above is exactly what the assignment recreates by hand: the mapper pairs by key, the reducer filters by predicate.
8.2.2 Hive HQL Join Syntax and Execution
In Hive QL, explicit table qualifications (table_name.column_name) resolve column ambiguities when joining multiple tables that share identical column identifiers.
Consider two normalized Hive tables:
metals: Contains transaction records with columnsshop_id,metal,short, andweight.metal_shops: Contains lookup metadata with columnsshop_idandshop_name.
To project transaction data alongside shop names, the query must specify fully qualified column names in both the SELECT projection list and the ON join predicate:
SELECT metals.shop_id, metals.metal, metals.short, metal_shops.shop_name, metals.weight
FROM metals
JOIN metal_shops ON metals.shop_id = metal_shops.shop_id;
Pitfalls:
- Ambiguous columns: Both tables have a
shop_idcolumn. Writing bareshop_idin theSELECTorONclause leaves Hive unable to tell which table you mean — qualify every shared column astable.column. - Equijoin only: The
ONpredicate must use=. Hive does not support inequality joins the way some SQL databases do. - Not every row survives: An inner join keeps only matching rows. A row in
metalswhoseshop_idhas no row inmetal_shops(or the reverse) silently disappears — use an outer join when unmatched rows must be kept.
When this HQL query executes:
- Hive compiles the declarative SQL statement into a MapReduce job plan and submits it to YARN.
- The YARN ResourceManager allocates resources and launches an ApplicationMaster container on a cluster node.
- Mappers tag input splits from
metalsandmetal_shopswith join key values (shop_id). - Reducers shuffle matching keys together, execute the join verification step, and output the joined dataset containing
shop_id,metal,short,shop_name, andweight.
Inner vs outer joins at a glance. The inner join keeps only matching rows. The textbook's sales/things example shows the contrast: a LEFT OUTER JOIN keeps every row of the left table and fills the right table's columns with NULL when no match exists; a RIGHT OUTER JOIN does the reverse; a FULL OUTER JOIN keeps both sides. Pick the inner join when you only want matched pairs, and an outer join when unmatched rows must survive (for example, finding employees with no salary record).
Real-world: Relational joins are the backbone of star-schema data warehouses, where a large fact table (transactions) joins to small dimension tables (shops, customers, dates) on key columns. Because Hive only supports equijoins, warehouse schemas are designed so that every join is an equality join on a key — a design rule that carries straight from Hive into modern SQL engines.
Recap + bridge: A join is a cross product followed by a predicate filter; Hive executes it as a MapReduce job where mappers tag rows by join key and reducers match them. HQL expresses this with JOIN ... ON table.column = table.column. With single-table bucketing and two-table joins in place, the next step is nesting one query inside another — subqueries and the multi-stage pipelines they trigger.
8.3 Hive Subqueries and Multi-Stage MapReduce Pipeline Execution
8.3.1 Subqueries and Derived Table Aggregations
Can a query's result be used as a table inside another query? Yes — that is exactly what a subquery does. Hive lets you wrap an inner SELECT in parentheses, give it an alias, and then run an outer query against it as if it were a stored table. The catch: get the parentheses or the alias wrong, and Hive throws a parser error at the word sum.
Hive supports subqueries (also called inner queries) in the FROM clause. A subquery wraps an intermediate result set into a temporary named table alias, allowing outer queries to perform subsequent filtering or aggregation operations.
Subquery = a query inside a query. The inner SELECT builds an intermediate result set; the outer SELECT treats that set like a table. In Hive, a FROM subquery must be (1) wrapped in parentheses and (2) given an explicit alias (a temporary name), for example temp_table. The outer query then references the inner query's columns by their names.
Suppose a business requirement calls for calculating the total weight of all metals purchased from each shop, aggregating across individual metal transactions. The inner query joins metals and metal_shops, and the outer query applies SUM(weight) grouped by shop_name:
SELECT shop_name, SUM(weight)
FROM (
SELECT metals.shop_id, metals.metal, metals.short, metal_shops.shop_name, metals.weight
FROM metals
JOIN metal_shops ON metals.shop_id = metal_shops.shop_id
) temp_table
GROUP BY shop_name;
Q: What causes the error cannot recognize input near 'sum' when executing a subquery in Hive? A: This error occurs when syntax elements are misplaced or parentheses are imbalanced. Every subquery in a Hive FROM clause must be enclosed in parentheses and given an explicit alias (such as temp_table). Omitting the alias or placing parentheses incorrectly prevents the HQL parser from recognizing the derived table — so the outer query's SUM(...) is no longer attached to any valid table and the parser fails at the word sum.
Scope of subquery support in Hive: Hive supports subqueries in the FROM clause (derived tables, as above) and uncorrelated subqueries in the WHERE clause via IN or EXISTS. Correlated subqueries — where the inner query references a column of the outer query — are not supported. A FROM subquery's columns must also have unique names so the outer query can refer to them without ambiguity.
8.3.2 Multi-Stage MapReduce Execution Pipeline
A query that combines a join with an aggregation does not run as one MapReduce job. Hive breaks it into a pipeline of stages, where each stage is its own MapReduce job.
Purpose of the multi-stage pipeline. One MapReduce job performs one transformation. A join is a transformation (combine rows on a key); a group-by aggregation is another (collapse rows per key). A query that needs both — like "total weight per shop" — therefore becomes two chained MapReduce jobs, where the second job reads the first job's output as its input.
Inputs and outputs of the pipeline:
- Input: raw data splits from
metalsandmetal_shops. - Intermediate output (after Stage 1): the joined dataset — rows of
shop_id, metal, short, shop_name, weightwritten to temporary HDFS files. - Final output (after Stage 2): one row per distinct
shop_namewith itsSUM(weight).
The two stages, step by step:
- Stage 1 (MapReduce Job 1 — Relational Join):
- Mapper 1: Reads raw data splits from
metalsandmetal_shops, emitting tuples keyed byshop_id. - Reducer 1: Joins matching records on
shop_idand writes the intermediate joined dataset to temporary HDFS files.
- Mapper 1: Reads raw data splits from
- Stage 2 (MapReduce Job 2 — Group By Aggregation):
- Mapper 2: Reads the intermediate join output generated by Reducer 1 and extracts
shop_nameas the grouping key andweightas the value. - Reducer 2: Computes
SUM(weight)for each distinctshop_namekey and writes final aggregated results to HDFS.
- Mapper 2: Reads the intermediate join output generated by Reducer 1 and extracts
Worked example — tracing the two-stage pipeline. The query asks for the total weight of all metals purchased from each shop.
Stage 1 (join): Mapper 1 reads the metals rows and the metal_shops rows. Every emitted tuple is keyed by shop_id, so rows from both tables that share a shop_id land in the same reducer. Reducer 1 pairs them on shop_id and writes the joined rows — one row per metal transaction, now carrying shop_name — to a temporary HDFS location. Suppose the joined result contains rows like (1, gold, B Modulus, 844), (2, silver, B Modulus, ...), (3, platinum, Joyalukkas, 913), and further rows for Lalitha Jewellery.
Stage 2 (aggregation): Mapper 2 reads those joined rows and emits (shop_name, weight) pairs; Reducer 2 adds the weights per shop:
| shop_name | weight contributions | SUM(weight) |
|---|---|---|
| B Modulus | 844 + ... | 844 grams |
| Joyalukkas | 913 + ... | 913 grams |
| Lalitha Jewellery | ... | aggregated weight |
Sense-check: the join stage must run first because shop_name exists only after metal_shops is joined in; the aggregation stage then collapses all transactions of a shop into one row. That ordering is why Hive emits two chained MapReduce jobs.
Pitfalls:
- Missing alias: A
FROMsubquery without an alias is a syntax error in Hive — the alias is mandatory. - Correlated subquery trap: Writing an inner query that references an outer column fails in Hive; use a join or restructure the query instead.
- Each stage costs a full HDFS round-trip: Every MapReduce job boundary writes its intermediate output to HDFS and reads it back. Pipelines with many stages are where Hive's disk-based execution becomes slow — engines like Tez or Spark collapse these stages into one in-memory DAG.
Real-world: The "join then aggregate" pattern is the shape of most reporting queries — total sales per region, total weight per shop, average spend per customer. Because each stage is a separate MapReduce job, production warehouses watch stage counts via EXPLAIN, and modern engines (Tez, Spark SQL) pipeline the same logic in memory to avoid repeated HDFS round-trips.
Recap + bridge: A subquery wraps an inner SELECT into a parenthesized, aliased derived table; the outer query aggregates over it. The join-plus-aggregate query becomes two chained MapReduce jobs — join first, then group-by. Subqueries give us multi-step SQL within one statement; next we move off the SQL side entirely and look at coordinating a whole cluster with Apache ZooKeeper.
8.4 Distributed Coordination with Apache ZooKeeper
8.4.1 Coordination Challenges in Distributed Clusters
Who decides which NameNode is the boss? In a distributed cluster, machines crash, networks partition, and two servers can each believe they are the leader. Left unchecked, a "split-brain" — two masters both accepting writes — corrupts the whole system. Apache ZooKeeper exists to answer the coordination questions that no single machine can answer alone: who leads, who is alive, and where is the service?
Distributed systems comprising active-passive master architectures and worker nodes encounter several critical operational challenges:
- Active/Passive Master Coordination: In high-availability HDFS setups, a primary NameNode operates as the active master while a secondary NameNode remains passive. The passive node syncs state from
fsimageandeditlogfiles. If the active master crashes, an external coordinator must detect the failure, prevent split-brain scenarios (where both masters claim leadership), and promote the passive node to active master. - Task Handoff and State Recovery: In YARN, an ApplicationMaster manages container execution across slave nodes. If an ApplicationMaster crashes mid-job, the replacement node must recover the exact state of completed and pending tasks executed by its predecessor.
- Dynamic Service Discovery and Centralized Configuration: Cluster services (such as Hive Metastore connections to MySQL databases) require centralized configuration management. When service endpoints change IP addresses dynamically across nodes, dependent services must discover the new endpoints automatically without hardcoded configuration edits.
Why the name? Apache ZooKeeper was created by Yahoo to solve coordination problems across distributed systems. It was named "ZooKeeper" because Hadoop ecosystem components are named after animals (Hadoop elephant, Hive, Pig), making ZooKeeper the service that manages the "zoo."
8.4.2 ZooKeeper Ensemble Architecture, Quorum Math, and Consistency Guarantees
ZooKeeper operates as a small, highly redundant cluster of nodes called an ensemble.
+-------------------------------------------------------------+
| ZooKeeper Ensemble |
| |
| +------------------+ +------------------+ |
| | Follower Node | | Follower Node | |
| +--------+---------+ +--------+---------+ |
| ^ ^ |
| | ZAB (Atomic Broadcast) | |
| +---------------+----------------+ |
| | |
| +--------+---------+ |
| | Leader Node | |
| +--------+---------+ |
+----------------------------^--------------------------------+
| Write Requests
+--------+---------+
| ZooKeeper Client |
+------------------+
- Leader-Follower Roles: One node in the ensemble is elected as the Leader, while all remaining nodes function as Followers. All write requests from application clients are directed to the Leader. Read requests can be served by any Leader or Follower node.
- Quorum Mathematics and Odd Node Rule: An ensemble must contain an odd number of servers (\(n = 3, 5, 7\)) to ensure clear majority voting during leader elections and quorum approvals. The minimum quorum size \(Q\) required to confirm writes is:
\[ Q = \left\lfloor \frac{n}{2} \right\rfloor + 1 \]
where \(n\) is the total number of servers in the ensemble, \(\lfloor \cdot \rfloor\) is the floor function (round down to the nearest integer), and \(Q\) is the smallest integer strictly greater than half of \(n\) — a strict majority.
Worked example — quorum math for 3-node and 5-node ensembles.
For \(n = 3\): \[ Q = \left\lfloor \frac{3}{2} \right\rfloor + 1 = \lfloor 1.5 \rfloor + 1 = 1 + 1 = 2 \] So 2 of the 3 nodes must confirm a write.
For \(n = 5\): \[ Q = \left\lfloor \frac{5}{2} \right\rfloor + 1 = \lfloor 2.5 \rfloor + 1 = 2 + 1 = 3 \] So 3 of the 5 nodes must confirm a write.
Failure tolerance: An ensemble keeps working as long as a majority of nodes is up. With \(n = 3\) (\(Q = 2\)), at most 1 node can fail. With \(n = 5\) (\(Q = 3\)), at most 2 nodes can fail — the remaining 3 still form a majority.
Sense-check: a 6-node ensemble tolerates only 2 failures too, because 3 remaining nodes are not a majority of 6 (\(3 < 4\)). Adding an even node buys no extra tolerance — that is why production ensembles use odd sizes.
Why 5 nodes is recommended over 3 nodes: In a 3-node ensemble (\(Q = 2\)), if a network partition isolates 1 node from the other 2, the isolated single node cannot satisfy quorum (\(1 < 2\)) and fails all operations — and if one of the remaining 2 nodes then crashes, quorum is lost entirely and the whole ensemble stops. In a 5-node ensemble (\(Q = 3\)), the system tolerates up to 2 simultaneous node failures while the remaining 3 operational nodes still maintain quorum. The odd-size rule plus the larger margin is why 5 is the common production recommendation.
- High Consistency via ZAB Protocol: ZooKeeper prioritizes consistency and partition tolerance (CP in the CAP theorem). Writes are replicated across ensemble nodes using the ZooKeeper Atomic Broadcast (ZAB) protocol, which runs in two phases:
- Phase 1 — Leader election: the machines elect a distinguished Leader; the other machines (Followers) synchronize their state with it.
- Phase 2 — Atomic broadcast: the Leader forwards every write request to the Followers and commits the change only when a majority have persisted it to disk.
The Leader acknowledges a write operation as successful to the client only after a quorum of nodes confirms writing the update to memory and disk. Subsequent reads served by any node return the updated consistent state. Because every write is committed to disk on a quorum before being acknowledged, a minority failure can never lose the latest state.
8.4.3 ZNode Types and Notification Watches
ZooKeeper structures stored data in a hierarchical tree format similar to a Unix file system. Each node in the tree is called a ZNode (ZooKeeper Node), identified by a slash-delimited path (such as /app/hadoop/master). Each ZNode can store small configuration datasets (typically under a few kilobytes).
/ (Root)
|
+-------------+-------------+
| |
/hadoop /services
| |
/master /hbase
(ZNode: IP/Port) (ZNode: Metadata)
ZooKeeper supports four distinct ZNode classifications:
- Ephemeral ZNodes: Temporary ZNodes linked directly to a client session. When the client session terminates or misses periodic heartbeats, ZooKeeper automatically deletes the Ephemeral ZNode.
- Use case: Dynamic failure detection. A master server creates an ephemeral ZNode at
/hadoop/master. A ZooKeeper Fault Controller (zkfc) library embedded in each node sends heartbeats to ZooKeeper. If the active master crashes, its session expires, the ephemeral ZNode disappears, and ZooKeeper triggers an event to promote a standby master.
- Use case: Dynamic failure detection. A master server creates an ephemeral ZNode at
- Persistent ZNodes: Permanent ZNodes that persist on disk regardless of client disconnects or node restarts.
- Use case: Storing cluster configuration settings, active job progress metadata, and static service endpoints accessible to all nodes.
- Container ZNodes: Specialized ZNodes used for distributed mutual exclusion and leader election. When a master node attempts to claim leadership, it creates a container ZNode. The first client to write succeeds and acquires an exclusive lock. Concurrent write requests from secondary clients are denied until the lock holder releases the ZNode or crashes.
- Sequential ZNodes: ZNodes where ZooKeeper automatically appends a monotonically increasing 10-digit sequence number (such as
node-0000000001) to the ZNode path, facilitating ordered queue management.
A note on the standard names: The ZooKeeper API spells these modes PERSISTENT, EPHEMERAL, PERSISTENT_SEQUENTIAL, EPHEMERAL_SEQUENTIAL, plus CONTAINER. The professor's four-way classification (Ephemeral, Persistent, Container, Sequential) maps onto these; "sequential" and "ephemeral" are flags that can combine — an ephemeral sequential ZNode exists for the client's lifetime and carries an increasing sequence number.
These ZNode types are only half the story. The other half is how clients learn that a ZNode has changed.
Q: How do ZooKeeper Watches eliminate polling in distributed systems? A: Clients register an asynchronous Watch on a specific ZNode path (such as /hadoop/master). When the target ZNode is modified, deleted, or created, ZooKeeper fires a one-time notification callback directly to the registered client. This push-based subscription model eliminates the need for clients to continuously poll ZooKeeper for state changes — the client sits idle and gets told the moment something changes.
8.4.4 Modern Alternatives: Kubernetes and Choreographic Architecture
Real-world: While legacy platforms (such as Apache Storm, HBase, Solr, and early Apache Kafka versions) relied heavily on ZooKeeper for distributed coordination, modern cloud-native architectures are transitioning away from dedicated coordination clusters:
- Kafka KRaft Mode: Apache Kafka version 4.0+ completely removes the ZooKeeper dependency, replacing it with an event-driven internal consensus protocol called KRaft (Kafka Raft).
- Kubernetes and Choreographic Architecture: Modern microservice deployments favor decentralized choreographic patterns over centralized instructive controllers like ZooKeeper. In Kubernetes environments, containers contain self-managed health rules and auto-scaling definitions rather than depending on an external ZooKeeper ensemble.
Pitfalls:
- Even-sized ensembles: Adding a 4th or 6th node does not increase failure tolerance — quorum needs a strict majority, so only odd sizes pay off.
- Split-brain by hand: Two masters that both believe they lead (with no quorum enforcement) will both accept writes and diverge; quorum voting exists precisely to stop this.
- ZooKeeper is not a database: ZNodes are meant for small metadata (a few kilobytes), not large payloads. Storing big data in ZNodes is a misuse.
Real-world: Apache ZooKeeper enables high-availability active/passive NameNode failover in HDFS, ResourceManager recovery in YARN, and dynamic service registration across distributed enterprise clusters — and it coordinates HBase's master election, which is exactly where we go next.
Recap + bridge: ZooKeeper is a small, odd-sized ensemble whose Leader replicates every write to a quorum (\(Q = \lfloor n/2 \rfloor + 1\)) via the ZAB protocol; ephemeral ZNodes make failure detection automatic and Watches replace polling. It coordinates the masters we met in earlier lectures. Next, ZooKeeper becomes the nervous system of a whole NoSQL database — Apache HBase.
8.5 NoSQL Real-Time Distributed Storage with Apache HBase
8.5.1 Motivation, RDBMS Bottlenecks, and Anti-Patterns
What happens when a database has a billion rows and users expect millisecond answers? A traditional relational database buckles: joins slow down, indexes bloat, and writes grind to a halt. Google faced exactly this problem and published a paper describing a radically simpler storage model. Apache HBase is the open-source implementation of that idea — a database that gives up SQL's power tools to get speed at massive scale.
Traditional Relational Database Management Systems (RDBMS) encounter severe scalability bottlenecks when handling massive volume and high-velocity throughput:
- RDBMS Scale Bottlenecks: At petabyte scale with billions of rows (such as Facebook user profiles or Amazon shopping carts), relational joins fail to scale, index maintenance degrades write performance, disk I/O locks up, and manual sharding requires complex database administration.
- Google Bigtable Paper: In 2006, Google published the foundational research paper Bigtable: A Distributed Storage System for Structured Data. Apache HBase was developed as an open-source implementation of Bigtable, running natively on top of HDFS.
- Core Philosophy: HBase provides low-latency, real-time random read and write access to petabyte-scale data stored on HDFS. It forfeits complex SQL join capabilities and ACID transactions in favor of horizontally scalable key-value lookups (
putandget).
The trade HBase makes. HBase keeps two things from the relational world — tables and columns — but drops the rest: no SQL joins, no multi-row ACID transactions, no secondary indexes. In exchange it gets predictable, low-latency random access by row key at a scale where an RDBMS would fall over. The unit of work is a single-row operation: put (write a cell) and get (read a cell).
When to Use HBase vs. Anti-Patterns.
| Recommended HBase Use Cases | HBase Anti-Patterns ("Sleepless Nights") |
|---|---|
| Low-latency random read/write lookups by key | Multi-row RDBMS transactions (such as banking debits/credits) |
| Sparse data tables with billions of rows | Column aggregation queries (SUM, AVG, COUNT) |
| Real-time user authentication and profile fetching | Queries filtering non-key attributes (WHERE age < 20) |
| Rapid write logging (such as clickstreams or IoT sensors) | Small datasets easily managed in standard RDBMS |
Anti-patterns — the "sleepless nights" warning: Using HBase for the wrong workload guarantees pain. Multi-row transactions (like a bank debit plus credit) need atomicity HBase does not provide. Column aggregations (SUM, AVG, COUNT) force full-table scans because there is no SQL engine to do the math efficiently. Queries filtering on non-key attributes (WHERE age < 20) would scan every HFile on every RegionServer. And small datasets are better off in a plain RDBMS. Match the tool to the workload, not the reverse.
8.5.2 HBase System Architecture
HBase follows a master-slave architecture built on top of HDFS and coordinated by Apache ZooKeeper:
+-----------------------------------------------------------------+
| HBase Architecture |
| |
| +---------------------+ |
| | HMaster (Active) | |
| +----------+----------+ |
| | Coordinated via |
| v ZooKeeper |
| +---------------------------+ |
| | ZooKeeper Cluster / ZNode | |
| +-------------+-------------+ |
| | |
| +----------------------+----------------------+ |
| v v |
| +---------------+ +---------------+ |
| | RegionServer 1| | RegionServer 2| |
| | +-----------+ | | +-----------+ | |
| | | Region A | | | | Region C | | |
| | +-----------+ | | +-----------+ | |
| | | Region B | | | | Region D | | |
| | +-----------+ | | +-----------+ | |
| +-------+-------+ +-------+-------+ |
| | | |
+---------|---------------------------------------------|---------+
v v
+-----------------------------------------------------------------+
| HDFS Storage Layer (DataNodes) |
+-----------------------------------------------------------------+
- HMaster: Manages DDL operations (
create,disabletables), monitors RegionServers, and performs region assignment. The HMaster handles initial client metadata requests. To avoid becoming a performance bottleneck, client read/write data operations bypass HMaster and communicate directly with RegionServers. - RegionServer: Manages and serves horizontal shards of table data called Regions. Each RegionServer handles data reads, writes, and local compaction operations across multiple regions.
- ZooKeeper Coordination: Coordinates HMaster election (active/standby), maintains region lookup locations (
metatable), and tracks active RegionServer health via ephemeral ZNodes.
How the pieces connect to earlier lectures: ZooKeeper is the coordinator that elects the HMaster (the active/passive pattern from section 8.4). HDFS at the bottom is the storage layer from the Hadoop lectures — HBase adds a real-time, random-access layer on top of HDFS's batch-oriented files. The RegionServer is where the data actually lives; the HMaster only manages metadata, which is why data paths bypass it.
8.5.3 HBase Multi-Dimensional Sparse Data Model
HBase organizes data into a 4-dimensional sparse mapping sorted lexicographically by Row Key:
\[ f(\text{RowKey}, \text{ColumnFamily}, \text{ColumnQualifier}, \text{Timestamp}) \rightarrow \text{CellValue} \]
where \(f\) maps the four coordinates to the value stored in the cell at their intersection.
Row Key: "2019MC04000"
|-- Column Family: "user_info"
| |-- Column Qualifier: "name" --> "Vignesh" (Timestamp: t1)
| |-- Column Qualifier: "photo" --> "http://site/pic.jpg" (Timestamp: t2)
|-- Column Family: "payment_info"
|-- Column Qualifier: "card" --> "4111xxxx" (Timestamp: t3)
- Row Key: A unique byte array identifier for every row. Rows are sorted lexicographically by Row Key. All data retrieval queries must specify a Row Key.
- Column Family: A logical and physical grouping of related columns defined during table creation. Columns within the same Column Family are stored together on disk in dedicated storage files (HFiles).
- Example: An e-commerce user table might define three Column Families:
user_info(name, age),order_data(items, totals), andpayment_info(card details).
- Example: An e-commerce user table might define three Column Families:
- Column Qualifier (Dynamic Columns): Dynamic attribute labels defined on the fly during record insertion under a specific Column Family (formatted as
ColumnFamily:ColumnQualifier).- Sparsity: Rows do not need identical columns. If a row lacks a specific column, no storage space is consumed (unlike RDBMS null padding).
- Timestamp / Multi-Version Concurrency Control (MVCC): Each cell maintains a configurable number of timestamped version histories. Inserting a new value into an existing cell creates a new version timestamped with system time.
- Example: Cell revision history enables tracking historical password changes or Google Web Crawler page indexing revisions (
com.cnn.comcrawling history).
- Example: Cell revision history enables tracking historical password changes or Google Web Crawler page indexing revisions (
- Data Type (
ByteArray): HBase has only one native data type:byte[](ByteArray). Strings, integers, floats, and binary objects are all stored as raw byte arrays. Applications handle serialization and type casting upon retrieval.
Worked example — Google Bigtable's web index. The 2006 Bigtable paper indexed crawled web pages using reversed domain names as Row Keys (such as com.cnn.com instead of www.cnn.com), so pages from the same site sort next to each other lexicographically. The table had two Column Families:
contents:— stores raw page HTML, versioned over time (each crawl adds a new timestamped version of the same row key).anchor:— stores external linking websites as dynamic column qualifiers; for example the qualifieranchor:cnnsi.comholds the anchor text of the link fromcnnsi.comtocnn.com. New backlinks become new qualifiers on the fly.
The table's 4-dimensional coordinates in action: \(f(\text{com.cnn.com}, \text{contents}, \langle\text{empty}\rangle, t_1) \rightarrow \text{HTML}_1\), and \(f(\text{com.cnn.com}, \text{anchor}, \text{cnnsi.com}, t_2) \rightarrow \text{anchor text}\). Sense-check: the reversed key groups one site's pages together for the crawler, and the sparse qualifier design lets any number of backlinks appear without pre-declaring columns.
8.5.4 DDL and DML Data Operations (CLI and API)
HBase operations are executed through the HBase Shell (Ruby-based CLI), Java Client API, REST web gateway, Thrift (Facebook), or Avro.
Table Creation (DDL).
Creating an HBase table requires specifying only the table name and the Column Family names. Column qualifiers and data types are not defined at creation:
create 'user_table', 'user_info', 'payment_info'
Data Insertion (put - DML).
The put command requires the table name, target Row Key, target ColumnFamily:ColumnQualifier, and cell value:
put 'user_table', '2019MC04000', 'user_info:name', 'Vignesh'
put 'user_table', '2019MC04000', 'user_info:picture', 'http://store.com/vignesh.jpg'
put 'user_table', '2019MC04000', 'payment_info:card', '4111222233334444'
Data Retrieval (get - DML).
The get command retrieves data for a specific Row Key, with optional parameters specifying Column Families, dynamic qualifiers, or historical version counts:
get 'user_table', '2019MC04000'
get 'user_table', '2019MC04000', {COLUMNS => ['user_info:name'], VERSIONS => 3}
Q: Why does HBase's get syntax require a Row Key, whereas SQL allows querying by any arbitrary column? A: HBase indexes data physically by Row Key across distributed HFiles. Querying by Row Key routes directly to the specific RegionServer hosting that key range. Searching by arbitrary non-key attributes would force HBase to scan every HFile across all RegionServers, destroying performance. Therefore, get operations require a Row Key.
Pitfalls:
- No secondary indexes: HBase has no index on non-key columns (no equivalent of
CREATE INDEX ... ON age). Every non-key lookup is a full scan. - No multi-row ACID: A single row is atomic; operations spanning several rows are not — do not model banking-style transfers here.
- Bytes in, bytes out: Everything is a
byte[]; the application must serialize and deserialize numbers and strings itself. - Row key design matters: Keys are sorted lexicographically as byte arrays, so a badly chosen key (for example, embedding a timestamp that changes on every write) can hotspot one RegionServer.
Real-world: Apache HBase powers real-time, high-concurrency low-latency applications including Facebook user profile lookups, Amazon shopping cart staging, Google search engine page-rank link indexing, and biometric authentication backends.
Recap + bridge: HBase trades SQL joins and ACID transactions for low-latency key-value access at petabyte scale. Data lives in a 4-dimensional sparse map \(f(\text{RowKey}, \text{ColumnFamily}, \text{ColumnQualifier}, \text{Timestamp}) \rightarrow \text{CellValue}\), sorted by Row Key, with dynamic qualifiers and versioned cells. ZooKeeper coordinates the master; HDFS stores the data. That completes the Hive → ZooKeeper → HBase arc of this lecture — next come the exam logistics.
8.6 Exam Guidance Summary
8.6.1 Quiz Scope and Assignment Deadlines
Exam note — Quiz 1 scope: Quiz 1 includes all topics covered up through HDFS, MapReduce, and Apache Pig. Apache Hive, Apache ZooKeeper, and Apache HBase are excluded from Quiz 1 — so the material of this lecture (bucketing, joins, subqueries, ZooKeeper, HBase) will not appear on Quiz 1.
Quiz 2 covers Apache Hive, ZooKeeper, HBase, NoSQL data management, and Apache Spark (scheduled immediately after completing the Spark module). The Hive, ZooKeeper, and HBase content from this lecture is therefore Quiz 2 material — keep the bucketing formula, the quorum formula, and the HBase data model fresh until then.
Assignment 1 will be released on Friday, September 10th, with a final submission deadline of October 15th. Students must implement a custom MapReduce join program in Java MapReduce and demonstrate identical query execution in Hive/Pig. The join mechanics from section 8.2 (mapper key emission, reducer cross-table matching) are exactly what this assignment asks you to build by hand.
8.6.2 Course Logistics and Class Scheduling
Exam note — scheduling: No class will take place on Sunday, September 12th. A makeup lecture is scheduled for Thursday, September 15th at 8:00 PM.
Mid-Semester regular exams occur on September 23rd, 24th, and 25th. Mid-Semester makeup exams occur on October 7th, 8th, and 9th. Plan the Assignment 1 submission (October 15th) against these dates — the assignment window overlaps the mid-semester exam period, so budget time accordingly.
8.7 Key Industry Applications
8.7.1 Enterprise Data Warehouse Query Acceleration
Real-world: Enterprise data warehouses use combined partitioning and bucketing over ORC/Parquet files to accelerate ad-hoc queries on petabyte-scale data lakes, skipping unneeded HDFS blocks during WHERE evaluation. The bucketing idea from section 8.1 — hash to a bucket, then read only the matching bucket file — is exactly the mechanism these warehouses exploit: coarse partitioning narrows the search to a directory, and bucketing narrows it further to a single file, so a WHERE on a high-cardinality key touches a tiny fraction of the data.
8.7.2 High-Availability Coordination and Real-Time NoSQL Storage
Real-world: Apache ZooKeeper enables high-availability active/passive NameNode failover in HDFS, ResourceManager recovery in YARN, and dynamic service registration across distributed enterprise clusters. Every place a cluster needs "one leader, no split-brain, and automatic failure detection," a ZooKeeper ensemble (or its modern successor) sits underneath — from Hadoop's own master nodes to Kafka's metadata management.
Real-world: Apache HBase powers real-time, high-concurrency low-latency applications including Facebook user profile lookups, Amazon shopping cart staging, Google search engine page-rank link indexing, and biometric authentication backends. These are all workloads that match the HBase use-case column from section 8.5: single-row key lookups on billions of rows, where a millisecond read by user ID beats any SQL feature set.
BDA Lecture 8 notes
Sections Breakdown
Explains how Hive assigns rows to bucket files via hash(column_value) mod N, why FLOAT/DOUBLE bucketing keys scatter rows through IEEE 754 bit-pattern hashing, how converting the key column to INT restores exact placement, and how WHERE queries on the bucketed column read only the matching bucket file.
Covers why tables are normalized, the two-step relational join definition (Cartesian cross product then equality-predicate filtering), and how the same join is expressed in Hive HQL with fully qualified columns and executed as one MapReduce job.
Explains Hive FROM-clause subqueries (parenthesized derived tables with a mandatory alias), the 'cannot recognize input near sum' parser error, and how a join-plus-aggregate subquery compiles into a two-stage MapReduce pipeline with intermediate results on HDFS.
Describes the coordination problems of distributed clusters (master failover, task recovery, service discovery), the ZooKeeper ensemble with quorum Q = floor(n/2) + 1 over the ZAB protocol, the four ZNode types, Watches versus polling, and modern alternatives such as Kafka KRaft.
Explains why RDBMS break at petabyte scale, how HBase trades SQL joins and ACID for low-latency key-value access, the HMaster/RegionServer/ZooKeeper architecture over HDFS, the 4-dimensional sparse data model, and the create, put, and get operations.
Course logistics: Quiz 1 excludes Hive, ZooKeeper, and HBase (this lecture is Quiz 2 material alongside NoSQL and Spark), Assignment 1 (Java MapReduce join) is released September 10 with an October 15 deadline, and the mid-semester exam schedule.
Real-world uses of this lecture's material: partitioning plus bucketing over ORC/Parquet for petabyte data-lake query acceleration, ZooKeeper for HDFS NameNode failover and YARN recovery, and HBase for real-time single-row-key lookups.
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.
8.1 Hive Bucketing Data Types and Precision Resolution
Must-know: bucket_id = hash(column_value) mod N; INT keys bucket exactly (value mod N) while FLOAT keys hash the IEEE 754 bit pattern and scatter rows; converting weight FLOAT->INT fixes the distribution and lets WHERE on the bucket column read only the target bucket file.
\[ \text{bucket\_id} = \text{hash}(\text{column\_value}) \pmod{N} \]
⚠️ Top pitfall: Using FLOAT or DOUBLE as a bucketing key; expecting visible speedups on tiny datasets; creating so many tiny buckets that NameNode metadata overhead outweighs the query savings.
Self-check: With N=3 buckets, which bucket file holds weight 6, and why did it previously land in the wrong file under FLOAT bucketing?
Connects to: 8.2, 8.7
8.2 Relational Joins in Apache Hive
Must-know: Join = Cartesian cross product |T_A x T_B| = |T_A| x |T_B|, then filter by the equality predicate TableA.id == TableB.id; Hive supports only equijoins; HQL joins need fully qualified table.column names; the join runs as one MapReduce job (mappers tag by key, reducers match).
\[ |T_A \times T_B| = |T_A| \times |T_B| \]
⚠️ Top pitfall: Omitting table qualification for shared columns like shop_id; expecting Hive to support inequality joins; forgetting that inner joins drop unmatched rows.
Self-check: With a 3-row salary table and a 4-row names table, how many tuples does the cross product generate and how many survive the inner join?
Connects to: 8.1, 8.3
8.3 Hive Subqueries and Multi-Stage MapReduce Pipeline Execution
Must-know: A FROM subquery must be enclosed in parentheses AND given an explicit alias or the parser fails at the outer query's first function (e.g. 'sum'); join+aggregate subqueries run as two chained MapReduce jobs — Stage 1 joins on shop_id, Stage 2 groups by shop_name and computes SUM(weight).
⚠️ Top pitfall: Omitting the derived-table alias; writing correlated subqueries (not supported by Hive); forgetting that every MapReduce stage writes intermediate output to HDFS (slow for many-stage pipelines).
Self-check: What two error-free requirements must every Hive FROM-clause subquery satisfy, and how many MapReduce jobs does a join-plus-aggregate subquery produce?
Connects to: 8.2, 8.4
8.4 Distributed Coordination with Apache ZooKeeper
Must-know: Quorum Q = floor(n/2) + 1: n=3 gives Q=2 (tolerates 1 failure), n=5 gives Q=3 (tolerates 2 failures); ensembles must be odd-sized; all writes go to the Leader and commit only after a quorum persists them (ZAB); ephemeral ZNodes auto-delete on session loss; Watches push one-time change notifications instead of polling.
\[ Q = \left\lfloor \frac{n}{2} \right\rfloor + 1 \]
⚠️ Top pitfall: Running an even-sized ensemble (no extra tolerance); assuming both masters can lead (split-brain); storing large payloads in ZNodes; thinking 6 nodes tolerate 3 failures.
Self-check: For a 5-node ensemble, what is the quorum size and how many simultaneous node failures can it tolerate?
Connects to: 8.3, 8.5
8.5 NoSQL Real-Time Distributed Storage with Apache HBase
Must-know: HBase data model is f(RowKey, ColumnFamily, ColumnQualifier, Timestamp) -> CellValue, sorted by Row Key, with dynamic qualifiers, versioned cells and byte[] as the only type; HMaster handles DDL, RegionServers serve data, ZooKeeper coordinates; get requires a Row Key because HBase has no secondary indexes.
\[ f(\text{RowKey}, \text{ColumnFamily}, \text{ColumnQualifier}, \text{Timestamp}) \rightarrow \text{CellValue} \]
⚠️ Top pitfall: Using HBase for multi-row transactions, column aggregations, or non-key WHERE filters ('sleepless nights'); forgetting there is only the byte[] type; designing a row key that hotspots one RegionServer.
Self-check: Why must every HBase get operation specify a Row Key, and what are the four coordinates of the HBase data model?
Connects to: 8.4, 8.6
8.6 Exam Guidance Summary
Must-know: Quiz 1 stops at Pig (no Hive/ZooKeeper/HBase); this lecture is Quiz 2 material alongside NoSQL and Spark. Assignment 1 (Java MapReduce join + identical Hive/Pig query) due October 15th. No class Sep 12; makeup lecture Sep 15 8 PM; mid-semester exams Sep 23-25, makeups Oct 7-9.
⚠️ Top pitfall: Assuming this lecture's Hive/ZooKeeper/HBase content is on Quiz 1 — it is excluded and appears on Quiz 2 instead.
Self-check: Which quiz covers Hive, ZooKeeper, and HBase, and what is the Assignment 1 deadline?
Connects to: 8.1, 8.2, 8.3, 8.4, 8.5
8.7 Key Industry Applications
Must-know: Bucketing accelerates warehouse queries over ORC/Parquet data lakes; ZooKeeper underpins HDFS HA failover, YARN RM recovery and service registration; HBase serves single-row-key real-time lookups (Facebook profiles, Amazon cart, Google link indexing, biometric auth).
⚠️ Top pitfall: Picking HBase for workloads that need joins or aggregation (anti-patterns) instead of the single-row-key real-time workloads it is built for.
Self-check: Name one real-world application for each of bucketing, ZooKeeper, and HBase.
Connects to: 8.1, 8.4, 8.5