理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

スレッドダンプを解析する地獄とFastThreadというWebサービス

スレッドダンプというと、JVM 言語に関する運用でマジで困ったときの頼みの綱みたいなイメージある。マジで困ったとき、じゃなくて、カジュアルに見れば良いんだけど、スレッドダンプのフォーマットが異常すぎてつらくなる。

そもそもなんなんだこのゴミみたいなフォーマットは。 一体誰がどういう思想の元でこのフォーマットを決めたのかさっぱり分からないし、人類を苦しめるために未来から来た AI が勝手に策定したのではないか。

"[ThreadPool Manager] - Idle Thread" #42 daemon prio=5 os_prio=31 tid=0x000000012a3a7800 nid=0x15503 in Object.wait() [0x000070000ab66000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:502)
        at org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor.run(Executor.java:106)
        - locked <0x0000000744b091c0> (a org.eclipse.equinox.internal.util.impl.tpt.threadpool.Executor)

   Locked ownable synchronizers:
        - None

"Worker-2" #40 prio=5 os_prio=31 tid=0x000000012a15e800 nid=0x13843 in Object.wait() [0x000070000a8da000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
        - locked <0x0000000741f00888> (a org.eclipse.core.internal.jobs.WorkerPool)
        at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
        at org.eclipse.core.internal.jobs.Worker.run(Worker.java:51)

運用してたシステムで Full GC が頻発したりする事象があって、スレッドダンプを目で解析するみたいな地獄みたいなことしたけど、結局原因がネットワークの輻輳にあったとかいう意味のなさだった。 で、そういうのつらすぎるとか思って何年も過ぎたんだけど、今はもう解析してくれる Web サービスとかあった。

単純にスレッドダンプをアップロードするだけで、無料で解析してくれるのが良いかんじ。 スレッドの状態とか、使われてるメソッドとか、CPU 消費してるメソッドとかも解析してダッシュボードに表示してくれる。

f:id:kiririmode:20170625205212p:plain

f:id:kiririmode:20170625205233p:plain

時代は進んでいるし、ゴミみたいなスレッドダンプを目で解析する時代は終わったみたい。そろそろスレッドダンプのフォーマットをマシにしても良いのではなかろうか。

2017/06/29 追記

ご指摘頂いているとおり、Full GC の解析はまずヒープのダンプを取って、どのクラスがどれだけヒープに溜まっているのかを解析するのが定石です。 誤解させてしまったようですみません。

上記の障害事例のときもヒープダンプを取りましたが原因が見えず、スレッドダンプを取っても不明、sar の結果を解析しても不明で、 ネットワークトラフィックと LB の状況からようやく結果が分かったという経緯でした。

Socket

最近ソケット通信実装のサポートに入っていたので、ちょっとまとめてみたいと思います。

Socket からのデータ読込

Java に関しては不勉強なところが多いんですが、Socket クラスは「ソケット」を抽象化したものであり、「ソケット」は TCP におけるコネクションの端点を抽象化したものです。 結果として、Socket クラスには、TCP における端点に必要な機能が抽象化されています。

一般に Java で Socket から生のバイトを読み込む場合はこういうコードになると思います。これは TCP のコネクションを都度接続する前提です。

byte[] buf = new byte[tlgrmSize]; 
InputStream is = new BufferedInputStream(socket.getInputStream());

// ByteArrayOutputStream baos = new ByteArrayOutputStream(tlgrmSize);
while((len = is.read(buf, 0, remaining)) != -1) {
    
    // do something.
    // ex., baos.write(buf, 0, len);

    remaining = hogehoge() // 読み込むべき残りバイトを計算
}

つまりは、以下のような事柄を、InputStream#read で -1 (EOF) が返ってくるまで続けます。 読み込むべき残りバイトを計算するか否かは要件に依存します。

  1. 受信用の byte[] バッファを生成
  2. Socket#getInputStream() で得られる InputStreamread メソッドで、データを byte[] バッファに読み込み
  3. byte[] バッファに読み込んだバイトに対して必要な処理を行う (ここでは、ByteArrayOutputStream に書き込んでいます)
  4. 読み込むべき残りバイトを計算する

