IMEN891K / IDSC501 / AXGS-501 · Data Engineering · Fall 2026

11. Big Data I: Batch Processing

Sangwoo Mo · POSTECH
Industrial & Management Engineering

Where We Are

PART IPART IIPART IIICollection and PreparationLec 2-5SQLLec 6-8NoSQLLec 9-10Big DataLec 11-13Monitoring and UpdatesLec 14-17

Outline: Big Data I

01

MapReduce

write map and reduce functions for distributed jobs

02

Spark

compose RDD operations, then describe table queries with DataFrames

01 · MapReduce

01

MapReduce

write map and reduce functions for distributed jobs

02

Spark

compose RDD operations, then describe table queries with DataFrames

§ Why MapReduce

MapReduceWhy MapReduceGoogle and HadoopWriting MapReduceFunctions, tasks and retriesRelational OperatorsSelect, group and join

Google Had a Data ProblemGoogle needed to turn large collections of web pages and request logs into useful data

Web documentspages collected from the webMany computersprocess different piecesSearch dataprepare indexes and summaries
Simple calculations became difficult when the input needed many computers

Google Publishes MapReduceIn 2004, Jeffrey Dean and Sanjay Ghemawat at Google published the MapReduce paper

Your calculationdefine map and reduceMapReduce runtimesplit, schedule and retryCluster of computersrun the pieces together
Programmers describe the calculation; the runtime coordinates the computers

Hadoop Makes the Idea AvailableDoug Cutting and Mike Cafarella developed Hadoop from the Nutch search project

Nutch web searchCutting and CafarellaHadoop, 2006open-source projectBroader usea community can build on it
Hadoop brought distributed storage and MapReduce to an open-source community

The Name HadoopThe elephant in the logo has a family story behind it

Original Hadoop elephant logo
  • Hadoop was the name of Doug Cutting’s son’s toy elephant
  • The family nickname became the software project’s name

Why Many MachinesDistributed processing divides work across computers when one computer is insufficient

One computer reads everythingThree computers share the readingAll input rowsone computer does all the workComputer 1: one pieceComputer 2: one pieceComputer 3: one pieceSame question, same answer; different computers handle different rows
Different computers read different rows to answer the same question

Scale Up or Scale OutScale up adds resources to one machine; scale out adds machines

Scale upScale outOne larger machinemore memory / processing powerWorker 1Worker 2Worker 3Simpler, until one machine is not enoughMore capacity, plus coordination and transfers
Extra machines add capacity and coordination work

Storage and ComputeStorage keeps the files; workers are processes that read files and calculate results

Shared storagesource files kept after the job endsWorker 1reads one piece and calculatesWorker 2reads one piece and calculatesWorker 3reads one piece and calculatesStorage keeps source data; workers run the computation
Durable input and temporary computation have different jobs

Distributed Files and TasksHDFS stores file blocks; MapReduce assigns computation to workers

Partitionone input pieceTaskprocess that input pieceWorkera process that runs the task
  • A distributed file system divides a large file into blocks
  • Replicas keep another copy if a storage server fails
  • Workers run tasks over pieces of the stored input

§ Writing MapReduce

MapReduceWhy MapReduceGoogle and HadoopWriting MapReduceFunctions, tasks and retriesRelational OperatorsSelect, group and join

Word CountThe input is a collection of documents; the output is one count per word

Document IDDocument contents
d1spark makes data simple
d2spark processes data
Question: How many times does each word occur across all documents?

Word Count FunctionsThe mapper emits one per word; the reducer sums values for that word

2025 MapReduce Word Count mapper and reducer functions
The Python walkthrough makes the same two functions explicit

Map: Emit a Word and OneEach yielded pair says that this word occurred once in this document

def map_doc(document_id, text):
    for word in text.split():
        yield (word, 1)
d1spark makes data simplemap_doc("d1", text)("spark", 1)("makes", 1)("data", 1)("simple", 1)
The output key is the word, so occurrences from different documents can meet

