JavaOne 2015 4日目メモ (10/28)

JavaOne 4日目の夜には、毎年トレジャーアイランドでOracle Appreciation Event(オラクルによる感謝祭?)が行われ、有名な方のライブが行われる。今年はエルトンジョンとBECKだった。毎年寒いので、薄手のダウンなど暖かい服装が必須。

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

[CON2709] The New HTTP Client API, Including HTTP/2 and WebSocket

JEP110で提案されている、Java SEに盛り込む新しいHTTPクライアントについて。JDK9には、HttpUrlConnectionに代わるHTTPクライアントを盛り込む計画があり、HTTP1.1/HTTP2/WebSocketのクライアントがサポートされる予定である。

セッションの前半はHTTP2の説明と、Java8から導入されたComputableFutureの振り返りであったため省略。後半から、具体的なAPIのアイディアが紹介された。いずれもまだOpenJDK9のリポジトリに含まれていないため、まだ変更が入る可能性が高い。

HTTP GETし、結果を文字列(HttpResponse.asStringメソッド)で取得:

HttpResponse resp = HttpRequest
                     .create(new URI("http://www.foo.com/")
                     .GET()
                     .response();

if (reap.statusCode() == 200) {
    String responseBody = resp.body(HttpResponse.asString());
    System.out.println(System.out.println(responseBody);
}

POSTする場合は、以下のようにbodyメソッドに設定する:

HttpResponse resp = HttpRequest
                     .create(new URI("http://www.foo.com/")
                     .body(HttpRequest.fromString("param1=1,param2=2")
                     .POST()
                     .response();

非同期実行。レスポンスボディをファイルに書き出す:

ComputableFuture<Path> future= HttpRequest.create(uri)
    .GET()
    .responseAsync()
    .thenApplyAsync(resp -> {
        if (reap.statusCode() == 200) {
            return reap.body(asFile(path));
        } else {
            throw new UncheckedIOException(new IOException());
        }
    });    //=> このメソッドチェーンの返り値はComputableFuture<Path>
future.join();  //=> thenApplyAsyncが完了するまでブロック

ExecutorServiceを渡せるので、Java EE環境でもComputableFutureが使いやすい:
(APサーバが管理しているスレッドプールManagedExecutorが渡せる)

HttpClient client = HttpClient.create()
              .executorService(Executors.newCachedThreadPool())
              .build();
HttpRequest request = client.request(URI.create("https://www.foo.com/")).GET();

HTTP2でリクエストするときは、メソッドチェーンに追加する:

HttpRequest request = client.request(URI.create("https://www.foo.com/"))
    .version(HttpClient.Version.HTTP_2)
    .GET();

セッションの中ではリソースクローズに関する言及がなかったが、HttpUrlConnectionのように、クリーンアップをどうやるかわかりくいAPIにならないことを願っている。

[CON2809] Deep Dive into Top Java Performance Mistakes in 2015

性能解析ツールのdynatrace社の方による、どんなメトリクス(ページサイズ、HTTPエラーレスポンス数などの指標)が、性能遅延に直結しやすいか実例を交えながら紹介するセッション。セッションではdynatraceによる以下のようなメトリクス収集結果のデモが紹介された。

メトリクス1: 画像などのリソース数、サイズに注意

  • アメフトのスーパーボウルのページは、1ページに434の画像などのリソース、合計20MBのサイズによりレスポンス遅延が発生していた
  • FIFAワールドカップのページで、faviconアイコンが370KBを超え、そのほかにも150KBを超えるCSS複数あって遅延
  • 大きな背景画像もかっこいいが、サイズには注意が必要
  • 遅いと思ったら、まずブラウザF12のデバッグツールで見てみる

メトリクス2: SQL大量row取得、同一SQLの繰返実行(N+1)、大量のコネクション生成

  • Room Reservertion System(会議室予約?) で1リクエストで2万回以上のSQL発行 (典型的なORMのN+1問題)
  • 1万以上のコネクション生成 (プールによるコネクション再利用漏れ)
  • データキャッシュをHashTableに全て置いて、ブロック多発による性能劣化

メトリクス3: 外部サービスの呼び出し数、WebAPI実行時のN+1問題

  • 100回以上の外部WebAPIの繰り返し呼出
  • ORMのN+1問題と同様に、キーごとに毎回APIコールすると同じ問題が起こる

メトリクス4: ロギング量

  • DEBUGレベルのログを大量出力して、log4jのcallAppenderメソッドでブロック多発

メトリクス5: GC回数、ライブヒープの増加

  • アプリケーションの改修に伴いライブヒープが一気に増加
  • 低負荷時のレスポンスタイムに変化はないが、高負荷時にライブヒープ増加に伴いCPUネックが顕在化し、大幅なレスポンス低下

他にも確認した方が良いデータ:

  • サーバログのException数
  • HTTP 40x、50xエラーレスポンス
  • DBメトリクス(SQL応答時間、発行回数)

ここからはセッション内容と関連しないメモ。有償であるdynatraceを使わずにELK(ElasticSearch+Logstash+Kibana)でメトリクスが収集できないか考える。

  • フロントエンド
    • リソースのサイズはApacheアクセスログからも取得可能 (%b)。kibanaで見るときに、xxxKBを超えるレスポンス数でフィルタすれば見れそう。
    • .jsや.cssはサイズでフィルタして、xxxKBを超えるものは、minify漏れでは?というチェックもできる。
  • DB関連
    • slow queryはログから取れる。
    • ログからのN+1の検知は難しそう。今まで通り、スロークエリが出てないがDBアクセスクラスで遅い場合は、デバッグ用途でORMのSQL出力を有効化するしかない?
    • コネクション数も、例えばPostgreSQLであればpg_stat_databaseビューのnumbackendsを拾ってくればいけそう。
  • 連携サービスのRPC呼び出し過多
    • バックエンドのAPサーバ群のHTTPアクセスログと、フロントWebサーバのアクセスログを比較すればわかりそう。送信元ごとにリクエスト数をグラフ化すれば良い。ユーザからは1リクエストなのに、バックエンドのサーバで1000リクエストがあったら、どこか繰り返し呼び出しをしてる。
    • サービスの粒度が細かく、アクセスログからでは全体の把握が苦しくなったらzipkin使えないか考える。3〜4つのREST APIの連携であればアクセスログ収集と解析でもいけそう。
  • ロギング過多
    • プロファイラ使わずにログからとなると dfによるディスク使用量ぐらい?
  • GC回数、ライブヒープの増加
  • OSリソース系
    • ELK構成でsar, df, proc xxx を取り込む以外にも、RHEL7(および6.7)以上であれば、Performance Co-Pilot + Netflix vectorの選択肢がある。vectorがELKより便利そうかは要調査。

[CON6446] WebSocket in Enterprise Applications

WebSocketの導入セッション。ゲーム以外のWebSocketの使い道をイメージするために聴講。前半はLong polling/SSE/WebSocket/HTTP2の違いについてだったので省略。

WebSocketを何に使うか:
いくつかの例が紹介された。

  • サーバプッシュ
    • 本来はSSEの領域だが、IE11/Edgeが対応していないため。
    • HTTP2のプッシュは、png, css, jsなどのページ構成リソースを、リクエストを待たずに送付するため。唐突にプッシュ通知するためのものではない。
  • 頻繁にリクエスト&レスポンスがある処理。株トレーディングなど。
  • リモートコントロール。リアルタイムでフィードバックを受けながら操作するもの。機械操作など。
  • リアルタイムモニタリング
  • バックエンドサーバ間の高速なリクエスト&レスポンス。
    • メモ: 高速RPC用途であれば、grpcなどの選択肢もあると思う

WebSocketのサブプロトコル:
WebSocket自体は、ブラウザの世界にソケット接続を持ってきたものなので、アプリケーション固有のjson以外にも、サブプロトコルがいくつか出ている。

  • RFC 7118 SIP over WebSocket
  • RFC 7395 XMPP over WebSocket
  • そのほかにも anything over WebSocket が可能

WebSocketのクラスタ化:
複数のサーバに分かれてWebSocket接続する場合は、他のサーバで接続するWebSocketの存在を考慮する必要がある。たとえば一斉通知する場合、Java EEであればJMSやJCacheによってWebSocketのサーバ間連携を実現する。また、サーバサイドでのフェールオーバは難しいため、障害時はクライアントから再接続する。

[CON6856] Saving the Future from the Past

Javaの非推奨(@Deprecated)の歴史と今後について紹介するセッション。
JJUG CCC 2014 Springで講演されていた、オラクルのStuart Marksさんが"Dr. Deprecator"として白衣を着て発表。

Deprecatedの歴史:

  • 歴史は古く、JDK1.1から既に非推奨は存在
  • 当初はJDK1.1で新しく多くのAPIが導入されたために、古いものを捨てる(abandon)ため、代わりのもっと良いもの(supersede)が出ていることを、ユーザに知らせる仕組みとして導入された。
  • ユーザに新しいものに移行したもらうためのものだった。

Deprecatedの意図の多様化:
当初は今後はメンテしない(abandon)、代わりに良いのがある(supersede)がDeprecatedの意図であったが、現在ではもっと多用な意図でDeprecatedが使われている。

  • CONDEMNED: 今後削除される可能性がある
  • DANGEROUS: データが壊れたり、デッドロックが起こる可能性がある。
    • DANGEROUSの代表例はThread.stop()

Deprecatedこそ付いていないものの、Vectorなどの古いAPIは、ユーザにとって"非推奨"と見られている。

Deprecatedに気が付かないパターン:
コンパイル警告、JavaDocIDEの取り消し線でユーザに知らせているが、JDKの移行の仕方によっては気が付かないパターンもある。再コンパイルせずにバイナリ互換を活かしてJDKのバージョンアップをした場合はユーザはわからない。

APIのライフサイクル:
以下のようなフローが理想とされているが、Java SEの実態は異なる。

  1. 新しいAPIが導入され、古いAPIが非推奨になる
    • しかし実態は、多くの人は非推奨警告を無視する
  2. ユーザは新しいAPIに移行する
    • 実態は様々な理由からなかなか移行しない
  3. 移行が済んだころに、APIは削除される
    • 実態は、JDK9となってもほとんどのAPIは削除されていない。

なぜJava SEはAPIを削除しないのか:

  • APIの削除は究極の非互換である
  • ソース互換性はコンパイルが通らず壊れる、バイナリ互換性もNoSuchMethodErrorで壊れる、振る舞い互換性もAPIがないのでもちろん壊れる
  • なぜ無くなったのか、何がなくなったのかjavadoc化が困難

これからのDeprecated:

  • 非推奨の理由カテゴリを再整理し、enum型で表現したい。
    • UNSPECIFIED(特になし)
    • CONDEMNED(将来的に削除予定)
    • DANGEROUS(リスクあり)
    • OBSOLETE(時代遅れで古い)
    • SUPERSEDED(もっと良い代替がある)
    • UNIMPLEMENTED(実装なし、呼ぶとUnSupportedOperationExceptionになるので注意)
@Deprecated(value={DANGEROUS,SUPERSEDED}, replacement="String#getBytes()", since="1.1")
public void getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)
  • ランタイム時にも@Deprecatedの警告を出して、ユーザに通知したい
  • javadocの"All Methods"から@Deprecatedのメソッドを削除して、ユーザに存在を知らせないようにしたい
  • IDEが非推奨APIの利用をもっと自動置換してくれるようになって欲しい
    • しかし、スレッドセーフを意図してHashTableを使っているなど、単純な置き換えはバグを招く