Javaの超低レイテンシなGCアルゴリズム、ZGCをコンパイルして動作を試す

The Z Garbage Collector

以下の資料を見てZGCのことを知りました。

The Z Garbage Collector

ZGCは、 "A Scalable Low Latency Garbage Collector" というものだそうで、まだ開発中でリリースはされていないです。

f:id:kuro_m88:20180218013120p:plain

数TBまでのヒープメモリのサイズを想定していて、なおかつGCの最大停止時間が10msというのがゴール。既にヒープサイズが128GBのベンチマークにおいて、パラレルGCやG1GCより圧倒的に停止時間が短い。ベンチマーク上では最大停止時間も10msを切っています。 アルゴリズムについては別途まとめてみようかと思っています。Colored Pointerという概念が特徴です。

f:id:kuro_m88:20180218013454p:plain

ZGCを試す

開発版のZGCは既に試すことができるようです。ですが、自分でビルドする必要があります。紹介したスライドで書かれている方法ではうまくいかなかったのですが、なんとか動かすところまでできたので紹介します。

環境はUbuntu16.04.3で、24CPU、48GBメモリのサーバで作業しました。メモリは8GBもあればビルドできるのではないかと思います。

必要なパッケージをインストール

ubuntu@java-z:~$ sudo apt install build-essential mercurial zip unzip libx11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev libcups2-dev libfontconfig1-dev libasound2-dev

ZGCプロジェクトのリポジトリをダウンロードしてくる

3GB以上あるので、そこそこ待たされます。

ubuntu@java-z:~$  hg clone http://hg.openjdk.java.net/zgc/zgc

configureする

ubuntu@java-z:~$ cd zgc
ubuntu@java-z:~/zgc$ sh ./configure

[...略]

checking for javac... no
checking for java... no
configure: Could not find a valid Boot JDK. You might be able to fix this by running 'sudo apt-get install openjdk-8-jdk'.
configure: This might be fixed by explicitly setting --with-boot-jdk
configure: error: Cannot continue

openjdk-8-jdk をインストールしろというメッセージが出ますが、実際に必要なのは JDK9 です。ここでOpenJDK 9をインストールしたくなりますが、後述する理由ビルドがコケます。

=== Output from failing command(s) repeated here ===
* For target buildtools_interim_langtools_modules_java.compiler.interim__the.BUILD_java.compiler.interim_batch:
javac: invalid flag: --module-path
Usage: javac <options> <source files>
use -help for a list of possible options
javac: no source files
Usage: javac <options> <source files>
use -help for a list of possible options

javacに --module-path というオプションがないというエラーです。実はこれ、OpenJDKとOracle JDKでコマンドオプションの引数の名前が違うせいです。このまま通るようにするにはOracleのJDK9をインストールする必要があります。

ubuntu@java-z:~/zgc$ sh ./configure

[...略]

Configuration summary:
* Debug level:    release
* HS debug level: product
* JDK variant:    normal
* JVM variants:   server
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64
* Version string: 10-internal+0-adhoc.ubuntu.zgc (10-internal)

Tools summary:
* Boot JDK:       openjdk version "9-internal" OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src) OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)  (at /usr/lib/jvm/java-9-openjdk-amd64)
* Toolchain:      gcc (GNU Compiler Collection)
* C Compiler:     Version 5.4.0 (at /usr/bin/gcc)
* C++ Compiler:   Version 5.4.0 (at /usr/bin/g++)

Build performance summary:
* Cores to use:   24
* Memory limit:   48287 MB

うまくいくと、上記のような出力が出るはずです。

makeする

ubuntu@java-z:~/zgc$ make images

ビルドが終わるまで待ちます。自分の環境だと4分半くらいで終わりました。

f:id:kuro_m88:20180225224416p:plain

CPUが全コアほぼ100%になった。

javaのバージョンの確認

ubuntu@java-z:~$ cd zgc/build/linux-x86_64-normal-server-release/images/jdk/bin
ubuntu@java-z:~/zgc/build/linux-x86_64-normal-server-release/images/jdk/bin$ ./java -version
openjdk version "10-internal" 2018-03-20
OpenJDK Runtime Environment (build 10-internal+0-adhoc.ubuntu.zgc)
OpenJDK 64-Bit Server VM (build 10-internal+0-adhoc.ubuntu.zgc, mixed mode)

ZGC開発用のJava10がビルドされたのがわかります。

ZGCを使ってみる

