およべさんとみんなとチーム開発の現場について雑談したー

##「およべさんと話をしたいなー」

「いいね」「どうせだから一般公開してやろうかー」ってところから始まったイベントで雑談してきた。

connpass.com

## みんなが主催者

みんなで色々とネタ出しをして、議題を決めて、わーって意見交換。

チームの育て方や、モブワークなどについて、雑談。

## すっきりしたー

終わった後は情報過多で頭の整理をしきれてない感じだった。↓同意。

でも、色々話が聞けて、話ができて、すっきりしたー。

## 確かにそうだな

あの場で話し合ったこと自体も良かったし、それに加えて最後におよべさんが言ってたのが確かにそうだなって思った。

「こういうことを話すことができる人たちと出会えたのが良かった」

## ありがとう

みんなと雑談できて色んな悩みや意見が聞けて楽しかった!

僕の話を聞いて「試してみようと思った」って言ってくれた人もいたし。嬉しい。

あと、あらた(@aratafuji)声でかいw(来てくれてありがとう)

また会おうねー。

怖くないR2DBC

注意:R2DBCはまだ本番では使わないようにね(2018-11-18現在)

昨日R2DBCを初めて触ってみて「ふーん、いまいちよく分かってないなぁ」と思いながら寝たんだけど。

bufferings.hatenablog.com

朝起きたら「あ、そういうことか」ってなったので、考えの整理。

## 全体概要

f:id:bufferings:20181118081104p:plain

実装で気にするのはConnectionFactoryを生成する部分くらいで、あとはr2dbc-spiを見れば良さそう。

r2dbc-spiは各RDBMSに対して実装しやすいようにシンプルなインターフェースになってるのでこれを直接使うとしんどい。利用者としてはこれを直接触るのではなく、r2dbc-clientみたいに人にとって使いやすい形にラッピングしてくれるやつを使う。

r2dbc-spiはSpring Frameworkとは全然関係なくて、reactive-streamsだけに依存しててPublisherを返す。r2dbc-clientやspring-data-r2dbcはMonoFluxを使ってる。

## 今日の予定

  1. PostgresqlConnectionFactoryを見てみる
  2. r2dbc-spiを見てみる

r2dbc-clientとspring-data-r2dbcはまた今度見る。

## 1. PostgresqlConnectionFactoryを見てみる

https://github.com/r2dbc/r2dbc-postgresql/blob/v1.0.0.M5/src/main/java/io/r2dbc/postgresql/PostgresqlConnectionFactory.java

PostgresqlConnectionConfigurationを受け取ってインスタンスを生成。

    public PostgresqlConnectionFactory(PostgresqlConnectionConfiguration configuration) {
        this(Mono.defer(() -> {
            Objects.requireNonNull(configuration, "configuration must not be null");

            return ReactorNettyClient.connect(configuration.getHost(), configuration.getPort()).cast(Client.class);
        }), configuration);
    }

    PostgresqlConnectionFactory(Mono<? extends Client> clientFactory, PostgresqlConnectionConfiguration configuration) {
        this.clientFactory = Objects.requireNonNull(clientFactory, "clientFactory must not be null");
        this.configuration = Objects.requireNonNull(configuration, "configuration must not be null");
    }

ClientFactoryMonoになってて、Connectionの生成部分ではMonoを返してる。

    @Override
    public Mono<PostgresqlConnection> create() {
        return this.clientFactory
            .delayUntil(client ->
                StartupMessageFlow
                    .exchange(this.configuration.getApplicationName(), this::getAuthenticationHandler, client, this.configuration.getDatabase().orElse(null), this.configuration.getUsername())
                    .handle(PostgresqlServerErrorException::handleErrorResponse))
            .map(client -> new PostgresqlConnection(client, new DefaultCodecs(client.getByteBufAllocator()), DefaultPortalNameSupplier.INSTANCE, new IndefiniteStatementCache(client)));
    }

