kagamihogeの日記

kagamihogeの日記です。

Spring Batch 4.1.x - Reference Documentation - What’s New in Spring Batch 4.1のテキトー翻訳

https://docs.spring.io/spring-batch/4.1.x/reference/html/whatsnew.html#whatsNew

https://qiita.com/kagamihoge/items/12fbbc2eac5b8a5ac1e0 俺の訳一覧リスト

1. What’s New in Spring Batch 4.1

Spring Batch 4.1 releaseは以下の新機能があります。

@SpringBatchTestアノテーション

Spring Batchにはバッチコンポーネントテスト用のユーティリティクラス(JobLauncherTestUtilsJobRepositoryTestUtils)とtest execution listeners(StepScopeTestExecutionListenerJobScopeTestExecutionListener)があります。しかし、これらユーティリティを使用するには、明示的なbean設定が必要です。今回リリースで導入する@SpringBatchTestは自動的にユーティリティのbeanとリスナをテストコンテキストに追加してautowiringで利用可能にします。

@RunWith(SpringRunner.class)
@SpringBatchTest
@ContextConfiguration(classes = {JobConfiguration.class})
public class JobTest {

   @Autowired
   private JobLauncherTestUtils jobLauncherTestUtils;

   @Autowired
   private JobRepositoryTestUtils jobRepositoryTestUtils;


   @Before
   public void clearMetadata() {
      jobRepositoryTestUtils.removeJobExecutions();
   }

   @Test
   public void testJob() throws Exception {
      // given
      JobParameters jobParameters =
            jobLauncherTestUtils.getUniqueJobParameters();

      // when
      JobExecution jobExecution =
            jobLauncherTestUtils.launchJob(jobParameters);

      // then
      Assert.assertEquals(ExitStatus.COMPLETED,
                          jobExecution.getExitStatus());
   }

}

このアノテーションについては、Unit Testingを参照してください。

1.2. @EnableBatchIntegration Annotation