Shuffle and ReduceThe runtime groups equal keys before calling reduce_word once for each key

def reduce_word(word, counts):
    yield (word, sum(counts))
Both mapper outputsspark: 1, 1data: 1, 1makes: 1simple: 1processes: 1After reducespark: 2data: 2makes: 1simple: 1processes: 1
Two documents produce seven tokens and five distinct word counts

Word Count: The Full Data Flowdid identifies a document, v is its text, and w is a word

Word Count: The Full Data Flow
Map emits (word, 1); shuffle groups words; reduce adds each group

Map Tasks and Reduce TasksA task is one unit of work; a worker process can run multiple tasks over time

Map Tasks and Reduce Tasks
Map tasks split the input; reduce tasks split the grouped keys

A Worker Can Fail

Durable inputkeep source filesWorker failstemporary output is lostRetry missing workread the input again
Retry needs available source data and a repeatable computation

Demo: Follow the Grouped CountSensor example: count readings at or above 80°C by machine

Track M1 from three input partitions to one result

Answer: Follow the Grouped CountMap emits five pairs, shuffle forms three keys, reduce returns three counts

Input partitionsM1 70 | M2 90 | M1 85M1 95 | M3 75 | M2 60M3 88 | M1 82 | M2 78Map: keep + emit(M2,1) (M1,1)(M1,1)(M3,1) (M1,1)Shuffle: group keysM1: [1, 1, 1]M2: [1]M3: [1]Reduce: add countsM1: 1 + 1 + 1 = 3M2: 1M3: 1Selection: temp >= 80 | Key: machine | Value: 1
The output counts sum to five retained readings

§ Relational Operators

MapReduceWhy MapReduceGoogle and HadoopWriting MapReduceFunctions, tasks and retriesRelational OperatorsSelect, group and join

Relational Operations in MapReduceSQL names the result; MapReduce requires you to write how records produce it

SQL operationWhat the MapReduce program must decide
WHEREwhich rows map emits
GROUP BY + SUMgrouping key and values to add
JOINjoin key, source tags and matching pairs
The same familiar SQL operation requires explicit record-processing code

The Records for Our SQL ExamplesThe query determines the intermediate key; a tuple position does not determine its meaning

R = [(123,21), (456,10), (123,4)]
S = [(21,67), (21,80), (4,9), (5,99)]
R(A, B)(123, 21)(456, 10)(123, 4)S(C, D)(21, 67)(21, 80)(4, 9)(5, 99)
R has three rows; S has two different rows with C = 21

Selection in MapReduceSELECT * FROM R WHERE A = 123 keeps only the two matching rows

Selection in MapReduce
The mapper checks the condition and emits the retained rows

A Map-Only SelectionA filter can finish in map; Hadoop supports map-only jobs by setting reducers to zero

Original map-only selection diagram with the redundant reducer crossed out
Only rows passing A = 123 need to be emitted

Selection: Code and Retained RowsA WHERE condition decides whether each input row emits an output row

# SELECT * FROM R WHERE A = 123
def select_row(a, b):
    if a == 123:
        yield (a, b)
Input R(123, 21)(456, 10)(123, 4)Mapper output(123, 21)(123, 4)
Filtering needs no cross-partition grouping

Grouping in MapReduceSELECT A, SUM(B) FROM R GROUP BY A adds B values within each A

Grouping in MapReduce
Key 123 groups values 21 and 4; reduce returns 25

GROUP BY: Choose the Key and ValueThe grouping column becomes the key; the quantity to sum becomes the value

# SELECT A, SUM(B) FROM R GROUP BY A
def map_sum(a, b):
    yield (a, b)

def reduce_sum(a, values):
    yield (a, sum(values))
Shuffle by A123: [21, 4]456: [10]Reducer output(123, 25)(456, 10)
Changing emitted values from B to 1 changes SUM into COUNT

