こんにちは、テクノロジー&エンジニアリングセンターの山本です。

本技術ブログでは、「Lernaのバッチ処理並列化ライブラリ を適用したNablarchバッチ 」 (以下、Lernaバッチ)の性能検証結果を紹介します。

最初にLerna のバッチ処理並列化ライブラリについて紹介したあと、検証環境、及び以下の3つの観点で検証した結果を紹介します。

  • Lerna のバッチ処理並列化ライブラリを組み込むことによるオーバーヘッドが無いこと
  • 多重度による性能向上
  • コア数を増やすことによる、さらなる性能向上

1.Lerna のバッチ処理並列化ライブラリの紹介

1.1.Lerna のバッチ処理並列化ライブラリとは

Lerna は高い可用性とスループットが求められるシステムを、クラウドを始めとするオープンな環境で構築するためのソフトウェアスタックです。そのコンテンツの1つとして、バッチ処理を高速化するためのライブラリ「nablarch-fw-batch-parallelizable 」を提供しており、これを「バッチ処理並列化ライブラリ」と呼びます。本ライブラリはNablarchバッチを機能拡張するライブラリで、大量データを処理する際に任意の多重度でバッチを並行に処理することで処理時間を短縮できます。

1.2.バッチ処理並列化ライブラリを作った理由

昨今のアプリケーションは、即応性、弾力性、耐障害性(レジリエンス)といった非機能面への要求が高度になっています。これらの要求に答えるのがリアクティブシステム と言われており、中でも耐障害性においてよりハイレベルな稼働率を要求するビジネスドメインへ適応するため、我々はLernaを開発しました。こういったビジネスドメインでは大量のトランザクションを扱う必要があり、バッチ処理が扱うデータ量も必然的に増加していきます。データ量の増加に伴う処理時間の増加は顕著でありながら、これらのバッチ処理は決められた時間までに処理を終えなければならないことに変わりはありません。データ量が増えるからと言って処理時間の増加が許容されるわけではないのです。 こういったケースでどんな対策がとれるでしょうか?

  • クエリのチューニング
  • ロジックの見直しによる処理の効率化
  • キャッシュやINSERTやUPDATEの一括処理によるDBアクセス数の削減
  • 処理の並列化
  • ハードウェアの増強

ケースによって取りうる戦略、効果的な戦略は異なりますが、多くの場合その対策における効果とリスクや対策にかかるコストとのトレードオフで選択することになるのではないでしょうか?その中で比較的コストのかかる「処理の並列化」を低コストで実現するためLerna のバッチ処理並列化ライブラリを開発しました。実際には、弊社内でデータ量の増加に伴う処理時間の増加に対してあらゆる対策を講じそれでも頭を悩ませていた某システム向けに開発した結果、非常に効果的であったためライブラリ化したという背景があります。

1.3.バッチ処理並列化ライブラリの特徴

Lernaのバッチ処理並列化ライブラリには次の3つの特徴があります。

  • 任意のキーにおける順序保証
  • Nablarchバッチへの適用、多重度の変更が容易
  • リアクティブストリームの実装

1.3.1.任意のキーにおける順序保証

処理を並列化する際に考慮すべき事項として順序性があります。バッチ処理のインプットとなるデータに対して逐次的に処理を行う場合は入力順に処理しますが、並列化すると異なる処理レーンの実行順は保証されないため順序の逆転が生じることがあります。本ライブラリは並列化する際に任意のキーで処理を行うレーンの振り分けを行うため、キーの値が同じであれば同じレーンで処理されることになります。これにより、そのキーにおける順序が逆転することはありません。例えば、顧客IDをキーに指定すると同じ顧客の売上(取消)データは同じレーンに振り分けられ、必ず入力順に処理されます。特定レーンの処理が遅延することで実行順が入れ替わり、売上データより先に売上取消を処理してしまい、「元取引が存在しない」というエラーが発生するという心配もありません。

1.3.2.Nablarchバッチへの適用、多重度の変更が容易

Nablarchバッチを利用している場合、本ライブラリは容易に適用 できます(或いは、Nablarchバッチを利用することで容易に適用できます)。Javaライブラリとして提供していますのでMavenのDependencyに追加し処理レーンの振り分けに使用するキーを指定するだけです。ただし、処理を並列に実行しますので、グローバルな変数を使っている場合は注意が必要です。また、運用中のデータ量の増加に合わせて多重度を変更できます。単にデータ量の増加に合わせて処理を実行するサーバーのハードウェアを増強するだけではCPUを効率的に利用できず十分な性能改善が得られないことがあります。その際、同時に多重度をチューニングすることで与えられたCPUをフル活用し性能を改善できます(もちろん、改善効果はボトルネックが何処にあるかによります)。