このあたりの処理フローは、Java だろうが C だろうがあまり変わらないと思います。

注意すべきは、InputStream#read メソッドで、要求したデータサイズが一度に読み込めないケースが多々ある点で、これは考慮しておかなければなりません。 上記コードですと、InputStream#read の第三引数で指定した「読み込む最大バイト数」よりも実際に読み込んだバイト数 (read の返却値) が小さい場合があります。

バッファサイズとウィンドウサイズ

上記の受信用バッファのサイズをどのくらい取れば良いのか、という話もあったのですが、このサイズは正直、あまりに大きなメモリ負荷を与えないのならどのくらいでも良いと思っています。 大した流量もなく、読み込むべきデータサイズが既知であり、かつ、それが数 KB に収まるなら、そのサイズくらい一気に取ってしまえば良いんじゃないですかね。

最初に見たコードは TCP のウィンドウサイズを意識したバッファサイズ設計になっていたというのが上の話に繋がるんですが、 Java の Socket において TCP のウィンドウサイズを意識する必要はないと思います。そもそも、TCP のウィンドウサイズの情報は Socket からは入手できないよね。

TCP のウィンドウサイズは大きく 2 つ存在していて、これらのサイズは通信状況に依って動的に調整されます。

  • フロー制御におけるウィンドウサイズ
    • TCP において、受信側の受信バッファをオーバーフローさせないためのウィンドウサイズ。
  • 輻輳制御におけるウィンドウサイズ

どのタイムスライスにおいて、どの程度のデータを送られ得るかは、両ウィンドウやその他の状況によって複雑に調整されます。この手の情報を抽象化するのは Kernel の役割です。

PoEAA: Coarse-Grained Lock

今日は Coarse-Grained Lock、関連した複数のオブジェクトのロックを取得する際のパターンです。

上の図における顧客 (Customer) と住所 (Address) の関係に代表されるように、互いに関連するオブジェクトを一緒にロックしたいというケースがあります。 それぞれのオブジェクトを馬鹿正直にロックする方針を採ると、デッドロックを生じさせないように「個々のオブジェクト」を「正しい順に」ロックしていかないといけなくなります。これを多数の開発者からなるチームに徹底させるのは大変困難です。また、都度、個々のオブジェクトをロックしていくのでは性能劣化も生じます。

Coarse-Grained Lock は、これらの問題を鑑み、複数の関連するオブジェクトをまとめて 1 回だけロックを取得するパターンです。 最初に、個々のオブジェクトを代表してロックするポイントを作ります。よく XX 排他制御用テーブルとか作ったり、「親テーブル」なるものに VERSION カラムを作ったりしますが、アレですね。

これに関連する概念として、aggregate、root がありますが、Coarse-Grained Lock では、このうちの root をロックすることになることが多いです。

  • aggregate: データ変更の単位となるオブジェクトの集合。さっきの図の例だと、Customer と Address が aggregate に相当します。
  • root: aggregate の中のオブジェクトを変更する際に、ロック用のアクセスを必要とする対象

これを実現するための方法として、以下の方法があります。

  1. aggregate に含まれる個々のオブジェクトに root へ直接的にリンクさせる
    • 個々のテーブルに、aggregate への外部キーを張るイメージ
  2. aggregate に含まれる個々のオブジェクトに root へ間接的にリンクさせる
    • 直接の親テーブルへの外部キーを保持し、それを繰り返すことによって root を参照するイメージ
    • グラフ構造が大きくなると、root に辿り着くまでの参照で性能劣化が起こりがちなので、

HTMLから画像をアップロードするためのinput type="file"とHTML Media Capture

HTML を使って、スマートフォンから画像をアップロードをするケースがあります。 このような場合、エンドユーザに対して

  • カメラを使ってアップロードさせる
  • フォトライブラリや iCloud Drive からアップロードさせる

