ラベル hive の投稿を表示しています。 すべての投稿を表示
ラベル hive の投稿を表示しています。 すべての投稿を表示

2014年2月5日水曜日

EMRってなんじゃ?(ImpalaでCloudfrontの爆速ログ集計)




EMRがImpalaをサポートするようになりました。ImpalaはClouderaが提供するオープンソースのクエリエンジンで、Hiveより断然速いそうです。

例として、Cloudfrontのログを、タイムスタンプをJSTに直して1時間ごとのアクセス数の集計をしてみます。


ログバケットの確認


まずCloudFrontのログが以下のS3にたまっているとします。
s3://memorycraft-impala-input/cf/logs





EMRクラスタの起動


次に、EMRクラスタを起動します。
EMRのダッシュボードで「Create Cluster」をクリックし、新規クラスタ作成画面を表示します。

Cluster Configuration

Cluster nameに適当なクラスタ名を入力します。また、今回はEMRのログは出力しないのでLoggingのチェックはOFFなんかにしておきます。起動したインスタンスの名前をつける場合は、TagsのNameとしてインスタンス名をつけておきます。



Software Configuration

Hadoopのバージョンとアプリケーションのリストを設定します。
Applications to be installedのリストにはデフォルトでHiveとPigが追加されていますが、
AMI versionは現時点で最新の3.0,2を選ぶち、下のAdditional applicationsのドロップダウンでImpalaが選択できるようになります。
Impalaを選択し、「Configure and add」をクリックして追加します。



Hardware Configuration

ここでVPCやゾーンやインスタンス数を設定します。
ここでは、EC2-Classicで、ゾーンはA、インスタンス数はデフォルトにしてみます。



Security and Access

EC2 key pairで、hadoopインスタンスに接続するときのキーペア名を設定します。
ここが未定義だと、インスタンスに接続できないので注意が必要です。



Bootstrap Actions & Steps

ここでは、起動した後の挙動や起動パラメータなどを定義します。
今回は未設定のままでOKです。
最後に「Create cluster」ボタンをクリックして、クラスタを作成します。





EMRのインスタンスへの接続


起動完了するとクラスタ詳細画面で、Master public DNSに、マスタインスタンスのPublicDNSが表示されます。


そのPublicDNSに対して、先ほど選択した鍵を使ってhadoopユーザーでSSH接続します。
$ ssh -i ~/.ssh/keys/memorycraft.pem hadoop@ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com
The authenticity of host 'ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com (54.199.41.72)' can't be established.
RSA key fingerprint is 0f:ac:d5:8d:50:2b:7c:92:6a:ad:74:5e:f3:d1:52:05.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-54-199-41-72.ap-northeast-1.compute.amazonaws.com,54.199.41.72' (RSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2013.09-release-notes/
13 package(s) needed for security, out of 46 available
Run "sudo yum update" to apply all updates.
--------------------------------------------------------------------------------

Welcome to Amazon Elastic MapReduce running Hadoop and Amazon Linux.

Hadoop is installed in /home/hadoop. Log files are in /mnt/var/log/hadoop. Check
/mnt/var/log/hadoop/steps for diagnosing step failures.

The Hadoop UI can be accessed via the following commands:

  ResourceManager    lynx http://localhost:9026/
  NameNode           lynx http://localhost:9101/

--------------------------------------------------------------------------------
$
接続が完了しました。



S3データのコピー


現状、ImpalaでExternalテーブルにS3のパスを直接指定するとエラーになってしまいました。方法がわからなかったため、S3DistCpでHDFS上の/input/にコピーしてきます。
$ vim ./import.sh
#!/bin/bash

. /home/hadoop/impala/conf/impala.conf

hadoop jar /home/hadoop/share/hadoop/common/lib/EmrS3DistCp-1.0.jar -Dmapreduce.job.reduces=30 --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://$HADOOP_NAMENODE_HOST:$HADOOP_NAMENODE_PORT/input/ --outputCodec 'none'