Shuffle Join: Move Both InputsMatching join keys must meet even when the two input tables start on different servers

Shuffle Join: Move Both Inputs
Redistribute both inputs by the join key, then join locally

Shuffle Join: Match Rows Within a KeyA source tag tells reduce which rows came from R and which came from S

Shuffle Join: Match Rows Within a Key
Within one key, reduce pairs each R row with each matching S row

JOIN: Emit the Join Key and a Source TagBoth mappers emit the same kind of key: R.B on the left and S.C on the right

# SELECT R.A, R.B, S.D
# FROM R JOIN S ON R.B = S.C
def map_r(a, b):
    yield (b, ("R", a))

def map_s(c, d):
    yield (c, ("S", d))
R mapper21: (R, 123)10: (R, 456)4: (R, 123)S mapper21: (S, 67)21: (S, 80)4: (S, 9)5: (S, 99)
Key 21 brings one R value and two S values together

JOIN: Pair the Values Within One KeyThe nested loops produce every R × S pair within one key

def reduce_join(key, tagged):
    rows = list(tagged)
    rs = [v for t, v in rows if t == "R"]
    ss = [v for t, v in rows if t == "S"]
    for a in rs:
        for d in ss:
            yield (a, key, d)
Key 21 arrives(R, 123)(S, 67)(S, 80)Two output rows(123, 21, 67)(123, 21, 80)
One reducer call can emit zero, one or many joined rows

JOIN: All Keys and the Final ResultAn inner join emits only keys with rows on both sides

Rows per key21: 1 R × 2 S = 24: 1 R × 1 S = 110: 1 R × 0 S = 05: 0 R × 1 S = 0Final (A, B, D)(123, 21, 67)(123, 21, 80)(123, 4, 9)
The three output rows preserve both matches for key 21

Caveat: Duplicate Join KeysExample change: add (789, 21) to R; inspect key 21 only

R values at key 21A = 123A = 789S values at key 21D = 67D = 80All four pairs(123, 21, 67)(123, 21, 80)(789, 21, 67)(789, 21, 80)
Two rows on each side create four combinations

Broadcast Join: Copy the Small InputIf S is small enough, each worker can receive a copy while R stays partitioned

Broadcast Join: Copy the Small Input
Copy S to the workers instead of shuffling the large input

Broadcast Join: Work Inside MapEach mapper builds a lookup table for S and probes it with its own R rows

Broadcast Join: Work Inside Map
The join can finish in map when each worker holds the small input

Broadcast JOIN: Keep Every MatchEach worker probes a local copy of the small table using its own R rows

from collections import defaultdict
lookup = defaultdict(list)
for c, d in S:  # The small input
    lookup[c].append(d)

def join_local(a, b):
    for d in lookup.get(b, []):
        yield (a, b, d)
Small lookup S21: [67, 80]4: [9]5: [99]Probe R (123, 21)lookup[21] = [67, 80](123, 21, 67)(123, 21, 80)
A list per key preserves the two matches; a single-value dictionary loses one

Two Join StrategiesThe data sizes help determine which input movement is practical

Shuffle joinBroadcast join
Input movementRedistribute both inputs by keyCopy the small input to each worker
Useful whenBoth inputs are largeOne input fits in each worker’s memory
Main costNetwork transfer and uneven keysCopies and memory for the small input

Why Move Beyond MapReduceMore complex calculations require more job code and intermediate data handling

One map/reduce jobexplicit functions and setupWrite intermediate filesnext job reads them againAnother jobrepeat the setup and wiring
  • Complex queries need several connected jobs
  • You write the steps and the intermediate data flow

02 · Spark

01

MapReduce

write map and reduce functions for distributed jobs

02

Spark

compose RDD operations, then describe table queries with DataFrames

§ RDDs

SparkRDDsPrograms and executionDataFramesTables and SQL

Spark Begins at BerkeleyA university research project grew into an open-source processing engine

