p4-mapreduce

Walk-through example

This document walks step-by-step through an example MapReduce job. Our staff Manager and Worker solutions produce logging output so you can see what’s going on. For brevity, we’ve omitted heartbeat messages and the sender’s copy of each TCP message.

Here’s an overview of the messages between the Client, Manager and Worker.

timing diagram

Files

Start in your project root directory. Take a look at a few of the input files.

$ pwd
/Users/awdeorio/src/eecs485/p4-mapreduce
$ ls tests/testdata/input_small
file01  file02 
$ cat tests/testdata/input_small/file01
hello world
hello eecs485
$ cat tests/testdata/input_small/file02
goodbye autograder
hello autograder

We’re going to run a word count job. Start by simulating the MapReduce job at the command line. This is the expected output. Save it to a file.

$ cat tests/testdata/input_small/* | tests/testdata/exec/wc_map.sh | sort | tests/testdata/exec/wc_reduce.sh
autograder	2
eecs485	1
goodbye	1
hello	3
world	1
$ cat tests/testdata/input_small/* | tests/testdata/exec/wc_map.sh | sort | tests/testdata/exec/wc_reduce.sh > correct.txt

These are the files we have before starting the server.

$ pwd
/Users/awdeorio/src/eecs485/p4-mapreduce
$ ls
bin  mapreduce  mapreduce.egg-info  requirements.txt  pyproject.toml  tests

Start Manager

Kill any stale MapReduce processes.

$ pkill -lf mapreduce-  # macOS
$ pkill -f mapreduce-   # Linux/WSL

Start the Manager.

$ mapreduce-manager --loglevel=DEBUG
Manager:6000 [INFO] Manager host=localhost port=6000 pwd=/Users/awdeorio/src/eecs485/p4-mapreduce
Manager:6000 [INFO] Created /tmp/mapreduce-shared-7r6il3a5
Manager:6000 [INFO] Start UDP server thread
Manager:6000 [INFO] Start TCP server thread
Manager:6000 [DEBUG] TCP bind localhost:6000
Manager:6000 [DEBUG] UDP bind localhost:6000

The Manager created a shared temporary directory /tmp/mapreduce-shared-7r6il3a5. We’re using the Python standard library tempfile.TemporaryDirectory to create and automatically clean up the temporary directory, so your path could be different.

Start Workers

Start two Workers in separate terminals.

$ mapreduce-worker --port 6001 --loglevel=DEBUG  # Terminal 2
Worker:6001 [INFO] Worker host=localhost port=6001 manager_host=localhost, manager_port=6000 pwd=/Users/awdeorio/src/eecs485/p4-mapreduce
Worker:6001 [INFO] Start TCP server thread
Worker:6001 [DEBUG] TCP bind localhost:6001
Worker:6001 [INFO] Sent connection request to Manager localhost:6000
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "register",
  "worker_host": "localhost",
  "worker_port": 6001
}
Manager:6000 [INFO] Registered Worker RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "register_ack"
}
Worker:6001 [DEBUG] Got register_ack {'message_type': 'register_ack'}
Worker:6001 [INFO] Connected to Manager localhost:6000
Worker:6001 [INFO] Start heartbeat thread
$ mapreduce-worker --port 6002 --loglevel=DEBUG  # Terminal 3
Worker:6002 [INFO] Worker host=localhost port=6002 manager_host=localhost, manager_port=6000 pwd=/Users/awdeorio/src/eecs485/p4-mapreduce
Worker:6002 [INFO] Start TCP server thread
Worker:6002 [DEBUG] TCP bind localhost:6002
Worker:6002 [INFO] Sent connection request to Manager localhost:6000
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "register",
  "worker_host": "localhost",
  "worker_port": 6002
}
Manager:6000 [INFO] Registered Worker RemoteWorker('localhost', 6002)
Worker:6002 [DEBUG] TCP recv
{
  "message_type": "register_ack"
}
Worker:6002 [DEBUG] Got register_ack {'message_type': 'register_ack'}
Worker:6002 [INFO] Connected to Manager localhost:6000
Worker:6002 [INFO] Start heartbeat thread
$

At this point, you’ll have 3 terminals open for the Manager and two Workers. three terminals

Pro-tip To produce logging output similar to the instructor solution, see the Logging section of the spec.

Submit a MapReduce job

Submit a word count job in a fourth terminal. We’re taking advantage of the submit defaults, see mapreduce-submit --help for more info.

$ mapreduce-submit -i tests/testdata/input_small
Submitted job to Manager localhost:6000
input directory      tests/testdata/input_small
output directory     output
mapper executable    tests/testdata/exec/wc_map.sh
reducer executable   tests/testdata/exec/wc_reduce.sh
num mappers          2
num reducers         2

The Manager receives the job.

Manager:6000 [DEBUG] TCP recv
{
  "message_type": "new_manager_job",
  "input_directory": "tests/testdata/input_small",
  "output_directory": "output",
  "mapper_executable": "tests/testdata/exec/wc_map.sh",
  "reducer_executable": "tests/testdata/exec/wc_reduce.sh",
  "num_mappers": 2,
  "num_reducers": 2
}
Manager:6000 [INFO] Received job Job(job_id=0, finished=False)
Manager:6000 [INFO] Created output
Manager:6000 [INFO] Created /tmp/mapreduce-shared-7r6il3a5/job-00000

The Manager created a final output directory output and a job-00000 directory for intermediate files inside of the shared temporary directory.

Map Stage

The Map Stage runs.

Manager:6000 [INFO] Begin Map Stage
Manager:6000 [DEBUG] stage=Stage.MAP prev_stage=Stage.CREATED
Manager:6000 [INFO] Assigned task to Worker Task(task_id=0, input_paths=['file01'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2) RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "new_map_task",
  "task_id": 0,
  "input_paths": [
    "tests/testdata/input_small/file01"
  ],
  "executable": "tests/testdata/exec/wc_map.sh",
  "output_directory": "/tmp/mapreduce-shared-7r6il3a5/job-00000",
  "num_partitions": 2
}
Worker:6001 [INFO] Received task Task(task_id=0, input_paths=['file01'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2)
Worker:6001 [INFO] Created /tmp/mapreduce-local-task00000-umi_e5q7
Worker:6001 [INFO] Executed tests/testdata/exec/wc_map.sh input=tests/testdata/input_small/file01
Worker:6001 [INFO] Sorted /tmp/mapreduce-local-task00000-umi_e5q7/maptask00000-part00000
Worker:6001 [INFO] Sorted /tmp/mapreduce-local-task00000-umi_e5q7/maptask00000-part00001
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00000-umi_e5q7/maptask00000-part00000 -> /tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00000-part00000
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00000-umi_e5q7/maptask00000-part00001 -> /tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00000-part00001
Worker:6001 [INFO] Removed /tmp/mapreduce-local-task00000-umi_e5q7
Worker:6001 [INFO] Completed task Task(task_id=0, input_paths=['file01'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2)
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "finished",
  "task_id": 0,
  "worker_host": "localhost",
  "worker_port": 6001
}
Manager:6000 [INFO] Finished task from Worker Task(task_id=0, input_paths=['file01'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2) RemoteWorker('localhost', 6001)
Manager:6000 [DEBUG] stage=Stage.MAP prev_stage=Stage.MAP
Manager:6000 [INFO] Assigned task to Worker Task(task_id=1, input_paths=['file02'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2) RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "new_map_task",
  "task_id": 1,
  "input_paths": [
    "tests/testdata/input_small/file02"
  ],
  "executable": "tests/testdata/exec/wc_map.sh",
  "output_directory": "/tmp/mapreduce-shared-7r6il3a5/job-00000",
  "num_partitions": 2
}
Worker:6001 [INFO] Received task Task(task_id=1, input_paths=['file02'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2)
Worker:6001 [INFO] Created /tmp/mapreduce-local-task00001-l84s1em6
Worker:6001 [INFO] Executed tests/testdata/exec/wc_map.sh input=tests/testdata/input_small/file02
Worker:6001 [INFO] Sorted /tmp/mapreduce-local-task00001-l84s1em6/maptask00001-part00000
Worker:6001 [INFO] Sorted /tmp/mapreduce-local-task00001-l84s1em6/maptask00001-part00001
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00001-l84s1em6/maptask00001-part00000 -> /tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00001-part00000
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00001-l84s1em6/maptask00001-part00001 -> /tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00001-part00001
Worker:6001 [INFO] Removed /tmp/mapreduce-local-task00001-l84s1em6
Worker:6001 [INFO] Completed task Task(task_id=1, input_paths=['file02'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2)
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "finished",
  "task_id": 1,
  "worker_host": "localhost",
  "worker_port": 6001
}
Manager:6000 [INFO] Finished task from Worker Task(task_id=1, input_paths=['file02'], executable='wc_map.sh', output_directory='job-00000', num_partitions=2) RemoteWorker('localhost', 6001)
Manager:6000 [INFO] End Map Stage

In this run, Worker 6001 finished each task before the Manager assigned the next one, so Worker 6001 ran every task. Your Manager may split tasks between Workers differently.

Note how the Workers created local temporary directories of their own for each task, and then moved the output files into the Manager’s shared temporary directory when they finished writing and sorting.

At the end of the Map Stage, mapping, partitioning, and sorting is complete. In this example, the Manager’s shared temporary directory (e.g., /tmp/mapreduce-shared-XYZ123/job-00000) contains 4 files. To see these files during debugging, set a breakpoint before the Manager’s shared temporary directory is deleted.

hello	1
hello	1
eecs485	1
world	1
hello	1
autograder	1
autograder	1
goodbye	1

With 2 reducers, we have 2 partitions. For example when map task 0 computes md5("hello") % 2 == 0, this key goes in partition 0, maptask00000-part00000. Similarly, when map task 1 computes md5("hello") % 2 == 0, this key goes in partition 0, maptask00001-part00000.

Reduce Stage

Finally, the Reduce Stage runs.

Manager:6000 [INFO] Begin Reduce Stage
Manager:6000 [DEBUG] stage=Stage.REDUCE prev_stage=Stage.MAP
Manager:6000 [INFO] Assigned task to Worker Task(task_id=0, input_paths=['maptask00000-part00000', 'maptask00001-part00000'], executable='wc_reduce.sh', output_directory='output') RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "new_reduce_task",
  "task_id": 0,
  "input_paths": [
    "/tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00000-part00000",
    "/tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00001-part00000"
  ],
  "executable": "tests/testdata/exec/wc_reduce.sh",
  "output_directory": "output"
}
Worker:6001 [INFO] Received task Task(task_id=0, input_paths=['maptask00000-part00000', 'maptask00001-part00000'], executable='wc_reduce.sh', output_directory='output')
Worker:6001 [INFO] Created /tmp/mapreduce-local-task00000-jovdb59e
Worker:6001 [INFO] Executed tests/testdata/exec/wc_reduce.sh
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00000-jovdb59e/part-00000 -> output/part-00000
Worker:6001 [INFO] Removed /tmp/mapreduce-local-task00000-jovdb59e
Worker:6001 [INFO] Completed task Task(task_id=0, input_paths=['maptask00000-part00000', 'maptask00001-part00000'], executable='wc_reduce.sh', output_directory='output')
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "finished",
  "task_id": 0,
  "worker_host": "localhost",
  "worker_port": 6001
}
Manager:6000 [INFO] Finished task from Worker Task(task_id=0, input_paths=['maptask00000-part00000', 'maptask00001-part00000'], executable='wc_reduce.sh', output_directory='output') RemoteWorker('localhost', 6001)
Manager:6000 [DEBUG] stage=Stage.REDUCE prev_stage=Stage.REDUCE
Manager:6000 [INFO] Assigned task to Worker Task(task_id=1, input_paths=['maptask00000-part00001', 'maptask00001-part00001'], executable='wc_reduce.sh', output_directory='output') RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "new_reduce_task",
  "task_id": 1,
  "input_paths": [
    "/tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00000-part00001",
    "/tmp/mapreduce-shared-7r6il3a5/job-00000/maptask00001-part00001"
  ],
  "executable": "tests/testdata/exec/wc_reduce.sh",
  "output_directory": "output"
}
Worker:6001 [INFO] Received task Task(task_id=1, input_paths=['maptask00000-part00001', 'maptask00001-part00001'], executable='wc_reduce.sh', output_directory='output')
Worker:6001 [INFO] Created /tmp/mapreduce-local-task00001-eghs47n6
Worker:6001 [INFO] Executed tests/testdata/exec/wc_reduce.sh
Worker:6001 [INFO] Moved /tmp/mapreduce-local-task00001-eghs47n6/part-00001 -> output/part-00001
Worker:6001 [INFO] Removed /tmp/mapreduce-local-task00001-eghs47n6
Worker:6001 [INFO] Completed task Task(task_id=1, input_paths=['maptask00000-part00001', 'maptask00001-part00001'], executable='wc_reduce.sh', output_directory='output')
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "finished",
  "task_id": 1,
  "worker_host": "localhost",
  "worker_port": 6001
}
Manager:6000 [INFO] Finished task from Worker Task(task_id=1, input_paths=['maptask00000-part00001', 'maptask00001-part00001'], executable='wc_reduce.sh', output_directory='output') RemoteWorker('localhost', 6001)
Manager:6000 [INFO] End Reduce Stage
Manager:6000 [DEBUG] stage=Stage.FINISHED prev_stage=Stage.REDUCE
Manager:6000 [INFO] Removed /tmp/mapreduce-shared-7r6il3a5/job-00000
Manager:6000 [INFO] Finished job Job(job_id=0, finished=True)

Output

After the job is finished, we can see the final output inside output/.

$ tree output/
output/
├── part-00000
└── part-00001
hello	3
autograder	2
eecs485	1
goodbye	1
world	1

Recall that md5("hello") % 2 == 0, so this key is in partition 0 (part00000). Similarly, md5("autograder") % 2 == 1, so this key is in partition 1 (part00001).

Sort the output so that we can easily compare with the correct answer.

$ cat output/* | sort
autograder	2
eecs485	1
goodbye	1
hello	3
world	1
$ cat output/* | sort > actual.txt
$ diff correct.txt actual.txt

Shutdown

Finally, send a shutdown signal to the server.

$ mapreduce-submit --shutdown         # Terminal 4
Manager:6000 [DEBUG] TCP recv
{
  "message_type": "shutdown"
}
Manager:6000 [INFO] Received shutdown {'message_type': 'shutdown'}
Manager:6000 [INFO] Sent shutdown to Worker RemoteWorker('localhost', 6001)
Worker:6001 [DEBUG] TCP recv
{
  "message_type": "shutdown"
}
Worker:6001 [INFO] Received shutdown {'message_type': 'shutdown'}
Worker:6001 [INFO] Shutdown TCP server thread
Manager:6000 [INFO] Sent shutdown to Worker RemoteWorker('localhost', 6002)
Manager:6000 [INFO] Shutdown TCP server thread
Worker:6002 [DEBUG] TCP recv
{
  "message_type": "shutdown"
}
Worker:6002 [INFO] Received shutdown {'message_type': 'shutdown'}
Worker:6002 [INFO] Shutdown TCP server thread
Manager:6000 [INFO] Removed /tmp/mapreduce-shared-7r6il3a5
Manager:6000 [INFO] Shutdown UDP server thread
Manager:6000 [INFO] Shutdown main thread
Worker:6002 [INFO] Shutdown heartbeat thread
Worker:6002 [INFO] Shutdown main thread
Worker:6001 [INFO] Shutdown heartbeat thread
Worker:6001 [INFO] Shutdown main thread

After you run the shutdown command and get the above output, hit Enter. You should see something similar to the following:

[1] Done mapreduce-manager
[2]- Done mapreduce-worker
[3]+ Done mapreduce-worker --port 6002

Verify that no MapReduce processes are running.

$ pgrep -lf mapreduce-  # macOS
$ pgrep -af mapreduce-  # Linux

Acknowledgments

This document is licensed under a Creative Commons Attribution-NonCommercial 4.0 License. You’re free to copy and share this document, but not to sell it. You may not share source code provided with this document.