$ chmod 755 ./import.sh
$ ./import.sh
14/02/02 20:12:41 INFO s3distcp.S3DistCp: Running with args: -Dmapreduce.job.reduces=30 --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/02 20:12:41 INFO s3distcp.S3DistCp: S3DistCp args: --src s3://memorycraft-impala-input/cf/logs/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/02 20:12:45 INFO s3distcp.S3DistCp: Using output path 'hdfs:/tmp/8f7379de-1ebf-4f31-be50-bd17aa54f2d5/output'
14/02/02 20:12:46 INFO s3distcp.S3DistCp: GET http://169.254.169.254/latest/meta-data/placement/availability-zone result: ap-northeast-1a
14/02/02 20:12:46 INFO s3distcp.S3DistCp: Created AmazonS3Client with conf KeyId XXXXXXXXXXXXXX
〜(中略)〜
  Combine output records=0
  WRONG_LENGTH=0
  WRONG_MAP=0
  WRONG_REDUCE=0
 File Input Format Counters
  Bytes Read=3406
 File Output Format Counters
  Bytes Written=0
14/02/02 20:13:37 INFO s3distcp.S3DistCp: Try to recursively delete hdfs:/tmp/8f7379de-1ebf-4f31-be50-bd17aa54f2d5/tempspace

コピーの実行が終わりました。HDFSの内容を見ると、コピーされているのが分かります。
$ hadoop fs -ls /input/
Found 20 items
-rw-r--r--   1 hadoop supergroup       1728 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.21fcnUK5
-rw-r--r--   1 hadoop supergroup       1726 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.9jiEU8sU
-rw-r--r--   1 hadoop supergroup       1477 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.9pD0UZAy
-rw-r--r--   1 hadoop supergroup       4996 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.CECpH1q4
-rw-r--r--   1 hadoop supergroup       3636 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.DmeAD298
-rw-r--r--   1 hadoop supergroup        979 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.GhmYMPjI
-rw-r--r--   1 hadoop supergroup       1915 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.IvAE5n9h
-rw-r--r--   1 hadoop supergroup       1853 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.K3Crm40n
-rw-r--r--   1 hadoop supergroup       6120 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.KILmQ81g
-rw-r--r--   1 hadoop supergroup        969 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.KdRXIw4s
-rw-r--r--   1 hadoop supergroup       1731 2014-02-02 20:13 /input/E2KNLXPGJLJRRQ.2014-01-30-07.MIAnZGc4
.....



Impalaの実行


Impalaはimpala-shellというコマンドで専用のプロンプトを起動して使います。hiveのhiveコマンドと同じようなものです。
$ impala-shell
Starting Impala Shell without Kerberos authentication
Connected to ip-10-146-59-193.ap-northeast-1.compute.internal:21000
Server version: impalad version 1.2.1 RELEASE (build 8c1da7709727f3545974009a4bb677a0004024ec)
Welcome to the Impala shell. Press TAB twice to see a list of available commands.

Copyright (c) 2012 Cloudera, Inc. All rights reserved.

(Shell build version: Impala Shell v1.2.1 (8c1da77) built on Sun Dec  1 20:57:24 PST 2013)
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >

まずは、入力テーブルをつくります。ファイルの場所はS3DistCpのコピー先に指定した/input/を指定します。
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >
CREATE EXTERNAL TABLE IF NOT EXISTS input (
  cf_date STRING,
  cf_time STRING,
  x_edge_location STRING,
  sc_bytes INT,
  c_ip STRING,
  cs_method STRING,
  cs_host STRING,
  cs_uri_stem STRING,
  sc_status STRING,
  cs_referrer STRING, 
  cs_user_agent STRING,
  cs_uri_query STRING,
  cs_cookie STRING,
  x_edge_result_type STRING,
  x_edge_request_id STRING,
  x_host_header STRING,
  cs_protocol STRING,
  cs_bytes INT
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/input/';

それでは、クエリを実行してみます。
[ip-10-146-59-193.ap-northeast-1.compute.internal:21000] >
SELECT
  w.jsttime,
  count(mydata)
FROM
  (
    SELECT
      SUBSTR(
        CAST(
          FROM_UTC_TIMESTAMP(
            FROM_UNIXTIME(
              UNIX_TIMESTAMP(CONCAT(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')
            ), 'JST'
          ) as STRING
        ), 1, 13
      ) AS jsttime,
      'AAA' AS mydata 
    FROM
      input
    WHERE
      cf_date NOT LIKE '#%'
  ) w
GROUP BY
  w.jsttime
;
Query: select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w group by w.jsttime
+---------------+---------------+
| jsttime       | count(mydata) |
+---------------+---------------+
| 2014-01-30 16 | 95            |
| 2014-01-30 17 | 33            |
+---------------+---------------+
Returned 2 row(s) in 0.88s
1秒かかりませんでした。



Hiveとの速度比較


これと同じ集計をHiveで行ってみます。
Total MapReduce jobs = 1
Launching Job 1 out of 1
Number of reduce tasks not specified. Estimated from input data size: 1
In order to change the average load for a reducer (in bytes):
  set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:
  set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:
  set mapred.reduce.tasks=<number>
Starting Job = job_1391362544864_0002, Tracking URL = http://10.132.128.116:9046/proxy/application_1391362544864_0002/
Kill Command = /home/hadoop/bin/hadoop job  -kill job_1391362544864_0002
Hadoop job information for Stage-1: number of mappers: 2; number of reducers: 1
2014-02-02 17:46:18,013 Stage-1 map = 0%,  reduce = 0%
2014-02-02 17:46:29,536 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:30,585 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:31,646 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:32,707 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:33,773 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:34,822 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:35,872 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:36,925 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 4.92 sec
2014-02-02 17:46:37,975 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
2014-02-02 17:46:39,034 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
2014-02-02 17:46:40,083 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 6.85 sec
MapReduce Total cumulative CPU time: 6 seconds 850 msec
Ended Job = job_1391362544864_0002
Counters:
MapReduce Jobs Launched:
Job 0: Map: 2  Reduce: 1   Cumulative CPU: 6.85 sec   HDFS Read: 58636 HDFS Write: 34 SUCCESS
Total MapReduce CPU Time Spent: 6 seconds 850 msec
OK
2014-01-30 16 95
2014-01-30 17 33
Time taken: 40.334 seconds, Fetched: 2 row(s)
40秒も掛かりました。いかにImpalaが速いかがわかります。



8億レコードでやってみた


上と全く同じ内容のクエリを使って、8億レコードのCloudFrontログ解析をImpala上で実行してみました。

データコピー(S3→HDFS)

$ . /home/hadoop/impala/conf/impala.conf
$
$ hadoop jar /home/hadoop/share/hadoop/common/lib/EmrS3DistCp-1.0.jar -Dmapreduce.job.reduces=30 --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://$HADOOP_NAMENODE_HOST:$HADOOP_NAMENODE_PORT/input/ --outputCodec 'none'
14/02/03 05:21:10 INFO s3distcp.S3DistCp: Running with args: -Dmapreduce.job.reduces=30 --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/03 05:21:10 INFO s3distcp.S3DistCp: S3DistCp args: --src s3://cloudfront-big-log/logs/cf/ --dest hdfs://172.31.0.95:9000/input/ --outputCodec none
14/02/03 05:21:14 INFO s3distcp.S3DistCp: Using output path 'hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/output'
14/02/03 05:21:15 INFO s3distcp.S3DistCp: GET http://169.254.169.254/latest/meta-data/placement/availability-zone result: ap-northeast-1a
14/02/03 05:21:15 INFO s3distcp.S3DistCp: Created AmazonS3Client with conf KeyId XXXXXXXXXXXXXXXXXXXXXXXX
14/02/03 05:21:16 INFO s3distcp.S3DistCp: Skipping key 'logs/cf/' because it ends with '/'
14/02/03 05:21:16 INFO s3distcp.FileInfoListing: Opening new file: hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/files/1
14/02/03 05:21:57 INFO s3distcp.S3DistCp: Created 1 files to copy 211761 files
14/02/03 05:21:57 INFO s3distcp.S3DistCp: Reducer number: 29
〜(中略)〜
  CPU time spent (ms)=16164160
  Physical memory (bytes) snapshot=18180935680
  Virtual memory (bytes) snapshot=53996851200
  Total committed heap usage (bytes)=14206107648
 Shuffle Errors
  BAD_ID=0
  CONNECTION=0
  IO_ERROR=0
  WRONG_LENGTH=0
  WRONG_MAP=0
  WRONG_REDUCE=0
 File Input Format Counters
  Bytes Read=39560891
 File Output Format Counters
  Bytes Written=0
14/02/03 06:30:50 INFO s3distcp.S3DistCp: Try to recursively delete hdfs:/tmp/9dc925c6-0f4e-4043-a19a-f21930f89b6b/tempspace


入力テーブル作成(input)

$ impala-shell
>
> CREATE EXTERNAL TABLE IF NOT EXISTS input (
>   cf_date STRING,
>   cf_time STRING,
>   x_edge_location STRING,
>   sc_bytes INT,
>   c_ip STRING,
>   cs_method STRING,
>   cs_host STRING,
>   cs_uri_stem STRING,
>   sc_status STRING,
>   cs_referrer STRING,
>   cs_user_agent STRING,
>   cs_uri_query STRING,
>   cs_cookie STRING,
>   x_edge_result_type STRING,
>   x_edge_request_id STRING,
>   x_host_header STRING,
>   cs_protocol STRING,
>   cs_bytes INT
> )
> ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
> LOCATION '/input/';
Query: create EXTERNAL TABLE IF NOT EXISTS input ( cf_date STRING, cf_time STRING, x_edge_location STRING, sc_bytes INT, c_ip STRING, cs_method STRING, cs_host STRING, cs_uri_stem STRING, sc_status STRING, cs_referrer STRING, cs_user_agent STRING, cs_uri_query STRING, cs_cookie STRING, x_edge_result_type STRING, x_edge_request_id STRING, x_host_header STRING, cs_protocol STRING, cs_bytes INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/input/'

Returned 0 row(s) in 266.06s


入力件数

> select count(*) from input;
Query: select count(*) from input

+-----------+
| count(*)  |
+-----------+
| 820545673 |
+-----------+

Returned 1 row(s) in 642.03s


出力テーブル作成(output)

> CREATE EXTERNAL TABLE IF NOT EXISTS output_pv (
>   dth STRING,
>   cnt BIGINT
> )
> ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
> LOCATION '/output_pv/';
Query: create EXTERNAL TABLE IF NOT EXISTS output_pv ( dth STRING, cnt BIGINT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/output_pv/'

Returned 0 row(s) in 2.23s


解析(input→output)

> insert into output
> select w.jsttime, count(mydata) from
> (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w
> group by w.jsttime
> ;
Query: insert into output select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%') w group by w.jsttime

Inserted 759 rows in 14940.46s



結果

データのコピー(S3→HDFS)約1時間
テーブル作成(input)約4分
テーブル作成(output)約2秒
解析(input→output)約4時間

想像していたより全然速いです。


ちなみに、特定のURIに絞ってクエリを実行したら15分ほどで終わってしまいました。
> insert into output_pv
> select w.jsttime, count(mydata) from
> (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%' and (cs_uri_stem = '/' or cs_uri_stem = '/index.html')) w
> group by w.jsttime
> ;
Query: insert into output_pv select w.jsttime, count(mydata) from (select substr(cast(from_utc_timestamp(from_unixtime(unix_timestamp(concat(cf_date, " ", cf_time), 'yyyy-MM-dd HH:mm:ss')), 'JST') as STRING), 1, 13) as jsttime, 'AAA' as mydata  from input where cf_date not like '#%' and (cs_uri_stem = '/' or cs_uri_stem = '/index.html')) w group by w.jsttime

Inserted 755 rows in 852.34s

今回はHiveで8億レコードは試せませんでしたが、いままでHiveを使用していた感覚からするとImpalaは異常なほどの速さです。
これからEMRでクエリを使用する場合はImpala一択になりそうです。

以上です。



2013年4月15日月曜日

EMRってなんじゃ?(HiveでS3のログをJSTに変換して1日分をまとめる)

S3のwebホスティングで、ログ出力の設定をしていた場合、ログファイルが大量に出力されます。
ログの記録時間は標準時で出力されていてわかりづらいです。

今回はEMRのHiveを利用して、日本時間の0時〜翌日の0時までのログを1ファイルにまとめてみたいと思います。


HiveScript



HiveScriptは以下のようにします。
まずフォーマット解析用のJARを読み込みます。

add jar /home/hadoop/hive/contrib/hive-contrib-0.8.1.jar;

入力用のテーブルを定義します。LOCATIONは引数から受け取ります。
ネックとなるのは、S3のログは基本的に半角スペース区切りですが、項目の値の中に半角スペースが入り込むため、「FIELDS TERMINATED BY」句ではなく、SERDEを使用して正規表現でログの1行を項目分けします。
今回は日付フィールドはそのまま年月日と時間部分に分けてしまいます。
CREATE EXTERNAL TABLE IF NOT EXISTS log (
 bucket_owner string,
 bucket_name string,
 log_time string,
 log_timezone string,
 remote_ip string,
 requester string,
 request_id string,
 operation string,
 key string,
 request_uri string,
 http_status string,
 error_code string,
 bytes_sent string,
 object_size string,
 total_time string,
 turn_around_time string,
 referer string,
 user_agent string,
 version_id string
) ROW FORMAT SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  "input.regex" = "([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ \"]*|\"[^\"]*\") ([^ ]*) ([^ ]*) (-|[0-9]*) (-|[0-9]*) (-|[0-9]*) (-|[0-9]*) ([^ ]*) ([^ \"]*|\"[^\"]*\") ([^ ]*)",
      "output.format.string" = "%1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s %10$s %11$s %12$s %13$s %14$s %15$s %16$s %17$s %18$s %19$s"
)
LOCATION '${INPUT_BUCKET_LOCATION}';



出力用のテーブルを定義します。LOCATIONは引数から取得します。
パーティションはyyyy,mm,ddという単位で分割します。
CREATE EXTERNAL TABLE IF NOT EXISTS log_archive (
 bucket_owner string, 
 bucket_name string, 
 log_time string,  
 remote_ip string, 
 requester string, 
 request_id string, 
 operation string, 
 key string, 
 request_uri string, 
 http_status string, 
 error_code string, 
 bytes_sent string, 
 object_size string, 
 total_time string, 
 turn_around_time string, 
 referer string, 
 user_agent string, 
 version_id string
 )
 PARTITIONED BY (yyyy string, mm string, dd string)
 ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' STORED AS TEXTFILE LOCATION '${OUTPUT_BUCKET_LOCATION}'; 


集計設定をします。
DynamicPartitionを無効にし、出力をgz圧縮します。
set hive.exec.dynamic.partition= false;
set hive.exec.compress.output = true;


集計します。
対象日のYYYY、MM、DDを引数から受け取ります。
ポイントとなるのは日付の扱いで、日時分割した年月日の方のカラムを0:00:00としてタイムスタンプに変換しJST⇛UTC変換したもので当日分のログとしてフィルタしています。
INSERT INTO TABLE log_archive PARTITION (yyyy='${YYYY}', mm='${MM}', dd='${DD}')
SELECT
 bucket_owner,
 bucket_name,
 concat("[", from_utc_timestamp(from_unixtime(unix_timestamp(concat(log_time, " ", log_timezone), '[dd/MMM/yyyy:HH:mm:ss Z]')), 'JST')," +0900]") as jsttime,
 remote_ip,
 requester,
 request_id,
 operation,
 key,
 request_uri,
 http_status,
 error_code,
 bytes_sent,
 object_size,
 total_time,
 turn_around_time,
 referer,
 user_agent,
 version_id
 FROM
 log
 WHERE
 unix_timestamp(concat(log_time, " ", log_timezone), '[dd/MMM/yyyy:HH:mm:ss Z]') >= unix_timestamp(to_utc_timestamp(concat('${YYYY}-${MM}-${DD}', ' 00:00:00'), 'JST')) 
AND
 unix_timestamp(concat(log_time, " ", log_timezone), '[dd/MMM/yyyy:HH:mm:ss Z]') < unix_timestamp(to_utc_timestamp(concat(date_add('${YYYY}-${MM}-${DD}', 1),' 00:00:00'), 'JST'))
 SORT BY
 jsttime
;



実行


Hiveジョブフローの主な起動設定などは以下の通りです。
Extra ArgsでHiveスクリプトに渡す引数を設定しています。





実行すると以下のように出力されます。




中身を見ると以下のように1日分がまとまっています。
また、日時の部分はJSTに変換して出力されています。

b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:44 +0900],10.115.82.47,arn:aws:iam::821635308497:user/iam-user,52A74398D7C4DBFE,REST.GET.VERSIONING,-,"GET /myfirst-bucket?versioning HTTP/1.1",200,-,162,-,28,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:44 +0900],10.115.82.47,arn:aws:iam::821635308497:user/iam-user,7CB888CE4F00C988,REST.GET.BUCKET,-,"GET /myfirst-bucket?prefix=&max-keys=100&marker=&delimiter=/ HTTP/1.1",200,-,1254,-,1044,1043,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:44 +0900],10.15.128.6,arn:aws:iam::821635308497:user/iam-user,473043B904924EB1,REST.GET.LOCATION,-,"GET /myfirst-bucket?location HTTP/1.1",200,-,142,-,29,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:44 +0900],10.15.149.57,arn:aws:iam::821635308497:user/iam-user,E1A726EAAA9ED195,REST.GET.LOCATION,-,"GET /myfirst-bucket?location HTTP/1.1",200,-,142,-,47,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:49 +0900],10.115.82.47,arn:aws:iam::821635308497:user/iam-user,648FA2CF0669ED48,REST.GET.VERSIONING,-,"GET /myfirst-bucket?versioning HTTP/1.1",200,-,162,-,26,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:49 +0900],10.115.82.47,arn:aws:iam::821635308497:user/iam-user,4620CF20BB92DE10,REST.GET.BUCKET,-,"GET /myfirst-bucket?prefix=&max-keys=100&marker=&delimiter=/ HTTP/1.1",200,-,1254,-,601,600,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:51 +0900],10.115.82.47,arn:aws:iam::821635308497:user/iam-user,AD489CC457FB430B,REST.GET.ACL,-,"GET /myfirst-bucket?acl HTTP/1.1",200,-,1317,-,406,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:51 +0900],10.115.144.24,arn:aws:iam::821635308497:user/iam-user,700100DED6F992D5,REST.GET.NOTIFICATION,-,"GET /myfirst-bucket?notification HTTP/1.1",200,-,115,-,21,-,"-","S3Console/0.4",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 01:29:51 +0900],10.115.144.24,arn:aws:iam::821635308497:user/iam-user,EEF9831033863E2B,REST.GET.LOGGING_STATUS,-,"GET /myfirst-bucket?logging HTTP/1.1",200,-,932,-,414,-,"-","S3Console/0.4",-