Spark, Berkeley, AMPLab and Databricks logos in the 2025 source
  • 2009: research at UC Berkeley
  • 2010: open-source release and the Spark paper
  • 2013: moved to Apache; 2014: became a top-level Apache project

Spark: A Brief HistoryThe MapReduce paper appeared in 2004; the Spark paper followed in 2010

Original timeline cropped to the published 2004 to 2014 milestones
A research idea became an open-source project and a broader community

A Shorter Word Count ProgramThe same word-count task needs much less job wiring in the Spark example

A Shorter Word Count Program
Compose operations instead of wiring a new job around each pair of functions

Word Count with Spark RDDsThe same Word Count now uses a chain of distributed collection operations

sc = spark.sparkContext
lines = sc.parallelize([
    "spark makes data simple",
    "spark processes data"], 2)
words = lines.flatMap(lambda s: s.split())
pairs = words.map(lambda w: (w, 1))
counts = pairs.reduceByKey(
    lambda a, b: a + b)
result = sorted(counts.collect())
Same two documents2 lines7 words7 (word, 1) pairs5 (word, count) pairs
Variable names identify the intermediate datasets in the graph

flatMap: Lines Become Wordsmap preserves one output element per line; flatMap flattens each line’s word list

nested = lines.map(lambda s: s.split())
words = lines.flatMap(lambda s: s.split())
map output: 2 elements[spark, makes, data, simple][spark, processes, data]flatMap output: 7 elementsspark, makes, data, simple,spark, processes, data
The function is the same; the resulting element type changes

map and reduceByKey: Word CountsreduceByKey combines values for each word; it may combine locally before the shuffle

pairs = words.map(lambda w: (w, 1))
counts = pairs.reduceByKey(
    lambda a, b: a + b)
pairs: one occurrence(spark, 1) × 2(data, 1) × 2(makes, 1)(simple, 1)(processes, 1)counts: one key total(spark, 2)(data, 2)(makes, 1)(simple, 1)(processes, 1)
The result has five keys, and the counts still sum to seven

Spark’s 2014 Sorting RecordHistorical sorting records drew attention to Spark as a practical processing engine

206
Different clusters and runs: 72 minutes in 2013, 23 minutes in 2014

Spark Libraries and LanguagesOne execution engine supports several kinds of work and several programming languages

Spark Libraries and Languages
Today’s route uses Python, DataFrames and Spark SQL

Driver, Executors and StorageThe driver coordinates the program; executor processes run tasks on the data

Driver, Executors and Storage
Spark handles the execution machinery behind the shorter program

Resilient Distributed DatasetsAn RDD gives distributed data a programmable collection interface

RDD: an immutable collection divided into partitions, with a record of how to rebuild it
Input dataavailable sourceRDD operationsmap, filter, join and other stepsResultcompute when requested
  • Distributed: partitions can run on different workers
  • Resilient: lineage records how lost partitions can be recomputed

Transformations Build Another RDDA transformation such as filter describes a new distributed collection

Transformations Build Another RDD
The program builds the calculation before requesting its result

Actions Request ResultsAn action such as count() requests a value and triggers the required computation

Actions Request Results
Transformations return RDDs; actions return values or save output

Lineage: Code Becomes a Dependency GraphLineage records parent RDDs and operations; the DAG shows these dependencies

# Reuse lines from the Word Count example
words = lines.flatMap(lambda s: s.split())
pairs = words.map(lambda w: (w, 1))
counts = pairs.reduceByKey(
    lambda a, b: a + b)
shufflelinestwo input partitionswords = lines.flatMap(...)one word per elementpairs = words.map(...)(word, 1)counts = pairs.reduceByKey(...)one total per word
The graph uses the same variable names and functions as the code

Lineage: File, Filter and MapA file, a filter and a map form the same kind of dependency chain

2025 file to filter to mapped RDD lineage
Parent data and recorded functions describe how to produce each derived RDD

An Action Executes the Required DAGAn action requests the answer and runs the calculation defined by the transformations