へー。こんな風に書くのか(まだリアクティブ周りよく分かってない。勉強しなきゃ。

あ、そういえば、r2dbc-postgresqlPublisherじゃなくてMonoFlux使ってるのか。ふーん。

### 使い方

使い方はr2dbc-clientのREADMEにこう書いてる。

PostgresqlConnectionConfiguration configuration = PostgresqlConnectionConfiguration.builder()
    .host("<host>")
    .database("<database>")
    .username("<username>")
    .password("<password>")
    .build();

R2dbc r2dbc = new R2dbc(new PostgresqlConnectionFactory(configuration));

このR2dbcはr2dbc-client側のクラスなんだけど、ConnectionFactoryを受け取って中で必要に応じてコネクションを生成して処理するんだろうな。

## 2. r2dbc-spiを見てみる

https://github.com/r2dbc/r2dbc-spi/tree/v1.0.0.M5/r2dbc-spi/src/main/java/io/r2dbc/spi

シンプル。だいたいこんな感じか。

f:id:bufferings:20181118091943p:plain

ConnectionStatementPublisherを返してる。

それとこの図には書いてないけどStatementと同じ並びにBatchインターフェースがある。

### Result

Resultには2つのメソッドがあって

    Publisher<Integer> getRowsUpdated();

    <T> Publisher<T> map(BiFunction<Row, RowMetadata, ? extends T> f);

getRowsUpdatedは更新された行数をPublisherで返す。

mapRow (とそのメタデータ)を受け取って、自分の好きな型に変換してPublisherで返す。

### 使い方

直接使うとこんな感じになる。r2dbc-postgresqlMonoFluxを使ってるからこんな感じに書けるけど、もし純粋にr2dbc-spiに対して書くならPublisherを使わなきゃだから、もうちょっとごちゃっとしそう?

  private Flux<String> sample() {
    var connectionFactory = getPostgresqlConnectionFactory();
    return connectionFactory.create()
        .flatMapMany(connection ->
            connection.createStatement("SELECT city FROM weather")
                .execute()
                .flatMap(result ->
                    result.map((row, metadata) ->
                        row.get("city", String.class)
                    )
                )
        );
  }

PostgresqlConnectionFactorycreate()で生成したMono<PostgresqlConnection>から結果のFluxを返すためにflatMapManyを使用。

コネクションからステートメントを生成して実行。

その結果のFlux<PostgresqlResult>に対するflatMapRowからStringを生成してFlux<String>返してる。

最初にも書いた通り、実際にはこれを直接使うんじゃなくて、r2dbc-clientのようなラッパーを使う。

ふむふむ。ちょっと理解が進んだ。

Hello R2DBC

## R2DBC?

https://r2dbc.io/

Spring One Platform 2018で発表されたリレーショナルデータベース用のリアクティブプログラミングAPI。みんなが待ってるやつ。

2016年頃からのSpring5を中心にしてWebやNoSQL周りはリアクティブ対応をしてきてるんだけど、RDBMSJDBCブロッキングなので対応できてなくて、そこが必要よなーってみんな待ってる。

R2DBCのビデオ見たんだけど本番では絶対使わないでねって言ってた。んで、R2DBCのゴールはADBA (Asynchronous Database Access API)のSpecに良い影響を与えること。ほほー。

## 今日の目標

ということで、現時点でPostgreSQL用のr2dbc-postgresqlがあるから、今日はそれでSelectくらいやってみようかな。Repositoryは作らずに直接SQL書く感じで。

コードはここ。

https://github.com/bufferings/hello-r2dbc/tree/20181117

## 注意点

WebFluxのMonoやFluxの扱いも、R2DBCの扱いも、全然良くわかってないので間違ったこと書いてるかも。ここで書いてることを鵜呑みにせずに自分で確かめてください。

## PostgreSQLの用意

Docker ComposeでPostgreSQLを用意。

version: "2"

services:
  db:
    image: postgres:11-alpine
    environment:
      POSTGRES_PASSWORD: mysecretpassword 
    ports:
      - 5432:5432
    volumes:
      - ./initdb.d:/docker-entrypoint-initdb.d

初期データはこんな感じで適当にPostgreSQLのTutorial (https://www.postgresql.org/docs/11/tutorial-table.html)から持ってきた。

CREATE TABLE weather (
    city            varchar(80),
    temp_lo         int,           -- low temperature
    temp_hi         int,           -- high temperature
    prcp            real,          -- precipitation
    date            date
);

CREATE TABLE cities (
    name            varchar(80),
    location        point
);

INSERT INTO weather (city, temp_lo, temp_hi, prcp, date) VALUES 
  ('San Francisco', 46, 50, 0.25, '1994-11-26'),
  ('San Francisco', 46, 50, 0.25, '1994-11-27'),
  ('San Francisco', 46, 50, 0.25, '1994-11-28'),
  ('San Francisco', 46, 50, 0.25, '1994-11-29');

## pom.xml

さて。SpringBoot 2.1.0.RELEASE + Reactive Webでプロジェクトを作って、そこにR2DBCのDependencyを追加。r2dbc-clientも使いたかったので追加。

        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
            <version>1.0.0.BUILD-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.r2dbc</groupId>
            <artifactId>r2dbc-client</artifactId>
            <version>1.0.0.BUILD-SNAPSHOT</version>
        </dependency>

SnapshotとかMilestoneバージョンを使うからリポジトリーも追加。

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

ConnectionFactory

こんな感じっぽい。

  private PostgresqlConnectionFactory getPostgresqlConnectionFactory() {
    var configuration = PostgresqlConnectionConfiguration.builder()
        .host("localhost")
        .database("postgres")
        .username("postgres")
        .password("mysecretpassword")
        .build();
    return new PostgresqlConnectionFactory(configuration);
  }

このFactoryから直接ごにょごにょやってSQLを発行してもいいんだけど、もうちょっと分かりやすく書けるR2dbcっていうクライアントがあるからそっちを使ってみた。

  private Flux<String> hello() {
    var connectionFactory = getPostgresqlConnectionFactory();
    var r2dbc = new R2dbc(connectionFactory);
    return r2dbc.inTransaction(h ->
        h.select("SELECT city, temp_lo, temp_hi, prcp, date FROM weather")
            .mapRow(row -> row.get("city", String.class)));
  }

んで、それをマッピング

  @Bean
  RouterFunction<ServerResponse> getRoute() {
    return route(GET("/"),
        req -> ok().body(hello(), String.class));
  }

## 実行

(cd db; docker-compose up -d)
Creating network "db_default" with the default driver
Creating db_db_1_d69d7879eaa0 ... done(cd demo-webflux-r2dbc; ./mvnw spring-boot:run)
(中略)
2018-11-17 20:59:32.474  INFO 25588 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2018-11-17 20:59:32.479  INFO 25588 --- [           main] c.e.d.DemoWebfluxR2dbcApplication        : Started DemoWebfluxR2dbcApplication in 2.047 seconds (JVM running for 5.796)

叩いてみる。

❯ curl localhost:8080
San FranciscoSan FranciscoSan FranciscoSan Francisco

んー。とりあえず動いてるっぽい。満足。

## TODO

そもそも、MonoとFluxの使い方をそろそろちゃんと勉強しておいたほうが良さそう。それからr2dbc-clientやspring-data-r2dbcあたりを触りたいなー。

Java 8でも安心。Dockerに対するCPU・メモリ対応。(2018年11月現在)

8u191でDocker対応がバックポートされたので、頭の整理と確認をしておいた。

## まとめ

Java 11使っておけばそもそも安心なんだけど、Java 8でも8u191以降を使えば安心

## 課題だったこと

DockerでJavaを動かすときJavaが「そのコンテナに割り当てられたCPU・メモリ」じゃなくて「Dockerが動いてるHostのCPU・メモリ」を見てしまうことが課題だった。

## Java 10以降

Java 10以降なら「そのコンテナに割り当てられたCPU・メモリ」を見る対応が入ってるから安心になった。

## Java 8は?

Java 8で入ってた対応は8u131のこれ:

詳しくはここに書いてある:

メモリ用のオプションが追加されたので、それをつければメモリの認識は大丈夫。CPUは一部対応。という話。

そのメモリ用のオプションは将来的につけなくても大丈夫にする予定だったからExperimental扱い(そして実際にJava 10以降でも8でもつけなくてよくなった)。という感じ。

## 8u191

そして、Java 10の対応が8u191でバックポートされたのでJava 8でも8u191以降なら安心になった。今の最新は8u192かな。

(∩´∀`)∩ワーイ

## 追加されてるオプション

  • -XX:-UseContainerSupportをつけるとこのコンテナサポートをオフにできる。
  • -XX:ActiveProcessorCount=countを指定すると自分でプロセッサーカウントを設定できる。

## 参照

## 確かめる

てことで確かめてみる。いつも通り、確認したいこととは関係ないところで色々勉強になったや。

  • 8u181
    • 制限なしでどうなるかを見ておく(ベースライン)
    • コンテナ制限を見てないことを確認
    • メモリオプションつけたらメモリは認識されることを確認
  • 8u192
    • 制限なしでどうなるかを見ておく(ベースライン)
    • コンテナ制限を見てることを確認
  • 11 (11.0.1.13)
    • 制限なしでどうなるかを見ておく(ベースライン)
    • コンテナ制限を見てることを確認

くらいでいいかな。8u191以降のOpenJDKのDockerイメージはまだ出てないみたいなので、AdoptOpenJDKで確認することにした。初めて使う!

実行環境はこんな感じ

❯ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   9ee9f40
 Built:        Thu Apr 26 07:17:38 2018
 OS/Arch:      linux/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   9ee9f40
  Built:        Thu Apr 26 07:15:45 2018
  OS/Arch:      linux/amd64
  Experimental: false

❯ docker-compose version
docker-compose version 1.23.1, build b02f1306
docker-py version: 3.5.0
CPython version: 3.6.7
OpenSSL version: OpenSSL 1.1.0f  25 May 2017

## 実行する内容

これを実行する。こんなところ気にしたことなかったからavailableProcessorsとかmaxMemoryとか初めて使った。バージョンはRuntime.Versionってのがあったから使おうとしたら導入されたのがJava 9やったから使えんかったw

public class A {
  public static void main(String[] args) {
    System.out.println("Version: " + System.getProperty("java.version"));
    System.out.println("availableProcessors: " + Runtime.getRuntime().availableProcessors());
    System.out.println("maxMemory: " + Runtime.getRuntime().maxMemory()/1024/1024 + "M");
  }
}

## 設定周り

docker直接でやっても良かったんだけど、docker-composeでごにょごにょやった。

# use v2 because v3 stop supporting cpu and memory options.
version: "2.4"

x-common: &common
  command: sh -c "javac A.java -d /tmp && cd /tmp && java A"
  environment:
    JAVA_TOOL_OPTIONS: ""
  working_dir: /work
  volumes:
    - .:/work

services:

  # jdk8u181
  jdk8u181-base: &jdk8u181-base
    image: adoptopenjdk/openjdk8:jdk8u181-b13-alpine
    <<: *common
  jdk8u181-limit: &jdk8u181-limit
    <<: *jdk8u181-base
    cpus: 2
    mem_limit: 512m
  jdk8u181-limit-option:
    <<: *jdk8u181-limit
    environment:
      JAVA_TOOL_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"

  # jdk8u192
  jdk8u192-base: &jdk8u192-base
    image: adoptopenjdk/openjdk8:jdk8u192-b12-alpine
    <<: *common
  jdk8u192-limit:
    <<: *jdk8u192-base
    cpus: 2
    mem_limit: 512m

  # jdk-11
  jdk-11-base: &jdk-11-base
    image: adoptopenjdk/openjdk11:jdk-11.0.1.13-alpine
    <<: *common
  jdk-11-limit:
    <<: *jdk-11-base
    cpus: 2
    mem_limit: 512m

### Compose file v2を使ってる理由

v3ではcpusmem_limit周りの機能が削除されてるから。悲しみ。

docker-compose におけるメモリ使用量の制限方法 - reboooot.net

結局、v3はSwarm Mode用だから、それ使わんならv2使いなよってことらしい。Docker社っぽいやり方だなw

How to specify Memory & CPU limit in version 3 · Issue #4513 · docker/compose · GitHub

### YAMLのAnchorについて

YAMLのAnchorはこの辺を参考に:

Don’t Repeat Yourself with Anchors, Aliases and Extensions in Docker Compose Files

Compose fileのExtension fields (x-)についてはここを参考に:

Compose file version 2 reference | Docker Documentation

今回は遊びだから適当に使ったけど、実際に仕事とかで使うときは「DRYにしすぎて意味がわからない」ってならないようにしたいね。

### JAVA_TOOL_OPTIONS

AdoptOpenJDKのイメージにはJAVA_TOOL_OPTIONSが設定されてるので、今回の検証ではそれを一旦空にしてから動かしてる。

@sugarlifeさんありがとうございます!OpenJDKのソースコード初めて読んだw

ちなみに設定されている値はこんな感じ。勉強になるー。

javacとかjavaを実行したときに毎回ログに出るからうざいなと思って消せないか探してみたけど

[JDK-8039152] Need a way to suppress message when picking up JAVA_TOOL_OPTIONS - Java Bug System

This will not be implemented, as it may introduce a vulnerability

ってことで脆弱性につながるから実装しないってことみたい。申し訳ございませんでした。納得。

## 実行スクリプト

docker-compose upするとログが混ざるから、こんなスクリプトを用意した。

#!/bin/bash

list=(
  jdk8u181-base
  jdk8u181-limit
  jdk8u181-limit-option
  jdk8u192-base
  jdk8u192-limit
  jdk-11-base
  jdk-11-limit
)

for item in ${list[@]}; do
  echo "# $item"
  docker-compose run --rm $item
  echo ""
done

## 結果

ちなみに、JAVA_TOOL_OPTIONSが2回出てるのはjavacの分とjavaの分。

❯ ./check.sh                                                                                                                                                                                           
# jdk8u181-base                                                                                                                                                                                        
Picked up JAVA_TOOL_OPTIONS:                                                                                                                                                                           
Picked up JAVA_TOOL_OPTIONS:
Version: 1.8.0_181
availableProcessors: 4
maxMemory: 3504M

# jdk8u181-limit
Picked up JAVA_TOOL_OPTIONS: 
Picked up JAVA_TOOL_OPTIONS: 
Version: 1.8.0_181
availableProcessors: 4
maxMemory: 3504M

# jdk8u181-limit-option
Picked up JAVA_TOOL_OPTIONS: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
Picked up JAVA_TOOL_OPTIONS: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
Version: 1.8.0_181
availableProcessors: 4
maxMemory: 114M

# jdk8u192-base
Picked up JAVA_TOOL_OPTIONS: 
Picked up JAVA_TOOL_OPTIONS: 
Version: 1.8.0_192
availableProcessors: 4
maxMemory: 3504M

# jdk8u192-limit
Picked up JAVA_TOOL_OPTIONS: 
Picked up JAVA_TOOL_OPTIONS: 
Version: 1.8.0_192
availableProcessors: 2
maxMemory: 123M

# jdk-11-base
Picked up JAVA_TOOL_OPTIONS: 
Picked up JAVA_TOOL_OPTIONS: 
Version: 11.0.1
availableProcessors: 4
maxMemory: 3942M

# jdk-11-limit
Picked up JAVA_TOOL_OPTIONS: 
Picked up JAVA_TOOL_OPTIONS: 
Version: 11.0.1
availableProcessors: 2
maxMemory: 123M

想定通りの結果だね。今日のソースコードはここ。

github.com

## 本筋とは関係ないところで勉強になったこと

  • AdoptOpenJDK使った
  • availableProcessorsmaxMemoryを知った
  • Runtime.VersionJava 9からだと知った(以前に一度確認したことがあったような気がしなくもない
  • JAVA_TOOL_OPTIONSの存在を知った
  • OpenJDKのソースコード初めて読んだ
  • JAVA_TOOL_OPTIONSが使われてることをログから消すことはできないってことを知った
  • Compose fileのCPU・メモリ制限がv2までで、v3はSwarm Modeサポート用だと知った
  • YAMLのAnchorについて勉強しなおした
  • Compose fileのExtension fieldsの存在を知った

面白かったー(∩´∀`)∩ワーイ

Spring Bootの起動を速くしてみるぞー

疲れた!

## How Fast is Spring?

というSpring One Platformのセッションのビデオを見て、自分でも試してみたお話。このセッション、すごく面白いのでおすすめ。

springoneplatform.io

## 今日のコード

github.com

↓JDK11でやった。

❯ java --version
openjdk 11.0.1 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)

↓こんな感じで実行できる。ベンチマークとってるので、結構時間かかる。

❯ ./mvnw clean package
❯ (cd benchmarks/; java -jar target/benchmarks.jar)

## これがやりたくて

これがやりたくて、Spring Boot Thin Launcherとか

JMHを勉強してたのだった。

いざ!仕上げ!

## 1. FluxBaseline

↓SpringInitializrでReactive Webだけ選んで、WebMVCスタイルでちょこっと書いただけのプロジェクト。

@SpringBootApplication
@RestController
public class DemoApplication {

  @GetMapping("/")
  public String home() {
    return "Hello";
  }

  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

↓Spring Bootのバージョンは2.1.0.RELEASE。

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

↓このアプリの起動時間は2.938 ± 0.287 s/opだった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op

ここから(Daveがセッションで言ってたことを参考にしたりしながら)色々やってみることにする。

## 2. Webとの比較

↓WebFluxじゃなくて、Webだとどうなんだろう?と思ったので比較。今回のサンプルプロジェクトでは、主にTomcatとNettyの起動時間の比較になってしまうのかな?

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case02_Web                               ss   10  3.281 ± 0.342   s/op

Fluxの方がちょっと速いね。

## 3. spring-context-indexer

次は、コンポーネントインデックスを作ってくれるというspring-context-indexerを使ってみた。

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-indexer</artifactId>
            <optional>true</optional>
        </dependency>

↓お?ちょっと遅くなった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case03_WithContextIndexer                ss   10  3.063 ± 0.102   s/op

↓spring.componentsを見てみたら、1個だけしか入ってなかった。そうか、これはもっと大きなプロジェクトで試してみないと分かんなさそう。

#
#Sun Nov 04 18:42:59 JST 2018
com.example.DemoApplication=org.springframework.stereotype.Component

## 4. Lazy Initialization

Lazy Initしてみた。

@Configuration
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      beanFactory.getBeanDefinition(beanName).setLazyInit(true);
    }
  }
}