などができると便利です。 このあたりをどのように実現するのかを調べてみたので、理屈上どのようになるかを説明したいと思います。

HTML

HTML を使ってファイルをアップロードさせるためには、<input type="file"> のタグを使うことになります。 一般に、<input type="file"> の要素があると、ブラウザはユーザに対して、ファイル選択ダイアログを表示します。

この <input type="file"> に対しては accept 属性を付与でき、この属性を用いて「どのようなファイルのアップロードが」期待されているのかを示すことができます。具体的には、「画像」「動画」あたりが代表的でしょうか。

この指定により、ブラウザは一般的なファイル選択ダイアログではなく、そのコンテキストに見当ったダイアログを出すことが可能になります。

例えば、画像のアップロードを行う場合、

<input type="file" name="upfile" id="upfile" accept="image/*" />

と書くことで、例えば Mobile Safari は以下のような画像選択用のダイアログを表示する実装になっています。

f:id:kiririmode:20170523011926p:plain

同様に、動画のアップロードを行う場合は、

<input type="file" name="upfile" id="upfile" accept="video/*" />

と書くことで(静止画像ではなく)動画撮影を選択することが可能です。

言っていれば、accept 属性に対しては、MIME Type のようなものを設定すれば良いということです。

ちなみに、一応、HTML 仕様上は .gif など、拡張子指定も可能ということになっていますが、Mac の Chrome は対応していた(※)ものの、iPhone 7 の Safari は未対応でした。

※Mac の場合は「形式」として Graphics Interchange Format と認識されていることが分かります。 f:id:kiririmode:20170523011525p:plain

HTML Media Capture

上記のような <input type="file"> のタグに加えて、ユーザデバイス上のカメラとかマイクとかを利用できるようにした拡張機能が HTML Media Capture です。 百聞は一見にしかずで、下記のタグを使うと、iPhone Safari から「ファイルを選択」ボタンを押下するだけで、直接カメラが立ち上がります。

<input type="file" name="upfile" id="upfile" accept="image/*" capture="user" />

ここでは capture 属性がミソで、captureuser にするとフロントカメラが立ち上がり、セルフィー撮影モードになります。一方、captureenvironment にすると、リアのカメラが立ち上がります。

この HTML Media Capture の仕様については、W3C では Candidate Recommendation となっていて、以下で定義されています。

ちなみに、Web 上のサンプルとしては、capture="camera" とかが散見されるんですが、これは上記の仕様には沿っていません。 Mobile Safari のドキュメントにも、その記載はありません。

補足

とりあえず、体験用に HTML ページを置いとくので、よかったらどうぞ。

https://gistpreview.github.io/?71a54c10507364abbc8335c4bfa7d1b0

ソースはこちらです。

PoEAA: Pessimistic Offline Lock

今日の Pessimistic Offline Lock は、任意のタイミングにおいて、データにアクセスするビジネストランザクションを 1 つに限定することで、データの不整合を防止するパターンです。

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

Optimistic Offline Lock の問題点は、データの競合に気付くのがトランザクションをコミットする時点まで遅れてしまうことです。結果として、1 つのデータに複数のトランザクションが同時に更新をかけるような状況が多く発生する場合、一人だけがコミットできて、他の人は全員排他エラーが発生するということになりますし、そういった「他の人」は、自分がデータを入力し終わってようやく一息ついたところでエラーが発生するという最悪のユーザ体験をする羽目になります。

Pessimistic Offline Lock では、ビジネストランザクションはデータを使う前に、ロックを取得します。このロックは他のビジネストランザクションが当該データにアクセスすることを防ぎ、データ更新が競合することを防ぎます。更新の競合が多いことが想定されるときに使用するパターンになります。 この Pessimistic Offline Lock の実装は 3 つのフェーズに分かれます。

  1. 取得するロックの種別を決定する
  2. ロックマネージャーの構築
  3. 業務トランザクションがどのようにロックを取得するかを決める

ロックの種別

