1台に入らない6TBの記録
ある全国チェーンのスーパーは、レジを通るたびに1件の記録を残します。2年たつと、記録は6TBになりました。
本社のどのディスクにも入りません。1台のパソコンでは、一度読み通すだけで8時間ほどかかります。
そこでビッグデータの道具は、ファイルを小さく切って、たくさんの機械に置きます。そして、各機械に自分の分だけ処理させます。
このレッスンのゴール
メモ
ファイルがどう分けられ、並行して処理されるかを説明できます。
キーワード
| ことば | いみ | れい |
|---|---|---|
| ブロック | 大きなファイルを決まった大きさに切った一片 | 128MB |
| NameNode | どのブロックがどこにあるかを知っている機械 | |
| DataNode | ブロックそのものを預かる機械 | |
| レプリカ | 別の機械に置く、ブロックの写し | |
| map | 各データを、キーと1の組に変える段階 | |
| reduce | 同じキーの組を足し合わせる段階 |
学生が入力するセルは、このプレビューでは空白です。
HDFSはファイルをブロックに切る
HDFSは、Hadoopのファイルシステムです。ファイルを、決まった大きさのブロックに切ります。初期値は128MBで、最後のブロックには残りが入ります。
各ブロックはDataNodeに置かれ、さらに別の機械へ写されます。機械が壊れても、何も失われません。NameNodeは1台だけで、ブロックの一覧と置き場所を覚えています。
例: 520MBのファイル
| ブロック | 大きさ | 残す写しの数 | 置き場所 |
|---|---|---|---|
| 1から4 | それぞれ128MB | それぞれ3つ | 別々の3台のDataNode |
| 5 | 8MB | 3つ | 別々の3台のDataNode |
| 合計 | 520MB | 15個の写し | NameNodeが持つのは一覧だけ |
学生が入力するセルは、このプレビューでは空白です。
ブロックの数を数える
# ファイルは、決まった大きさのブロックに切られます。
size_mb = 520
block_mb = 128
full = size_mb // block_mb
rest = size_mb % block_mb
blocks = full
if rest > 0:
blocks = blocks + 1
print("ブロックの数:", blocks)
print("最後のブロックの大きさ:", rest, "MB")
copies = blocks * 3
print("ディスクに残る写しの数:", copies)
# 520 を 900 に変えて、もう一度実行してみましょう。
ラボは小さな実物です
このラボのDataNodeは1台だけです。そのため、ブロックの写しは3つではなく1つです。考え方は同じで、本物のクラスタは機械が増えるだけです。
ラボのターミナルには、hadoopコマンドがありません。NameNodeのWebアドレスに、curlで尋ねます。
入力フォルダを一覧する
curl -s 'http://namenode:9870/webhdfs/v1/user/lab/input?op=LISTSTATUS' | grep -o '"pathSuffix":"[^"]*"'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ブロックの大きさと長さを尋ねる
curl -s 'http://namenode:9870/webhdfs/v1/user/lab/input/sentence.txt?op=GETFILESTATUS' | grep -o '"blockSize":[0-9]*\|"length":[0-9]*'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
写しをいくつ持つか尋ねる
curl -s 'http://namenode:9870/webhdfs/v1/user/lab/input/sentence.txt?op=GETFILESTATUS' | grep -o '"replication":[0-9]*'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
フォルダが使うディスクを尋ねる
curl -s 'http://namenode:9870/webhdfs/v1/user/lab/input?op=GETCONTENTSUMMARY' | grep -o '"spaceConsumed":[0-9]*'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
NameNodeがファイルを持っていると考えがちです。実際に持つのは、ブロックの一覧と置き場所だけです。データそのものはDataNodeにあります。「中身を持つのは誰か」と考えると、答えはDataNodeです。
MapReduceは仕事をデータへ送る
6TBをネットワークで運ぶと、とても遅くなります。そこでHadoopは、小さなプログラムを、ブロックを持つ機械へ送ります。
各機械は、自分のブロックにだけmapを実行し、ほかの機械とは話しません。できた組はキーごとに集められます。これをシャッフルと呼びます。最後にreduceが、集まりごとに足し合わせます。
例: レシートの単語を数える
| 段階 | ブロック1 | ブロック2 |
|---|---|---|
| 入力 | pan milk / milk rice | pan rice / pan milk |
| map | pan 1, milk 1, milk 1, rice 1 | pan 1, rice 1, pan 1, milk 1 |
| シャッフル (単語ごとに集める) | pan: 1, 1, 1 / milk: 1, 1, 1 / rice: 1, 1 | 両方のブロックの組がここで出会う |
| reduce (足し算) | pan 3, milk 3, rice 2 | 単語ごとに合計が1つ |
学生が入力するセルは、このプレビューでは空白です。
やさしいPythonでmapとreduce
# レシートの記録を、2つのブロックに分けたものです。
block_one = ["pan milk", "milk rice"]
block_two = ["pan rice", "pan milk"]
def apply_map(lines):
# mapの段階: 単語ごとに [単語, 1] の組を作ります。
pairs = []
for line in lines:
for word in line.split():
pairs.append([word, 1])
return pairs
def apply_reduce(pairs):
# reduceの段階: 同じ単語の1を足し合わせます。
totals = {}
for pair in pairs:
word = pair[0]
if word in totals:
totals[word] = totals[word] + pair[1]
else:
totals[word] = pair[1]
return totals
everything = apply_map(block_one) + apply_map(block_two)
print(apply_reduce(everything))
# block_two に "rice thyme" という行を足して、もう一度実行してみましょう。
ジョブは結果をHDFSに残す
MapReduceの1回の実行を、ジョブと呼びます。ResourceManagerが仕事を割り当て、結果はファイルとしてHDFSに書かれます。
リデューサーごとに、part-r- と番号のついたファイルができます。空の _SUCCESS というファイルがあれば、ジョブは終わっています。
ジョブの出力フォルダを一覧する
curl -s 'http://namenode:9870/webhdfs/v1/user/lab/wordcount-out?op=LISTSTATUS' | grep -o '"pathSuffix":"[^"]*"'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
単語 the の合計を読む
curl -s -L 'http://namenode:9870/webhdfs/v1/user/lab/wordcount-out/part-r-00000?op=OPEN' | grep '^the'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ジョブの終わり方を尋ねる
curl -s http://resourcemanager:8088/ws/v1/cluster/apps | grep -o '"finalStatus":"[A-Z]*"'ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
さらに先へ: 送る前に足す
ブロック2は、シャッフルへ4つの組を送ります。そのうち2つは「pan 1」です。本物のジョブでは、各ブロックの中で先に足すことができます。この段階をコンバイナーと呼びます。
MapReduceのコードの apply_map を、自分のブロックの合計を組で返すように変えて、実行してください。ブロック2から出る組はいくつになりますか。最後の答えは同じですか。