↓結果はこう。ほんのちょっと速くなったかな。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case04_WithLazyInit                      ss   10  2.844 ± 0.129   s/op

## 5. NoVerify

-noverifyをつけて実行したら

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case05_WithNoVerifyOption                ss   10  2.582 ± 0.060   s/op

ちょっと速くなったー。オプションの意味分かってないから、今度調べる。

## 6. TieredStopAtLevel

-XX:TieredStopAtLevel=1をつけて実行したら

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option      ss   10  1.980 ± 0.037   s/op

うぉー。だいぶ速くなったなー。2秒切った。でも、これも意味分かってないから、今度調べる。

## 7. SpringConfigLocationを明示的に指定

-Dspring.config.location=classpath:/application.propertiesをつけて実行。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case07_WithSpringConfigLocationOption    ss   10  3.026 ± 0.139   s/op

あれ、遅くなっちゃった。

## 8. JMXをOFFに

-Dspring.jmx.enabled=falseをつけて実行すると

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case08_WithJmxDisabledOption             ss   10  2.877 ± 0.097   s/op

ちょっとだけ速くなったのかな。

## 9. Logbackを除外

こっから先はライブラリーを外していく、まずはLogback

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
        </dependency>

結果はこうなった。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case09_WithoutLogback                    ss   10  2.904 ± 0.096   s/op