100KB程度のオブジェクトを大量に生成するだけのプログラムを作りました。

import java.util.Arrays;

class Data {
    byte[] data;

    Data() {
        byte[] array = new byte[100000];
        Arrays.fill(array, (byte)1);
        this.data = array;
    }
}

public class Hello {
    public static void main(String[] args){
        System.out.println("########## START ##########");
        for(int i =0; i < 1000; i++) {
            new Data();
        }
        System.out.println("########## END ##########");
    }
}

コンパイル

ubuntu@java-z:~$ zgc/build/linux-x86_64-normal-server-release/jdk/bin/javac Hello.java

実行

-XX:+UseZGC をつけるとZGCが有効になります。-Xlog:gc* で詳細なGCのログが出ます。 ヒープサイズを最大100MBまでに制限して実行してみましょう。

ubuntu@java-z:~$ zgc/build/linux-x86_64-normal-server-release/jdk/bin/java -Xmx100M -XX:+UseZGC -Xlog:gc* Hello
[0.006s][info][gc,init] Initializing The Z Garbage Collector
[0.006s][info][gc,init] Version: 10-internal+0-adhoc.ubuntu.zgc (release)
[0.006s][info][gc,init] NUMA Support: Enabled
[0.006s][info][gc,init] NUMA Nodes: 2
[0.006s][info][gc,init] CPUs: 24 total, 24 available
[0.006s][info][gc,init] Memory: 48287M
[0.006s][info][gc,init] Large Page Support: Disabled
[0.006s][info][gc,init] Workers: 15 parallel, 3 concurrent
[0.010s][info][gc,init] Pre-touching: Disabled
[0.010s][info][gc,init] Pre-mapping: 100M
[0.035s][info][gc     ] Using The Z Garbage Collector
[0.187s][info][gc,start] GC(0) Garbage Collection (Warmup)

[略...]

[0.483s][info][gc,ref    ] GC(7) Clearing All Soft References
[0.483s][info][gc,start  ] GC(7) Garbage Collection (Allocation Stall)
[0.483s][info][gc,phases ] GC(7) Pause Mark Start 0.141ms
[0.485s][info][gc,phases ] GC(7) Concurrent Mark 2.015ms
[0.486s][info][gc,phases ] GC(7) Pause Mark End 0.667ms
[0.486s][info][gc,phases ] GC(7) Concurrent Process Non-Strong References 0.133ms
[0.486s][info][gc,phases ] GC(7) Concurrent Reset Relocation Set 0.054ms
[0.486s][info][gc,phases ] GC(7) Concurrent Destroy Detached Pages 0.001ms
[0.486s][info][gc        ] Allocation Stall (main) 3.605ms
[0.490s][info][gc,phases ] GC(7) Concurrent Select Relocation Set 3.574ms
[0.490s][info][gc,phases ] GC(7) Concurrent Prepare Relocation Set 0.289ms
[0.491s][info][gc,phases ] GC(7) Pause Relocate Start 0.519ms
[0.491s][info][gc        ] Allocation Stall (main) 1.226ms
[0.493s][info][gc,phases ] GC(7) Concurrent Relocate 1.953ms
[0.493s][info][gc,load   ] GC(7) Load: 0.61/0.70/0.51
[0.493s][info][gc,mmu    ] GC(7) MMU: 2ms/16.2%, 5ms/52.6%, 10ms/75.0%, 20ms/84.0%, 50ms/88.6%, 100ms/93.2%
[0.493s][info][gc,marking] GC(7) Mark: 8 stripe(s), 1 proactive flush(es), 1 terminate flush(es), 0 completion(s), 0 continuation(s)
[0.493s][info][gc,reloc  ] GC(7) Relocation: Successful, 1M relocated
[0.493s][info][gc,nmethod] GC(7) NMethods: 128 registered, 0 unregistered
[0.493s][info][gc,ref    ] GC(7) Soft: 153 encountered, 19 discovered, 19 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Weak: 147 encountered, 78 discovered, 78 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Final: 0 encountered, 0 discovered, 0 dropped, 0 enqueued
[0.493s][info][gc,ref    ] GC(7) Phantom: 3 encountered, 3 discovered, 3 dropped, 0 enqueued
[0.493s][info][gc,heap   ] GC(7)                Mark Start          Mark End        Relocate Start      Relocate End           High               Low
[0.493s][info][gc,heap   ] GC(7)  Capacity:      100M (100%)        100M (100%)        100M (100%)        100M (100%)        100M (100%)        100M (100%)
[0.493s][info][gc,heap   ] GC(7)   Reserve:       62M (62%)          62M (62%)          62M (62%)          62M (62%)          44M (44%)          62M (62%)
[0.493s][info][gc,heap   ] GC(7)      Free:        0M (0%)            0M (0%)            0M (0%)            0M (0%)           10M (10%)           0M (0%)
[0.493s][info][gc,heap   ] GC(7)      Used:       38M (38%)          38M (38%)          38M (38%)          38M (38%)          56M (56%)          28M (28%)
[0.493s][info][gc,heap   ] GC(7)      Live:         -                 1M (2%)            1M (2%)            1M (2%)             -                  -
[0.493s][info][gc,heap   ] GC(7) Allocated:         -                 0M (0%)           12M (12%)          38M (38%)            -                  -
[0.493s][info][gc,heap   ] GC(7)   Garbage:         -                36M (36%)          24M (24%)          16M (16%)            -                  -
[0.493s][info][gc,heap   ] GC(7) Reclaimed:         -                  -                12M (12%)          20M (20%)            -                  -
[0.493s][info][gc        ] GC(7) Garbage Collection (Allocation Stall) 38M(38%)->38M(38%)

