👌

Docker ComposeでHadoopとHiveを使ってみた

2024/09/28に公開

目的

プロジェクトでHadoopを利用するので、色々勉強したくて手軽に使える環境を作ってみました。

筆者の知識

  • Dockerは使える
  • Hadoop、Hiveは初めて使う

利用するデータ

警視庁が公開している交通事故統計情報を使います。2019年から2023年の全国の交通事故のデータが年度ごとのcsvファイルにまとまっています。今回は2019年から2021年の本票のデータを使用します。(2022年からCSVファイルの形式が変わっているため)

出典:
https://www.npa.go.jp/publications/statistics/koutsuu/opendata/index_opendata.html

データの下準備

Hiveテーブルを作る際に年度でパーティションを切るために各年度のディレクトリ配下にファイルをおきます。パーティションについてはのちほどHiveテーブル作成とあわせて説明します。

traffic_accident_data/
    - year=2019/
        - honhyo_2019.csv
    - year=2020/
        - honhyo_2020.csv
    - year=2021/
        - honhyo_2021.csv

Docker Compose

docker-compose.yml
x-hadoop-variables: &hadoop_environment
  CORE-SITE.XML_fs.default.name: hdfs://namenode
  CORE-SITE.XML_fs.defaultFS: hdfs://namenode
  HDFS-SITE.XML_dfs.namenode.rpc-address: namenode:8020
  HDFS-SITE.XML_dfs.replication: "1"

services:
  hive:
    image: apache/hive:3.1.3
    hostname: hive
    ports:
      - 10000:10000
      - 10002:10002
    environment:
      <<: *hadoop_environment
      HIVE_VERSION: 3.1.3
      SERVICE_NAME: hiveserver2

  namenode:
    image: apache/hadoop:3
    hostname: namenode
    command: ["hdfs", "namenode"]
    ports:
      - 9870:9870
    environment:
      <<: *hadoop_environment
      ENSURE_NAMENODE_DIR: "/tmp/hadoop-hadoop/dfs/name"

  datanode:
    image: apache/hadoop:3
    hostname: datanode
    command: ["hdfs", "datanode"]
    environment: *hadoop_environment

それぞれapache公式のDockerイメージから作成しました。環境変数は下記の公式ドキュメントを参考にしています。
https://hub.docker.com/r/apache/hadoop

HDFSにデータを転送する

docker compose cpコマンドでローカルのcsvをNameNodeコンテナにコピーします。

$ docker compose cp traffic_accident_data/ namenode:/tmp

NameNodeコンテナにログインします。

$ docker compose exec -it namenode /bin/bash

HDFSにCSVファイルをPUTします

NameNodeコンテナ
// HDFSにディレクトリ作成
$ hdfs dfs -mkdir /hadoop

// CSVを転送
$ hdfs dfs -put ./traffic_accident_data/ /hadoop/

// 確認
$ hdfs dfs -ls -R /hadoop/traffic_accident_data
drwxr-xr-x   - hadoop supergroup          0 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2019
-rw-r--r--   1 hadoop supergroup   69767380 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2019/honhyo_2019.csv
drwxr-xr-x   - hadoop supergroup          0 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2020
-rw-r--r--   1 hadoop supergroup   56580583 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2020/honhyo_2020.csv
drwxr-xr-x   - hadoop supergroup          0 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2021
-rw-r--r--   1 hadoop supergroup   55851877 2024-09-26 14:40 /hadoop/traffic_accident_data/year=2021/honhyo_2021.csv

Hiveテーブルを作成する

Hiveサーバのコンテナにログインします。

$ docker compose exec -it hive /bin/bash

 
beelineコマンドでHiveサーバにログインします。

Hiveコンテナ
$ beeline -u jdbc:hive2://localhost:10000/

 
HQLを実行しHiveテーブルを作成します

