Effective Java 第3版 は 2017年に出るかもしれない

Java の良書といえば真っ先に名前が上がるだろう Effective Java ですが、第2版の原本が出たのが既に 9年近く前で、流石に古くなってる感があります。

ラムダや Optional の使い方等、最近の Java の機能を加えた改訂版を待っていたのですが、 Quora の回答から、著者の言及にたどり着いたので、紹介しておきます。

今年中には出るんじゃないかってことみたいです。

追記 2017/04/19

Amazon で予約受付が始まりました。

https://www.amazon.co.jp/dp/0134685997

Effective Java (3rd Edition)

Effective Java (3rd Edition)

追記 2017/10/18

ピアソンのページが出来てました。 12/19 発売で、eBook もあります。

Java 9 にも対応するみたいです。

www.informit.com

Docker Compose の構成について

Docker は image が手元にあれば一瞬でサービスを立ち上げることができるので主に開発環境として便利に使ってます。

Docker のベストプラクティスとして “one process per container” が上げられており、一つのシステムを立ち上げるのにコンテナが複数必要になることもよくありますね。コンテナ間の連携は、レガシーな Link 機能で一つずつ繋げていた時代から進化し、Docker Network 機能で独自のネットワークを構成できるようになりました。そして、Docker Compose でコンテナを一気に立ち上げ、システムを構成できます。

で、今この Docker Compose の構成について悩んでいます。こんなとこかなという案をいくつか上げておくので、運用してみて後日談を後ほどまとめたいと思います。机上の空論なので、間違ってるところがあるかも。

    1. Dockerfile 置き場案
    1. Docker プロジェクト内包案
    1. Image 取り込み案

a. Dockerfile 置き場案

SPA なシステムで MySQL にデータを突っ込むようなものを想定しています。

.
├── README.md
├── docker-compose.yml
├── dockerfiles/
│   ├── Dockerfile_api     # <- API 実行環境
│   ├── Dockerfile_front   # <- フロントエンドアプリビルド環境
│   ├── Dockerfile_proxy   # <- Web サーバ
│   └── Dockerfile_mysql   # <- MySQL (公式の MySQL イメージでいいかも)
└── volumes/
    ├── app_source/        # <- API のソースコード
    ├── front_dist/        # <- ビルドされたフロントエンドアプリの出力先
    └── data_volume/       # MySQL のデータディレクトリ
  • Dockerfile 1枚だけのコンテナばかりならこれでよさそう
  • ソースコードは別途 Git 管理されてて git clone http://github/ikosin/some-app volumes/app_source する想定

b. Docker プロジェクト内包案

.
├── README.md
├── docker-compose.yml
├── api/
│   ├── Dockerfile         # <- API 実行環境
│   └── source/            # <- API のソースコード
├── front/
│   ├── Dockerfile         # <- フロントエンドアプリビルド環境
│   └── dist/              # <- ビルドされたフロントエンドアプリの出力先
├── proxy/
│   └── Dockerfile         # <- Web サーバ
└── mysql/
    ├── Dockerfile         # <- MySQL (公式の MySQL イメージでいいかも)
    └── data/              # MySQL のデータディレクトリ
  • だいたいどんなコンテナでも対応できるように分けた構成で、これが王道な気がする
  • ソースコードはどう管理しましょうかね……。Git のサブモジュールにしておくと次の Image 取り込み案に移行するのも簡単でいいかもしれません

(追記)

こちらのブログではこのパターンを採用してそうですね。ソースコードもモノリポで一緒に管理してるんでしょうか。 複数の rails プロジェクトが共存する開発環境を Docker 化した話を晒してみる · カウル Tech Blog

c. Image 取り込み案

.
├── README.md
└── docker-compose.yml
  • ソースコード管理しているリポジトリに Dockerfile も突っ込んでそっちで Image ビルドしてしまいましょう案
  • Docker イメージのプライベートリポジトリが欲しくなるけど、無くても各サーバで各イメージをビルドすればなんとかなる
  • すごい薄い機能のコンテナをわざわざ切り離すのも面倒かもしれないけど、汎用性を意識するようになっていいかも

やってみる

上からお手軽な順にあげてみたつもりです。

現在、CONBUCONBU-API の実行環境を Docker 化しようと現在進めている所なので、ひとまず上からやってみてフィードバックもらいたいと思います。

ISUCON6予選惨敗日記