remote chunking jobの設定にはいくつかのbean定義が必要です。

  • メッセージングミドルウェア(JMS, AMQPなど)からコネクションを得るためのコネクションファクトリ
  • MessagingTemplate マスタからワーカーにリクエストを送信してその戻りの処理
  • メッセージングミドルウェアからメッセージを得るSpring Integrationの入出力チャネル
  • 処理と書き込みをするワーカーにデータのchunkを送信する、マスター側のitem writer(ChunkMessageChannelItemWriter
  • マスターからデータを受信する、ワーカー側のメッセージリスナー(ChunkProcessorChunkHandler

これは一見するだけで設定が面倒なのが分かります。今回リリースではAPIに加えて設定を簡易化するための@EnableBatchIntegrationも追加しています。以下はこのアノテーションAPIの使用例です。

@Configuration
@EnableBatchProcessing
@EnableBatchIntegration
public class RemoteChunkingAppConfig {

   @Autowired
   private RemoteChunkingMasterStepBuilderFactory masterStepBuilderFactory;

   @Autowired
   private RemoteChunkingWorkerBuilder workerBuilder;

   @Bean
   public TaskletStep masterStep() {
         return this.masterStepBuilderFactory
                         .get("masterStep")
                         .chunk(100)
                         .reader(itemReader())
                         .outputChannel(outgoingRequestsToWorkers())
                         .inputChannel(incomingRepliesFromWorkers())
                         .build();
   }

   @Bean
   public IntegrationFlow worker() {
         return this.workerBuilder
                         .itemProcessor(itemProcessor())
                         .itemWriter(itemWriter())
                         .inputChannel(incomingRequestsFromMaster())
                         .outputChannel(outgoingRepliesToMaster())
                         .build();
   }

   // Middleware beans setup omitted
}

このアノテーションは基盤となるbean設定の負荷を軽減します。ワーカー側のSpring Integration flowと同じように、マスターのstepを設定します。remote chunkingのこれら新規APIを使用するサンプルはsamples moduleで、詳細はSpring Batch Integrationにあります。

remote chunking設定の簡易化に加えて、今回のバージョンはremote partitioningのセットアップを簡易化するAPIRemotePartitioningMasterStepBuilderRemotePartitioningWorkerStepBuilder、も導入しています。以下例のように@EnableBatchIntegrationを付与することで、これらのbeanをautowiredできます。

@Configuration
@EnableBatchProcessing
@EnableBatchIntegration
public class RemotePartitioningAppConfig {

   @Autowired
   private RemotePartitioningMasterStepBuilderFactory masterStepBuilderFactory;

   @Autowired
   private RemotePartitioningWorkerStepBuilderFactory workerStepBuilderFactory;

   @Bean
   public Step masterStep() {
            return this.masterStepBuilderFactory
               .get("masterStep")
               .partitioner("workerStep", partitioner())
               .gridSize(10)
               .outputChannel(outgoingRequestsToWorkers())
               .inputChannel(incomingRepliesFromWorkers())
               .build();
   }

   @Bean
   public Step workerStep() {
            return this.workerStepBuilderFactory
               .get("workerStep")
               .inputChannel(incomingRequestsFromMaster())
               .outputChannel(outgoingRepliesToMaster())
               .chunk(100)
               .reader(itemReader())
               .processor(itemProcessor())
               .writer(itemWriter())
               .build();
   }

   // Middleware beans setup omitted
}

この新規APIの詳細はSpring Batch Integrationを参照してください。

1.3. JSON support

Spring Batch 4.1はJSONフォーマットをサポートします。今回リリースは以下フォーマットのJSONを読み込むitem readerを追加しました。

[
  {
    "isin": "123",
    "quantity": 1,
    "price": 1.2,
    "customer": "foo"
  },
  {
    "isin": "456",
    "quantity": 2,
    "price": 1.4,
    "customer": "bar"
  }
]

XML用のStaxEventItemReaderの類似機能で、JsonItemReaderはchunkのJSONオブジェクトを読み込むのにストリーミングAPIを使用します。Spring Batchは以下2つのライブラリをサポートします。

上記以外のライブラリを使う場合、JsonObjectReaderインタフェースを実装してください。

また、JSONデータの書き込みにはJsonFileItemWriterがあります。JSONサポートの詳細については、ItemReaders and ItemWritersを参照してください。

1.4. Bean Validation API support

今回リリースではValidatingItemProcessor実装のBeanValidatingItemProcessorを提供しており、これはBean Validation API (JSR-303)アノテーションでitemをvalidateします。例えば、以下のPersonがあるとします。

class Person {

    @NotEmpty
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

BeanValidatingItemProcessorのbeanを作成してchunk stepにprocessorとしてアプリケーションコンテキストに登録してitemをvalidateします。

@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
        BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
        beanValidatingItemProcessor.setFilter(true);

        return beanValidatingItemProcessor;
}

1.5. JSR-305 support

今回リリースはJSR-305向けの機能を追加しています。Spring FrameworkNull-safetyアノテーションを使用し、Spring Batchのpublic APIに追加します。

これらアノテーションはSpring Batch API使用時にnull-safetyを強制するだけでなく、IDEにnullに関する有用な情報を提供するのにも使えます。たとえば、ItemReaderインタフェースを実装するとして、JSR-305アノテーションをサポートするIDEは以下を生成可能になります。

public class MyItemReader implements ItemReader<String> {

        @Nullable
        public String read() throws Exception {
                return null;
        }

}

readメソッドの@Nullableアノテーションnullを返す可能性があるという契約を明示しています。Javadocの記述である、readメソッドはデータソース読み込み完了時にnullを返す、をアノテーションにより強制しています。

1.6. FlatFileItemWriterBuilder enhancements

今回リリースの小規模変更にフラットファイル書き込み設定簡易化があります。具体的には、デリミタと固定幅ファイルの簡易化です。以下は変更前と後の例です。

// Before
@Bean
public FlatFileItemWriter<Item> itemWriter(Resource resource) {
        BeanWrapperFieldExtractor<Item> fieldExtractor =
            new BeanWrapperFieldExtractor<Item>();
        fieldExtractor.setNames(new String[] {"field1", "field2", "field3"});
        fieldExtractor.afterPropertiesSet();

        DelimitedLineAggregator aggregator = new DelimitedLineAggregator();
        aggregator.setFieldExtractor(fieldExtractor);
        aggregator.setDelimiter(";");

        return new FlatFileItemWriterBuilder<Item>()
                        .name("itemWriter")
                        .resource(resource)
                        .lineAggregator(aggregator)
                        .build();
}

// After
@Bean
public FlatFileItemWriter<Item> itemWriter(Resource resource) {
        return new FlatFileItemWriterBuilder<Item>()
                        .name("itemWriter")
                        .resource(resource)
                        .delimited()
                        .delimiter(";")
                        .names(new String[] {"field1", "field2", "field3"})
                        .build();
}

Spring Batch 4.1.x - Reference Documentation - Spring Batch Introductionのテキトー翻訳

https://docs.spring.io/spring-batch/4.1.x/reference/html/spring-batch-intro.html#spring-batch-intro

https://qiita.com/kagamihoge/items/12fbbc2eac5b8a5ac1e0 俺の訳一覧リスト

1. Spring Batch Introduction

エンタープライズ領域の多くのアプリケーションはミッションクリティカル環境におけるビジネスオペレーションの実行にバルク処理を必要とします。これらのビジネスオペレーションには以下が含まれます。

  • 大規模な情報の複雑な処理を、自動的に、ユーザ操作を介さず出来る限り効率的に処理します。これらのオペレーションは、基本的には、時間駆動のイベント(月末計算・通知・対応)を持ちます。
  • 複数の大規模データセット(保険給付決定または料率調整)を繰り返し処理する複雑なビジネスルールを定期実行するアプリケーション。
  • 内外システムから受信する情報の一貫性保持。これには、フォーマット・validation・そのシステムのレコードに投入するトランザクションルールでの処理、が必要となります。エンタープライズで日々数十億トランザクションを処理するのにバッチ処理を使います。

Spring Batchは軽量・包括的なバッチフレームワークで、エンタープライズシステムの日常的なオペレーションで不可欠であるロバストなバッチアプリケーション開発に用います。Spring BatchはSpring Frameworkの上にあるので開発者が期待する機能(効率的な、POJOベース開発など使い勝手の良い機能群)を備えており、開発者は必要に応じて高度なエンタープライズサービスの利用と活用が出来ます。Spring Batchはスケジューリングフレームワークではありません。エンタープライズのスケジューラは他に良いプロダクトが商用・OSSで多数存在します(Quartz, Tivoli, Control-Mなど)。設計意図としてはスケジューラと組み合わせて動作させ、スケジューラを置き換えるものではありません。

Spring Batchは、大量レコード処理・ログ/追跡・トランザクション管理・ジョブ処理統計・ジョブのリスタートとスキップ・リソース管理、で必要不可欠で繰り返し使用する機能を提供します。また、より高度でテクニカルなサービスも提供し、これらは最適化とパーティショニングを通じて極めて大量でハイパフォーマンスなバッチジョブを可能にします。Spring Batchは、複雑で大規模なユースケース(DB間で大規模データの移動や変換など)だけでなくシンプルなユースケース(ファイルを読んでDBに入れたり、ストアドを実行するなど)の、どちらでも使用出来ます。大規模バッチジョブで大量データを処理するにはフレームワークの非常にスケーラブルな機能が便利です。

1.1. Background

オープンソースソフトウェアプロジェクトとその関連コミュニティはwebとマイクロサービスのアーキテクチャフレームワークに強い関心を寄せていますが、エンタープライズIT環境ではバッチ処理のニーズが根強くあったにも関わらず、Javaベースのバッチ処理のニーズを満たすアーキテクチャフレームワークに対する関心が欠如していました。スタンダードとなるものが無く、バッチアーキテクチャは無数に増殖し、クライアントのエンタープライズIT環境でその企業専用に開発されていました。

SpringSource (now Pivotal) とAccentureはこれを変えるための協力をしました。Accentureのバッチアーキテクチャ実装における実事業と技術的経験、SpringSourceの深い技術的経験、Springの実績あるプログラミングモデルが、高品質でエンタープライズJavaでのギャップを埋めることを目的とした市場向けのソフトウェアを作成、これは自然で強力な協働関係を生みました。Springベースのバッチアーキテクチャソリューションを開発する際に似たような問題を解決してきた多数のクライアントと両社は開発をしてきました。これにより、実世界の課題に適用できるソリューションを保証する、実世界の制約と詳細をもたらしました。

AccentureはSpring Batchプロジェクトに以前からプロプライエタリバッチ処理アーキテクチャフレームワークをコントリビュートし続けており、サポートのためのコミッタ―リソース・機能拡張・既存機能セット、の提供もしています。Accentureのコントリビュートは直近数世代のプラットフォーム、COBOL/Mainframe, C++/Unix, 最近ではJava/anywhere、でのバッチアーキテクチャ構築の数十年にわたる経験の裏付けがあります。

AccentureとSpringSourceの共同作業は、バッチアプリケーション作成時にエンタープライズのユーザが活用可能な、software processing approaches・フレームワーク・ツール、標準化の促進がねらいでした。スタンダードでエンタープライズIT環境に実証済みのソリューションを提供したい企業と政府機関はSpring Batchの恩恵を受けられます。

1.2. Usage Scenarios

一般的なバッチ処理は、

  • DB・ファイル・キューから大量のレコードを読み込み。
  • なんらかのデータ処理。
  • 更新後の形式でデータを書き込み。

Spring Batchは、基本的にはユーザ操作を介さないオフライン環境で、上記のバッチのイテレーションを自動実行し、セットとしてトランザクション処理する機能を提供します。バッチのジョブはたいていのITプロジェクトの一部を形成しており、Spring Batchはロバストエンタープライズスケールのソリューションを提供する唯一のオープンソースフレームワークです。

ビジネスシナリオとしては、

  • 周期的にバッチ処理をコミット
  • コンカレントなバッチ処理: ジョブのパラレル処理
  • Staged, エンタープライズメッセージ駆動処理
  • 大規模なパラレルバッチ処理
  • 失敗後の手動もしくはスケジュールによるリスタート
  • 依存ステップのシーケンシャル処理(ワークフロー駆動バッチのエクステンション使用)
  • パーシャル処理: スキップ駆動(例: ロールバック時に使用)
  • バッチ全体のトランザクション。バッチサイズが小さいか既存のストアドプロシージャないしスクリプトを実行する場合など。

技術上の目的としては、

  • バッチ処理の開発者はSpringのプログラミングモデルを使用する: ビジネスロジックに集中し、インフラ部分はフレームワークに任せる。
  • インフラ・バッチ実行環境・バッチアプリケーションそれぞれの関心事を明確に分離する。
  • インターフェースの形で汎用的なコア実行サービスを提供し、プロジェクトでそれを実装する。
  • 基本的にはそのまま使用できる、コア実行インタフェースのシンプルなデフォルト実装を提供する。
  • springフレームワークをすべてのレイヤで活用し、設定・カスタマイズ・サービス拡張を容易にする。
  • シンプルなデプロイメントモデルを提供し、バッチのjarはMavenでビルドするアプリケーションと完全に分離する。

1.3. Spring Batch Architecture

Spring Batchは拡張性と多様なユーザをターゲットに設計しています。以下は拡張性と開発容易性のためのレイヤーアーキテクチャを示した図です。

Figure 1. Spring Batch Layered Architecture

このレイヤーアーキテクチャは三つの主要な高レベルコンポーネント、Application, Core, Infrastructure、に焦点を当てています。ApplicationはSpring Batchを使用する開発者が書くコードとすべてのバッチジョブです。Batch Coreはバッチジョブの起動と制御に必要なコアランタイムクラスで、JobLauncher, Job, Stepの実装があります。ApplicationとCoreは両者とも共通インフラ上に作ります。このインフラには汎用のリーダー・ライター・サービス(RetryTemplateなど)があり、これらはアプリケーション開発者(リーダーItemReaderとライターItemWriterなど)とコアフレームワーク自身(リトライは自前のライブラリ)の両方が使用します。

1.4. General Batch Principles and Guidelines

以下の核となる原則、ガイドライン、一般的な考慮事項はバッチソリューションの構築時に一考すべきです。

  • バッチのアーキテクチャは基本的にはオンラインのそれに影響を与えるし、逆もまた真です。可能な限り共通のコンポーネントアーキテクチャと環境を設計するよう心掛けてください。
  • 可能な限りシンプルにして、単一バッチアプリケーションに複雑なロジック構造を作り込むのは避けて下さい。
  • 処理とデータストレージは物理的に近くに置いて下さい(つまり処理が発生場所にデータを置く)
  • 特にI/Oのシステムリソース使用を最小化する。可能な限りオペレーションを内部メモリで実行する。
  • 不要な物理I/Oの回避を確認するためアプリケーションI/O(SQLの分析)をレビューする。特に、以下4つのありがちなミスを見つける必要があります。 あるデータを一度読んでワーキング領域に保持したりキャッシュ出来るのに、トランザクションのたびにデータを読み込む。 あるデータを同一トランザクションの最初の方で読んでいるのに、再度データを読み込む。 不要なテーブルもしくはインデックススキャンを発生させる。 SQLのWHEREにキーを指定しない。
  • バッチ実行中に二回同じことをしない。たとえば、レポーティングで何らかのデータ集約をする場合、初回データ処理の際に集約データを(できれば)保存し、レポーティングアプリケーションで同一データの再処理を避けて下さい。
  • ある処理中に時間のかかるアロケーションの再発生を避けるには、バッチアプリケーションの開始時に十分なメモリを割り当ててください。
  • データ整合性は常に最悪を想定してください。十分なチェックとデータ整合性を維持するレコードvalidationを実装してください。
  • 必要に応じて内部的なvalidationにチェックサムを実装してください。たとえば、フラットファイルがファイルの総レコード数およびキーフィールドの集約があるtrailer recordを持つ場合など。
  • 負荷テストの計画と実施を実データと本番環境でなるべく早期に行ってください。
  • 大規模バッチシステムでは、バックアップが困難な場合があり、特に24-7が基本のオンラインでコンカレントに実行する場合です。DBバックアップは基本的にはオンラインのバックアップ機能がありますが、ファイルバックアップも同様に重要な考慮が必要です。バッチがフラットファイルに依存する場合、ファイルバックアップ処理はしかるべき設定とドキュメント化だけでなく、定期的に十分なテストをしてください。

1.5. Batch Processing Strategies

バッチシステムの設計と実装の指針となるよう、基本的なバッチアプリケーションの構成要素とパターンを、設計者と開発者にサンプルの構造図とcode shellsで示します。バッチジョブの設計をする時、ビジネスロジックは以下の標準的な構成要素で実装出来るよう複数のステップに分解します。

  • Conversion Applications: ファイルが外部システムへ生成もしくは提供される種類の場合、提供されるトランザクションレコードを処理に必要な形式へと変換するアプリケーションを構築します。この種のバッチアプリケーションは一部または全体的に変換ユーティリティモジュールで構築します(Basic Batch Services参照)。
  • Validation Applications: validationアプリケーションはすべての入出力レコードが正確かつ矛盾の無いことを保証します。validationは基本的には、ヘッダーとフッター、チェックサムとvalidationアルゴリズム、レコード単位のクロスチェック、で作成します。
  • Extract Applications: この種のアプリケーションは、データベースや入力ファイルからレコードセットを読み込み、定義済みルールでレコードを選択し、出力ファイルにレコードを書き込みます。
  • Extract/Update Applications: アプリケーションがDBないし入力ファイルからレコードを読み込み、DB更新あるいは入力レコード各行に応じて出力ファイルを作成する。
  • Processing and Updating Applications: 抽出やvalidationアプリケーションからの入力トランザクションに対する処理を実行するアプリケーション。通常、この処理は必要なデータ取得のためのDB読み込みがあり、出力処理としてDB更新やレコード作成を伴う場合もある。
  • Output/Format Applications: アプリケーションで入力ファイルを読み込み、そのレコードのデータを何らかの標準フォーマットで再構成し、別のプログラムやシステムで表示や変換するための出力ファイルを生成する。

なお、前述の構築部品では作れないビジネスロジック用の、基礎的なアプリケーションシェルも必要です。

主要な構築部品に加え、個々のアプリケーションは一つ以上の標準ユーティリティステップを使用する場合があります。例えば、

  • Sort: 入力ファイルを読み込んでソートキーで並べ替えした出力ファイルを生成するプログラム。ソートは通常は標準システムユーティリティを使用する。
  • Split: 単一入力ファイルを読み込んで入力行に応じていずれかの出力ファイルに書き込むプログラム。スプリットはパラメータ駆動の標準システムユーティリティで実装あるいは設定します。
  • Merge: 複数入力ファイルから読み込んだレコードを統合して単一の出力ファイルを生成するプログラム。スプリットはパラメータ駆動の標準システムユーティリティで実装あるいは設定します。

また、バッチアプリケーションは入力ソースによる分類もあります。

  • DB駆動アプリケーションはDBから取得した行や値を基に動作する。
  • ファイル駆動アプリケーションはファイルから取得した行や値を基に動作する。
  • メッセージ駆動アプリケーションはメッセージキューから取得したメッセージを基に動作する。

バッチシステムの中核は処理方針です。方針の選択に影響を与える要因としては、概算バッチシステムボリューム、オンラインシステムや他のバッチシステムとの同時並行性、利用可能なバッチウィンドウ*1、があります。(ただし、より多くのエンタープライズ環境が24-7稼働を望んでいるので、明確なバッチウィンドウが消失している)

バッチの一般的な処理オプション(実装の複雑性を増加させる)は以下のとおりです。

  • オフラインモードのバッチウィンドウ内では通常処理。
  • コンカレントバッチ or オンライン処理を選べる。
  • 多数の異なるバッチをパラレル処理、あるいは、同時にジョブをパラレル処理。
  • パーティショニング(同時に同一ジョブの複数インスタンスを処理)
  • 先行オプションの組み合わせ(A combination of the preceding options)

これらオプションのいくつかあるいは全部を商用スケジューラでサポートしている場合があります。

以降のセクションではこれら処理オプションの詳細について解説します。この点は重要で、経験則として、バッチ処理が採るコミットとロッキングストラテジは処理の種類に依存するので、そのオンラインのロッキングストラテジも同一方針を利用します。そうすると、全体アーキテクチャの設計時にバッチアーキテクチャを単なる後付けには出来ません。

ロッキングストラテジは単にDBのロックを使う場合もあれば、そのアーキテクチャでカスタムのロッキングサービスを実装する場合もあります。ロッキングサービスはDBロックに追従(例えば、専用のDBテーブルに必要情報を保存するなど)し、DB操作を要求するアプリケーションプログラムにパーミッションを与えたり拒否したりします。リトライロジックもこのアーキテクチャで実装可能で、ロックが絡む場合にバッチジョブのアボートを回避します。

1. Normal processing in a batch window データ更新があり別々のバッチウィンドウで動作するシンプルなバッチ処理で、オンラインユーザや他のバッチ処理が居ない場合、同時並行性は問題とはならずバッチ実行の最後で単一コミットを実行できます。

大半のケースで、最もロバストな方針が最も適切です。バッチシステムは時が経つにつれて、複雑性と扱うデータボリューム両方が、拡大する傾向に注意してください。ロッキングストラテジを正しく実装しない場合、そのシステムは単一のコミットポイントに依存する事となり、バッチプログラムの修正が困難となります。よって、極めて単純なバッチシステムであっても、リスタート&リカバリのコミットロジックの必要性と、以降のセクションで解説する複雑なケースについても考慮してくださ。

2. Concurrent batch or on-line processing オンラインのユーザからも同時に更新されうるデータをバッチアプリケーションで処理する場合、オンラインで数秒以上必要となるデータ(DBやファイル)はロックしない方が良いです。また、更新は少数のトランザクションが終わるごとにコミットします。それにより、他プロセスで利用できないデータとその時間を最小化します。

物理ロックを最小化する他のオプションとしては、楽観的ロックか悲観的ロックどちらかのパターンで低レベルの論理ロックを実装します。

  • 楽観ロックはレコード競合の可能性が低い前提条件を想定します。これは通常、DBテーブルにタイムスタンプのカラムを定義し、バッチとオンライン処理双方で同時に使用します。アプリケーションが処理で行を取得するとき、タイムスタンプも取得します。次に、アプリケーションが処理対象行を更新するとき、WHERE節にオリジナルのタイムスタンプを使用してUPDATEします。タイムスタンプが合致する場合、データとタイムスタンプは更新されます。もしタイムスタンプが不一致の場合、別のアプリケーションが取得と更新の間に同一行を更新していることを意味します。よって、その更新は実行できません。
  • 悲観ロックはレコード競合の可能性が高い前提条件を想定するため、取得時に物理か論理ロックを必要とします。ある種の悲観的な論理ロックはDBテーブルにロック用のカラムを使用します。アプリケーションが更新で行を取得するとき、ロック用カラムにフラグをセットします。そのフラグを見て、同一行を取得しようとする他アプリケーションは論理レベルで失敗します。フラグをセットするアプリケーションが行を更新するとき、フラグも一緒にクリアすることで、他のアプリケーションからその行が取得可能になります。なお、データの整合性を初回取得とフラグセットの間も維持する事が必須です(例えばDBロック(SELECT FOR UPDATE)などを使用)。また、このやり方は物理ロックと同様の欠点を持ちますが、レコードロック中にユーザが昼飯に行った場合にロックをタイムアウトさせるメカニズムを構築するのが多少簡単なのが異なります。

これらのパターンは必ずしもバッチ処理に最適ではないですが、コンカレントバッチとオンライン処理ではたいてい使用します(DBが行レベルロックをサポートしない場合など)。一般的には、楽観ロックはオンラインアプリケーションに適し、悲観ロックはバッチアプリケーションに適します。論理ロックを使用する場合は、論理ロックで保護するデータエンティティにアクセスするすべてのアプリケーションで同一の方式を採用することが必須となります。

これらの方法は単一レコードのロックを扱うに過ぎない点に注意してください。場合によって、論理的に関連のあるレコードグループのロックの必要が出てきます。物理ロックでは、デッドロックの可能性を避けるために極めて注意深くレコードグループを管理してください。論理ロックでは、保護したい論理レコードグループを管理可能な論理ロックマネージャを構築するのがベストで、このマネージャが一貫性と非デッドロックを保証します。通常、論理ロックマネージャは、ロック管理・競合レポート・タイムアウト機構・他色々、を行うためのテーブルを持ちます。

3. Parallel Processing パラレル処理は、合計処理時時間最小化のため、複数バッチ実行あるいはパラレルにジョブ実行を行います。これはジョブが、同一ファイル・DBテーブル・インデックス空間、を共有しないのであれば問題はありません。共有する場合、サービスはパーティションデータによる実装の必要が出てきます。別の方法として、制御テーブル(control table)で相互依存関係を維持させるアーキテクチャモジュールを構築します。制御テーブルは共有リソースそれぞれに対する行を持ち、その行がアプリケーションで使用中かどうかを持ちます。パラレルジョブのバッチアーキテクチャやアプリケーションは必要とするリソースへのアクセスを取得可能かどうかを確認するために制御テーブルから情報を取得します。

データアクセスが問題とならない場合、パラレル処理にスレッドを追加しての実装が可能です。メインフレーム環境では、すべてのプロセスで十分なCPUタイムを保証する目的で、パラレルジョブクラスが伝統的に使用されています。つまるところ、すべての実行プロセスでタイムスライスを保証するのに十分ロバストでなければなりません。

パラレル処理の別の課題には、ロードバランシング、一般的なシステムリソース、例えばファイル・DBバッファプールなどの可用性、があります。また、制御テーブル自体がたやすくクリティカルなリソースになる危険性があります。

4. Partitioning パーティショニングにより大規模なバッチアプリケーションの複数バージョンをコンカレントに実行できます。この用途は長時間バッチジョブの処理時間短縮です。正常にパーティション化可能なプロセスとは、スプリット可能な入力ファイルや異なるデータセットに対して実行可能なパーティションテーブル、を使用します。

また、パーティション化プロセスは割り当てられたデータセットのみを処理するよう設計する事が必須です。パーティショニングアーキテクチャはDB設計とDBのパーティショニング方法と密接な関係を持ちます。なお、DBパーティショニングは必ずしもDBの物理パーティショニングを意味するわけではありませんが、基本的にはそれを推奨します。下図はパーティショニングのアプローチです。

このアーキテクチャパーティション数を動的設定可能にするため十分に柔軟にします。自動と手動設定の両方が考えられます。自動設定は入力ファイルサイズと入力レコード数などのパラメータに基づく事になると思われます。

4.1 Partitioning Approaches パーティショニング方針の選択はケースバイケースです。以下はいくつかのパーティショニング方針の解説です。

1. Fixed and Even Break-Up of Record Set

この方針では入力レコードセットを(例えば10であれば、各グループはレコードセット全体の1/10となります)偶数のグループに分割します。各グループは単一のバッチ/抽出アプリケーションで処理します。

この方針を使用する際には、事前処理としてレコードセットの分割が必要です。分割結果は上限下限となり、その番号はバッチ/抽出アプリケーションへの入力に用い、そのグループのみ処理するために使われます。

事前処理のオーバーヘッドが大きくなりすぎる可能性があり、これはレコードセットのグループ境界の計算と決定が必要なためです。

2. Break up by a Key Column

この方針では入力レコードを、キーカラム・ロケーションコード・各キーからバッチインスタンスに割り当てたデータ、で分割します。これを行うには、カラム値を以下いずれかにします。

前者の場合、新しい値の追加はバッチ/抽出の手動再設定を意味します。これは新しい値が特定インスタンスに追加されることを保証するためです。

後者の場合、すべての値がバッチジョブインスタンスを介してカバーされることが保証されます。ただし、一つのインスタンスで処理する件数はカラム値の分布に依存します(0000-0999は多数で、1000-1999は少数しかない場合があり得る)。この方針の場合、データ範囲はパーティショニング前提で設計する必要があります。

両者とも、バッチインスタンスに渡すレコードの最適均等配分は出来ません。バッチインスタンスの個数設定が動的では無いためです。

3. Breakup by Views

この方針は基本的にはDBレベルではないキー列で分割します。レコードセットをビューに分割します。このビューを処理中にバッチアプリケーションの個々のインスタンスで使用します。分割はデータのグルーピングにより行います。

この方針では、バッチアプリケーションの個々のインスタンスは(元のテーブルではなく)特定のビューに紐付けます。新規データ追加時、データの新規グループをビューに含めます。動的設定の機能は無く、インスタンス数の変更をするとビューも変更になります。 4. Addition of a Processing Indicator

この方針はインジケーター役となる新規列を入力テーブルに追加します。事前処理段階では、すべてのインジケーターは未処理になっています。バッチアプリケーションのフェッチ段階で、未処理状態のレコードが読み込まれ、一度読み込まれると(ロック扱い)、処理中状態になります。そのれおーどが処理完了すると、インジケーターは完了かエラーのどちらかで更新します。大量のバッチアプリケーションを変更無しに開始可能で、これはインジケーター列でレコードが一度だけ処理したことを保証するためです。端的に言えば"完了時、インジケータが完了状態になる"です。

この方針では、テーブルへのI/Oは動的に増加します。更新系の場合、いずれにせよ書き込みが不可避なので、影響は少ないです。

5. Extract Table to a Flat File

この方針はテーブルをファイルに抽出します。このファイルは複数セグメントに分割し、バッチインスタンスの入力になります。

この方針では、テーブルからファイル抽出と分割の追加オーバーヘッドが発生し、マルチパーティショニングの効果を打ち消す可能性があります。ファイル分割スクリプトの変更により動的設定が可能です。

6. Use of a Hashing Column

レコードを決定するのに使用するハッシュカラム(key/index)をDBテーブルに付けます。このハッシュカラムにはインジケーターを持ち、バッチアプリケーションのインスタンスがどの行を処理するかを決定するのに使用します。たとえば、3つのバッチインスタンスがあるとして、1番インスタンスが処理する行のインジケーターには'A'、2番インスタンスは'B'、3番インスタンスは'C'、にします。

レコードを参照する時には、特定のインジケーターを持つすべての行を選択するためにWHEREを追加します。テーブルへのINSERT時には、デフォルトのインスタンスを示す値(例.'A')をインジケーターカラムに設定します。

インジケータの更新にはシンプルなバッチアプリケーションを使用し、たとえば、異なるインスタンス間で負荷の再配分をします。極めて大量の新規行を追加する時に、新規行を別のインスタンスに振り分け直すのにこのバッチを使います(バッチウィンドウを除けば何時でもOK)。

新規インスタンス用にインジケーターを再配分するには前述の通りなので、追加インスタンスは単にバッチを実行するだけです。

4.2 Database and Application Design Principles

キー列でのパーティションテーブルを使用するアプリケーション方針のアーキテクチャは、パーティションパラメータを格納するセントラルパーティションリポジトリを持ちます。このリポジトリは単一テーブルを持ち、いわゆるpartition tableと呼ばれています。

partition tableに持つ情報は、基本的には静的で、DBAがメンテナンスします。このテーブルはアプリケーションの個々のパーティションに対する一行を持ちます。カラムには、Program ID Code, Partition Number(パーティションの論理ID), パーティションで使うDBキーカラムのLow ValueとHigh Value、を持ちます。

プログラム開始時に、program idとpartition numberをアーキテクチャからアプリケーションに渡します(具体的には、Control Processing Taskletから渡す)。キー列を使用する方針の場合、アプリケーションで処理するデータ範囲を決定するpartition tableを読むのにこれらの変数を使用します。また、partition numberは以下の処理でも使用します。

  • 適切なマージ処理を実行するため、出力ファイルやDB更新に追加
  • バッチログへの正常処理レポートやアーキテクチャエラーハンドラに何らかのエラーを報告

4.3 Minimizing Deadlocks

アプリケーションをパラレルやパーティションで動作させる場合、DBリソースの競合とデッドロックの可能性があります。重要なのは、DB設計チームが潜在的な競合の可能性を削除することで、これをなるべくDB設計の一部として実施します。

また、開発者はデッドロック防止とパフォーマンスに気を付けつつDBのインデックステーブルを設計すべきです。

デッドロックホットスポットが、ログテーブル・制御テーブル・ロックテーブルなど、管理やアーキテクチャが持つテーブルで発生することがあります。これらの影響も同様に気を付ける必要があります。本番に近い負荷テストでアーキテクチャに発生しうるボトルネックを特定することが重要です。

データのコンフリクトの影響を最小化するため、DB接続時やデッドロック発生時に、アーキテクチャはwait-and-retryなどのサービスを提供すべきです。これはDBの特定のリターンコードに対応する機構で、エラーを即時報告するのではなく、所定時間待機してDB操作をリトライします。

4.4 Parameter Passing and Validation

パーティションアーキテクチャはアプリケーション開発者になるべく透過的であるべきです。パーティションモードのアプリケーション実行に紐付くすべてのタスクをアーキテクチャが実行します。

  • アプリケーション開始前のパーティションパラメータの取得
  • アプリケーション開始前のパーティションパラメータのvalidating
  • 開始時にアプリケーションにパラメータを渡す

validationは以下をチェックします。

データベースがパーティション化されている場合、その単一パーティションがDBパーティションを拡張していないことを確認するvalidationも必要な場合があります。

また、アーキテクチャパーティションの統合を考慮に入れておきます。

*1:ディスクバックアップとか時間制約が強いバッチで利用可能な時間のこと

JEP 348: Java Compiler Intrinsics for JDK APIsをテキトーに訳した

http://openjdk.java.net/jeps/348

JEP 348: Java Compiler Intrinsics for JDK APIs

Author   Brian Goetz
Owner   Vicente Arturo Romero Zaldivar
Type    Feature
Scope   SE
Status  Candidate
Component   tools
Discussion  amber dash dev at openjdk dot java dot net
Reviewed by Alex Buckley, Brian Goetz, Vicente Arturo Romero Zaldivar
Endorsed by Alex Buckley
Created 2018/06/25 21:23
Updated 2019/02/21 02:08
Issue   8205637

Summary

Javaコンパイラでinvokedynamicなどの代替的な変換(alternate translation)*1を使用可能にします。この目的はコンパイラintrinsic候補(compiler intrinsic candidates)に指定した特定のJDKのメソッドのパフォーマンス向上です。特に、String::formatObjects::hashをintrinsifyします。

Motivation

大部分において、JVM実行時のバイトコード最適化に問題はありません。しかし、ある特定のメソッドにおいて、Javaコンパイラ標準の変換は最適化困難なバイトコードになります。最たる例はString::formatで、そのシグネチャ)は以下の通りです。

public static String format(String formatString, Object... args) { ... }

javacString::formatに対し生成するバイトコードは、JVMJITコンパイラが最善を尽くしても、最適化困難です。一般的なパターンはプリミティブ引数を持つ事で、これらはボクシングの必要があります。varargs配列を生成して引数で初期化します。フォーマット文字列はほとんどのケースで定数文字列ですが、String::formatを実装するたび毎回パースします。その実装は、予想出来るように、インライン化するには大きすぎます。その結果、バイトコードは、思ったよりも遥かに低速です。

String::formatObjects::hash(どちらも同一シグネチャ))などのメソッドは特に重要で、その理由はtoStringhashCodeの実装を簡潔かつ堅実に行う方法だからです。開発者の中には、これらメソッドの実装を面倒くさがり、冗長で、パフォーマンス度外視でエラーになりやすい方法を使う者がいます。String::formatObjects::hashの最適化により、可読性とメンテナンス性が高いtoStringhashCodeの実装方法をすると、最もパフォーマンスが出るようになります。