〜〜中略〜〜

+0900],10.89.198.21,b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,139A543A03AB1E01,REST.GET.ACL,-,"GET /?acl=&x-amz-security-token=AQYGQXBwVGtubLMUm1%2BEi%2BJ69YRAEYru2QGPPayzdgIKktgcIhBULb3hBbeFaN3DjPydiCyvDuvQ7g4kucsy0nQsWnAMBR2zfK1R%2B7Y7%2BdVgxVeH2fVJ9FQLFTkg2kkCaVj%2Fj%2FEWi8r%2F1hShQ4prv8OLBfDFETmtdw%3D%3D HTTP/1.1",200,-,1317,-,14,-,"-","aws-sdk-java/unknown-version Linux/2.6.18-194.17.4.el5.acc4xen Java_HotSpot(TM)_Server_VM/20.12-b01",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 17:36:44 +0900],10.89.198.21,b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,5FA69CCF9D1F8ABF,REST.GET.BUCKET,-,"GET /?max-keys=0&x-amz-security-token=AQAGQXBwVGtu2GApXRs%2FDi9mf5qWz55MQRSyS9v%2FnmGvqPo7lsP%2BAkR1V57UhWpJlAn6tYGp2tOL%2BC6Ai1BrRVOWUdUp6JkO%2FMqep7zd4h%2B5xJP8HtcO6pEWEV6t%2BikUh0qYfG8TatCWKvh15j6qu3XExYR5fnveRw%3D%3D HTTP/1.1",200,-,237,-,19,19,"-","aws-sdk-java/unknown-version Linux/2.6.18-194.17.4.el5.acc4xen Java_HotSpot(TM)_Server_VM/20.12-b01",-
b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,myfirst-bucket,[2013-03-20 17:36:44 +0900],10.89.198.21,b00fb3b3fbeb37e2dc44f010cdbeff2c31bc467a2022f00401bf18abbf9e4543,EB88F164094C5ECB,REST.GET.ACL,-,"GET /?acl=&x-amz-security-token=AQEGQXBwVGtuw71vZ%2F%2BhXOKS3VoBVeYAGzc2Csc0R73DDmsCndxf9cMwWqyG9fGRYM%2F%2BJVpuKk4gdS%2Ftr64M7O2VA%2BmzWfPUz8eSwYA3zvEmdBvC8JJrDlvDImPfhqi8mXHF5weAqj2byecuDWLTqJ7AQZBtY3b8dQ%3D%3D HTTP/1.1",200,-,1317,-,26,-,"-","aws-sdk-java/unknown-version Linux/2.6.18-194.17.4.el5.acc4xen Java_HotSpot(TM)_Server_VM/20.12-b01",-


