HDFS SnapshotsとDistCpを利用したHDFSデータの差分更新


[Cloudera Engineering Blog](https://blog.cloudera.com/blog/2015/12/distcp-performance-improvements-in-apache-hadoop/)にてCDH5.5にDistCpの高速化が実装されたと紹介されていたので、試してみました。

リンク先の説明を読んだ限り、2つのHDFS Snapshot間の差分情報を使って

* 削除・名前変更はDistCpを使わずに反映
* 新規作成・変更されたファイルだけをDistCpで同期

することで、高速化を図っているようです。

試した環境は、

* CentOS: 7.2 (64bit)
* CDH 5.7.0 (擬似分散)

です。

# HDFSの準備

今回は、

* 同期元ディレクトリ：/user/hadoop/source
* 同期先ディレクトリ：/user/hadoop/target

という状況とします。

```
[hadoop@localhost ~]$ hdfs dfs -mkdir source
[hadoop@localhost ~]$ hdfs dfs -mkdir target
[hadoop@localhost ~]$ hdfs dfs -ls
Found 2 items
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:25 source
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:25 target
```

sourceディレクトリには適当なファイルを入れておきます。

```
[hadoop@localhost ~]$ echo foobar > Data.txt
[hadoop@localhost ~]$ hdfs dfs -put Data.txt source
[hadoop@localhost ~]$ hdfs dfs -ls source
Found 1 items
-rw-r--r--   1 hadoop supergroup          7 2016-04-08 21:26 source/Data.txt
```

両方のディレクトリをスナップショット取得可能にしておきます。
`hdfs dfsadmin`コマンドの`-allowSnapshot`オプションで設定します。

```
[root@localhost ~]# sudo -u hdfs hdfs dfsadmin -allowSnapshot /user/hadoop/source
Allowing snaphot on /user/hadoop/source succeeded
[root@localhost ~]# sudo -u hdfs hdfs dfsadmin -allowSnapshot /user/hadoop/target
Allowing snaphot on /user/hadoop/target succeeded
```

スナップショット可能になっていることを確認します。
`hdfs lsSnapshottableDir`コマンドで確認できます。

```
[hadoop@localhost ~]$ hdfs lsSnapshottableDir
drwxr-xr-x 0 hadoop supergroup 0 2016-04-08 21:26 0 65536 /user/hadoop/source
drwxr-xr-x 0 hadoop supergroup 0 2016-04-08 21:25 0 65536 /user/hadoop/target
```

# HDFS SnapshotとDistCpによる差分更新の手順

## 同期元ディレクトリのスナップショットを取得

スナップショット名はs1とします。
取得には`hdfs dfs`コマンドの`-createSnapshot`オプションを利用します。

```
[hadoop@localhost ~]$ hdfs dfs -createSnapshot source s1
Created snapshot /user/hadoop/source/.snapshot/s1
```

取得したスナップショットは対象ディレクトリの.snapshotディレクトリ下に論理的に配置されるため`hdfs dfs -ls`で確認できます。

```
[hadoop@localhost ~]$ hdfs dfs -ls source/.snapshot
Found 1 items
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:29 source/.snapshot/s1
```

## DistCpにてスナップショット状態の同期元ディレクトリを同期先にコピー


```
[hadoop@localhost ~]$ hadoop distcp -update source/.snapshot/s1 target
```

同期が取れたことを確認します。

```
[hadoop@localhost ~]$ hdfs dfs -ls target
Found 1 items
-rw-r--r--   1 hadoop supergroup          7 2016-04-08 21:32 target/Data.txt
```

## 同期先ディレクトリのスナップショットを取得

distcpのベースにしたスナップショットと同名のs1で取得します。

```
[hadoop@localhost ~]$ hdfs dfs -createSnapshot target s1
Created snapshot /user/hadoop/target/.snapshot/s1
[hadoop@localhost ~]$ hdfs dfs -ls target/.snapshot
Found 1 items
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:33 target/.snapshot/s1
```

## 同期元ディレクトリの情報を更新

新しくファイルを追加します。

```
[hadoop@localhost ~]$ echo hogehoge > Data2.txt
[hadoop@localhost ~]$ hdfs dfs -put Data2.txt source
[hadoop@localhost ~]$ hdfs dfs -ls source
Found 2 items
-rw-r--r--   1 hadoop supergroup          7 2016-04-08 21:26 source/Data.txt
-rw-r--r--   1 hadoop supergroup          9 2016-04-08 21:34 source/Data2.txt
```

## 同期元ディレクトリのスナップショットを取得

今度はs2という名前にします。

```
[hadoop@localhost ~]$ hdfs dfs -createSnapshot source s2
Created snapshot /user/hadoop/source/.snapshot/s2
[hadoop@localhost ~]$ hdfs dfs -ls source/.snapshot
Found 2 items
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:29 source/.snapshot/s1
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 21:35 source/.snapshot/s2
```

スナップショット間の差分情報を確認しておきます。

```
[hadoop@localhost ~]$ hdfs snapshotDiff source s1 s2
Difference between snapshot s1 and snapshot s2 under directory /user/hadoop/source:
M	.
+	./Data2.txt

```

確かにData2.txtが追加（「+」で表現されます）されていますね。

## DistCpで差分更新

sourceディレクトリの2つのスナップショットs1, s2の差分情報を使って、targetディレクトリにデータを差分更新します。

コマンドの引数は例によってHadoopのドキュメントではさっぱりわからないのですが、Clouderaのブロクの情報より
```
$ hadoop distcp -update -diff <snapshotName1> <snapshotName2> <sourceDir> <targetDir>
```

とのこと。
今回だと、以下のようになります。

```
[hadoop@localhost ~]$ hadoop distcp -update -diff s1 s2 /user/hadoop/source /user/hadoop/target
```

```
[hadoop@localhost ~]$ hdfs dfs -ls target
Found 2 items
-rw-r--r--   1 hadoop supergroup          7 2016-04-08 21:32 target/Data.txt
-rw-r--r--   1 hadoop supergroup          9 2016-04-08 23:08 target/Data2.txt
```

### ハマりどころ

`distcp -diff`の際に同期元と同期先のディレクトリのパスを相対パスで指定すると、以下のようなエラーになりました。

```
[hadoop@localhost ~]$ hadoop distcp -update -diff s1 s2 source target
16/04/08 21:37:14 INFO tools.DistCp: Input Options: DistCpOptions{atomicCommit=false, syncFolder=true, deleteMissing=false, ignoreFailures=false, maxMaps=20, sslConfigurationFile='null', copyStrategy='uniformsize', sourceFileListing=null, sourcePaths=[source], targetPath=target, targetPathExists=true, preserveRawXattrs=false, filtersFile='null'}
16/04/08 21:37:14 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
16/04/08 21:37:15 ERROR tools.DistCp: Exception encountered 
java.lang.IllegalArgumentException: java.net.URISyntaxException: Relative path in absolute URI: hdfs://localhost:8020./source/.snapshot/s2
	at org.apache.hadoop.fs.Path.initialize(Path.java:206)
	at org.apache.hadoop.fs.Path.<init>(Path.java:197)
	at org.apache.hadoop.tools.SimpleCopyListing.getPathWithSchemeAndAuthority(SimpleCopyListing.java:193)
	at org.apache.hadoop.tools.SimpleCopyListing.addToFileListing(SimpleCopyListing.java:202)
	at org.apache.hadoop.tools.SimpleCopyListing.doBuildListingWithSnapshotDiff(SimpleCopyListing.java:243)
	at org.apache.hadoop.tools.SimpleCopyListing.doBuildListing(SimpleCopyListing.java:172)
	at org.apache.hadoop.tools.CopyListing.buildListing(CopyListing.java:86)
	at org.apache.hadoop.tools.DistCp.createInputFileListingWithDiff(DistCp.java:388)
	at org.apache.hadoop.tools.DistCp.execute(DistCp.java:164)
	at org.apache.hadoop.tools.DistCp.run(DistCp.java:123)
	at org.apache.hadoop.util.ToolRunner.run(ToolRunner.java:70)
	at org.apache.hadoop.tools.DistCp.main(DistCp.java:436)
Caused by: java.net.URISyntaxException: Relative path in absolute URI: hdfs://localhost:8020./source/.snapshot/s2
	at java.net.URI.checkPath(URI.java:1823)
	at java.net.URI.<init>(URI.java:745)
	at org.apache.hadoop.fs.Path.initialize(Path.java:203)
	... 11 more
```

どうも同期元のパスとして絶対パスを期待しているようなので、ここは素直に従います。

なお、この事象はHDFS-10216に報告されていました。
distcp -diff relative path exception
https://issues.apache.org/jira/browse/HDFS-10216


## 同期先ディレクトリの状態をスナップショットで保存

差分更新した状態のスナップショットs2として取得しておきます。

```
[hadoop@localhost ~]$ hdfs dfs -createSnapshot target s2
Created snapshot /user/hadoop/target/.snapshot/s2
[hadoop@localhost ~]$ hdfs dfs -ls target/.snapshot
Found 2 items
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 22:29 target/.snapshot/s1
drwxr-xr-x   - hadoop supergroup          0 2016-04-08 23:10 target/.snapshot/s2
```

後はこれを繰り返すだけ。

# 感想

DistCpの際に全データを対象としなくなるので、DistCpを定期的に実行する際にはかなり便利だと思われます。

ブロクに書かれているように、スナップショットの差分情報から更新すべき情報に変換するロジックはそれなりに大変そうです。HDFSはファイルのUPDATEができないという制約があるのがせめてもの救いかもしれません。

しかし、HDFS Snapshotのコマンドの統一性の無さはどうにかしてほしいわ・・・


以上！