[略...]

[0.528s][info][gc,heap,exit] Heap
[0.528s][info][gc,heap,exit]  ZHeap           used 30M, capacity 100M, max capacity 100M
[0.528s][info][gc,heap,exit]  Metaspace       used 6395K, capacity 6442K, committed 6656K, reserved 8192K

こんな感じでGCのログが出ました。これでZGCをベンチマーク取ったりして試して遊べますね!

サーバの大量構築をした事がある学生を80人養成した

この記事はwhywaitaアドベントカレンダー21日目の記事です。 タイトルは言い過ぎかもしれません。
2年ぶりの参加です。前回は雑すぎてごめんよ (参考)

ICTトラブルシューティングコンテストという参加者も運営も学生が行うインフラ系のコンテストがあります。学生は本戦の問題作成および競技ネットワーク等の設計で忙しいので予選問題はお手伝いをしてる社会人(僕含め)で作りました。9問出題されましたが、この記事はそのうちの1問の紹介です。

ここでサーバを100台構築させる問題を出題させ、何かしらの構築を効率化できる手法を用いたチームが25チーム中16チームいて、1チーム5名なので80人の学生がサーバの大量構築をしたことがあるということになります。(難易度は別として)

問題

問題文は長いのでこちらに貼っておきます。伏せる内容もないので全文公開です。

【公開用】ICTSC9予選 問1 · GitHub

内容を簡単に言うと、

  • sshして、nginxをインストールしてもらうまでのチュートリアル
  • 同じサーバでnginxに固定のJSONを返すための設定を投入してもらう (問 1-1)
  • 同様な構成で100台構築してもらう (問 1-2)

となっていてチュートリアル的にnginxをインストールしつつ、ちょこっと設定等を変えてもらい、それを100台のサーバに対して展開してもらうという問題構成です。 問1として出題したので、Linuxのサーバにsshしてパッケージをインストールするという操作にあまり慣れていない学生でも点数を取れる難易度を目指しました。結果、0点のチームは居ませんでした!

学生のうちに大量のサーバにsshして構築する経験をした学生は居ないだろうという想像のもと、100台構築してもらうことにしました。100台というのはきりがよかったのと、手動でコマンドを打つのは諦めたくなるくらいにしたかったという意図があります。 何かしらの方法で構築を効率化/自動化できたチームは高得点で、手動でやっていたチームは10台くらい構築して時間の無駄だと思ったのか、諦めた形跡がありました。

出題環境

予備も含めるとサーバを3,200ホスト用意しなければなりません。予選環境としてさくらインターネット様にさくらのクラウドを提供して頂いたのですが、流石にこれだけのために3,200ホストを実際に建てるわけにはいきません。ただ大量のホストは欲しい…そこでLXDです!!Dockerじゃないです。LXDですよ。 LXDはLinux Containerを使ってコンテナの中でinitプロセスが立ち上がり、普通のサーバマシンのように振る舞います。Dockerがアプリケーションコンテナであれば、LXDはシステムコンテナです。

Linux Containers - LXD - イントロダクション