んー。気持ちだけ?

## 10. Jacksonを除外

次はJackson

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-json</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

結果は

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case10_WithoutJackson                    ss   10  2.789 ± 0.093   s/op

ちょっとだけ速くなった。

## 11. HibernateValidatorを除去

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>hibernate-validator</artifactId>
                    <groupId>org.hibernate.validator</groupId>
                </exclusion>
            </exclusions>
        </dependency>

結果はこう

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case11_WithoutHibernateValidator         ss   10  2.857 ± 0.084   s/op

まぁこれも気持ちだけ。ライブラリー除去はこれくらい。

## 12. AppCDS

Oracle JDKの商用機能としてあったAppCDS(Application Class Data Sharing)という機能が、OpenJDK 10から使えるようになってる。共有アーカイブに情報をダンプしておいて、それを使うので起動が速くなるらしい。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case12_WithAppCds                        ss   10  2.957 ± 0.079   s/op

んー。速くなってないなぁ。って、CDS自体をぼーっと調べてたら、みけさんの記事を見つけて

mike-neck.hatenadiary.com

おー!そういうことかーってなった。SpringBootのFatJARだと中のライブラリーがCDSの対象になってないのか。

## 13. Flux with Thin Launcher

あぁ、ベンチマークの名前Explodedのままだった・・・。最初はThin Launcherを使わずに、単純にFatJARを解凍して使おうと思ったんだけど、それだと結局CDSが使えなかったので、Thin Launcherで実行することにしたからExplodedって言う名前になってるす。