ロックの種別としては、以下のものがあります。これらについては、システムの並行性、業務要件、コードの単純さを観点に、正しいものを選択する必要があります。

  1. 排他書込みロック
    • 業務トランザクションがデータを編集するときだけロックを取得する。読込については制限しないので、参照トランザクションが単に直近のデータを取得すれば良いのであれば、この種別で十分。
  2. 排他読込ロック
    • 暁霧トランザクションが、直近のデータを保持する場合に選択するロックの種別。このロック種別では、データを取得するタイミングでロックを取得することになり、システムの並行性を強く制限する。
  3. 読込/書込みロック
    • 両者の中間のロック種別。読込ロックと書込ロックの 2 つのロックを利用する。
    • 読込ロックと書込ロックは互いに排他的であり、どちらかのロックを取得すると、他方のロックは取得できない。
    • 同時並行した読込ロックは許容される。
    • つまり、読込ロック取得中は、ロック対象レコードの編集のみ不可となる。

ロックマネージャ

ロックマネージャの責務は、業務トランザクションからのロック要求、ロック解除要求を許可するか、拒否するかを判断することにあります。 これを実現するために、ロックマネージャは、対象データがロックされているか、そして、ロックされているようならそのロックしているユーザが誰かを判断する必要があります。ロックマネージャを構成するのは、ロックとその保有者をマッピングする単一のテーブルになります。

また、消失した業務トランザクションに対するロックタイムアウトの管理も重要です。 クライアントが業務トランザクション中にクラッシュしてしまうと、取得していたロックが外れなくなる場合があります。理想的には、アプリケーションサーバが面倒を見るところです。

業務トランザクションのロックプロトコル

ロックプロトコルでは、以下のような内容を決めます。

  • いつロックを取得し、いつ解放するのか
  • 何をロックするのか
  • ロックが取得できなかった場合どういう挙動をするのか

いつロックを取得するのかについてはデータのロード前、ロックする対象については対象レコードの主キー、ロックの解放はゴランザクション完了後、というのが一般的なパターンです。 ロックを取得できなかったときの業務トランザクションについて、最もシンプルなのはそのトランザクションを失敗させることです。Pessimistic Offline Lock では、この失敗についても業務トランザクションの早期に検知できるだめ、おそらくは許容可能でしょう。また、開発者はこのロック取得の試行をより早いタイミングで実施すべきです。

PoEAA: Optimistic Offline Lock

今日からは Offline Concurrency Patterns ということで、排他制御の話に入ってきます。今日は、Optimistic Offline Lock ということで、楽観的排他制御です。

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

Patterns of Enterprise Application Architecture (Addison-Wesley Signature Series (Fowler))

Optimistic Offline Lock は、コミットしようとしているデータ変更が他のセッションでの変更と競合しないことを確認することで不整合を回避するパターンです。 Pessimistic Offline Lock が、競合の頻度が高いケースを想定している一方で、Optimistic Offline Lock はその競合頻度が低いことを想定しています。

Optimistic Offline Lock は、セッションが特定レコードをロードしてから、他のセッションが当該レコードを変更していないことを確認するというものです。よく見られる実装としては、個々のレコードに対してバージョン番号を付与するというものがあります。 レコードを変更するタイミングでバージョン番号をインクリメントすることをルール化しておくことができれば、Optimistic Offline Lock はレコードデータを変更するタイミングにおける当該レコードのバージョンと、セッションに保持しているデータのバージョンが一致していることを確認することと同義になります。 RDBMS をデータストアとすれば、以下のようにしてこのパターンを実現することができます。

  1. UPDATE や DELETE 文といったデータ変更を行う SQL の criteria にバージョン番号を付与する
  2. SQL で変更が起こったレコード数を比較し、(SQL の意図する変更レコード数が 1 の場合)0 件の場合は、他のセッションがデータを更新していた

バージョン番号だけでなく、更新者や更新時刻を使用することができます。この場合、「いつ」「誰が」そのレコードを(自分以外で)更新したのかといった情報を提示することができます。ただし、この場合は時刻の粒度には気をつける必要があります。