1.3.3.リアクティブストリームの実装

リアクティブストリーム を実装した Akka Streams ※ により並列化しています。Akka Streamsのフロー制御により実現しているためデータの抽出を行いつつ非同期に処理を進めていくことや、処理レーン毎に速度が異なるときに特定レーンが全体の処理進捗を妨げないようバッファを設けるなどの細かなチューニングも容易にできます。詳細なアーキテクチャはLerna のバッチ処理並列化ライブラリの公式ドキュメント をご参照ください。

※Akka Streamsについては、 ThinkIT「Akka Streamsで実装するリアクティブストリーム」 で解説しています。

2.性能検証の環境

このように並列化によってバッチの性能を向上させることができる Lerna ですが、実際どの程度性能の向上が見込めるのか検証してみました。この章では、検証で使用したAWS(Amazon Web Services)環境と、検証用バッチアプリケーション(以下、性能検証サンプル)について説明します。

2.1.検証で使用したAWS環境

バッチサーバとしてAmazon EC2(以下、EC2)を、データベースサーバとしてAmazon RDS for PostgreSQL(以下、RDS)を用いた以下の構成にて検証しました。

2.2.検証用バッチアプリケーション(性能検証サンプル)

性能検証サンプルは、以下で公開されているLernaバッチのサンプルバッチアプリケーション(以下、標準サンプル)を、並列化の効果を正確に計測することとLernaバッチの順序保証の特徴を表現することを目的に改造したものです。
https://github.com/lerna-stack/nablarch-fw-batch-parallelizable-example/

標準サンプルは、日本郵便が公開している郵便番号データのCSVファイル から読み込んだ郵便番号情報をそのままデータベースに書きこむバッチアプリケーションです。
性能検証サンプルは、これに対して、以下の2点の改造を加えたものです。

  • ダミーウエイトを挿入

標準サンプルでは、処理負荷がI/O負荷に対して軽すぎるため、I/Oがボトルネックとなってしまい、並列化による効果が十分に確認できません。
そこで、性能検証サンプルには、ダミーウエイトとして、それぞれの郵便番号レコードに対して、RSAキーペアを生成・追記する処理を挿入しました。

  • 順序保証を利用できるようにする

上記の郵便番号データのCSVファイルでは、1つの郵便番号を1行で表しているので、標準サンプルはCSVファイルの1行をバッチの1要素として処理しています。
標準サンプルは処理する全ての要素が独立しており、実行順序を保証する必要はありません。今回は、バッチ処理のよくあるユースケースとして順序保証が必要なサンプルにするため、以下のように改造しました。

- 1つの郵便番号に対するデータベースの登録と更新(RSAキーペア生成・追記)を、CSVファイル上で2行に分ける。
- 郵便番号を要素のキー項目とし、同一郵便番号の登録と更新に関連性を持たせる。

2.2.1.性能検証サンプルの処理概要

  1. CSVファイルから1行読み込む。(※CSVファイルの構成は後述。)
  2. 処理区分が「0:登録」の場合は、読み込んだデータをそのままデータベースに登録(INSERT)する。
  3. 処理区分が「1:更新」の場合は、郵便番号が同じレコードをデータベースから抽出し(SELECT)し、RSAのキーペアを生成したうえで、キーペアをレコードに追記(UPDATE)する。

2.2.2.性能検証サンプルのCSVファイルの構成

  • CSVの各行の処理区分列には処理の内容(0:登録 or 1:更新)を表すフラグが記されている。
  • 2行で1つの郵便番号に対する処理となる。
  • 行数は20,000行とする。(郵便番号としては10,000個)

2.2.3.性能検証サンプルの公開先

性能検証サンプルやCSVファイルは以下の場所に公開しました。
https://github.com/lerna-stack/nablarch-fw-batch-parallelizable-performancetest-example/

3.性能検証の内容と結果

検証は前述の環境において、以下の3つの観点にて実施しました。

3.1.Lerna のバッチ処理並列化ライブラリを組み込むことによるオーバーヘッドが無いこと