これで日本時間でのアクセス集計ができました。 以上です。

2013年2月22日金曜日

EMRってなんじゃ?(HiveScriptの場合)

前回の記事ではSSHでEMRの中でhiveコンソールをつかって手動でクエリを打ちましたが、通常のプログラムなどからではHiveScriptで起動と同時に実行することが多いと思うので、細かいことですが前回との違いを紹介します。

HiveScriptの場合はスクリプトファイルの場所と、オプションで入力ファイルのS3ロケーションと出力ファイルのS3ロケーションなどを設定します。
前回やったとおり、入出力のロケーションについてはHiveScript中にもEXTERNAL TABLEとして記載できるのでここではHiveScriptのみの設定でOKです。

ただしHiveScriptの場合、スクリプトのS3ロケーションの部分を前回と内容を少しかえる必要があります。
前回は入力のバケットファイルをファイルパスまで指定しましたが、今回はバケットフォルダで指定し、その下の全てのファイルが入力となります。



hive.q
CREATE EXTERNAL TABLE IF NOT EXISTS csv_origin (yyyy string, mm string, dd string, title string, body string) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' LOCATION 's3://csv-origin/input/';

CREATE EXTERNAL TABLE IF NOT EXISTS csv_archive (dd string, title string, body string) PARTITIONED BY (yyyy string, mm string) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' STORED AS TEXTFILE LOCATION 's3://csv-archive/rslt/';                                                              