Optimistic Offline Lock では、読取りの一貫性(inconsistent read) の検知ができない点には気をつける必要があります。例えば、今年度の住民税を計算・更新するには、現在の住所をベースにする必要がありますが、住民税を計算するセッションと住所を更新するセッションが競合した場合、住民税を計算するセッションはなかなか住所の更新競合が発生していることに気付きにくいという問題です。 こういった場合は、Coarse-Grained Lock の方が望ましい場合も多くでてくるでしょう。

PoEAA: Data Transfer Object

今日は Data Transfer Object、いわゆる DTO と呼ばれるパターンです。

DTO の目的は、リモート呼出の効率化です。リモート呼出の際に必要な情報を DTO というオブジェクトの中にこれでもか、これでもかと詰めてやることで、リモート呼出の回数を削減することができます。 DTO はその役割上、シリアライズされる必要があるので、ドメインオブジェクトではなく、単に値を持ちロジックを持たないオブジェクトとすることがほとんどになります。

Docker ComposeでPostgresバックエンドのgitbucket/redmineを構築する

Redmine をマジメに使ったことがなかったのですが、使う機会が多くなってきているので、ちょっと慣れとかないと厳しいなぁと思っておりました。 とりあえず本を買ったは良いのですが、手元で確認したいというのと、同じく良く使われるようになってきている Gitbucket も手元で使いたい、 それに、せっかくだったら全部 Postgresql をバックエンドにしておきたいという思いもありました。

というわけで、これらの環境を手元にサクっと構築できるように、Docker Compose で立ち上げられるようにしておくか、というのが本エントリの趣旨になります。

これにより、以下のコマンドで、redmine と gitbucket が動き出します。

$ docker-compose run -d

ベースとなるコンテナ

ベースとしたのは以下のコンテナです。

コンテナ URL
redmine:3.3.3 redmine
postgres:9.6.2 postgres
takezoe/gitbucket:4 gitbucket

Redmine

Redmine のコンテナについては、環境変数でバックエンドの DB を切り替えられるようになっています。このため、docker-compose.yml に何の工夫もなく記述するだけで良くて楽でした。

docker-compose.yml 上のサービス定義は以下のようになります。

  redmine:
    image: redmine:3.3.3
    ports:
      - 3000:3000
    environment:
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_USERNAME: postgres
      REDMINE_DB_PASSWORD: password
    depends_on:
      - db
    restart: always

Gitbucket

Gitbucket については、以下の 2 つの課題がありました。

  1. Gitbucket はデータベース設定は設定ファイル database.conf で持つようになっているため、バックエンドを Postgres に設定した database.conf を使ってコンテナをビルドしておく必要があります。つまり、Dockerfile を作っておく必要がある。
  2. Gitbucket は起動時にデータベーススキーマを作るので、Postgres のコンテナ上で Postgres が立ち上がるまで Gitbucket の起動を待つ必要がある

前者は Dockerfile で ADD を使えば問題ないです。

後者については、コンテナの依存関係を示す depends_on だけではダメで、Postgresql の LISTEN ポートが有効になるまで待つ仕組みを入れる必要があります。 これを簡単に実現するのが wait-for-it.sh という Bash スクリプトです。 このスクリプトは、一定時間ほど指定された host:port の open を試み、その状態でブロックしてくれるというものです。

実装を見ると、/dev/tcp へのリダイレクトでこれを実現しておりまして、これで TCP open してくれるんや…知らんかった…。

というわけで、この wait-for-it.sh を組み込んだ Dockerfile はこちら。

# -*- mode: Dockerfile -*-
FROM takezoe/gitbucket

MAINTAINER kiririmode

ADD database.conf /gitbucket/database.conf
ADD wait-for-it/wait-for-it.sh /gitbucket/wait-for-it.sh