Lerna のバッチ処理並列化ライブラリを組み込むことによるオーバーヘッドが無いことを確認するために、多重度を1に設定したLernaバッチとNablarchバッチで同じ業務処理を実施させ、そのスループット(1分間に処理できる件数)を比較しました。
どちらも同一性能の仮想マシンを使用し、それぞれ、5回実行した結果から、スループットの最高値、最低値を除いた3回分の平均値をとりました。

[検証環境]

ノード インスタンスタイプ
バッチサーバ(EC2) c5.2xlarge(vCPU数=8)
データベースサーバ(RDS) m4.2xlarge

[検証結果]

種類 スループット[件/分]
Nablarchバッチ 357.8
Lernaバッチ 357.9

両者の差異は0.02%であり、差としては無視できる程度です。したがって、Lerna のバッチ処理並列化ライブラリを組み込むことによるオーバーヘッドは無いと判断できます。

3.2.多重度による性能向上

Lernaバッチにおいて、多重度を1~20まで変化させたときのスループット、CPU使用率を比較しました。
いずれも同一性能の仮想マシンを使用し、それぞれ5回実行した結果から、スループットの最高値、最低値を除いた3回分の平均値をとり、それをプロットしました。

[検証環境]

ノード インスタンスタイプ
バッチサーバ(EC2) c5.2xlarge(vCPU数=8)
データベースサーバ(RDS) m4.2xlarge

[検証結果]


※「スループット向上倍率」は 各多重度に対するスループットが多重度1のスループットの何倍であるかを表したものです。

多重度を上げていくとCPU使用率がほぼ100%となっていることから、Lernaバッチによって、マシンのCPUリソースを限界近くまで活用できていることが確認できました。
また、多重度=4までは、ほぼリニアにスループットが向上していますが、多重度=5以降はスループットの伸びが鈍化しています。
これは、JavaではGCやJITコンパイラなどがバックグランドで動作しており、多重度が小さい場合は、バッチの主処理とバックグランド処理が互いにvCPUを独占できますが、多重度が大きい場合は、同じvCPUをシェアする割合が増え、効率が低下していくためと考えられます。

3.3. コア数を増やすことによる、さらなる性能向上

多重度だけでは頭打ちになる場合でも、コア数を増やすことによって、さらなる性能向上が可能であるかを確認するため、vCPU数の異なる仮想マシンを使用して、前述の「多重度による性能向上」と同じ内容の検証を実施しました。

[検証環境]

ノード インスタンスタイプ
バッチサーバ(EC2) c5.2xlarge(vCPU数=8)
c5.4xlarge(vCPU数=16)
c5.9xlarge(vCPU数=36)
c5.12xlarge(vCPU数=48)
データベースサーバ(RDS) m4.2xlarge

[検証結果]

上記4つの仮想マシンにおける検証結果を同一グラフ上にプロットしました。点線は、それぞれに対するスループットの傾向を表します。

多重度が小さい場合(処理の並列化が十分になされていないアプリケーションもこれにあたる)はvCPUの増加による性能向上効果が小さく、多重度が大きくなるとvCPU数の増加によるその効果が大きくなっていることが分かります。そして、vCPU数が大きいマシンほど、スループットの限界値が高くなることが分かります。

さらに、4つの仮想マシンにおけるスループットの最大値をとり、vCPU数との関係をグラフ上にプロットしました。

vCPU数に対するスループットとしてはvCPU=48でも、まだ収束していません。このような場合は、処理を適切に多重化した上でvCPU数を増やせば、さらなる性能向上が期待できます。
なお、向上倍率としてはリニアな数値には届いていません。これは、内部に並列化できない部分が含まれているためと考えられます。(参考:アムダールの法則 

4.まとめ

本検証により、得られた知見は以下のとおりです。

  • NablarchバッチにLerna のバッチ処理並列化ライブラリを組み込むことによるオーバーヘッドは無い。
  • Lernaバッチの多重度を適切に指定することにより、CPU資源を限界まで引き出せる。
  • コア数を増やすことによって、性能をさらに高めていくことができる。

ビジネスの拡大などでデータ量の増加が予測される中、バッチにも性能の弾力性が求められる場合があります。
このような場合、Lerna のバッチ処理並列化ライブラリを最初から組み込んでおき、データ量の変化に応じて、多重度を調整したり、コア数の多いマシンへ切り替えたりすることで、弾力的に性能を拡張させることができます。

順序保証が必要のないケースではNablarchバッチの仕組みを用いて処理の並列化が可能です。
順序保証が必要で、並列実行により性能をスケールさせていきたい場合は、Lernaバッチが特に有効です。