set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
FROM csv_origin co INSERT OVERWRITE TABLE csv_archive PARTITION (yyyy, mm)
SELECT
 co.dd,
 co.title,
 co.body,
 co.yyyy,
 co.mm
DISTRIBUTE BY co.yyyy, co.mm;



スクリプト用のバケットにスクリプトファイルを配置します。



EMRでJob Flowを作成し、Hive Programを選択します。
いまアップしたスクリプトファイルの場所を設定します。

Script Location: s3://hivescript-test/hive.q



あとはそのまま実行します。
しばらくするとジョブが終了し、出力を確認できます。




03,title32,body32
16,title33,body33



以上です。

2013年1月28日月曜日

EMRってなんじゃ?(HiveのDynamicPartitionでファイルを分割してS3に出力)

S3にある数年分のデータが1つのファイルに含まれていて、EMRで年や月単位で分割したいという要望が一部であったので、やってみました。

例として、以下のようなCSVがあるとします。

test.csv
2012,01,12,title1,body1
2012,01,22,title2,body2
2012,02,02,title3,body3
2012,03,01,title4,body4
2012,03,11,title5,body5
2012,03,18,title6,body6
2012,04,04,title7,body7
2012,05,02,title8,body8
2012,05,05,title9,body9
2012,06,13,title10,body10
2012,06,14,title11,body11
2012,06,29,title12,body12
2012,07,03,title13,body13
2012,07,05,title14,body14
2012,07,14,title15,body15
2012,07,30,title16,body16
2012,08,12,title17,body17
2012,09,23,title18,body18
2012,10,10,title19,body19
2012,11,23,title20,body20
2012,12,22,title21,body21
2013,01,04,title22,body22
2013,01,05,title23,body23
2013,01,06,title24,body24
2013,01,17,title25,body25
2013,01,18,title26,body26
2013,01,23,title27,body27
2013,02,03,title28,body28
2013,02,04,title29,body29
2013,02,05,title30,body30
2013,02,06,title31,body31
2013,03,03,title32,body32
2013,03,16,title33,body33
2013,04,03,title34,body34
2013,04,04,title35,body35
2013,04,09,title36,body36
2013,04,12,title37,body37
2013,04,14,title38,body38
2013,04,17,title39,body39