四年連続四回目の ISUCON は、最初の Perl 実装すらスコアが出ず、Go に変更してアレコレするも終始スコア0のまま終了を迎えるという最高に不甲斐ない結果に終わった。 今後に向け、自戒の念をこめて日記を残しておこう。

日記

事前にチームで打ち合わせ。実装は Go で行こう。メンバーの共通言語が Java しか無いし、自分以外は Windows 使い、開発環境を整えやすい Go にしよう。消去法だ。実際、ほぼ初心者な状態でも Go の開発・デプロイ周りを整備するのには時間がかからなかった。いい選択だったと思う。

自分はインフラ等の足回りを引き受ける。Azure のアカウント作成や去年の予選問題を Azure でデプロイして素振りをする。この素振りが当日すごく役に立った。イメージを共有できない Azure では、当日も同じように Azure リソース マネージャーのテンプレートと Ansible によるプロビジョニングで始まる方式だった。素振りのおかげで SSH でサーバに入った直後にソースが見つからなくてパニックになるという事態を回避できた。感謝しかない。

デプロイが完了して今回の課題を見る。元ネタは、はてなキーワード(もしくは、はてな匿名ダイアリー:増田)とはてなスターかな?はてなさん出題だしなー、そうきたかー。このキーワードをリンクに置換してるのは重そうだな。

他のメンバーにはローカルで開発できる環境を整備してソースを読み込んでもらう。自分は足回り担当としてスロークエリの設定・解析 (pt-query-digest) や MySQL パラメータと ulimit のチューニング (Linux VM での MySQL パフォーマンスの最適化 | Microsoft Azure)、アクセスログの解析 (tkuchiki/alp: Access Log Profiler) を進めていこう。この辺りは流石に四回目ともなると慣れてきていい感じ。

ようやく昼頃に環境が整った。ソースを追ってボトルネックになりそうなところを探そう。アクセスログの解析で、/ が壊滅的に重いところまで把握。トップページの処理の中でどこがボトルネックになっているかな?後で思い返すと、このボトルネックの特定が雑だったのが致命的だった。Ruby 実装なら rack-lineprof でどの処理が重いのかしっかり見極めるんだが、Go のプロファイリングツールに良さそうなのが見つからない。ぱっと見、isuda から isutar へ HTTPリクエストでスターを取得している部分が目につく。スター周りの改善をやろう。この時点で、最初にアプリを見た時の直感は忘れさられおり htmlify は放置。今回の敗北を決定付けるミスである。

スター対策として、isuda と isutar を一つのサービスに統合する事も検討。いや、他でも使うだろうし Redis を入れて isuda はそちらを参照するように実装を変えよう。スターにユーザ名が必要なのか確認してみると Go実装でユーザ名を保存してない。他言語の実装だと保存されてるっぽいのになー。これはバグっぽいなと実装を直す。同時に公式のパッチが提供される。やっぱりかー。Redis 化の実装変更とバグ取りは思った以上に時間がかかり、動くようになる頃には17時近くになってしまう。

スター対策がスコアに反映されず、頭を抱える。最後の1時間でスロークエリを潰して悪あがきしよう。CHARACTER_LENGTH(keyword) が重そうなので、keyword_length を別カラムに持つ。ざくっと修正して最後の望みを託すもののこちらもスコアに反映されず無念のタイムアップ。

Go の正規表現が遅いの知らなかったので、みんGo 買います。

感謝

非常に悔しい結果だったけど、今回も非常に楽しい ISUCON でした。運営の皆さんありがとうございました。

反省

推測するな計測せよ

  • ちゃんとプロファイリングツールで重い処理を特定しないといけなかった
    • せめて New Relic ぐらいは入れて置くべきだった
  • tcpdumpベンチマークツールのリクエスト解析したかった
  • Nginx での静的ファイル配信をすっかり忘れていた
    • 最低限やれることは事前にリストアップしておかないと

作業メモ

おまけで、当日記録していたメモを貼っておく。時間は後で追記したものなのでブレがありそう。

09:00 システム構成の把握

  • Ansible の yaml が /tmp/isucon6-qualifier/provisioning/image/ansible にあったので読む
  • 画面を見るに、増田とはてなスターっぽいアプリ
  • systemd を確認
    • isuda, isutar, isupam が今回のアプリ名(マイクロサービスだ)

10:00 Go言語に切り替え

  • systemd にハマる
    • stop/start, せず enable/disable だけで切り替えた気になっていた

10:30 pt-query-digest:スロークエリ解析ツール