LXDで大量のホスト(コンテナ)を運用する上でいろいろつまづきましたがそれは別記事で紹介します。当初はCPU36コア, メモリ224GBの仮想マシンの上で1,500ホスト(コンテナ)稼働させるのにチャレンジするつもりで構築していましたが、本番を見据えた構成でホストを生成していったところ、1,300コンテナあたりでcgroupが壊れてしまいました。各ホストには1CPU, メモリ256MBの環境となるように制限を掛けていたのですが、途中から新しいcgroupが作れなくなりました。ロードアベレージもアイドル状態で600くらいだったのでプロセス数が異常に増えている環境だとかなり厳しい状態になるということがわかりました。

ubuntu@s01:~$ sudo mkdir /sys/fs/cgroup/memory/test
mkdir: cannot create directory '/sys/fs/cgroup/memory/test': Cannot allocate memory

試しに手動でcgroupを作ってみてもこんな感じで、さらにsyslogを見るとよくわからないエラーが…

Dec 14 05:57:38 s01 kernel: [14972.817859] BUG: unable to handle kernel NULL pointer dereference at           (null)
Dec 14 05:57:38 s01 kernel: [14972.819204] IP: [<          (null)>]           (null)
Dec 14 05:57:38 s01 kernel: [14972.820514] PGD 0
Dec 14 05:57:38 s01 kernel: [14972.820584] Oops: 0010 [#2] SMP
[略]
Dec 14 05:57:38 s01 kernel: [14972.836239] Fixing recursive fault but reboot is needed!
Dec 14 08:18:02 s01 kernel: [23396.544849] htb: too many events!

徹夜に近い状態で準備をしていたのであきらめてCPU16コア、メモリ196GBの仮想マシンを4台つかって、1台あたり800ホスト(コンテナ)、4台で3200ホスト(コンテナ)ということになります。

4台分の監視は普段業務で使っていて慣れているのでDatadogを使いました。 f:id:kuro_m88:20171220133022p:plain

CPUとメモリとロードアベレージとiowaitとネットワークトラフィックくらいしか監視していませんでしたが。メモリもだいぶ余裕があったのでZFSの重複排除機能をONにしていました。結果は…

ubuntu@s02:~$ sudo zpool list
NAME       SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
lxd-pool   248G  3.11G   245G         -     5%     1%  63.49x  ONLINE  -

1ホストあたり 256MB x 800ホスト ≒ 200GBのディスク容量消費で見積もっていたのですが、結果的には重複排除が63.49倍効いた結果3.11GBしか消費しませんでした。同じ環境で全員が同じようなことをするので当たり前といえば当たり前ですが、重複排除すごいですね。

CPUとメモリは検証結果からそこまで心配していなくて、ディスクとネットワークを心配していたのですが、実際ネットワークは本番競技中に一瞬ひやっとしました。とあるチームが100台同時に構築するスクリプトを回したようで、一瞬440Mbpsほど帯域を使いました。1Gbpsで張り付いたらどうしようと思っていたのですが、これ以上帯域を使うことはなく、一瞬で落ち着きました。 最終的にトラブルもなく環境を提供できたのは良い経験になりました。

出題してみてどうだったか

無事100台構築できたチームは、1チームを除き、踏み台サーバからシェルスクリプトを使ってsshをして遠隔でコマンドを投入する方式でした。プロビジョニングツールを使うほどの作業内容でもなさそう & コンテストなのでとりあえず動けばいいということでみんなシェルスクリプトかなと想像していました。

一番シンプルだった解答はこんな感じです。

export SSHPASS=password
for (( i=2; $i <= 100; i++ )); do
  target=`printf "teamxx-c%03d" $i`
  sshpass -e ssh -oStrictHostKeyChecking=no $target 'sudo apt install nginx -y && echo "{\"hostname\": \"`hostname`\"}" | sudo tee /var/www/html/hostname.json'
done

ちなみに芸術点があるのではないかという参加者の声を聞きましたが、芸術点とかはなく、機械的に全台に対してリクエストを投げて点数をモニタリングしていました。構築が終わる前に構築が終わったという報告をしてもすぐに分かる状況になっていたということです。(実際にそういうチームがあったかは触れません)常に監視して採点をしていたので、構築完了の報告を受けたらそのホストに入って手順の報告と全く違う構築方法をしていないか調べていました。

1チームだけansibleを使ってplaybookを書いてきちんとプロビジョニングをしているチームがありました。時間がない中ansibleを使ったということは普段から使い慣れていたのでしょう。素晴らしい。

感想

徹夜して急いで作成した問題にしてはいい感じの問題が出題できたのではないかと思っています。 予選なのでトップ層の点差がつかない事は気にしておらず、予選落ちするチームが勉強になる/楽しんで貰える問題を出したいなと思っていました。本戦は順位を決めるための勝負です。優秀なトラコン運営学生が問題と競技ネットワークの設計を練っているので期待です。 競技用ネットワークを設計している学生はこんなことを言っています。

f:id:kuro_m88:20171220133734p:plain

前回はCLOSトポロジやVXLAN、BGP MPLSにチャレンジしていましたね。

www.slideshare.net

今回はどんなネットワークなのでしょうか。クラウドもOpenStackを卒業して自作しているみたいですね。社会人から見ても恐ろしいです。

ということで、予選を突破したチームの皆さんは本戦で日頃の技術研鑽の結果を見せつけて下さい! 予選に落ちてしまったチームはまたの挑戦をお待ちしております!

Slackで寿司を回転させる技術

こんなツイートをしたらいいねが1000件以上ついたのでやり方を紹介します。

きっかけ

以前Slackで絵文字を回転させる方法を紹介しましたが、もっとちゃんと寿司を回転させたくなりました。

kurochan-note.hatenablog.jp

画像生成

今回もImage Magickを使います。Mac上で生成しました。

絵文字を3行3列の9個順番に並べるといい感じに回っているように見えるのですが、中心は冒頭で紹介した記事で絵文字を回転させてください。今回は中心以外の8種類の画像を生成します。

出来上がったものはこちらです。

f:id:kuro_m88:20170918183136g:plainf:id:kuro_m88:20170918183152g:plainf:id:kuro_m88:20170918183156g:plainf:id:kuro_m88:20170918183202g:plainf:id:kuro_m88:20170918183210g:plainf:id:kuro_m88:20170918183214g:plainf:id:kuro_m88:20170918183218g:plainf:id:kuro_m88:20170918183221g:plain

スクリプトはこんな感じです。最後のgifアニメ生成時の -colors 80 -fuzz 11% というオプションはSlackの絵文字のファイルサイズの64KB制限に引っかからないように画質を手でパラメータ調整した結果です。もしオーバーするようであれば値をいじってみてください。

gist.github.com

もちろん寿司以外にも使えます。いろんな絵文字を回していきましょう。寿司を静止させられるように明日から頑張ります。

DataDogの監視設定からTerraformのresourceを生成する

作ったものはこれ。

github.com

やりたいこと

TerraformのDataDog Providerを使って監視ルールをTerraformで管理したい。resource定義さえ書けば監視ルールがTerraformで管理されるようになります。 参考: Datadog: datadog_monitor - Terraform by HashiCorp

ただ、 query = "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2" のようなクエリを書くのは厳しいので、テンプレート化する前はDataDogのWeb UIでルールを作成したいですよね。 そこで既存の監視設定を取り込んでTerraformの設定を生成するようにしました。

Terraform importとどう違うのか

Terraformには terrafrom import という便利なコマンドがあり、既存のterraform管理されていないリソースをterraform管理下に置くことができます。しかしながら、以下の記事にも書いてあるとおり、 tfstate ファイルに状態として取り込まれるだけで、 設定を/構成を管理する tf ファイルには反映されません。

dev.classmethod.jp

使い方

詳細はREADMEに書きました。

datadog_monitor2terraform/README.md at master · kurochan/datadog_monitor2terraform · GitHub

ただのrubyスクリプトなので

$ ruby ./monitor-import.rb dynamodb_user_error_count 112233

resource "datadog_monitor" "dynamodb_user_error_count" {
  name               = "DynamoDB UserError count is above the Threshold !!"
  type               = "metric alert"
  message            = <<EOF
@slack-metric-alert DynamoDB UserError count is above the Threshold !!
EOF
  query = "sum(last_5m):sum:aws.dynamodb.user_errors{*} > 10"
  thresholds {
    warning = 5.0
    critical = 10.0
  }
  notify_no_data = false
  no_data_timeframe = 2
  renotify_interval = 0
  timeout_h = 0
  require_full_window = true
  notify_audit = false
  tags = []
}

という感じで引数にterraformで管理するリソースの名前と、DataDog上での対象のIDを指定するとTerraformのresourceが生成されます。 これで監視ルールの生成と管理が楽にできますね!