kagamihogeの日記

kagamihogeの日記です。

JEP 343: Packaging Toolをテキトーに訳した

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

JEP 343: Packaging Tool

Owner   Kevin Rushforth
Type    Feature
Scope   JDK
Status  Candidate
Component   deploy / packager
Discussion  core dash libs dash dev at openjdk dot java dot net
Effort  M
Duration    M
Relates to  JEP 311: Java Packager API & CLI
Reviewed by Alexander Matveev, Alexey Semenyuk, Andy Herrick, Kevin Rushforth, William Harnois
Created 2018/04/04 19:22
Updated 2018/10/05 17:13
Issue   8200758

Summary

自己完結型Javaアプリケーションをパッケージングする新規ツールの作成。

Goals

JavaFXのjavapackagerベースのシンプルなパッケージングツールを作成します。

  • 個別環境のエンドユーザにお馴染みのインストール方法を提供するため、環境固有のパッケージングフォーマットをサポートする。そのフォーマットには、Windowsmsi, macOSのpkgとapp, Linuxdebrpm、を含む。
  • パッケージング時に指定可能なlaunch-timeパラメータを使用可能。
  • ToolProvider APIを介して、プログラム的にもコマンドラインから直接的にも、呼び出し可能。

Non-Goals

  • javapackagerの以下機能はサポートしません。
  • GUIツールは無し。CLIで十分。
  • ロスコンパイルサポートは無し。例えば、Windowsパッケージの作成にはWindowsでのツール実行が必須。プラットフォーム固有のツールに依存する。
  • JMODファイル以上のlegal filesの特別なサポートは無し(e.g., no aggregation of individual license files).。
  • ネイティブのスプラッシュスクリーンサポートは無し。
  • 自動更新機能は無し。

Motivation

ある種のJavaアプリケーションは、単純にクラスパスやモジュールパスに配置するのではなく、ネイティブプラットフォームにインストールする必要があります。アプリケーション開発者はJARファイルを配備するだけで終わりでは無く、ネイティブプラットフォームに適したインストール用パッケージも必要です。Javaアプリケーションの配布・インストール・アンインストールをそのプラットフォームのユーザに適した方法で扱えるツールを作成します。例えば、Windowsユーザはソフトウェアをインストールするにはインストーラをダブルクリックしてアンインストールにはコントロールパネルを使えることを期待します。macOSユーザDMGファイルをダブルクリックしてアプリケーションフォルダにドラッグ出来ることを期待します。加えて、サービスデーモンとしてインストールするアプリケーションはOSのサービス登録も必要な場合があります。

ターゲットシステムにインストールするためにJDK自体をパッケージするツールも必要です。こうしたツールが無い場合、JDKイメージはtar.gz zip形式でしか配布できません。

パッケージングルールは旧機能である、OracleJDK 11で削除されるJava Web Startおよびpack200や将来リリースにおいて削除予定のためJDK 11でdeprecatedになる、とのギャップを埋める一助ともなります。開発者は必要に応じて最小モジュールセットにJDKをjlinkで縮小可能で、ターゲットマシンにデプロイ可能なインストール可能で圧縮されたイメージの生成にパッケージングツールを使用します。

以前よりこうした要求には、javapackagerというパッケージングツールがOracle JDK 8から配布していました。しかし、JavaFXの分離に伴いOracle JDK 11から削除されました。

Description

jpackagerは入力としてJavaアプリケーションとランタイムイメージを取り、すべての必要な依存性を含むJavaアプリケーションイメージを生成します。WindowsのexeやmacOSdmgなどプラットフォーム固有のフォーマットを生成可能です。各種の方法でパッケージングするアプリケーションをカスタマイズ可能なオプションがあります。

Features

以下の機能を提供します。

  • アプリケーションイメージの生成
  • エンドユーザが使い慣れたインストール方法を提供するための、環境固有のパッケージングフォーマットのサポート。以下フォーマットをサポートする。

アプリケーションはエンドユーザがインストール中に別のディレクトリを指定しない場合、各プラットフォームで一般的なデフォルトディレクトリにインスト―ルされます。(例、Linuxのデフォルトディレクトリ/usr/bin)

  • WindowsmacOSのアプリストアへのサブミットに適合するようなJavaアプリケーションのパッケージングのサポート。
  • パッケージング時にJDKおよびアプリケーション引数を指定する機能。その値はアプリケーション起動時に使用される。
  • 各プラットフォームとパッケージアプリケーションを関連付けする機能。
    • 拡張子関連付け
    • Windowsのスタートメニューなどの、プラットフォーム固有のメニューグループからの起動。
    • インストール可能パッケージの更新ルールを指定するオプション(rpm/debなど)
  • --dry-runオプションで各環境のパッケージングコマンドを実行せずに生成します。開発者はより複雑な要求を扱う場合にこれが使えます。各環境のパッケージングコマンドにパラメータを追加したり、ツールから直接は使えないフォーマットでのパッケージを使用したい開発者がこの機能を使用します。