まずは、CDSを使う前に、単にThin Launcherで固めたJARで実行してみる。

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot.experimental</groupId>
                        <artifactId>spring-boot-thin-layout</artifactId>
                        <version>1.0.15.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

Thin Launcherで固めてはいるけど、起動時の処理を少しでも減らしたいので、Thin Launcherの起動クラスは使わずに、クラスパスだけ生成して、Mainクラスを指定して実行した。結果はこう。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case13_Exploded                          ss   10  2.476 ± 0.091   s/op

お。ちょっと速いね。

## 14. Thin Launcher + CDS

てことで、それにCDSを適用してみる。ExplodedWithAppCdsって名前になってるけど、実際はThinWithAppCdsです。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case14_ExplodedWithAppCds                ss   10  1.535 ± 0.036   s/op

うぉー。めっちゃ速くなったー!

## 15. 全部入り

最後に全部入りで実行してみた。

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case15_AllApplied                        ss   10  0.801 ± 0.037   s/op

おー。1秒切ったー!(∩´∀`)∩ワーイ

遅くなってしまったspring-context-indexerSpringConfigLocationも入れてるから、それを外しといたらもうちょい速くなるんかな?(力尽きた)

## もう一歩先

Daveのセッションでは、Functional Bean Definitionsとか、SpringBootを使わずにSpringで色々やったりしててもっと速くなってた。あんまり良くわかってない。またそのうち!

## TODO

  • -noverifyの意味を調べる
  • -XX:TieredStopAtLevel=1の意味を調べる

## 最後に

結果一覧

Benchmark                                          Mode  Cnt  Score   Error  Units
MyBenchmark.case01_FluxBaseline                      ss   10  2.938 ± 0.287   s/op
MyBenchmark.case02_Web                               ss   10  3.281 ± 0.342   s/op
MyBenchmark.case03_WithContextIndexer                ss   10  3.063 ± 0.102   s/op
MyBenchmark.case04_WithLazyInit                      ss   10  2.844 ± 0.129   s/op
MyBenchmark.case05_WithNoVerifyOption                ss   10  2.582 ± 0.060   s/op
MyBenchmark.case06_WithTieredStopAtLevel1Option      ss   10  1.980 ± 0.037   s/op
MyBenchmark.case07_WithSpringConfigLocationOption    ss   10  3.026 ± 0.139   s/op
MyBenchmark.case08_WithJmxDisabledOption             ss   10  2.877 ± 0.097   s/op
MyBenchmark.case09_WithoutLogback                    ss   10  2.904 ± 0.096   s/op
MyBenchmark.case10_WithoutJackson                    ss   10  2.789 ± 0.093   s/op
MyBenchmark.case11_WithoutHibernateValidator         ss   10  2.857 ± 0.084   s/op
MyBenchmark.case12_WithAppCds                        ss   10  2.957 ± 0.079   s/op
MyBenchmark.case13_Exploded                          ss   10  2.476 ± 0.091   s/op
MyBenchmark.case14_ExplodedWithAppCds                ss   10  1.535 ± 0.036   s/op
MyBenchmark.case15_AllApplied                        ss   10  0.801 ± 0.037   s/op

色々勉強になったなー。というかもっと勉強しなきゃなーと思ったなー。

後編:JMH(Java Microbenchmark Harness)のサンプルを動かしながら勉強

これの続き

bufferings.hatenablog.com

## 実行時オプション

サンプルを実行しながらなんとなくそんな気がしてたけど

❯ java -jar target/benchmarks.jar JMHSample_11 -f 1 -w 3 -wi 1 -r 3 -i 5 

これだと

という意味になる。-hをつけることでオプションの一覧を見ることができる。

## ループの最適化の影響に注意する

自分でループを使うんじゃなくてJMHの計測の繰り返し処理に任せる。ループを使うと最適化によってアンロールされてパイプライン化されたりしてしまうから。

Benchmark                               Mode  Cnt  Score   Error  Units
JMHSample_11_Loops.measureRight         avgt    5  2.262 ± 0.333  ns/op
JMHSample_11_Loops.measureWrong_1       avgt    5  2.657 ± 1.407  ns/op
JMHSample_11_Loops.measureWrong_10      avgt    5  0.315 ± 0.135  ns/op
JMHSample_11_Loops.measureWrong_100     avgt    5  0.031 ± 0.012  ns/op
JMHSample_11_Loops.measureWrong_1000    avgt    5  0.028 ± 0.021  ns/op
JMHSample_11_Loops.measureWrong_10000   avgt    5  0.021 ± 0.002  ns/op
JMHSample_11_Loops.measureWrong_100000  avgt    5  0.019 ± 0.003  ns/op

へー。ループ10回するだけで、もう1回あたりの平均実行時間は、ずれるんだねぇ。

どうしてもループを使わなきゃいけない場合は、Blackholeに吸わせることで、この最適化を避けることができる。

    @Benchmark
    public void measureRight_1(Blackhole bh) {
        for (int x : xs) {
            bh.consume(work(x));
        }
    }

試しにJMHSample_11_LoopsをBlackholeに吸わせてJMHSample_11_Loops2を作ってみたらこんな感じになった。 へー。良くなった。

❯ java -jar target/benchmarks.jar JMHSample_11 -f 1 -wi 1 -r 3 -i 3 -w 3

...

Benchmark                                Mode  Cnt  Score   Error  Units
JMHSample_11_Loops.measureRight          avgt    3  2.940 ± 1.462  ns/op
JMHSample_11_Loops.measureWrong_1        avgt    3  2.911 ± 2.070  ns/op
JMHSample_11_Loops.measureWrong_10       avgt    3  0.372 ± 0.523  ns/op
JMHSample_11_Loops.measureWrong_100      avgt    3  0.042 ± 0.021  ns/op
JMHSample_11_Loops.measureWrong_1000     avgt    3  0.039 ± 0.085  ns/op
JMHSample_11_Loops.measureWrong_10000    avgt    3  0.030 ± 0.025  ns/op
JMHSample_11_Loops.measureWrong_100000   avgt    3  0.027 ± 0.005  ns/op
JMHSample_11_Loops2.measureRight         avgt    3  3.240 ± 5.628  ns/op
JMHSample_11_Loops2.measureWrong_1       avgt    3  3.234 ± 4.986  ns/op
JMHSample_11_Loops2.measureWrong_10      avgt    3  2.966 ± 2.372  ns/op
JMHSample_11_Loops2.measureWrong_100     avgt    3  3.013 ± 3.742  ns/op
JMHSample_11_Loops2.measureWrong_1000    avgt    3  3.003 ± 3.001  ns/op
JMHSample_11_Loops2.measureWrong_10000   avgt    3  2.838 ± 0.355  ns/op
JMHSample_11_Loops2.measureWrong_100000  avgt    3  2.853 ± 1.185  ns/op

## プロファイルに基づく最適化の影響に注意する

Profile-Guided Optimizations(プロファイルに基づく最適化)の影響を避けるために、フォークして別プロセスで実行する。デフォルトでは5つのフォークが実行されるようになっている。

・・・なるほど?(←わかってない)よく分かってないけど、同じプロセスだと最適化の影響があるっぽいことだけ分かった。

@Forkアノテーションを使ってフォーク数を指定できる。↓だと1個

    @Benchmark
    @Fork(1)
    public int measure_4_forked_c1() {
        return measure(c1);
    }

↓0だとフォークなしで、これは良くない(警告が出る)。

    @Benchmark
    @Fork(0)
    public int measure_1_c1() {
        return measure(c1);
    }

フォーク数を指定すると、その数だけ実行されるので実行ごとの分散を評価することができる。

    @Benchmark
    @Fork(20)
    public void fork_1(SleepyState s) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(s.sleepTime);
    }

@Forkを指定しなかったらデフォルトは5。

## グルーピング

@Groupを使うと、複数のベンチマークメソッドをグルーピングして、それぞれのメソッドが使用するスレッドの割合を調整して実行することができる。あぁ、これ用にScope.Groupがあるのか(前回の記事参照)。

❯  java -jar target/benchmarks.jar JMHSample_15 -f 1                                                                                                                                                   
...
# Threads: 4 threads (1 group; 1x "get", 3x "inc" in each group), will synchronize iterations                                                                                                          
...

Benchmark                      Mode  Cnt   Score    Error  Units
JMHSample_15_Asymmetric.g      avgt    5  53.744 ± 12.789  ns/op
JMHSample_15_Asymmetric.g:get  avgt    5  28.416 ± 10.066  ns/op
JMHSample_15_Asymmetric.g:inc  avgt    5  62.187 ± 15.156  ns/op

## SyncIterationオプション(-si)

複数のスレッドで実行するときにスレッドの開始終了部分がパフォーマンスに影響するのを避けるために、立ち上げと終了を除去する仕組み。デフォルトでtrueになってる。

trueの場合

❯  java -jar target/benchmarks.jar JMHSample_17 \
             -w 1s -r 1s -f 1 -t 64 -si true

...

Benchmark                          Mode  Cnt    Score   Error   Units
JMHSample_17_SyncIterations.test  thrpt    5  134.461 ± 7.350  ops/ms

falseの場合

❯  java -jar target/benchmarks.jar JMHSample_17 \
             -w 1s -r 1s -f 1 -t 64 -si false

...

Benchmark                          Mode  Cnt    Score   Error   Units
JMHSample_17_SyncIterations.test  thrpt    5  143.721 ± 5.549  ops/ms

起動終了部分の影響でスコアは良くなるけど、この結果は、あてにならない。

## アノテーションで指定

WarmupやMeasurementの回数や時間などはアノテーションで指定可能。クラスでもメソッドでもつけられる。

@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)

## 他にも

  • BlackholeにCPUを消費させることができたり
  • 複数スレッドがデータを扱う際の、FalseSharingによるパフォーマンス劣化に対する対応をStateが最初からしてくれてたり
  • 一定時間内に実行できた回数でパフォーマンスをはかるより、計測回数を指定してパフォーマンスを図った方が良い場合はシングルショットxバッチサイズでやることができる
  • 設定ごとのパフォーマンスの違いを見たい場合は@Paramアノテーションでパラメータを指定する。パラメータの組み合わせもやってくれるっぽい

とか、色々ある。面白かったー!!

## 試しに

あんまり考えずに、こういうコードを書いてみた。

  • 文字列の連結を+StringBuilderappendでやる
  • それぞれ10000回を5イテレーション
  • その前にウォームアップで10000回を1イテレーション
  • 1回あたりの結果をミリ秒で表示
@Fork(1)
@Warmup(iterations=1, batchSize = 10000)
@Measurement(iterations=5, batchSize = 10000)
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
public class MyBenchmark {

    String s = "";

    StringBuilder sb = new StringBuilder();

    String word = "a";

    @Benchmark
    public String baseline() {
        return s;
    }

    @Benchmark
    public String measureStringPlus() {
        s += word;
        return s;
    }

    @Benchmark
    public StringBuilder measureStringBuilderAppend() {
        sb.append(word);
        return sb;
    }
}

実行してみたらこうなった。

❯ java -jar target/benchmarks.jar MyBenchmark                                                                                                                                                          
# JMH version: 1.21                                                                                                                                                                                    
# VM version: JDK 1.8.0_191, Java HotSpot(TM) 64-Bit Server VM, 25.191-b12  
...
Benchmark                               Mode  Cnt    Score     Error  Units
MyBenchmark.baseline                      ss    5    0.430 ±   0.243  ms/op
MyBenchmark.measureStringBuilderAppend    ss    5    0.697 ±   0.317  ms/op
MyBenchmark.measureStringPlus             ss    5  147.748 ± 246.386  ms/op

へー。200倍くらい違う。

なんとなく、Java 11に変えてみる。

❯ sdk use java 11.0.1-open

Using java version 11.0.1-open in this shell.

❯ java -jar target/benchmarks.jar MyBenchmark
...
# JMH version: 1.21                                                                                                                                                                                    
# VM version: JDK 11.0.1, OpenJDK 64-Bit Server VM, 11.0.1+13 
...
Benchmark                               Mode  Cnt   Score     Error  Units
MyBenchmark.baseline                      ss    5   0.752 ±   0.017  ms/op
MyBenchmark.measureStringBuilderAppend    ss    5   0.834 ±   0.641  ms/op
MyBenchmark.measureStringPlus             ss    5  75.690 ± 171.308  ms/op

お。+Java 8のときより速くなった。

んー。なんとなくEpsilonGC(何もしないGC)をONにしてみる

❯ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -jar target/benchmarks.jar MyBenchmark
...
Benchmark                               Mode  Cnt    Score     Error  Units
MyBenchmark.baseline                      ss    5    0.970 ±   2.173  ms/op
MyBenchmark.measureStringBuilderAppend    ss    5    0.641 ±   0.911  ms/op
MyBenchmark.measureStringPlus             ss    5  161.040 ± 302.442  ms/op

あれ。速くなるのかなぁと思ってたら、Java 8のときより遅くなっちゃった。ふむー。面白い。それぞれで何がどう影響してこういう結果になってるのか全然分かんない。もっと勉強しなきゃなぁ。マイクロベンチマークって難しいな。

## ところで、何でJMHチェックし始めたんだっけ?

と思ったら、そうだ、S1Pのセッションを見てたんだった。次はそれ見てみよっと。意味が分かるようになってるといいな。

kustomizeメモ

## kustomize?

k8sYAMLをカスタマイズすることができるkustomizeというツール。

github.com

### Install

インストールはここに書いてある通りにやるだけ。シンプル。

https://github.com/kubernetes-sigs/kustomize/blob/master/docs/INSTALL.md

### Documents

ドキュメントは、メインのREADME.mdとあとこの2つくらい読んだら良さそう。

https://github.com/kubernetes-sigs/kustomize/blob/master/docs/kustomization.yaml

https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md

## 何ができるの?

kustomization.yamlというファイルに設定を書いてkustomize buildを実行したらYAMLを出力してくれる。

## リソースをひとまとめにする

こんな風にファイルを用意して

❯ tree
.
└── hello
    ├── deployment.yaml
    ├── kustomization.yaml
    └── service.yaml

1 directory, 3 files

hello/kustomize.yamlはこう。

resources:
- deployment.yaml
- service.yaml

hello/deployment.yamlもシンプルにこうで

apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  selector:
    matchLabels:
      deployment: hello
  replicas: 1
  template:
    metadata:
      labels:
        deployment: hello
    spec:
      containers:
      - name: the-container
        image: nginx:1.15.5
        ports:
        - containerPort: 80

hello/service.yamlもこんな感じ

kind: Service
apiVersion: v1
metadata:
  name: the-service
spec:
  selector:
    deployment: hello
  type: LoadBalancer
  ports:
  - port: 80

でkustomizeを実行すると結果はこうなる。単純にマージされてるだけだね。

❯ kustomize build hello
apiVersion: v1
kind: Service
metadata:
  name: the-service
spec:
  ports:
  - port: 80
  selector:
    deployment: hello
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      deployment: hello
  template:
    metadata:
      labels:
        deployment: hello
    spec:
      containers:
      - image: nginx:1.15.5
        name: the-container
        ports:
        - containerPort: 80

使うときはkubectl applyに渡してあげればいい。

❯ kustomize build hello | kubectl apply -f -
service/the-service created
deployment.apps/the-deployment created

Minikubeだとこんな感じで叩くとnginxのページが返される。

❯ curl $(minikube service the-service --url)

まぁ、単純にマージするだけだと、そんなに嬉しくないけど、ビルドするときにごにょっとできるみたい。例えばラベル。

## 全てのラベルとセレクターにラベルを挿入する

kustomization.yamlcommonLabelsを書き加えてbuildを実行すると

commonLabels:
  someName: someValue
  owner: alice
  app: bingo

resources:
- deployment.yaml
- service.yaml

結果はこうなった。全部のlabelsselectorに追加されるのか。ほほー。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: bingo
    owner: alice
    someName: someValue
  name: the-service
spec:
  ports:
  - port: 80
  selector:
    app: bingo
    deployment: hello
    owner: alice
    someName: someValue
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: bingo
    owner: alice
    someName: someValue
  name: the-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bingo
      deployment: hello
      owner: alice
      someName: someValue
  template:
    metadata:
      labels:
        app: bingo
        deployment: hello
        owner: alice
        someName: someValue
    spec:
      containers:
      - image: nginx:1.15.5
        name: the-container
        ports:
        - containerPort: 80

ってことは、元々のやつからラベルとセレクターを取り除いて、こういう風にしても大丈夫ってことか

kustomization.yaml

commonLabels:
  someName: someValue

resources:
- deployment.yaml
- service.yaml

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: the-container
        image: nginx:1.15.5
        ports:
        - containerPort: 80

service.yaml

kind: Service
apiVersion: v1
metadata:
  name: the-service
spec:
  type: LoadBalancer
  ports:
  - port: 80

こうなった。minikubeに適用したらちゃんと動いた。

❯ kustomize build hello
apiVersion: v1
kind: Service
metadata:
  labels:
    someName: someValue
  name: the-service
spec:
  ports:
  - port: 80
  selector:
    someName: someValue
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    someName: someValue
  name: the-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      someName: someValue
  template:
    metadata:
      labels:
        someName: someValue
    spec:
      containers:
      - image: nginx:1.15.5
        name: the-container
        ports:
        - containerPort: 80

ふむふむ。他にもネームスペースとか、名前のプレフィックスとか、アノテーションもそれぞれ共通で追加できるし、ConfigMapを生成したりもできるみたい。あと、Overlayって機能で元のファイルを一部書き換えたものを生成できるみたいだし、変数を定義できたりもするみたいだから、ちょこちょこ読んで触ってみてみようかな。