SlideShare a Scribd company logo
Hiroshi Nakamura
Software Engineer
Treasure Data, K.K.
『Embulk』に見るモダンJavaの実践的テクニック

∼並列分散処理システムの実装手法∼
1
#ccc_cd4 / #embulk
#ccc_cd4 / #embulk
Today’s talk
Embulkとは
> バルクデータ転送の難しさ
> Embulkのアプローチ
> アーキテクチャ概要
Java実装技術
> Java 7ネイティブ
> Guiceによるコンポーネント間の接続
> ServiceLoaderによる拡張
> Jacksonによるモデルクラス、Immutable
> Nettyバッファアロケータ、Unsafe
2
#ccc_cd4 / #embulk
Embulkとは? - http://embulk.org/
> オープンソースのバルクデータ転送ツール
> “A” から “B” へレコードを転送
> プラグイン機構
> 多様な “A” と “B” の組み合わせ
> データ連携を容易に
> システム構築の頭痛の種の一つ
Storage, RDBMS,
NoSQL, Cloud Service, …
broken records,

error recovery, maintenance,

performance, …
3
#ccc_cd4 / #embulk
Embulk committers
Hiroshi Nakamura
@nahi
Muga Nishizawa
@muga_nishizawa
Sadayuki Furuhashi
@frsyuki
4
#ccc_cd4 / #embulk
バルクデータ転送の難しさ
> 入力データの正規化
> エラー処理
> メンテナンス
> 性能
#ccc_cd4 / #embulk
入力データ正規化の難しさ
データエンコーディングのバリエーション
> null、時刻、浮動小数点
> 改行、エスケープ、レコード/カラム区切り
> 文字コード、圧縮有無
→ 試行によるデータ正規化
#ccc_cd4 / #embulk
エラー処理の難しさ
例外値の扱い
ネットワークエラーからの復旧
ディスクフルからの復旧
重複データ転送の回避
→ データバリデーション、リトライ、リジューム
#ccc_cd4 / #embulk
メンテナンスの難しさ
継続的な動作の確保
データ転送要件変更への対応
→ ドキュメント、汎用化、OSS化
#ccc_cd4 / #embulk
性能の問題
転送データ量は通常増えていく
対象レコードも増えたりする
→ 並列・分散処理
#ccc_cd4 / #embulk
バルクデータ転送の例
指定された 10GB CSV file をPostgreSQLにロード
> 1. コマンド叩いてみる → 失敗
> 2. データを正規化するスクリプトを作成
”20150127T190500Z”→“2015-01-27 19:05:00 UTC”に
“null”→“N”に変換
元データを見ながら気付く限り…
> 3. 再度チャレンジ → 取り込まれたが元データと合わない
“Inf”→“Infinity”に変換
> 4. ひたすら繰り返す
> 5. うっかりレコードが重複して取り込まれた…
#ccc_cd4 / #embulk
バルクデータ転送の例
指定された 10GB CSV file をPostgreSQLにロード
> 6. スクリプトが完成
> 7. cronに登録して毎日バルクデータロードするよう登録
> 8. ある日別の原因でエラーに…
不正なUTF-8 byte sequenceをU+FFFDに変換
#ccc_cd4 / #embulk
バルクデータ転送の例
過去の日次 10GB CSV file を 730個 取り込む(2年分)
> 1. たいていのスクリプトは遅い
> 最適化してる暇がない
> 1ファイル1時間、エラー発生しなかったとして1ヶ月
> 2. 並列データロードするようスクリプト変更
> 3. ある日ディスクフル/ネットワークエラーで失敗
> どこまで読み込まれた?
> 4. 障害後に再開し易いよう、データロード単位を調整
> 5. 安全な再開機能をスクリプトに追加
#ccc_cd4 / #embulk
システム構築の頭痛の種
様々な転送データ、データストレージ
> CSV, TSV, JSON, XML, MessagePack, SequenceFile,
RCFile
> S3, Salesforce.com, Google Cloud Storage,
BigQuery, Elasticsearch
> MySQL, PostgreSQL, Oracle, MS SQL Server,
Amazon Redshift, Redis, MongoDB
#ccc_cd4 / #embulk
HDFS
MySQL
Amazon S3
CSV Files
SequenceFile
Salesforce.com
Elasticsearch
Cassandra
Hive
Redis
Broken
script :(
Sometimes
fails :(
No one
can fix :(
14
#ccc_cd4 / #embulk
HDFS
MySQL
Amazon S3
CSV Files
SequenceFile
Salesforce.com
Elasticsearch
Cassandra
Hive
Redis
Broken
script :(
Sometimes
fails :(
No one
can fix :(
N x M

scripts!
> Poor error handling
> No retrying / resuming
> Low performance
> Often no maitainers
15
#ccc_cd4 / #embulk
Embulkのアプローチ
> プラグインアーキテクチャ
> 入力データ正規化支援: guess, preview
> 並列・分散実行
> 繰り返し実行
> トランザクション制御
16
#ccc_cd4 / #embulk
HDFS
MySQL
Amazon S3
CSV Files
SequenceFile
Salesforce.com
Elasticsearch
Cassandra
Hive
Redis
Broken
script :(
Sometimes
fails :(
No one
can fix :(
17
#ccc_cd4 / #embulk
HDFS
MySQL
Amazon S3
CSV Files
SequenceFile
Salesforce.com
Elasticsearch
Cassandra
Hive
Redis
Reliable
framework :-)
18
#ccc_cd4 / #embulk
HDFS
MySQL
Amazon S3
CSV Files
SequenceFile
Salesforce.com
Elasticsearch
Cassandra
Hive
Redis
PluginsPlugins
Reusable
plugins
19
#ccc_cd4 / #embulk
プラグインアーキテクチャ
拡張ポイントが再利用可能なコンポーネントを定義
従っている限りフレームワークの恩恵を受けられる
> 並列処理、繰り返し実行、エラー処理、リカバリ
20
#ccc_cd4 / #embulk
Embulkプラグインの例
RubyGemsとして配布 - http://www.embulk.org/plugins/
> DB
> Oracle, MySQL, PostgreSQL, Amazon Redshift
> 検索エンジン
> Elasticsearch
> クラウドサービス
> Salesforce.com
> Amazon S3
> Google Cloud Storage, Google BigQuery
> ファイルフォーマット
> CSV, TSV, JSON, XML
> pcap packet capture files
> gzip, bzip2, zip, tar, cpio
21
#ccc_cd4 / #embulk
デモ
> guessとpreview
> 並列・分散実行
> 繰り返し実行
> トランザクション処理
> プラグインのサンプル
22
#ccc_cd4 / #embulk
Embulkアーキテクチャ概要
#ccc_cd4 / #embulk
InputPlugin OutputPlugin
Executor plugin
Filter plugin
Filter plugin
Filter plugins
records
Threads,
MapReduce
records
convert, …
input, … output.
24
records
config
#ccc_cd4 / #embulk
InputPlugin
FileInput plugin
OutputPlugin
Decoder plugin
Parser plugin
HDFS, S3,

Riak CS, …
gzip, bzip2,

aes, …
CSV, JSON,

pcap, …
buffer
buffer
Filter plugin
Filter plugin
Filter plugins
records
records
Executor plugin
25
records
config
#ccc_cd4 / #embulk
InputPlugin
FileInput plugin
OutputPlugin
FileOutput plugin
Encoder plugin
Formatter plugin
Decoder plugin
Parser plugin
HDFS, S3,

Riak CS, …
gzip, bzip2,

aes, …
CSV, JSON,

pcap, …
buffer
buffer
buffer
buffer
Filter plugin
Filter plugin
Filter plugins
recordsrecords
Executor plugin
26
records
config
#ccc_cd4 / #embulk
Embulkアーキテクチャ概要
4種のプラグインとそれを組み上げるフレームワーク
1. Executor: 実行
2. Input: バルクデータのレコード群を取り込み
FileInput, Decoder, Parser: ファイル操作
3. Filter: レコードに対するデータ操作
4. Output: バルクデータのレコード群を出力
FileOutput, Encoder, Formatter: ファイル操作
#ccc_cd4 / #embulk
InputPlugin OutputPlugin
Executor plugin
Filter plugin
Filter plugin
Filter plugins
28
records records
records
config diff
#ccc_cd4 / #embulk
InputPlugin OutputPlugin
Executor plugin
Filter plugin
Filter plugin
Filter plugins
29
task
schema
report
task
schema
report
records records
task
schema
records
resume state
config
#ccc_cd4 / #embulk
Embulkの実装技術
> Java 7ネイティブ
> Guiceによるコンポーネント間の接続
> ServiceLoaderによる拡張
> Jacksonによるモデルクラス
> Immutableなモデルクラス
> Nettyバッファアロケータ
> sun.misc.Unsafeによるバッファコピー回避
30
#ccc_cd4 / #embulk
Java 7ネイティブ
try-with-resources
ファイル操作:Files & Paths API
※Date and TimeはJRubyの実装を利用
try (SetCurrentThreadName dontCare =
new SetCurrentThreadName(“transaction”))
{
return doRun(config);
}
Path basePath = Paths.get(“.”).normalize();
Path file = basePath.resolve(“relative.csv”);
#ccc_cd4 / #embulk
Guiceによるコンポーネント間の接続
32
public class EmbulkService
{
protected final Injector injector;
public EmbulkService(ConfigSource systemConfig)
{
ImmutableList.Builder<Module> modules = ImmutableList.builder();
modules.add(new SystemConfigModule(systemConfig));
modules.add(new ExecModule());
modules.add(new ExtensionServiceLoaderModule(systemConfig));
modules.add(new BuiltinPluginSourceModule());
modules.add(new JRubyScriptingModule(systemConfig));
injector = Guice.createInjector(modules.build());
}
public Injector getInjector()
{
return injector;
}
}
#ccc_cd4 / #embulk
Guiceによるコンポーネント間の接続
33
public class EmbulkService
{
protected final Injector injector;
public EmbulkService(ConfigSource systemConfig)
{
ImmutableList.Builder<Module> modules = ImmutableList.builder();
modules.add(new SystemConfigModule(systemConfig));
modules.add(new ExecModule());
modules.add(new ExtensionServiceLoaderModule(systemConfig));
modules.add(new BuiltinPluginSourceModule());
modules.add(new JRubyScriptingModule(systemConfig));
injector = Guice.createInjector(modules.build());
}
public Injector getInjector()
{
return injector;
}
}
public class ExecModule implements Module
{
@Override
public void configure(Binder binder)
{
...
binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON);
registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class);
#ccc_cd4 / #embulk
Guiceによるコンポーネント間の接続
34
public class EmbulkService
{
protected final Injector injector;
public EmbulkService(ConfigSource systemConfig)
{
ImmutableList.Builder<Module> modules = ImmutableList.builder();
modules.add(new SystemConfigModule(systemConfig));
modules.add(new ExecModule());
modules.add(new ExtensionServiceLoaderModule(systemConfig));
modules.add(new BuiltinPluginSourceModule());
modules.add(new JRubyScriptingModule(systemConfig));
injector = Guice.createInjector(modules.build());
}
public Injector getInjector()
{
return injector;
}
}
public class ExecModule implements Module
{
@Override
public void configure(Binder binder)
{
...
binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON);
registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class);
public class LocalExecutorPlugin implements ExecutorPlugin
{
private final ExecutorService executor;
@Inject
public LocalExecutorPlugin(LocalThreadExecutor executor)
{
this.executor = executor.getExecutorService();
...



(InjectedPluginSource)

public T newPlugin(Injector injector)
{
return (T) new FileInputRunner((FileInputPlugin) injector.getInstance(impl));
#ccc_cd4 / #embulk
Guiceによるコンポーネント間の接続
XMLでなくすべてJavaで書く系のDI
Annotationによる宣言的なDI、

injectorによる動的なDIの組み合わせ
> 動的なモジュール差し替えがし易い
#ccc_cd4 / #embulk
ServiceLoaderによる拡張
public class ExtensionServiceLoaderModule implements Module
{
private final ConfigSource systemConfig;
public ExtensionServiceLoaderModule(ConfigSource systemConfig)
{
this.systemConfig = systemConfig;
}
@Override
public void configure(Binder binder)
{
ServiceLoader<Extension> serviceLoader =
ServiceLoader.load(Extension.class, classLoader);
for (Extension extension : serviceLoader) {
for (Module module : extension.getModules(systemConfig)) {
module.configure(binder);
}
}
}
}
#ccc_cd4 / #embulk
ServiceLoaderによる拡張
jarをclasspathに入れるだけでモジュール追加/差し替え
簡単でClassLoaderいじるよりは安全
標準Plugin群の登録に利用
※Pluginの読み込みはClassLoader
#ccc_cd4 / #embulk
Jacksonによるモデルクラス(task)
public class CsvParserPlugin
implements ParserPlugin
{
public interface PluginTask
extends Task, LineDecoder.DecoderTask, TimestampParser.ParserTask
{
@Config("columns")
public SchemaConfig getSchemaConfig();
@Config("header_line")
@ConfigDefault("null")
public Optional<Boolean> getHeaderLine();
@Config("skip_header_lines")
@ConfigDefault("0")
public int getSkipHeaderLines();
public void setSkipHeaderLines(int n);
@Config("delimiter")
@ConfigDefault("","")
public char getDelimiterChar();
#ccc_cd4 / #embulk
InputPlugin OutputPlugin
Executor plugin
Filter plugin
Filter plugin
Filter plugins
39
task
schema
report
task
schema
report
records records
task
schema
records
config
config diff
resume state
#ccc_cd4 / #embulk
Jacksonによるモデルクラス(schema)
public class ColumnConfig
{
private final String name;
private final Type type;
@JsonCreator
public ColumnConfig(
@JsonProperty("name") String name,
@JsonProperty("type") Type type)
{
this.name = name;
this.type = type;
}
@JsonProperty("name")
public String getName() { return name; }
@JsonProperty("type")
public Type getType() { return type; }
}
#ccc_cd4 / #embulk
Jacksonによるモデルクラス
デ/シリアライズが重要
> 並列・分散実行のため
> Ruby <-> Javaのやりとりのため
IDL生成でなくすべてJavaで書く系のモデル
#ccc_cd4 / #embulk
Immutableなモデルクラス
ソースコード中のfinalなメンバー変数の割合
> Presto:83%(4714変数)
> Embulk:72%(255変数)
> Cassandra:59%(2348変数)
> Elasticsearch:51%(6871変数)
> Nashorn (OpenJDK 8):43%(852変数)
> JRuby:40%(3154変数)
> Hadoop:31%(9280変数)
> Hive:23%(4600変数)
#ccc_cd4 / #embulk
Nettyバッファアロケータ
レコード群のためのメモリをすべて自前管理
> OutOfMemoryが起きる前に検出
> GCコスト削減
複数のバルクロードセッションを

サーバプロセス内で同時実行可能に
#ccc_cd4 / #embulk
Nettyバッファアロケータ
レコード群のためのメモリをすべて自前管理
> OutOfMemoryが起きる前に検出
> GCコスト削減
複数のバルクロードセッションを

サーバプロセス内で同時実行可能に
public Buffer allocate(int minimumCapacity)
{
int size = MINIMUM_BUFFER_SIZE;
while (size < minimumCapacity) {
size *= 2;
}
return new NettyByteBufBuffer(nettyBuffer.buffer(size));
}
#ccc_cd4 / #embulk
Unsafe
airlift/slice - sun.misc.Unsafe APIのwrapper
> バイト列の直接操作(デ/シリアライズ)
> コピー削減
参考: http://frsyuki.hatenablog.com/entry/
2014/03/12/155231
#ccc_cd4 / #embulk
Unsafe
airlift/slice - sun.misc.Unsafe APIのwrapper
> バイト列の直接操作(デ/シリアライズ)
> コピー削減
参考: http://frsyuki.hatenablog.com/entry/
2014/03/12/155231
public void addRecord()
{
// record header
bufferSlice.setInt(position, nextVariableLengthDataOffset);
bufferSlice.setBytes(position + 4, nullBitSet);
count++;
this.position += nextVariableLengthDataOffset;
this.nextVariableLengthDataOffset = fixedRecordSize;
Arrays.fill(nullBitSet, (byte) 0);
// flush if next record will not fit in this buffer
if (buffer.capacity() < position +

nextVariableLengthDataOffset + stringReferenceSize) {
flush();
}
}
47
ユーザー
転送
管理コンソール
から実行
トレジャー
クラウドストレージ
Embulk
Worker
管理コンソール
からアクセス
S3のインポートから,エクスポー
トまでを完全自動化
#ccc_cd4 / #embulk
Contributing to the Embulk project
> Pull-requests & issues on Github
> Posting blogs
> “使ってみた”
> “コードを読んでみた”
> “ここがイケてる / イケてない”
> Talking on Twitter with a word “embulk"
> Writing & releasing plugins
> Windows support
> Integration to other software
> ETL tools, Fluentd, Hadoop, Presto, …
48
1. Distributed Systems Engineer
2. Integration Engineer
3. Software Engineer, MPP DBMS
4. Sales Engineer
5. Technical Support Engineer
(日本,東京,丸の内)

https://jobs.lever.co/treasure-data
We’re hiring!
ANALYTICS INFRASTRUCTURE. SIMPLIFIED IN THE CLOUD.
50
#ccc_cd4 / #embulk
FluentdとEmbulk
52
This?
53
Or this?
M x N → M + N
Nagios
MongoDB
Hadoop
Alerting
Amazon S3
Analysis
Archiving
MySQL
Apache
Frontend
Access logs
syslogd
App logs
System logs
Backend
Databases
buffer/filter/route
#ccc_cd4 / #embulk
FluentdとEmbulk
ストリーミングデータコレクター

vs バルクデータローダー
ストリーミングデータか、転送単位がはっきり
しているバルクデータか
任意データ vs データバリデーション・正規化
即時 vs トランザクション性
#ccc_cd4 / #embulk
Embulkの実行
インストール
guess
preview
繰り返し実行
56
# install
$ wget https://bintray.com/artifact/download/
embulk/maven/embulk-0.2.0.jar -o embulk.jar
$ chmod 755 embulk.jar
Installing embulk
Bintray

releases
Embulk is released on Bintray
wget embulk.jar
# install
$ wget https://bintray.com/artifact/download/
embulk/maven/embulk-0.2.0.jar -o embulk.jar
$ chmod 755 embulk.jar

# guess
$ vi partial-config.yml
$ ./embulk guess partial-config.yml

-o config.yml
Guess format & schema in:
type: file
paths: [data/examples/]
out:

type: example
in:
type: file
paths: [data/examples/]
decoders:
- {type: gzip}
parser:
charset: UTF-8
newline: CRLF
type: csv
delimiter: ','
quote: '"'
header_line: true
columns:
- name: time

type: timestamp

format: '%Y-%m-%d %H:%M:%S'
- name: account

type: long
- name: purchase

type: timestamp

format: '%Y%m%d'
- name: comment

type: string
out:

type: example
guess
by guess plugins
# install
$ wget https://bintray.com/artifact/download/
embulk/maven/embulk-0.2.0.jar -o embulk.jar
$ chmod 755 embulk.jar

# guess
$ vi partial-config.yml
$ ./embulk guess partial-config.yml

-o config.yml

# preview
$ ./embulk preview config.yml
$ vi config.yml # if necessary
+--------------------------------------+---------------+--------------------+
| time:timestamp | uid:long | word:string |
+--------------------------------------+---------------+--------------------+
| 2015-01-27 19:23:49 UTC | 32,864 | embulk |
| 2015-01-27 19:01:23 UTC | 14,824 | jruby |
| 2015-01-28 02:20:02 UTC | 27,559 | plugin |
| 2015-01-29 11:54:36 UTC | 11,270 | fluentd |
+--------------------------------------+---------------+--------------------+
Preview & fix config
# install
$ wget https://bintray.com/artifact/download/
embulk/maven/embulk-0.2.0.jar -o embulk.jar
$ chmod 755 embulk.jar

# guess
$ vi partial-config.yml
$ ./embulk guess partial-config.yml

-o config.yml

# preview
$ ./embulk preview config.yml
$ vi config.yml # if necessary
# run
$ ./embulk run config.yml -o config.yml
in:
type: file
paths: [data/examples/]
decoders:
- {type: gzip}
parser:
charset: UTF-8
newline: CRLF
type: csv
delimiter: ','
quote: '"'
header_line: true
columns:
- name: time

type: timestamp

format: '%Y-%m-%d %H:%M:%S'
- name: account

type: long
- name: purchase

type: timestamp

format: '%Y%m%d'
- name: comment

type: string
last_paths: [data/examples/sample_001.csv.gz]
out:

type: example
Deterministic run
in:
type: file
paths: [data/examples/]
decoders:
- {type: gzip}
parser:
charset: UTF-8
newline: CRLF
type: csv
delimiter: ','
quote: '"'
header_line: true
columns:
- name: time

type: timestamp

format: '%Y-%m-%d %H:%M:%S'
- name: account

type: long
- name: purchase

type: timestamp

format: '%Y%m%d'
- name: comment

type: string
last_paths: [data/examples/sample_002.csv.gz]
out:

type: example
Repeat
# install
$ wget https://bintray.com/artifact/download/
embulk/maven/embulk-0.2.0.jar -o embulk.jar
$ chmod 755 embulk.jar

# guess
$ vi partial-config.yml
$ ./embulk guess partial-config.yml

-o config.yml

# preview
$ ./embulk preview config.yml
$ vi config.yml # if necessary
# run
$ ./embulk run config.yml -o config.yml
# repeat
$ ./embulk run config.yml -o config.yml
$ ./embulk run config.yml -o config.yml
#ccc_cd4 / #embulk
Writing Embulk plugins
62
InputPlugin
module Embulk
class InputExample < InputPlugin
Plugin.register_input('example', self)
def self.transaction(config, &control)
# read config
task = {
'message' =>
config.param('message', :string, default: nil)
}
threads = config.param('threads', :int, default:
2)
columns = [
Column.new(0, 'col0', :long),
Column.new(1, 'col1', :double),
Column.new(2, 'col2', :string),
]
# BEGIN here
commit_reports = yield(task, columns, threads)
# COMMIT here
puts "Example input finished"
return {}
end
def run(task, schema, index, page_builder)
puts "Example input thread #{@index}…"
10.times do |i|
@page_builder.add([i, 10.0, "example"])
end
@page_builder.finish
commit_report = { }
return commit_report
end
end
end
OutputPlugin
module Embulk
class OutputExample < OutputPlugin
Plugin.register_output('example', self)
def self.transaction(
config, schema,
processor_count, &control)
# read config
task = {
'message' =>
config.param('message', :string, default: "record")
}
puts "Example output started."
commit_reports = yield(task)
puts "Example output finished. Commit
reports = #{commit_reports.to_json}"
return {}
end
def initialize(task, schema, index)
puts "Example output thread #{index}..."
super
@message = task.prop('message', :string)
@records = 0
end
def add(page)
page.each do |record|
hash = Hash[schema.names.zip(record)]
puts "#{@message}: #{hash.to_json}"
@records += 1
end
end
def finish
end
def abort
end
def commit
commit_report = {
"records" => @records
}
return commit_report
end
end
end
GuessPlugin
# guess_gzip.rb
module Embulk
class GzipGuess < GuessPlugin
Plugin.register_guess('gzip', self)
GZIP_HEADER = "x1f
x8b".force_encoding('ASCII-8BIT').freeze
def guess(config, sample_buffer)
if sample_buffer[0,2] == GZIP_HEADER
return {"decoders" => [{"type" => "gzip"}]}
end
return {}
end
end
end
# guess_
module Embulk
class GuessNewline < TextGuessPlugin
Plugin.register_guess('newline', self)
def guess_text(config, sample_text)
cr_count = sample_text.count("r")
lf_count = sample_text.count("n")
crlf_count = sample_text.scan(/rn/).length
if crlf_count > cr_count / 2 && crlf_count >
lf_count / 2
return {"parser" => {"newline" => "CRLF"}}
elsif cr_count > lf_count / 2
return {"parser" => {"newline" => "CR"}}
else
return {"parser" => {"newline" => "LF"}}
end
end
end
end
#ccc_cd4 / #embulk
Releasing to RubyGems
Examples
> embulk-plugin-postgres-json.gem
> https://github.com/frsyuki/embulk-plugin-postgres-json
> embulk-plugin-redis.gem
> https://github.com/komamitsu/embulk-plugin-redis
> embulk-plugin-input-sfdc-event-log-files.gem
> https://github.com/nahi/embulk-plugin-input-sfdc-event-
log-files
#ccc_cd4 / #embulk
plugin bundle
> embulk bundle <dir>
> Gemfile
Attention!
Presto Presto Presto Presto
Presto Presto Presto Presto
Presto Presto Presto Presto
Presto Presto Presto Presto
Presto Presto Presto Presto
68
Attention!
Hive Hive Hive Hive
Hive Hive Hive Hive
Hive Hive Hive Hive
Hive Hive Hive Hive
69
Hive Hive Hive Hive
Hive Hive HiveHive
PrestoPrestogres
hba.conf
PostgreSQL
70

More Related Content

Embulk 20150411

  • 1. Hiroshi Nakamura Software Engineer Treasure Data, K.K. 『Embulk』に見るモダンJavaの実践的テクニック
 ∼並列分散処理システムの実装手法∼ 1 #ccc_cd4 / #embulk
  • 2. #ccc_cd4 / #embulk Today’s talk Embulkとは > バルクデータ転送の難しさ > Embulkのアプローチ > アーキテクチャ概要 Java実装技術 > Java 7ネイティブ > Guiceによるコンポーネント間の接続 > ServiceLoaderによる拡張 > Jacksonによるモデルクラス、Immutable > Nettyバッファアロケータ、Unsafe 2
  • 3. #ccc_cd4 / #embulk Embulkとは? - http://embulk.org/ > オープンソースのバルクデータ転送ツール > “A” から “B” へレコードを転送 > プラグイン機構 > 多様な “A” と “B” の組み合わせ > データ連携を容易に > システム構築の頭痛の種の一つ Storage, RDBMS, NoSQL, Cloud Service, … broken records,
 error recovery, maintenance,
 performance, … 3
  • 4. #ccc_cd4 / #embulk Embulk committers Hiroshi Nakamura @nahi Muga Nishizawa @muga_nishizawa Sadayuki Furuhashi @frsyuki 4
  • 5. #ccc_cd4 / #embulk バルクデータ転送の難しさ > 入力データの正規化 > エラー処理 > メンテナンス > 性能
  • 6. #ccc_cd4 / #embulk 入力データ正規化の難しさ データエンコーディングのバリエーション > null、時刻、浮動小数点 > 改行、エスケープ、レコード/カラム区切り > 文字コード、圧縮有無 → 試行によるデータ正規化
  • 10. #ccc_cd4 / #embulk バルクデータ転送の例 指定された 10GB CSV file をPostgreSQLにロード > 1. コマンド叩いてみる → 失敗 > 2. データを正規化するスクリプトを作成 ”20150127T190500Z”→“2015-01-27 19:05:00 UTC”に “null”→“N”に変換 元データを見ながら気付く限り… > 3. 再度チャレンジ → 取り込まれたが元データと合わない “Inf”→“Infinity”に変換 > 4. ひたすら繰り返す > 5. うっかりレコードが重複して取り込まれた…
  • 11. #ccc_cd4 / #embulk バルクデータ転送の例 指定された 10GB CSV file をPostgreSQLにロード > 6. スクリプトが完成 > 7. cronに登録して毎日バルクデータロードするよう登録 > 8. ある日別の原因でエラーに… 不正なUTF-8 byte sequenceをU+FFFDに変換
  • 12. #ccc_cd4 / #embulk バルクデータ転送の例 過去の日次 10GB CSV file を 730個 取り込む(2年分) > 1. たいていのスクリプトは遅い > 最適化してる暇がない > 1ファイル1時間、エラー発生しなかったとして1ヶ月 > 2. 並列データロードするようスクリプト変更 > 3. ある日ディスクフル/ネットワークエラーで失敗 > どこまで読み込まれた? > 4. 障害後に再開し易いよう、データロード単位を調整 > 5. 安全な再開機能をスクリプトに追加
  • 13. #ccc_cd4 / #embulk システム構築の頭痛の種 様々な転送データ、データストレージ > CSV, TSV, JSON, XML, MessagePack, SequenceFile, RCFile > S3, Salesforce.com, Google Cloud Storage, BigQuery, Elasticsearch > MySQL, PostgreSQL, Oracle, MS SQL Server, Amazon Redshift, Redis, MongoDB
  • 14. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( 14
  • 15. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( N x M
 scripts! > Poor error handling > No retrying / resuming > Low performance > Often no maitainers 15
  • 16. #ccc_cd4 / #embulk Embulkのアプローチ > プラグインアーキテクチャ > 入力データ正規化支援: guess, preview > 並列・分散実行 > 繰り返し実行 > トランザクション制御 16
  • 17. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Broken script :( Sometimes fails :( No one can fix :( 17
  • 18. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis Reliable framework :-) 18
  • 19. #ccc_cd4 / #embulk HDFS MySQL Amazon S3 CSV Files SequenceFile Salesforce.com Elasticsearch Cassandra Hive Redis PluginsPlugins Reusable plugins 19
  • 21. #ccc_cd4 / #embulk Embulkプラグインの例 RubyGemsとして配布 - http://www.embulk.org/plugins/ > DB > Oracle, MySQL, PostgreSQL, Amazon Redshift > 検索エンジン > Elasticsearch > クラウドサービス > Salesforce.com > Amazon S3 > Google Cloud Storage, Google BigQuery > ファイルフォーマット > CSV, TSV, JSON, XML > pcap packet capture files > gzip, bzip2, zip, tar, cpio 21
  • 22. #ccc_cd4 / #embulk デモ > guessとpreview > 並列・分散実行 > 繰り返し実行 > トランザクション処理 > プラグインのサンプル 22
  • 24. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins records Threads, MapReduce records convert, … input, … output. 24 records config
  • 25. #ccc_cd4 / #embulk InputPlugin FileInput plugin OutputPlugin Decoder plugin Parser plugin HDFS, S3,
 Riak CS, … gzip, bzip2,
 aes, … CSV, JSON,
 pcap, … buffer buffer Filter plugin Filter plugin Filter plugins records records Executor plugin 25 records config
  • 26. #ccc_cd4 / #embulk InputPlugin FileInput plugin OutputPlugin FileOutput plugin Encoder plugin Formatter plugin Decoder plugin Parser plugin HDFS, S3,
 Riak CS, … gzip, bzip2,
 aes, … CSV, JSON,
 pcap, … buffer buffer buffer buffer Filter plugin Filter plugin Filter plugins recordsrecords Executor plugin 26 records config
  • 27. #ccc_cd4 / #embulk Embulkアーキテクチャ概要 4種のプラグインとそれを組み上げるフレームワーク 1. Executor: 実行 2. Input: バルクデータのレコード群を取り込み FileInput, Decoder, Parser: ファイル操作 3. Filter: レコードに対するデータ操作 4. Output: バルクデータのレコード群を出力 FileOutput, Encoder, Formatter: ファイル操作
  • 28. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 28 records records records config diff
  • 29. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 29 task schema report task schema report records records task schema records resume state config
  • 30. #ccc_cd4 / #embulk Embulkの実装技術 > Java 7ネイティブ > Guiceによるコンポーネント間の接続 > ServiceLoaderによる拡張 > Jacksonによるモデルクラス > Immutableなモデルクラス > Nettyバッファアロケータ > sun.misc.Unsafeによるバッファコピー回避 30
  • 31. #ccc_cd4 / #embulk Java 7ネイティブ try-with-resources ファイル操作:Files & Paths API ※Date and TimeはJRubyの実装を利用 try (SetCurrentThreadName dontCare = new SetCurrentThreadName(“transaction”)) { return doRun(config); } Path basePath = Paths.get(“.”).normalize(); Path file = basePath.resolve(“relative.csv”);
  • 32. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 32 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } }
  • 33. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 33 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } } public class ExecModule implements Module { @Override public void configure(Binder binder) { ... binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON); registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class);
  • 34. #ccc_cd4 / #embulk Guiceによるコンポーネント間の接続 34 public class EmbulkService { protected final Injector injector; public EmbulkService(ConfigSource systemConfig) { ImmutableList.Builder<Module> modules = ImmutableList.builder(); modules.add(new SystemConfigModule(systemConfig)); modules.add(new ExecModule()); modules.add(new ExtensionServiceLoaderModule(systemConfig)); modules.add(new BuiltinPluginSourceModule()); modules.add(new JRubyScriptingModule(systemConfig)); injector = Guice.createInjector(modules.build()); } public Injector getInjector() { return injector; } } public class ExecModule implements Module { @Override public void configure(Binder binder) { ... binder.bind(LocalThreadExecutor.class).in(Scopes.SINGLETON); registerPluginTo(binder, ExecutorPlugin.class, "local", LocalExecutorPlugin.class); public class LocalExecutorPlugin implements ExecutorPlugin { private final ExecutorService executor; @Inject public LocalExecutorPlugin(LocalThreadExecutor executor) { this.executor = executor.getExecutorService(); ...
 
 (InjectedPluginSource)
 public T newPlugin(Injector injector) { return (T) new FileInputRunner((FileInputPlugin) injector.getInstance(impl));
  • 36. #ccc_cd4 / #embulk ServiceLoaderによる拡張 public class ExtensionServiceLoaderModule implements Module { private final ConfigSource systemConfig; public ExtensionServiceLoaderModule(ConfigSource systemConfig) { this.systemConfig = systemConfig; } @Override public void configure(Binder binder) { ServiceLoader<Extension> serviceLoader = ServiceLoader.load(Extension.class, classLoader); for (Extension extension : serviceLoader) { for (Module module : extension.getModules(systemConfig)) { module.configure(binder); } } } }
  • 38. #ccc_cd4 / #embulk Jacksonによるモデルクラス(task) public class CsvParserPlugin implements ParserPlugin { public interface PluginTask extends Task, LineDecoder.DecoderTask, TimestampParser.ParserTask { @Config("columns") public SchemaConfig getSchemaConfig(); @Config("header_line") @ConfigDefault("null") public Optional<Boolean> getHeaderLine(); @Config("skip_header_lines") @ConfigDefault("0") public int getSkipHeaderLines(); public void setSkipHeaderLines(int n); @Config("delimiter") @ConfigDefault("","") public char getDelimiterChar();
  • 39. #ccc_cd4 / #embulk InputPlugin OutputPlugin Executor plugin Filter plugin Filter plugin Filter plugins 39 task schema report task schema report records records task schema records config config diff resume state
  • 40. #ccc_cd4 / #embulk Jacksonによるモデルクラス(schema) public class ColumnConfig { private final String name; private final Type type; @JsonCreator public ColumnConfig( @JsonProperty("name") String name, @JsonProperty("type") Type type) { this.name = name; this.type = type; } @JsonProperty("name") public String getName() { return name; } @JsonProperty("type") public Type getType() { return type; } }
  • 41. #ccc_cd4 / #embulk Jacksonによるモデルクラス デ/シリアライズが重要 > 並列・分散実行のため > Ruby <-> Javaのやりとりのため IDL生成でなくすべてJavaで書く系のモデル
  • 42. #ccc_cd4 / #embulk Immutableなモデルクラス ソースコード中のfinalなメンバー変数の割合 > Presto:83%(4714変数) > Embulk:72%(255変数) > Cassandra:59%(2348変数) > Elasticsearch:51%(6871変数) > Nashorn (OpenJDK 8):43%(852変数) > JRuby:40%(3154変数) > Hadoop:31%(9280変数) > Hive:23%(4600変数)
  • 43. #ccc_cd4 / #embulk Nettyバッファアロケータ レコード群のためのメモリをすべて自前管理 > OutOfMemoryが起きる前に検出 > GCコスト削減 複数のバルクロードセッションを
 サーバプロセス内で同時実行可能に
  • 44. #ccc_cd4 / #embulk Nettyバッファアロケータ レコード群のためのメモリをすべて自前管理 > OutOfMemoryが起きる前に検出 > GCコスト削減 複数のバルクロードセッションを
 サーバプロセス内で同時実行可能に public Buffer allocate(int minimumCapacity) { int size = MINIMUM_BUFFER_SIZE; while (size < minimumCapacity) { size *= 2; } return new NettyByteBufBuffer(nettyBuffer.buffer(size)); }
  • 45. #ccc_cd4 / #embulk Unsafe airlift/slice - sun.misc.Unsafe APIのwrapper > バイト列の直接操作(デ/シリアライズ) > コピー削減 参考: http://frsyuki.hatenablog.com/entry/ 2014/03/12/155231
  • 46. #ccc_cd4 / #embulk Unsafe airlift/slice - sun.misc.Unsafe APIのwrapper > バイト列の直接操作(デ/シリアライズ) > コピー削減 参考: http://frsyuki.hatenablog.com/entry/ 2014/03/12/155231 public void addRecord() { // record header bufferSlice.setInt(position, nextVariableLengthDataOffset); bufferSlice.setBytes(position + 4, nullBitSet); count++; this.position += nextVariableLengthDataOffset; this.nextVariableLengthDataOffset = fixedRecordSize; Arrays.fill(nullBitSet, (byte) 0); // flush if next record will not fit in this buffer if (buffer.capacity() < position +
 nextVariableLengthDataOffset + stringReferenceSize) { flush(); } }
  • 48. #ccc_cd4 / #embulk Contributing to the Embulk project > Pull-requests & issues on Github > Posting blogs > “使ってみた” > “コードを読んでみた” > “ここがイケてる / イケてない” > Talking on Twitter with a word “embulk" > Writing & releasing plugins > Windows support > Integration to other software > ETL tools, Fluentd, Hadoop, Presto, … 48
  • 49. 1. Distributed Systems Engineer 2. Integration Engineer 3. Software Engineer, MPP DBMS 4. Sales Engineer 5. Technical Support Engineer (日本,東京,丸の内)
 https://jobs.lever.co/treasure-data We’re hiring! ANALYTICS INFRASTRUCTURE. SIMPLIFIED IN THE CLOUD.
  • 50. 50
  • 54. M x N → M + N Nagios MongoDB Hadoop Alerting Amazon S3 Analysis Archiving MySQL Apache Frontend Access logs syslogd App logs System logs Backend Databases buffer/filter/route
  • 55. #ccc_cd4 / #embulk FluentdとEmbulk ストリーミングデータコレクター
 vs バルクデータローダー ストリーミングデータか、転送単位がはっきり しているバルクデータか 任意データ vs データバリデーション・正規化 即時 vs トランザクション性
  • 57. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar Installing embulk Bintray
 releases Embulk is released on Bintray wget embulk.jar
  • 58. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml Guess format & schema in: type: file paths: [data/examples/] out:
 type: example in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string out:
 type: example guess by guess plugins
  • 59. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary +--------------------------------------+---------------+--------------------+ | time:timestamp | uid:long | word:string | +--------------------------------------+---------------+--------------------+ | 2015-01-27 19:23:49 UTC | 32,864 | embulk | | 2015-01-27 19:01:23 UTC | 14,824 | jruby | | 2015-01-28 02:20:02 UTC | 27,559 | plugin | | 2015-01-29 11:54:36 UTC | 11,270 | fluentd | +--------------------------------------+---------------+--------------------+ Preview & fix config
  • 60. # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary # run $ ./embulk run config.yml -o config.yml in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string last_paths: [data/examples/sample_001.csv.gz] out:
 type: example Deterministic run
  • 61. in: type: file paths: [data/examples/] decoders: - {type: gzip} parser: charset: UTF-8 newline: CRLF type: csv delimiter: ',' quote: '"' header_line: true columns: - name: time
 type: timestamp
 format: '%Y-%m-%d %H:%M:%S' - name: account
 type: long - name: purchase
 type: timestamp
 format: '%Y%m%d' - name: comment
 type: string last_paths: [data/examples/sample_002.csv.gz] out:
 type: example Repeat # install $ wget https://bintray.com/artifact/download/ embulk/maven/embulk-0.2.0.jar -o embulk.jar $ chmod 755 embulk.jar
 # guess $ vi partial-config.yml $ ./embulk guess partial-config.yml
 -o config.yml
 # preview $ ./embulk preview config.yml $ vi config.yml # if necessary # run $ ./embulk run config.yml -o config.yml # repeat $ ./embulk run config.yml -o config.yml $ ./embulk run config.yml -o config.yml
  • 62. #ccc_cd4 / #embulk Writing Embulk plugins 62
  • 63. InputPlugin module Embulk class InputExample < InputPlugin Plugin.register_input('example', self) def self.transaction(config, &control) # read config task = { 'message' => config.param('message', :string, default: nil) } threads = config.param('threads', :int, default: 2) columns = [ Column.new(0, 'col0', :long), Column.new(1, 'col1', :double), Column.new(2, 'col2', :string), ] # BEGIN here commit_reports = yield(task, columns, threads) # COMMIT here puts "Example input finished" return {} end def run(task, schema, index, page_builder) puts "Example input thread #{@index}…" 10.times do |i| @page_builder.add([i, 10.0, "example"]) end @page_builder.finish commit_report = { } return commit_report end end end
  • 64. OutputPlugin module Embulk class OutputExample < OutputPlugin Plugin.register_output('example', self) def self.transaction( config, schema, processor_count, &control) # read config task = { 'message' => config.param('message', :string, default: "record") } puts "Example output started." commit_reports = yield(task) puts "Example output finished. Commit reports = #{commit_reports.to_json}" return {} end def initialize(task, schema, index) puts "Example output thread #{index}..." super @message = task.prop('message', :string) @records = 0 end def add(page) page.each do |record| hash = Hash[schema.names.zip(record)] puts "#{@message}: #{hash.to_json}" @records += 1 end end def finish end def abort end def commit commit_report = { "records" => @records } return commit_report end end end
  • 65. GuessPlugin # guess_gzip.rb module Embulk class GzipGuess < GuessPlugin Plugin.register_guess('gzip', self) GZIP_HEADER = "x1f x8b".force_encoding('ASCII-8BIT').freeze def guess(config, sample_buffer) if sample_buffer[0,2] == GZIP_HEADER return {"decoders" => [{"type" => "gzip"}]} end return {} end end end # guess_ module Embulk class GuessNewline < TextGuessPlugin Plugin.register_guess('newline', self) def guess_text(config, sample_text) cr_count = sample_text.count("r") lf_count = sample_text.count("n") crlf_count = sample_text.scan(/rn/).length if crlf_count > cr_count / 2 && crlf_count > lf_count / 2 return {"parser" => {"newline" => "CRLF"}} elsif cr_count > lf_count / 2 return {"parser" => {"newline" => "CR"}} else return {"parser" => {"newline" => "LF"}} end end end end
  • 66. #ccc_cd4 / #embulk Releasing to RubyGems Examples > embulk-plugin-postgres-json.gem > https://github.com/frsyuki/embulk-plugin-postgres-json > embulk-plugin-redis.gem > https://github.com/komamitsu/embulk-plugin-redis > embulk-plugin-input-sfdc-event-log-files.gem > https://github.com/nahi/embulk-plugin-input-sfdc-event- log-files
  • 67. #ccc_cd4 / #embulk plugin bundle > embulk bundle <dir> > Gemfile
  • 68. Attention! Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto Presto 68
  • 69. Attention! Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive Hive 69
  • 70. Hive Hive Hive Hive Hive Hive HiveHive PrestoPrestogres hba.conf PostgreSQL 70