以下機能は時間があればサポートします。

  • サービスやデーモンとしてアプリケーションを起動させられるサービスバンドル。
  • single vs multipleインスタンスのサポート。アプリケーションの複数インスタンス起動の防止。 マルチプルランチャ、これは単一の自己完結型アプリケーションパッケージで配布されるアプリケーションスイートを有効化するためのもの。
  • JDKおよびアプリケーション引数を設定するためのAPI

Running the tool

jpackagerへの入力は以下の通りです。Javaランタイムイメージ、フォーマットに従うJavaアプリケーション、最終的なイメージやパッケージ生成を制御するためのコマンドラインオプション各種。

アプリケーションの種類は以下が使用可能です。

  • カスタムランタイムイメージにjlinkしたmoduleアプリケーション
  • module jarファイルもしくはjmodファイル内のmoduleアプリケーション
  • クラスパスで実行する従来型アプリケーション、および、jarファイル内のアプリケーション

未カスタムのランタイムイメージを指定する場合、ツールはそのアプリケーション用のJDKを生成するためにjlinkを実行します。

jpackagerの出力はJavaアプリケーションイメージになり、これにはJavaの依存性がすべて含まれます。イメージはファイルシステムの単一ディレクトリに保存され、以下を含めることが出来ます。

  • ネイティブアプリケーションランチャ(ツールが生成)
  • Javaランタイムイメージ(モジュール化アプリケーションの場合、アプリケーションモジュールを含む)
  • アプリケーションリソース(e.g., jar, icns, ico, png
  • 設定ファイル(e.g., plist, cfg, properties)

例として、HelloWorldアプリケーションのイメージフォーマットは以下のようになります。

HelloWorld/
    HelloWorld.exe     [this is the native launcher]
    app/
      [application resource, configuration, and support files go here]
    runtime/
      [custom Java runtime image goes here]

アプリケーションを開始すると、ランチャは設定ファイルを読み込み、指定の引数で組み込まれたJavaランタイムイメージを起動します。

アプリケーションイメージはそのまま再配布可能で、ネイティブやインストール可能パッケージ(例,msiとかdmg)にパッケージング出来ます。

後者の場合、ツールで、生成済みアプリケーションイメージからネイティブパッケージを生成したり、ネイティブパッケージを生成できます。ネイティブパッケージは以下を持ちます。

  • 上述のアプリケーションイメージ
  • Support for signing packages
  • ファイル関連付けなど後処理のパッケージ

Delivering jpackager

jpackagerは新しくjdk.packagerモジュールでJDKの一部として提供される予定です。本ツールはjavapackagerベースで、Java Web StartJavaFX関連機能は削除されます。CLIJEP 293: Guidelines for JDK Command-Line Tool Optionsに従います。また、jpackagerはToolProvider API(java.util.spi.ToolProvider)経由でもアクセス可能です。

単一インスタンスランチャーなど、ある種の機能はアプリケーションで呼び出し可能なランタイムAPIが必要になる場合があります。もしそうなる場合、ランタイムモジュールをAPIで提供します。

Issues

イメージのレイアウトやサポートするものとしないものの定義を決める必要があります。これは開発者が依存するものとしなくて良いものを明確にする作業で、たとえば、ランチャの場所・ユーザが編集可能な設定ファイル、などがそれに当たります。

コマンドライン例のサンプルと、生成されるランタイムイメージの内容記述の提供を行う予定です。

Testing

大半のテストが自動テスト化が可能ですが、以下の点は考慮が必要です。

  • ネイティブ環境のインストーラのテストは別途ツールが必要になる場合があります。別途ツールが無いシステムの場合、そうしたテストはスキップされるように記述する必要があります。
  • 各種ネイティブパッケージ(Windowsの.exe, macOSのappやdmg)の検証は手動テストが必要なものがあります。
  • クリーンなインストールとアンインストール可能なネイティブパッケージを保証する必要があり、これは開発者が開発環境の汚染を恐れずにテスト可能にするためです。

Dependencies

ネイティブパッケージは各プラットフォームのツールで生成します。Windowsの場合、開発者がネイティブパッケージを生成する場合、以下のツールを追加でインストールする必要があります。

ネイティブランチャを生成するjlinkの機能強化は鋭意作業中です。jlinkとjpackagerでいくらかの調整が必要となる可能性があります。

RedisのLISTに1件ずつとまとめてPUSHするときの速度差

Redisをはじめて触るので色々と試している。とりあえず基本的なパフォーマンスを見るということで、LISTに1件ずつPUSHする場合と、ある程度まとめてPUSHする場合の速度差を見る。それによってRedisを学ぶのが目的である。

ソースコード

pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>10</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

Java

100万件連番の数字をLISTにL/RPUSHしていく。速度計測はお手軽にSystem.currentTimeMillis()の差で行う。また、一度実行するたびにキャッシュはキーを指定して削除している。

1件ずつPUSHの場合。

        long start = System.currentTimeMillis();
        
        BoundListOperations<String, String> ops = redis.boundListOps("list001");
        for (int i=0; i<1_000_000; i++) {
//            ops.leftPush(String.valueOf(i));
            ops.rightPush(String.valueOf(i));
        }
        
        System.out.println(System.currentTimeMillis() - start);

10万件ずつ10回PUSHの場合。*1

        long start = System.currentTimeMillis();
        
        BoundListOperations<String, String> ops = redis.boundListOps("list001");
        for (int i=0; i<10; i++) {
            String[] array = IntStream.range(0, 100_000).mapToObj(n -> String.valueOf(n)).toArray(String[]::new);
//            ops.leftPushAll(array);
            ops.rightPushAll(array);
        }
        
        System.out.println(System.currentTimeMillis() - start);

計測結果

1 2 3
leftPush 123020 122401 122132
rightPush 122080 121225 123555
leftPushAll 856 772 1080
rightPushAll 821 782 771

以下はn回目のPUSHに要した時間。最初の5回だけで以降は省略。

1 2 3 4 5
296 1 1 0 2
all 401 59 44 30 31

感想とか

やはり通信が発生する以上、1件ずつ追加するよりまとめて追加する方が圧倒的に早い。このあたりはRDBMSと同様、オーバーヘッドの削減が実行時間に関係する。

単純にRPUSHもしくはLPUSHする場合は実行時間に差は見られない。これはマニュアルに「Redisリストは双方向リスト~」とあるので、それが確認できている。

PUSHそのものの実行時間について。まず、最初の1回目だけはリストの初期化処理が何らかあるらしく、ほんのちょっと遅い。

次に2回目以降について。1件より10万件の方が遅いが、極めて僅かな差でしかない。PUSH一回の時間がほぼ同じとなると、まとめてPUSHすればするほど早くなると思われる。どの位のデータ量が分岐点になるかは今回のコードからは見えてこないが、あまりデカい配列作るとメモリの方が気になりそう。

ところで、一気に大量に追加するコストがそもそも相当に安いのだが、1件の場合はもっと早くてSystem.currentTimeMillis()で差が取れないくらいである(ただこの計測はJavaとRedisを同一マシンでやっているので、リモートの場合はまた別かもしれない)。

PUSHのコストは安いとなると、そんなに頻繁なPUSHが発生せず実行時間がさほど気にならないのであれば、1件ずつPUSHでもまぁよくね? となるかもしれない。逆に、極めて頻繁にPUSHが発生するのなら、なるべくまとめてPUSHすれば、実行時間の改善に寄与する可能性が高い。

といっても、高速なPUSHを活かすのがRedisの用途だから、バッチ的なバルク処理はあんま無かったりするんですかね?

*1:配列の生成コストが若干気になるが誤差だよ誤差。

spring-bootでredisつかう

spring-bootspring-boot-starter-data-redisを使用してRedisにアクセスする。とりあえずhello worldレベルのことをやる。

ソースコード

pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath />
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>10</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

application.yml

src/main/resources/application.ymlに接続などの設定情報を記述する。

spring:
  redis:
    host: 192.168.10.23

java

起動用のクラス。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.StringRedisTemplate;

@SpringBootApplication
public class App implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    @Autowired
    StringRedisTemplate redis;
    
    @Override
    public void run(String... args) throws Exception {
        redis.boundValueOps("hoge").set("hogeValue");
        System.out.println(redis.boundValueOps("hoge").get());
    }
}