Elasticsearchのソースコードを読む - Elasticsearchの起動

この記事は [スタンバイ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/stanby)の18日目の記事です。

# はじめに

最近、Elasticsearchのカスタムプラグインを開発する機会があったのと、Elasticsearchへの理解がまだ足りないことを感じ、今期からチームメンバーと社内Elasticsearchのソースコードリーディング勉強会を行っています。

今回の記事では、これまでの勉強会の内容を大枠にまとめて、Elasticsearchの起動プロセスを整理しました。

今回利用したElasticsearchのバージョンは7.14で、起動前の準備（環境変数の設定など）もあるけど、ここで割愛します。知識不足に関してあらかじめご了承ください。記事の内容についてこんなところをこう理解すればいいよなどアドバイスをいただけると非常に嬉しいです！

# Elasticsearchが起動されるまでの概要

Elasticsearchの起動プロセスは大体以下のステップ

1. **org.elasticsearch.bootstrap.Elasticsearch#main()**
2. **org.elasticsearch.bootstrap.Bootstrap**
3. **org.elasticsearch.node.Node**

まずElasticsearchのエントリーポイントを説明します。elasticsearchの起動スクリプトから(bin/elasticsearch)確認して、エントリーポイントはorg.elasticsearch.bootstrap.Elasticsearchです

```
  exec \
    "$JAVA" \
    "$XSHARE" \
    $ES_JAVA_OPTS \
    -Des.path.home="$ES_HOME" \
    -Des.path.conf="$ES_PATH_CONF" \
    -Des.distribution.flavor="$ES_DISTRIBUTION_FLAVOR" \
    -Des.distribution.type="$ES_DISTRIBUTION_TYPE" \
    -Des.bundled_jdk="$ES_BUNDLED_JDK" \
    -cp "$ES_CLASSPATH" \
    org.elasticsearch.bootstrap.Elasticsearch \
    "$@" <<<"$KEYSTORE_PASSWORD"
```



## **org.elasticsearch.bootstrap.Elasticsearch#main()**

entry pointのコードは以下になります

![スクリーンショット 2021-11-14 7.48.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410069/95861fd0-5794-2912-e980-dd0089f93274.png)

やることは以下になります

* 起動コマンドからDNS Cache Policyを設定するなら、DNS Cache Policyをオーバーライドする
* SecurityManagerを設定、checkPermissionで全部の権限を許可

:::note info
JavaのSecurityManagerはセキュリティーを損なう恐れのある操作(File、Socketをいじるなど)の実行権限をチェック、権限あれば実行、なければ例外を出す
:::

* エラーログリスナーを登録
    * 早めにログリスナーを使って、ログが記録できないことを防ぐ
* Elasticsearchインスタンスを作って、staticの`elasticsearch.main(args, terminal)`を呼び出し、作成されたElasticsearchインスタンス、パラメータとTerminalデフォルト値を渡す
    * ElasticsearchクラスはEnvironmentAwareCommandクラスを継承され、EnvironmentAwareCommandクラスはCommandクラスを継承され、Elasticsearchクラスはmainメソッドをoverrideしないのでelasticsearch.mainはCommandのmainメソッドを使う
    
     以下はCommandのmainメソッドコード
    
    ![スクリーンショット 2021-11-14 16.25.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410069/173bb980-7a78-f3e8-4675-b9dd95e08a5a.png)

    
    継承があるので以下の順番で実行していく

    
* **Command#main**
   * RuntimeのaddShutdownHookメソッドでshutdownHookを登録
        * スレッド終了時、Elasticsearch#closeを呼び出す
        * 異常でshutdownされる場合にスタックトレースをプリントアウト
   * mainWithoutErrorHandlingを呼び出す

:::note info
JavaのShutdownHookはJVMから終了連絡を受けて、ShutdownHook内のメソッドを呼び出し、掃除処理を行い、スムーズにJVM終了できる
:::