まず、入力用のバケットと出力用のバケットを用意します。

入力
 csv-origin(上記のtest.csvをアップしておきます。)
出力
 csv-archive (rsltというフォルダを作成しておきます。)





EMRでジョブフローを作成します。今回はインタラクティブジョブフローを使います。
これはEMRクラスタにSSHで入って、hiveコンソールで直接クエリを実行する方式です。

まず、EMRで「Create New Job Flow」をクリックします。
ダイアログが立ち上がるのでJob FlowにHive Programを選択します。




今回HiveScriptは使用せずインタラクティブなので、「Start as Interactive Hive Session」を選択肢ます。




インスタンス数やサイズなどはとりあえずそのままでOKです。




SSHでログインするときのキーを選択して




以降はそのまま「Continue」を続けます。




すると、以下のようにジョブフローが起ち上がり、マスターノードのエンドポイントが記されます。




このエンドポイントに対して、指定したキーを使ってhadoopユーザーでSSHログインします。
ssh -i ./memorycraft.pem hadoop@ec2-175-41-212-129.ap-northeast-1.compute.amazonaws.com

hiveコマンドを打つとhiveコンソールに接続されます。
$ hive
Logging initialized using configuration in file:/home/hadoop/.versions/hive-0.8.1/conf/hive-log4j.properties
Hive history file=/mnt/var/lib/hive_081/tmp/history/hive_job_log_hadoop_201301280945_987980527.txt
hive>


