見習いプログラミング日記

Java EE を中心に色々なことを考えてみます。目指せ本物のプログラマ。

JavaOne 2015 3日目メモ (10/27)

3日目も晴れている。今年は週間天気予報に雨マークが出てたから折りたたみ傘を持ってたが、4回目で今のところ傘が必要なほどの雨に振られたことがない。

今日は以下のセッションに参加。

[CON6712] Enhanced Process APIs

JDK9に向けて、ProcessBuilderの改善アイディアについて。
プロセスの情報をもっとJavaAPIで取得したい:

  • allProcesses()でpsのようにマシン上のプロセス一覧を取得
    • (OSのアクセス制限の範囲で)
  • getCurrent()で現在のプロセス情報、of(pid)で対象pidの情報
  • getPid(), info()のようにプロセス情報の収集
  • onExit()でCoumputableFutureによる非同期ハンドリング?

ProcessHandle.Infoクラスから、対象のプロセス情報を収集する。ユーザ、実行したコマンド、コマンド引数、開始時刻、CPU時間など。取れる情報もあれば、OSの制限により取れない情報もあるので、返り値はOptionalでラップして、例えば Optional user = ph.info().user(); のように返す。

以下の例はプロセスID、親プロセス、ユーザ、コマンドをロギング。

showProcess(ProcessHandle.current());

static void showProcess(ProcessHandler ph) { 
    ProcessHandle.Info info = ph.info();
    log.printf("pid: %d, parent: %s, user: %s, cmd: %s%n",
        ph.getPid(), ph.getParent(), info.user().orElse("none"), info.command().orElse("none"));
}

Java8のストリームAPIへの対応:
以下の例は現在のユーザのプロセス一覧をプロセスID順にソートしてロギングする。

Optional<String> currUser = ProcessHandle.current();
ProcessHandle.allProcesses()
    .filter(p1 -> p1.info().user().equals(currUser))
    .sorted(CodeSamples::parentComparator)
    .forEach(CodeSamples::showProcess);

static int parentComparator(ProcessHandle p1, ProcessHandle p2) {
    return Long.compare(p1.parent().get().getPid(), p2.parent().get().getPid());
}

非同期による外部プロセスの起動:
Java8から入ったComputableFutureをProcess.onExit()メソッドの返り値で返す。
以下の例はプロセスの起動と終了を複数の多重度でプールループで実行している。プロセス終了のタイミングでプロセスID、終了コードのロギングと、再度プロセスの起動を行っている。

Semaphore count = new Semaphore(11);
CountDownLatch end = new CountDownLatch(1);

// paralleism分の多重度でプロセスを繰り返し起動
for (int i = 0; i < paralleism; i++) 
    start(pb1, count, end);

// 並行で実行する全てのプロセスが起動するまでラッチで待機
end.await();

// paralleism分の多重度でプロセスが起動したら、
// Futureと同じような動きをするonExit.get()をプロセスごとに実行して、
// 各プロセスの完了までブロックする
ProcessHandle.current()
    .children().forEach(CodeSample::waitForExit);

static void start(ProcessBuilder pb, Semaphore count, CountDownLatch end) {
    try {
        if (count.tryAcquire()) {
            Process p = pg.start();
            p.onExit()
              .thenAccept(CodeSamples::logExit)
              .thenRun(() -> start(pb, count, end));
        } else {
            end.release();
        }
    } catch (IOException ioe) {
        throw new RuntimeException("Process start failed", joe);
    }
}

static void logExit(Process p) {
    log.printf("exit: %d, status: %d%s", p.getPid(), p.exitValue());
}

static void waitForExit(ProcessHandle p) {
    try { p.onExit().get(); } catch (Exception e) { ... }
}

ProcessBuilderへのパイプの適用:
ls | fgrep dukeと同じようなパイプ繋ぎのコマンドを、Javaで実現する。

ProcessBuilder pb1 = new ProcessBuilder();
ProcessBuilder pb2 = new ProcessBuilder();