* **Command#mainWithoutErrorHandling**
   * パラメータをパースしてterminalを設置して、EnvironmentAwareCommandのexecute()を実行（Commandクラスの抽象executeメソッドをオーバーライト）
* **EnvironmentAwareCommand#execute**
   * パラメータからSettingを判別、Settingがなければvm optionsからpath.data、path.home、path.logsをSettingに配置
   * execute(terminal, options, createEnv(settings))を呼び出し
* **EnvironmentAwareCommand#createEnv**
   * prepareEnvironmentを経由elasticsearch.ymlをロードして、Environmentを作成
* **Elasticsearch#execute**
    * daemonizer/pidFile/quietの値を取得、Bootstrapのinitを呼び出し

## **org.elasticsearch.bootstrap.Bootstrap**

initのコードが多くて、リンクだけ残します
https://github.com/elastic/elasticsearch/blob/fc519e380029c6daecc950802435f7266f08708e/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java#L334

最初はBootstrapインスタンスを作って、KeepAliveスレッドを起動

```Java
private final CountDownLatch keepAliveLatch = new CountDownLatch(1);
    private final Thread keepAliveThread;
    private final Spawner spawner = new Spawner();

    /** creates a new instance */
    Bootstrap() {
        keepAliveThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //count
                    keepAliveLatch.await();
                } catch (InterruptedException e) {
                    // bail out
                }
            }
        }, "elasticsearch[keepAlive/" + Version.CURRENT + "]");
        keepAliveThread.setDaemon(false);
        // keep this thread alive (non daemon thread) until we shutdown
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                //thread終了時、KeepAliveLatchが0になって、keepAliveThread終了
                keepAliveLatch.countDown();
            }
        });
    }
```
:::note info
CountDownLatchは他のスレッドの処理を待機する仕組みです。他のスレッドを処理完了後処理することができます。
:::

:::note info
Javaはユーザースレッドとデーモンスレッドがあります。
JVMの終了フロー
1. ユーザースレッドがない場合にJVM終了を始める
2. JVMが全デーモンスレッドを終了
3. JVM終了
:::

keepAliveThreadはCountDownLatchで設定されてかつユーザースレッドとして設定されます。
なので、ここの流れは

:::note info
1. JVMが終了請求を受ける
2. shutdownHook実行
3. keepAliveLatch.countDown()実行
4. KeepAliveLatchが0になって、keepAliveThread終了
5. JVM終了
:::

KeepAliveスレッドを起動後、Bootstrapのinit()について、大枠のプロセスは以下のように

* SettingとconfigによってES実行時Environmentを作る
* セキュリティとログ周りの設定
    * keystoreのセキュリティ設定をロードする。keystoreファイルない時に作って保存する。keystoreファイルある時に復号化して、keystoreを更新する
    * setNodeName ログをプリント時Node名を設定、log4j2.propertiesの設定ファイルからログの設定をロード
* PIDファイルがあるかどうかを確認、なければ作る、現在のpidを書き込む
* Luceneのバージョンを確認
* setDefaultUncaughtExceptionHandlerでCatchされない例外の処理方法を設置

:::note info
マルチスレッドで他のスレッドから出す例外をcatchできないケースもあります。例外をcatchと処理することが必要で、UncaughtExceptionHandlerはこんな処理です
:::

* INSTANCE.setup(true, environment);INSTANCE.start();
    * Nodeインスタンスを作るため準備して、Nodeインスタンスを作って、Nodeインスタンスを起動


# 感想など
* Elasticsearchの起動だけでこんなプロセスがあることを理解できて、細かい粒度でElasticsearchの仕組みを把握できるようになりました。
* CountDownLatchなどのプロセス調査により、Javaのマルチスレッドプログラミングに詳しくなりました。


# 参考
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/SecurityManager.html

https://docs.oracle.com/javase/8/docs/technotes/guides/lang/hook-design.html

https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/CountDownLatch.html

https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Thread.UncaughtExceptionHandler.html