先日、2026-09-11(金)にEmbulkユーザーミートアップを開催する告知を公開しました。 その記事にある通り、クリアコードはこれまでEmbulk自体の開発には関わっていません。 一方で、Embulkを使っているお客さまへのサポートは行っており、現在も継続しています。
そのサポートの中心にあるのが EmbulkEmbed という仕組みです。
あまり知られていないと思います。私も相談を受けるまで知りませんでした。
そして、ドキュメントがほとんどありません。
本記事では、EmbulkとEmbulkEmbedがそれぞれ何なのかを説明し、EmbulkEmbedを使うと何が得られて何を背負うことになるのかを整理します。
Embulkとは
Embulkは、並列バルクデータローダーです。 「どこかにある大量のデータを、別のどこかへ転送する」ための道具、と考えるとわかりやすいと思います。 データベースからS3へ、CSVファイルからデータベースへ、といった転送を担当します。
Embulkの特徴はプラグイン構成です。転送処理は、次のようなプラグインを組み合わせて作ります。
- データの読み込み(input)
- 書き出し(output)
- テキストの解析(parser)
- 整形(formatter)
- 圧縮・展開(encoder/decoder)
- 加工(filter)
利用者はYAMLで設定を書き、embulk run config.yml のようにコマンドラインから実行します。
なお、Embulkは現在メンテナンス・モードになっています。
EmbulkEmbedとは何か
Embulkをコマンドラインから実行すると、1回の実行ごとにJVMの起動とプラグインの読み込み・初期化が走ります。
この初期化コストが特に効いてくるのは、データベースへのアクセスが前提となる処理です。テーブルごとに転送を分けて実行するなら、テーブルの数だけEmbulkの起動を繰り返すことになるからです。
転送対象が1つのテーブルだけなら気になりませんが、数十・数百のテーブルを順番に転送するようなバッチ処理では、この初期化コストが実行回数の分だけ積み上がります。
実際ある記事では、embulk run をforループで10回起動すると実時間で2分47秒かかったのに対し、EmbulkEmbedで同じ処理を1プロセス内で10回ループ実行すると16秒、100回に増やしても19秒だった、という実測が報告されています(Embulkの利用でミスマッチをしてしまった話)。
EmbulkEmbedを使えばこれを1回で済ませられる、という発想です。 ただし、この発想が効くのは「短時間で終わる処理を何度も実行している」場合に限られます。 一回あたりの処理そのものに時間がかかっていて実行回数自体は多くない、という状況であれば、効果は限定的です。
EmbulkEmbedは、Embulkをコマンドとして起動する代わりに、自分のJavaアプリケーションの中にEmbulkを組み込んで動かすための仕組みです。
org.embulk.EmbulkEmbed というクラスとして提供されています。
PostgreSQLから読んだデータをCSVにしてgzip圧縮し、S3へ出力する場合、コードはおおよそこうなります。
final EmbulkEmbed.Bootstrap bootstrap = new EmbulkEmbed.Bootstrap();
// config.yml のプラグイン type 名と、対応するクラスを紐付けます
bootstrap.builtinInputPlugin("postgresql", org.embulk.input.postgresql.PostgreSQLInputPlugin.class);
bootstrap.builtinFormatterPlugin("csv", org.embulk.formatter.csv.CsvFormatterPlugin.class);
bootstrap.builtinEncoderPlugin("gzip", org.embulk.encoder.gzip.GzipFileEncoderPlugin.class);
bootstrap.builtinFileOutputPlugin("s3v2", org.embulk.output.s3v2.S3V2FileOutputPlugin.class);
final EmbulkEmbed embulk = bootstrap.initialize();
final ConfigSource config = embulk.newConfigLoader().fromYamlFile(new File("config.yml"));
embulk.run(config);
YAMLによる設定は引き続き使えます。
しかし、YAMLに type: csv と書いたときに実際どのクラスが使われるのかは、アプリケーション側のコードで登録しておかなければなりません。
builtinXXXPlugin(名前, クラス) という呼び出しが、その対応付けです。
得られるものは、初期化コストを1回にまとめられること、そしてEmbulkの実行を自分のアプリケーションの処理フローに組み込めることです。 引き換えに背負うのは、プラグインの登録と依存関係の管理、そしてEmbulkのライフサイクル管理です。
v0.11でプラグインがすべて分離された
ここが最初のつまずきどころです。
v0.9までは、CSVのパーサーやgzipのエンコーダーといった基本的なプラグインが embulk-standards としてEmbulk本体に同梱されていました。
v0.10からv0.11にかけて、これらはEmbulk本体から切り離され、それぞれ独立したリポジトリとMavenのアーティファクトになりました。
この背景にある設計方針は、EEP(Embulk Enhancement Proposal、Embulkの設計方針を議論・記録する文書)としてまとめられています。その1つがEEP-3: The "Compact Core" Principleです。
コマンドラインからEmbulkを使っている限り、この変更にはほとんど気づきません。 実行バイナリの中に標準プラグインが同梱されていて、Embulkがそれを自動的に見つけてくれるからです。
しかし、EmbulkEmbedはその実行バイナリを使いません。
そのため、「CSVを読む」ためにも embulk-parser-csv を依存関係に追加して builtinParserPlugin で登録する必要があります。
読み込み側だけでなく、書き出し側のformatterやencoderも同様です。
ここを見落とすと、実行時にプラグインが見つからないというエラーになります。
Gradleで書くと、こうなります。
dependencies {
implementation "org.embulk:embulk-core:0.11.5"
implementation "org.embulk:embulk-deps:0.11.5"
// 使用するプラグインを追加します
implementation "org.embulk:embulk-input-postgresql:0.13.2"
implementation "org.embulk:embulk-formatter-csv:0.11.2"
implementation "org.embulk:embulk-encoder-gzip:0.11.1"
// JDBCドライバーも自分で追加します
implementation "org.postgresql:postgresql:42.6.0"
}
embulk-core だけでなく embulk-deps も必要です。
Embulkは自身が使う依存ライブラリをプラグインから隠すために別のアーティファクトに分けているため、実行時にはこちらも必要になります。
登録メソッドの選び方にも注意が必要です。
たとえば embulk-output-s3v2 の S3V2FileOutputPlugin は OutputPlugin ではなく FileOutputPlugin を実装しています。登録には builtinFileOutputPlugin を使います。
builtinFileOutputPlugin で登録しておけば、Embulk側が FileOutputRunner で包んでくれるため、YAMLには通常通り type: s3v2 と書けます。
つまり、使いたいプラグインがどのインターフェースを実装しているかを、あらかじめ知っておく必要があります。
Liquidテンプレートは自分で処理する
EmbulkをCLIで使う場合、設定ファイルの拡張子を .yml.liquid にしておくと、Liquidテンプレートとして処理されます。
{{ env.AWS_S3_BUCKET_NAME }} のように書いて環境変数を埋め込めるので、バケット名や認証情報を設定ファイルに直接書かずに済みます。
EmbulkEmbedでは、これが効きません。
Liquidの処理はCLIの実行経路である EmbulkRunner が担当していて、しかも中身はJRuby経由でRubyのLiquidを呼び出す実装になっています。
EmbulkEmbedから使う ConfigLoader の方には、Liquidの処理が一切入っていません。
fromYamlFile() も fromYamlString() も、渡されたYAMLをそのままパースするだけです。
なので、Liquidを使いたければ自分でレンダリングする必要があります。 Java実装のliqpを使ったサンプルは以下のとおりです。
// config.yml をテキストとして読み込みます
final String rawYaml = new String(Files.readAllBytes(Paths.get("config.yml")));
// 環境変数を "env" キーで渡します。設定ファイル側から env.XXXXXX として参照できます
final Map<String, Object> variables = new HashMap<>();
variables.put("env", System.getenv());
final String renderedYaml = Template.parse(rawYaml).render(variables);
final ConfigSource config = embulk.newConfigLoader().fromYamlString(renderedYaml);
こうしておくと、設定ファイル側ではCLIと同じ書き方ができます。
out:
type: s3v2
bucket: "{{ env.AWS_S3_BUCKET_NAME }}"
temp_path: "{{ env.EMBULK_TMP_DIR | default: '/tmp' }}"
環境変数の設定次第で、検証環境・本番環境向けに動作を切り替える、というようなことが可能になります。
ドキュメントがほとんどない
EmbulkEmbedを使う上での一番の壁は、おそらくこれです。
embulk.orgにEmbulkEmbedの使い方の説明はありません。 Embulkのリポジトリを探すと、設計ドキュメントやEEPの中に断片的な言及が出てくるだけです。
有志の方が書かれたブログ記事はいくつか見つかります。
しかし、その多くはv0.9以前の古い仕組みを前提にしたもので、最新版ではそのまま動きません。
v0.10から0.11の間に、Guiceによる依存性注入が取り除かれ、embulk-standards が分離され、システム設定が java.util.Properties ベースに変わっています。
昔の記事のコードをそのまま持ってきてもコンパイルが通らない、という状況です。
結局、embulk-core のソースコードを読むのが一番確実でした。
この記事で挙げているプラグイン解決の順序やクラスローダーの挙動も、ソースコードを読んで確認したものです。
公式は「1プロセスで1回だけ実行する」を前提にしている
EmbulkEmbedには、設計上の前提もあります。
Embulkのコアライブラリの依存関係について書かれたEEP-7に、次の一文があります。
Embulk is basically designed for one-shot execution in one Java process.
「Embulkは基本的に、1つのJavaプロセスで1回だけ実行することを想定して設計されている」と明言されています。 そして同じ段落には、EmbulkEmbedを使って1つのJavaプロセスから複数回実行することは推奨されない、という注意も書かれています。理由は、リソースリークのような避けがたい問題を引き起こしやすいからです。
EmbulkEmbedを使いたくなる動機は「起動と初期化のコストを1回で済ませたい」ことであり、それはそのまま「同じJVMで何回も実行する」という設計に直結します。 実際にお客さまからリソースリークの対処について相談を受け、問題解決に向けてやりとりをさせていただいてます。
クリアコードがやっていること
私たちは今、EmbulkEmbedを使ったデータ転送アプリケーションの開発を検討しているお客さまに対して、サンプルアプリケーションの提供や技術サポートを行っています。 これまでに、以下のようなサポートを提供してきました。
- 実際に動くサンプルプロジェクトの作成と提供
- PostgreSQL・Oracle・LocalStackを含む検証環境の構築
- 出力プラグインのリソースリークの特定と、パッチの作成・提供
冒頭に書いた通り、クリアコードはEmbulk自体の開発に関わったことはありませんでした。 それでも、ドキュメントがなければソースコードを読み、原因を特定し、必要ならパッチを書く、というやり方は変わりません。 これはGroongaやFluentdで長くやってきたことと同じです。
さいごに
EmbulkEmbedは、Embulkを自分のJavaアプリケーションに組み込む仕組みです。 プラグインの登録とライフサイクルの管理を、アプリケーション自身が引き受ける仕組みです。 その代わりに、初期化コストを削り、環境依存の少ない構成を作れます。 ただし、ドキュメントは乏しく、公式が推奨していない領域にあります。
クリアコードではFLOSSインシデントサポートを提供しています。 メンテナンス・モードのソフトウェアや、更新が止まったプラグインについても、ソースコードを読んで原因を特定し、必要に応じてパッチを提供します。 お困りのことがあればお問い合わせフォームよりお気軽にご相談ください。
また、2026-09-11(金)にEmbulkユーザーミートアップを開催します。 まだEmbulkを使っている方、Embulkの今後に興味がある方はぜひご参加ください。