JEP 280invokedynamicstring concatenationを置換した結果、高速なバイトコードアロケーションの散乱抑制、均一な最適化、となりました。我々はString::formatなどのメソッドに同じやり方、代替的な変換機能でメソッド呼び出しをコンパイル、を適用します。コンパイル時に利用可能な情報、static型と呼び出し時の引数値、に基づいて特定の呼び出しに対するバイトコードをカスタマイズします。

Goals

JDK開発者に対して以下を提供します。(1)コンパイル時にintrinsificationの候補とするタグ付け機能、(2)候補メソッドの仕様に準拠するintrinsification候補の代替的な変換の適切な記述(describe appropriate alternate translations of intrinsification candidates that conform to the specification of the candidate method.)

Non-Goals

JDKライブラリ外で使えるようにintrinsification機能の公開は行わない。

Description

コンパイル時intrinsificationを行う機能には2つの特徴があります。

  • 所定のメソッド実行で代替的な変換を選択するようにJavaコンパイラをAuthorizing
  • 特定のintrinsification候補に対し代替的な変換を行えるように特定のJavaコンパイラで実装

前者はJava SEアノテーション@IntrinsicCandidateを作成し、JDKライブラリ作者は'候補'メソッドをintrinsificationに適するというタグ付けに使用します。これにより、コンパイラには代替的な変換を選択する余地が与えられ、振る舞いは維持したまま、変換を行います。コンパイラが行う事ではなく、コンパイラがしてもよい事、を指定するだけです。JLS 13.1はこのオプトインを許容するよう更新します。