この Dockerfile をベースにして、docker-compose.yml のサービス定義は以下のようになります。

  gitbucket:
    build:
      context: .
      dockerfile: Dockerfile.gitbucket
    ports:
      - 8080:8080
    command: ["/gitbucket/wait-for-it.sh", "db:5432", "--", "java", "-jar", "/opt/gitbucket.war"]
    depends_on:
      - db
    restart: always

Postgresql

Postgres も特に特筆すべきことはしていないのですが、Gitbucket の前提が「起動時にデータベースがあること」なので、そのデータベースを作るように設定します。

Postgres のコンテナは、/docker-entrypoint-initdb.d というディレクトリに放り込んだスクリプトを起動時に実行してくれるので、以下のようなスクリプトを init-gitbucket-db.sh として保存しておいて、Dockerfile の ADD を使って送り込めば良い。

#!/bin/bash

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<EOF
    CREATE USER gitbucket PASSWORD 'password';
    CREATE DATABASE gitbucket;
    GRANT ALL PRIVILEGES ON DATABASE gitbucket TO gitbucket;
EOF

docker-compose.yml のサービス定義は以下のとおりです。

  db:
    build:
      context: .
      dockerfile: Dockerfile.postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    restart: always

つくったもの

特定ディレクトリ配下の拡張子の一覧を取得するワンライナー

特定ディレクトリ配下で特定の拡張子を持つファイルを探すのは find コマンドで一発ですが、一方で、特定ディレクトリ配下の拡張子をすべて抽出する、というユースケースがあります。 ぼくは、.gitattributes に書く拡張子って何にしよう、ってときに、その必要が生じました。

結論から言うと、以下で可能です。

$ find . -type d -name '.git' -prune -o -type f -exec basename {} \; | grep -o '\.[^.]*$' | sort | uniq
.MF
.aaa
.class
.classpath
.component
.config
.container
(略)

find コマンドについては、.git ディレクトリ配下のファイルは無視するという前提でファイルパスを抽出し、basename コマンドにかけてファイル名を抽出しています。

-prune のオプションについては、こちらが分かりやすいかと。

ファイル名を取得できた後、grep-o オプションを使います。

grep は、別に合致した行を抽出できるだけでなくて、「合致した箇所」を -o オプションで抽出することができます。ここでは、 「最後の . の後に続く、. を含まない文字列」を拡張子として定義しました。

あとは、おなじみの sort | uniq で、重複した拡張子を削除するってかんじです。

Excelの結合セルの高さを調整する伝家の宝刀

Excel 方眼紙を使いこなすぼくたちにとって、セルの結合というのはエクスカリバーに近しい最終兵器です。 そんなエクスカリバーたる結合セルに入れる文章というのもまた、明治の文豪がその人生を賭して綴り捻り出す人生そのものであって、セル内改行に伴って文字が切れてしまうようなことは絶対にあってはなりません。

しかしですね、Excel というソフトウェアは、結合セル内の文章に合わせてセルの高さを調整する機能を有しておりません。このため、Excel 方眼紙を作り出すユーザは、逐一セルの高さを手動で揃えなければならない。これは由々しき問題です。

いやね、セルを結合していなければ、自動で高さ調整をしてくれるんです。

ところが、結合セルに対しては調整をしてくれない。Microsoft には、石に刺さったエクスカリバーを引き抜くことができなかったとばかりです。

しかしぼくは、ついにエクスカリバーを引き抜くことができるアーサー王を発見した。それが、AutoFitRowEx という Excel アドインです。

これをインストールすることでコンテキストメニューに「行の高さを自動調整」というメニューが追加されます。 高さ調整したいセルを選択した状態でこのメニューを使用することで、Excel マクロが起動され、行の高さが良い感じに自動調整されます。

「印刷したら文字切れしてるぞ」みたいな指摘をしたりされたりして修正に時間を要するのは時間をドブに捨てているようなものですから、積極的に使っていきたいと思いました。 Microsoft に何とかしてほしいものですが、Excel は表計算ソフトですから、みたいな言葉の暴力で撲殺される恐れがあるので、そういうことは言わない。