# Transformations above define counts
result = sorted(counts.collect())
# [("data", 2), ("makes", 1),
#  ("processes", 1), ("simple", 1),
#  ("spark", 2)]
Define the calculationlines → wordswords → (word, 1)group and add by wordRequest the answercounts.collect()run the required operationsreturn five word counts
Transformations describe the calculation; an action requests its result

Cache and Two Actions on One RDDTwo actions reuse the same intermediate words; cache and lineage serve different roles

words = lines.flatMap(lambda s: s.split()).cache()
n = words.count()  # 7, fills the cache
k = words.filter(lambda w: w == "spark").count()
# k == 2; reuse available cached words
words.unpersist()
words = flatMap(...).cache()cache is filled when computedwords.count()7 word occurrencesfilter(w == "spark").count()2 matching occurrences
The first action counts all seven words; the second counts two spark occurrences

Cache: Repeated Log QueriesThe filtered log records can serve more than one later query

lines = sc.textFile("logs/")
errors = lines.filter(
    lambda s: s.startswith("ERROR")).cache()
errors.count()
errors.filter(lambda s: "sqlite" in s).count()
errors.unpersist()
logs / linessource texterrors = filter(ERROR).cache()reuse the filtered recordsfilter(sqlite).count()one later query
The first action computes errors; later queries can reuse its cached partitions

Cache: Reusing Filtered LogsThis log diagram shows where an intermediate result can be reused

2025 cached errors to result reuse diagram
Cached errors are computed data; lineage remains the recipe for recomputation

Lineage and One Missing PartitionSpark uses available parent data and recorded operations to recompute missing work

Recorded dependencyinput partition P1flatMap(split)map(word, 1)If its pairs are missingread available P1 againapply the recorded functionsrebuild the missing output
Lineage describes a recipe; cache holds computed data

The Same Join in SparkKeep R.A = 123, match R.B with S.C, then count the three joined rows

# Same R(A, B) and S(C, D)
# Keep A = 123, then join B = C
# Expected (A, B, D):
# (123, 21, 67)
# (123, 21, 80)
# (123, 4, 9)
Selected R(123, 21)(123, 4)S(21, 67)(21, 80)(4, 9)(5, 99)
The two matches at key 21 must survive in every API

A Join Query as RDD OperationsPair RDD join uses the first tuple element as its key; re-key R from (A, B) to (B, A)

R = sc.parallelize([(123,21),(456,10),(123,4)])
S = sc.parallelize([(21,67),(21,80),(4,9),(5,99)])
selected = R.filter(lambda r: r[0] == 123)
left = selected.map(lambda r: (r[1], r[0]))
right = S  # Already (C, D)
joined = left.join(right)
answer = joined.count()  # 3
R(A, B)3 rowsS(C, D)4 rowsselectedA = 123: 2 rowsleft(B, A): key firstright(C, D): key firstjoined = left.join(right)3 matching pairs
The two input paths meet at join; count requests the joined row count

RDD JOIN: Read the Nested Outputjoin preserves the key and nests the two values; map arranges the desired output columns

rows = joined.map(
    lambda kv: (kv[1][0], kv[0], kv[1][1]))
# Result elements are now (A, B, D)
result = sorted(rows.collect())
join: (key, (left, right))(21, (123, 67))(21, (123, 80))(4, (123, 9))map: (A, B, D)(123, 21, 67)(123, 21, 80)(123, 4, 9)
The record structure explains every index in the lambda

Case study: A Filtered JoinThis query filters both inputs before joining on their shared A key

# R(A, B) and S(A, C)
R = sc.parallelize([(1,250),(2,180),(3,300)])
S = sc.parallelize([(1,50),(1,120),(3,80)])
RB = R.filter(lambda r: r[1] > 200)
SC = S.filter(lambda s: s[1] < 100)
J = RB.join(SC)
answer = J.count()  # 2
R after B > 200(1, 250)(3, 300)S after C < 100(1, 50)(3, 80)
Two retained keys produce two joined rows