後者をJDKjavac実装でするのは、intrinsic processorsの宣言および登録する機構の整備により行う予定です。このプロセッサはコンパイラがintrinsic候補の実行を検出すると呼び出され、標準の変換を最適化済みの変換に置換する方法と、置換するかどうか、をコンパイラに指示します。これらintrinsificationは任意です。コンパイラは全くintrinsifyしないかもしれないし、intrinsificationのON/OFFを指定可能なコマンドラインオプションを提供するかもしれません。

ボクシング・varargsのオーバーヘッドと定数フォーマット文字列が繰り返しアナライズされる事、を回避するため、String::format(とPrintStream::printfなどの関連メソッド)をintrinsifyする予定です。String::formatの以下のような呼び出しを考えます。

String name = ...
int age = ...
String s = String.format("%s: %d", name, age);

上記のコードを実行すると、ageはボクシングしてIntegerになり、varargsにアロケートされ、nameとボクシングしたageがvarargsに入れられ、フォーマット文字列のパースと解釈、になります。これが呼び出す度に発生します。フォーマット文字列が定数の場合、コンパイル時のアナライズで以下のような代替的な変換を常に選択可能です。

String s = name + ": " + Integer.toString(age);

更にJEP 280の機能でinvokedynamicに更に最適化が可能です。なお、代替的な変換を選択するにはnameもageもconstant variablesである必要はありません。

同様に、Objects::hashString::formatの3つの問題のうちボクシングとvarargsがあります。

int hashCode() { return Objects.hash(name, age); }

これもageがボクシングされ、ボクシングしたnameとageがvarargsに入ります(varargsを使うメソッドは通常防御的コピーをする)。しかし、これは以下のようにも変換が可能です。

int hashCode() { return name.hashCode() + 31 * Integer.hashCode(age); }

これにより不必要なコストを避けられます。

Risks and Assumptions

もし正しく実装されない場合、この変換が仕様やオリジナルの実装と振る舞いの互換性を完全には持てない可能性がある。

もし正しく実装されたとしても、この変換が将来的にオリジナルの実装に加えられた変更を正しくトラッキング出来ない可能性がある。

もし正しく実装しトラッキングされたとしても、intrinsic候補メソッドと変換のメンテナンスが、2箇所の変更が必要かつ振る舞いを同一に保つ事が必須なため、困難になります。

*1:標準の変換じゃなくてこっちの変換も使用可能ってニュアンスでalternateなんだが上手い単語思いつかなんででとりあえず直訳を当てている。