List<Process> processes = ProcessBuilder.startPipe(pb1, pb2);
processes.forEach(p -> {
    try {
        int status = p.waitFor();
        log.printf("status: %d%n", status);
    } catch (InterruptedException ie) {
    }
}

他にも今までのProcessはInputStream/OutputStreamを返すが、NIOのChannelに対応させる案の紹介。ノンブロッキングIOに使われているselector()を使って、応答が返ってきたタイミングで通知を受ける案も紹介された。

[CON11284] JDK 9 Language and Tooling Features

Jigsaw以外のJDK9の変更点についてまとめたセッション。
対話式でjavaを実行するjshellの導入、javadocの検索フォームの導入以外はよくわからなかったので要復習。
セッション資料はここで公開されている。

[CON3239] Tuning JavaServer Faces

JSFでパフォーマンスに関する問題を起こさないために、どんなことに注意すべきか紹介するセッション。

JSFの性能にインパクトを与える観点:

  • ページサイズ
  • セッションのサイズ (コンポーネントツリーとアプリケーション分の両方)
  • リクエストパラメータのサイズ
  • EL式の評価
    • EL式のメソッド呼び出しでDBリクエストしない
  • JSFのコンフィグフラグ
    • web.xmlで設定できるチューニングパラメータ 参考
  • FacesContext.getCurrentContext()の繰り返し呼び出し

Bloated component tree対策:
panelタグや、Primefacesなどのコンポーネントにあるタブ機能を使うと、Bloadted component tree (広がりすぎたコンポーネントツリー)に陥りがちである。これは、output系のタグにrendered=false属性を指定しても、画面に表示されないだけで、コンポーネントとしては存在するので性能に影響を与える。以下のような観点で対処を行う。

  • <panel>はdivやspanに置き換えられないか
  • JSF2.2から導入されたステートレスビューの導入
  • 動的ローディングの機能がないか探す
    • たとえばタブであれば、遅延ロード機能が用意されているはず

セッションをなるべく使わない:
セッションの代わりに可能な限り以下を使い、ライブヒープによるGCコスト増加や、レプリケーションコストの増大を避ける。

  • 可能であればリクエストスコープ、Ajaxで更新する画面ならビュースコープ。
  • Flowスコープ(JSF2.2)、Conversationスコープ(CDI)などの期限付きスコープを使う。

id名を短縮してレスポンスデータ量削減:
標準では フォームID:テキスト入力フォームID のような形式でコロン区切りで長いIDが付く。このID名が長い場合は、<h:form id="f" ... のように、1文字IDを明示的に指定すると、テーブルの中にフォームがあるような、何度も繰り返し長い文字列のIDが表示されるケースでデータ量削減に有効。

PartialSubmitを使ってリクエストデータ量削減:
標準の<h:commandButton>は、テーブルの列にフォーム入力がある場合など、大量の変更可能な入力項目の1つを変えただけでも、フォームに含まれるすべてのテキストインプットの内容を再送信する。Primefacesなどのライブラリが用意するコンポーネントを使うと、変更したフォーム内容だけ再送信するPartialSubmit機能がある。
この機能により、リクエスト送信量の削減、送信されたパラメータを再処理するJSFサイクルコストの低減が図れる。

不必要なFacesContext.getCurrentInstance()呼び出しを避ける:
getCurrentInstance()のコストは少なくないため、たとえば同一メソッド内での複数呼び出しは避ける。

[CON2483] Java SE 8 for Java EE Developers

Java EE開発者に有効なJava SE 8の機能を紹介するセッション。

Java EE7でInstantを使う:
JPAの@Convertを使って、DateからData and Time APIのInstantに変換する。Java EE7はDate and Time APIに対応していないため、自作する。

public class DateConverter implements AttributeConverter<Instant, Date> {
    public Date convertToDatabaseColumn(Instant instant) {
        return Date.from(instant);
    }
    public Instant convertToEntityAttribute(Date date) {
        return date.toInstant();
    }
}

以下のようにエンティティクラスのフィールドに適用する。

@Convert(converter=DateConverter.class)
@Temporal(TemporalType.instance);
private Instant instance;

その他もろもろ:

  • StringJoiner使おう
  • ComparatorもComparator.comparingBy(Person::getLastName)のように簡潔に書く
  • MapのcomputIfAbsent、getOrDefaultはEEアプリケーションでもよく使う
  • Files.lines(path)でファイルは読む
  • APサーバ上で間違ってparallelStream()が使われてしまうことへの対策
    • System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "1")

JDK9 JEP269の話:
JEP269がJDK9に盛り込まれれば、EEの世界でもよく使うコレクション初期化がシンプルにできる。

List<String> strints = List.of("one", "two", "three", "four");

[CON2876] JSR 373: New Java EE Management API

Java EE 8に向けて検討が始められている、RESTベースの監視、管理APIの標準化について。結論からまとめると、決まっていることはまだないので、セッション参加者に色々アンケートととって、仕様策定においての悩みをユーザに共有セッション。
フィードバックフォームはここで公開されている。

[BOF7768] Let’s Visualize Log Files for Troubleshooting Java Applications

@cero_tさんと、Koji Ishidaさんのセッション。トラブルシューティングに欠かせないログの可視化について。

冒頭、logをGoogleイメージ検索した、大量の丸太の写真と絵で笑いを取る。海外でも笑いを取りにいく@cero_tさんはすごい、場が暖まる。

内容はホテルの予約サイトの性能、エラー多発トラブルの実例をベースに、どのように問題対処していったかについて。

まずはじめに、seleniumによるテストケース作成と、ソースコードリーディングで問題解析しようとしたが、コード量も多く、困っている問題事象はプロダクション環境でしか起こらなかったため、あまり有効でなかった。

プロダクション環境でしか起こらない問題は、プロダクション環境を対象に解析する必要がある。サービスに影響を与えられないのでログを活用した。ELK(ElasticSearch + Logstash + Kibana)のログ収集の基盤を作り、Apacheアクセスログからリクエスト遅延とMySQLのスロークエリに相関があることを見つけ、性能問題を対処した。

エラーの多発もLogstashで収集したログを、javaプログラムにロードして解析し、原因解析を行った(この部分聞き逃したので少し自信なし)。

ログを解析するにあたって、ElasticSearchでの可視化、cron+javaプログラムでのエラー解析、ファイルサーバに転送してエディタで目視するためには、ログを一度溜め込んで、各ツールに分配するLog Stream Hubが必要となった。今回の事例ではRedisをハブとした。

このようなトラブルログ分析基盤は開発者向けであり、お金が付きにくい。そこでkibanaを販売状況などのビジネスデータの可視化にも利用した。ELK構成はいずれもオープンソースなので、まずは小さくはじめて見ようとセッションを締めくくった。