CREATE TABLE IF NOT EXISTS traffic_accidents (
  document_type INT,
  prefecture_code INT,
  police_station_code INT,
  ticket_number VARCHAR(20),
  accident_details VARCHAR(255),
  deaths INT,
  injuries INT,
  route_code VARCHAR(10),
  direction VARCHAR(10),
  location_code VARCHAR(10),
  municipality_code VARCHAR(10),
  occurrence_year INT,
  occurrence_month INT,
  occurrence_day INT,
  occurrence_hour INT,
  occurrence_minute INT,
  day_night VARCHAR(10),
  weather VARCHAR(50),
  terrain VARCHAR(50),
  road_condition VARCHAR(50),
  road_shape VARCHAR(50),
  roundabout_diameter DECIMAL(5,2),
  traffic_signal VARCHAR(10)
)
PARTITIONED BY (year STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION 'hdfs://namenode/hadoop/traffic_accident_data/'
TBLPROPERTIES ("skip.header.line.count"="1");

いくつかポイントを解説すると、
 

CREATE TABLE IF NOT EXISTS traffic_accidents (
  document_type STRING,
  prefecture_code INT,
  report_number STRING,
  ...

元のCSVのカラムが日本語なので独自に英語のカラム名を定義しています。
CSVのカラムが多いので今回は23カラムまでを取り込みました。
 

PARTITIONED BY (year STRING)

年度でパーティションを切るためにキーを指定しています。実際にパーティションにcsvをロードするのはこのあとに実行するクエリになります。
 

TBLPROPERTIES ("skip.header.line.count"="1");

CSVファイルの1行目をヘッダ行として無視して読み込みます.

テーブルを作成できたら年度ごとのCSVファイルをパーティションにロードします。

ALTER TABLE traffic_accidents ADD
PARTITION (year='2019') LOCATION 'hdfs://namenode/hadoop/traffic_accident_data/year=2019'
PARTITION (year='2020') LOCATION 'hdfs://namenode/hadoop/traffic_accident_data/year=2020'
PARTITION (year='2021') LOCATION 'hdfs://namenode/hadoop/traffic_accident_data/year=2021'

クエリ実行

年度ごとの事故件数

> SELECT year, COUNT(*) AS total_accidents
FROM traffic_accidents
GROUP BY year
ORDER BY year;

+-------+------------------+
| year  | total_accidents  |
+-------+------------------+
| 2019  | 381237           |
| 2020  | 309178           |
| 2021  | 305196           |
+-------+------------------+

2019年->2020年で事故件数が大きく減少してますね。

天候別の事故件数

> SELECT
    weather,
    SUM(CASE WHEN year = 2019 THEN weather ELSE 0 END) AS weather_sum_2019,
    SUM(CASE WHEN year = 2020 THEN weather ELSE 0 END) AS weather_sum_2020,
    SUM(CASE WHEN year = 2021 THEN weather ELSE 0 END) AS weather_sum_2021
FROM traffic_accidents
WHERE year IN (2019, 2020, 2021)
GROUP BY weather
ORDER BY weather;

+----------+-------------------+-------------------+-------------------+
| weather  | weather_sum_2019  | weather_sum_2020  | weather_sum_2021  |
+----------+-------------------+-------------------+-------------------+
| 1        | 252210            | 202322            | 208656            |
| 2        | 162064            | 127722            | 111530            |
| 3        | 134580            | 122649            | 111423            |
| 4        | 720               | 456               | 312               |
| 5        | 14775             | 9990              | 17780             |
+----------+-------------------+-------------------+-------------------+

コード定義は以下のとおり。

雨の日は事故が多いイメージがありますが、曇りの日と同じくらいなんですね。

市街地などの状況別

> SELECT
    terrain,
    SUM(CASE WHEN year = 2019 THEN terrain ELSE 0 END) AS terrain_sum_2019,
    SUM(CASE WHEN year = 2020 THEN terrain ELSE 0 END) AS terrain_sum_2020,
    SUM(CASE WHEN year = 2021 THEN terrain ELSE 0 END) AS terrain_sum_2021
FROM traffic_accidents
WHERE year IN (2019, 2020, 2021)
GROUP BY terrain
ORDER BY terrain;

+----------+-------------------+-------------------+-------------------+
| terrain  | terrain_sum_2019  | terrain_sum_2020  | terrain_sum_2021  |
+----------+-------------------+-------------------+-------------------+
| 1        | 186213            | 153396            | 155005            |
| 2        | 231820            | 189632            | 183136            |
| 3        | 237342            | 182898            | 175869            |
+----------+-------------------+-------------------+-------------------+

コード定義は以下の通り

人口が少ない地域の方が事故が多いですね。スピードを出しやすいからでしょうか。



※各コード定義は
https://www.npa.go.jp/publications/statistics/koutsuu/opendata/2019/codebook_2019.pdf から抜粋

Discussion