Case study: The Filtered Join DAGR and S follow separate filter paths and meet at the join

2025 two input filters feeding one RDD join
The two input branches explain the structure of the filtered-join code

Case study: The Same Filtered Join as TablesRDD functions, DataFrame columns and SQL express the same filtered join

R = spark.createDataFrame(
    [(1,250),(2,180),(3,300)], ["A","B"])
S = spark.createDataFrame(
    [(1,50),(1,120),(3,80)], ["A","C"])
J = R.filter(R.B > 200).join(
    S.filter(S.C < 100), on="A")
answer = J.count()  # 2
Equivalent SQLSELECT COUNT(*)FROM R JOIN SON R.A = S.AWHERE R.B > 200AND S.C < 100Joined (A, B, C)(1, 250, 50)(3, 300, 80)
Both the RDD program and the table query count two matches

What RDDs SimplifyRDDs remove much of the job wiring, but the program still works with individual objects

MapReduce programSpark RDD program
Write mapper and reducer classesCall map, filter, join or reduceByKey
Wire a sequence of jobs and intermediate filesCompose operations in one program
Manage key/value logic for each operationReuse built-in distributed operations
We still write functions and choose the operation sequence

§ DataFrames

SparkRDDsPrograms and executionDataFramesTables and SQL

Why DataFramesNamed columns let us express the query as table operations

R.filter(lambda row: row[1] > 200)
What is row[1]? The programmer must know the record layout.
  • A DataFrame gives columns names and types
  • Table operations describe the query in a form Spark can optimize

The Same Join with DataFramesColumn names replace tuple positions and manual re-keying

R = spark.createDataFrame(
    [(123,21),(456,10),(123,4)], ["A","B"])
S = spark.createDataFrame(
    [(21,67),(21,80),(4,9),(5,99)], ["C","D"])
rows = (R.filter(R.A == 123)
    .join(S, R.B == S.C)
    .select("A", "B", "D"))
answer = rows.count()  # 3
Named columnsfilter: A = 123join: B = Cselect: A, B, DSame rows(123, 21, 67)(123, 21, 80)(123, 4, 9)
filter chooses rows; select chooses output columns

The Same Join in Spark SQLThe same table query produces the same three rows through Spark SQL

R.createOrReplaceTempView("R")
S.createOrReplaceTempView("S")
rows = spark.sql("""
    SELECT R.A, R.B, S.D
    FROM R JOIN S ON R.B = S.C
    WHERE R.A = 123
""")
rows.show()
answer = rows.count()  # 3
All three APIs agree(123, 21, 67)(123, 21, 80)(123, 4, 9)
The engine chooses the physical join strategy for the table query

DataFrames and the OptimizerSQL and DataFrame expressions produce a plan the optimizer can inspect

DataFrames and the Optimizer
Describe the table query; let Spark choose an execution plan

A Historical DataFrame Performance ExampleThis historical count benchmark illustrates how a table API can enable optimized execution

A Historical DataFrame Performance Example
The pictured workload is one example; the speed difference depends on the query and environment

MapReduce, RDDs and DataFramesEach step lets us express more of the task through a higher-level interface

MapReduceSpark RDDSpark DataFrame / SQL
You writemap/reduce functions and job setupFunctions and operation chainsColumns, table operations or SQL
You manageKeys, source tags and job boundariesObject layout and operation sequenceSchema and query meaning
The engine providesDistribution, shuffle and retriesDistributed operators and lineageThose execution services plus SQL optimization

Takeaway: MapReduce, RDDs and DataFrames

1MapReduce: write map/reduce functions, keys and job setup
2Spark RDDs: compose distributed operations in a shorter program
3DataFrames and SQL: describe named-column queries and use the optimizer
← → / Space · S notes · O overview · F fullscreen · number + Enter
Speaker notes