ここでまず入力用バケットを表すテーブルを作成します。
  • LOCATION句で入力用S3バケットのtest.csvのパスを指定します。
hive> CREATE EXTERNAL TABLE IF NOT EXISTS csv_origin (yyyy string, mm string, dd string, title string, body string)
      > ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'
      > LOCATION 's3://csv-origin/test.csv';



次に、出力用のバケットを表すテーブルを作成します。
  • 年ごと月ごとにフォルダ分けされるようにyyyyとmmでPARTITIONED BYを指定します。
  • テーブルの項目にはyyyy, mm以外の項目を指定します。
  • 出力ファイルがテキストファイルになるようSTORED AS TEXTFILEを指定します。
  • LOCATION句で出力用S3バケットのパスを指定します。
hive> CREATE EXTERNAL TABLE IF NOT EXISTS csv_archive (dd string, title string, body string) PARTITIONED BY (yyyy string, mm string)
      > ROW FORMAT DELIMITED
      > FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'
      > STORED AS TEXTFILE
      > LOCATION 's3://csv-archive/rslt/';



あとはINSERT ~ SELECTなのですが、普通にやると、PARTITION(yyyy='2012', mm='02')のようにパーティションを決め打ちで指定しないといけなくなるので、DynamicPartitionというものを使います。
(通常のパーティションは別の記事で改めて書いてみたいと思います。)

DynamicPartitionを有効にするには、hive.exec.dynamic.partitionとhive.exec.dynamic.partition.modeというパラメータをセットしつつ、以下の様な構文を使用します。
hive> set hive.exec.dynamic.partition=true;
hive> set hive.exec.dynamic.partition.mode=nonstrict;
hive> FROM csv_origin co
      > INSERT OVERWRITE TABLE csv_archive PARTITION (yyyy, mm)
      > SELECT
      >  co.dd,
      >  co.title,
      >  co.body,
      >  co.yyyy,
      >  co.mm
      > DISTRIBUTE BY co.yyyy, co.mm;


このクエリにより、Map/Reduceが行われ、完了すると出力用のバケットに以下のようにフォルダが作成され、それぞれのフォルダに該当データが集約されたファイルが出力されます。



たとえば、yyyy=2013/mm=03というフォルダに出力されたファイルには以下の様に2013/03のデータだけが記述されています。

03,titile32,body32
16,titile33,body33

無事に年月単位でデータが分割されるようになりました。
今回のサンプルは数がすくないですが、大量にバラバラなデータが入ったファイルを規則的に分割する場合はこういった方法が有効かもしれません。

以上です。