sudo pt-query-digest /var/lib/mysql/mysql-slow.log > ~/digest.txt
10:40 alp: アクセスログ解析ツール(https://github.com/tkuchiki/alp)
sudo alp -f /var/log/nginx/access.log > ~/access_log.txt

初期結果

+-------+--------+--------+---------+--------+--------+--------+--------+--------+------------+------------+-------------+-----------+--------+-----------------------------------+
| COUNT |  MIN   |  MAX   |   SUM   |  AVG   |   P1   |  P50   |  P99   | STDDEV | MIN(BODY)  | MAX(BODY)  |  SUM(BODY)  | AVG(BODY) | METHOD |                URI                |
+-------+--------+--------+---------+--------+--------+--------+--------+--------+------------+------------+-------------+-----------+--------+-----------------------------------+
|     1 |  0.000 |  0.000 |   0.000 |  0.000 |  0.000 |  0.000 |  0.000 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /js/star.js                       |
|     1 |  0.001 |  0.001 |   0.001 |  0.001 |  0.001 |  0.001 |  0.001 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /css/main.css                     |
|     1 |  0.120 |  0.120 |   0.120 |  0.120 |  0.120 |  0.120 |  0.120 |  0.000 |     15.000 |     15.000 |      15.000 |    15.000 | GET    | /initialize                       |
|     6 |  0.028 |  0.428 |   0.664 |  0.111 |  0.000 |  0.029 |  0.137 |  0.148 |  86351.000 |  86351.000 |  431755.000 | 71959.167 | GET    | /js/jquery.min.js                 |
|     5 |  0.028 |  0.428 |   0.663 |  0.133 |  0.028 |  0.030 |  0.138 |  0.153 |   1092.000 |   1092.000 |    5460.000 |  1092.000 | GET    | /favicon.ico                      |
|     6 |  0.017 |  0.429 |   0.621 |  0.103 |  0.000 |  0.026 |  0.119 |  0.150 |  28631.000 |  28631.000 |  143155.000 | 23859.167 | GET    | /js/bootstrap.min.js              |
|    11 |  0.028 |  0.429 |   1.127 |  0.102 |  0.000 |  0.031 |  0.287 |  0.128 | 106015.000 | 106015.000 | 1060150.000 | 96377.273 | GET    | /css/bootstrap.min.css            |
|     6 |  0.028 |  0.431 |   0.667 |  0.111 |  0.000 |  0.030 |  0.138 |  0.149 |  16849.000 |  16849.000 |   84245.000 | 14040.833 | GET    | /css/bootstrap-responsive.min.css |
|     6 |  0.001 |  0.432 |   0.651 |  0.108 |  0.001 |  0.030 |  0.120 |  0.149 |     93.000 |     93.000 |     465.000 |    77.500 | GET    | /img/star.gif                     |
|    58 |  0.014 |  1.984 |  27.837 |  0.480 |  0.014 |  0.287 |  1.964 |  0.491 |     10.000 |     10.000 |      60.000 |     1.034 | POST   | /login                            |
|     1 |  2.320 |  2.320 |   2.320 |  2.320 |  2.320 |  2.320 |  2.320 |  0.000 |   2255.000 |   2255.000 |    2255.000 |  2255.000 | GET    | /keyword/121系                    |
|    42 |  0.023 |  2.545 |  34.590 |  0.824 |  0.023 |  0.672 |  2.193 |  0.580 |      0.000 |      6.000 |     192.000 |     4.571 | POST   | /keyword                          |
|     2 |  3.768 |  3.970 |   7.738 |  3.869 |  3.768 |  3.768 |  3.768 |  0.101 |   6973.000 |   6973.000 |   13946.000 |  6973.000 | GET    | /keyword/平尾山                   |
|     2 |  5.007 |  5.931 |  10.938 |  5.469 |  5.007 |  5.007 |  5.007 |  0.462 |   9367.000 |   9381.000 |   18748.000 |  9374.000 | GET    | /keyword/2100年                   |
|     2 |  8.629 |  9.163 |  17.792 |  8.896 |  8.629 |  8.629 |  8.629 |  0.267 |   2498.000 |   2498.000 |    4996.000 |  2498.000 | GET    | /keyword/ウーズ                   |
|     1 | 13.389 | 13.389 |  13.389 | 13.389 | 13.389 | 13.389 | 13.389 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/北消防署                 |
|     1 | 13.729 | 13.729 |  13.729 | 13.729 | 13.729 | 13.729 | 13.729 |  0.000 |   5370.000 |   5370.000 |    5370.000 |  5370.000 | GET    | /keyword/ジョージ・グローヴ       |
|     1 | 14.019 | 14.019 |  14.019 | 14.019 | 14.019 | 14.019 | 14.019 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/巨大基数                 |
|     2 | 11.320 | 14.478 |  25.798 | 12.899 | 11.320 | 11.320 | 11.320 |  1.579 |   2541.000 |   2541.000 |    5082.000 |  2541.000 | GET    | /keyword/輪状甲状筋               |
|     1 | 14.527 | 14.527 |  14.527 | 14.527 | 14.527 | 14.527 | 14.527 |  0.000 |   3162.000 |   3162.000 |    3162.000 |  3162.000 | GET    | /keyword/1051年                   |
|     1 | 14.992 | 14.992 |  14.992 | 14.992 | 14.992 | 14.992 | 14.992 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/パールヴァティー         |
|     1 | 14.998 | 14.998 |  14.998 | 14.998 | 14.998 | 14.998 | 14.998 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/きたのじゅんこ           |
|     1 | 14.999 | 14.999 |  14.999 | 14.999 | 14.999 | 14.999 | 14.999 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/西那須野駅               |
|     1 | 15.000 | 15.000 |  15.000 | 15.000 | 15.000 | 15.000 | 15.000 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/湯田村駅                 |
|     2 | 15.000 | 15.000 |  30.000 | 15.000 | 15.000 | 15.000 | 15.000 |  0.000 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/河出書房新社             |
|     2 | 14.998 | 15.000 |  29.998 | 14.999 | 14.998 | 14.998 | 14.998 |  0.001 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/内田修平                 |
|     2 |  0.123 | 15.000 |  15.123 |  7.561 |  0.123 |  0.123 |  0.123 |  7.439 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/1097年                   |
|     2 | 14.999 | 15.005 |  30.004 | 15.002 | 14.999 | 14.999 | 14.999 |  0.003 |      0.000 |      0.000 |       0.000 |     0.000 | GET    | /keyword/イギリス政府             |
|    22 | 14.986 | 20.838 | 335.785 | 15.263 | 14.986 | 15.000 | 15.001 |  1.217 |      0.000 |  60169.000 |   60169.000 |  2734.955 | GET    | /                                 |
+-------+--------+--------+---------+--------+--------+--------+--------+--------+------------+------------+-------------+-----------+--------+-----------------------------------+

11:15 MySQL チューニング

12:00 limits.conf チューニング

sudo su -
cat << EOC >> /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
* soft nproc 65536
* hard nproc 65536
EOC
ulimit -SHn 65536
ulimit -SHu 65536
echo “ulimit -SHn 65536” >>/etc/rc.local
echo “ulimit -SHu 65536” >>/etc/rc.local
exit

12:20 デプロイスクリプト用意

  • git pull
  • make を叩いてビルド
  • systemd の isuda.go.service, isutar.go.service をリスタート
  • アクセスログとスロークエリログを削除して nginx, MySQL もリスタート

13:00 ソースを読んで方針検討

  • isuda から isutar へ HTTP でスターの情報を取りに行ってるのが無駄そう

14:00 Redis 入れる

sudo apt-get install build-essential tcl
cd /tmp
curl -O http://download.redis.io/redis-stable.tar.gz
tar xzvf redis-stable.tar.gz
cd redis-stable
make
make test
sudo make install
sudo mkdir /etc/redis
sudo cp /tmp/redis-stable/redis.conf /etc/redis
sudo vi /etc/redis/redis.conf # supervised no => supervised systemd, dir ./ => dir /var/lib/redis
sudo sh -c "cat << EOF > /etc/systemd/system/redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
User=redis
Group=redis
ExecStart=/usr/local/bin/redis-server /etc/redis/redis.conf
ExecStop=/usr/local/bin/redis-cli shutdown
Restart=always

[Install]
WantedBy=multi-user.target
EOF"
sudo adduser --system --group --no-create-home redis
sudo mkdir /var/lib/redis
sudo chown redis:redis /var/lib/redis
sudo chmod 776 /var/lib/redis
sudo systemctl start redis
sudo systemctl enable redis

15:00 スターの alt, title にユーザ名がつかない

  • /star の POST でユーザ名はクエリストリングと FormData とどっちが正しい?

=== メモはここで途切れている……。余裕がなくなったようだ。 ===