業務でGraalVMとKotlinを使ってサーバレス開発やってみたのでちょっとハンズオンしてみる

# ３行で結論
JavaとかKotlinみたいなJVM系言語でLambdaを動かすと激遅。
でも、GraalVMを使えば爆速で動く。
案件としてリリースできそうまで持って行けたのでハンズオン書くよ

# サーバレス開発って何
この記事ではサーバレス開発を以下の様に定義します。

世の中のシステムはネットワーク構築とかサーバ運用とかのインフラが必須ですが、
AWSなどが提供するクラウドインフラのマネジメントサービスをつかうことで
**サーバを使わない**（=**サーバレスにする**）ことでインフラの運用管理のコストを下げたり、 **柔軟なスケーラビリティ**（拡張性）を実現する開発のことを**サーバレス開発**といいます。

- サーバレスに使える代表的な技術
    - コード実行：AWS Lambda、GCP CloudFunction等
    - データベース：AWS RDS、GCP CloudSQL等

# GraalVMとは

GraalVMは、Java仮想マシン（JVM）上で実行されるプログラム言語を統一的に実行するランタイムの１つで、AOTコンパイラでバイナリ出力が可能。

### つまりどうなるの？

- **GraalVMを使うといろんな言語を統合的に使えちゃう**
- **Graalコンパイラによるバイナリ出力で爆速アプリが作れちゃう**

# Graalコンパイラ？バイナリ？＝爆速アプリ？
なんとなく知ってる人はスキップして大丈夫

そもそもJavaやKotlin、（あとはGrooby）などのJVM上で動かす言語は、JITコンパイラが基本です。
just-in-time、プログラム実行時の"まさにそのときに"機械語に翻訳するコンパイル方式です。
これでどうやってパフォーマンスを維持しているかと言うと、何度も呼ばれるメソッドなどはJITコンパイラの中でもより性能の高いコンパイラを通して実行し、それに合わせて最適化していきます。
また、投機的最適化など、事前にプロファイラを用意しておくことで最初から最適化された状態にもできます。

一見すると問題なさそうに見えますが、
これはサーバレスアーキテクチャと相性最悪です。

クラスのロード、動的コンパイル、プロファイル情報の収集などは計算コストが高く、プロファイラについては相応のメモリが必要です。
そしてプログラム起動時に必要な時間とメモリも増えてしまいます。
基本的なサーバレスアーキテクチャはリクエストに合わせてイメージとプログラムを起動させていきます。
そして、時間が経ったらそれは破棄します。たった１回のAPIリクエストのためにプログラムの起動からプロファイルの収集までの時間を持たせるのはとても無駄です。

それを解決するのがGraalコンパイラによるAOTコンパイルです。
AOTコンパイルはahead-of-time、事前コンパイルのことで、JITでコンパイル方式ではプログラム起動後にやる計算を事前に全てやるコンパイルです。
それによって出力されたバイナリファイルはマシン語そのもので、Graalコンパイラはさらに最適化するように設計されております。（＝**ネイティブコンパイル**）
これにより、JITコンパイルされたプログラムよりも高速に実行することが可能です。

:::note info
速度比較については他の記事でも散々やってもらっているのでこの記事では省略しますがGraalコンパイラによるネイティブコンパイルは通常のJITコンパイルに比べて10倍の実行速度です。
:::

# ネイティブコンパイルをやってみよう

GraalVMをインストール
https://www.graalvm.org/latest/docs/getting-started/#install-graalvm
ここから対応OSのページに飛んで手順に乗っ取ってインストールしていきましょう

:::note info
上記のように手動でもいいですが、SDKMAN!でgraalvmランタイムをインストールしてもいいです。そっちの方が楽かも。
:::

:::note warn
最終的には`java --version`でPATHが通っていることを確認してください。こんな感じになるはず
```
java --version

openjdk 17.0.5 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
```
:::

サクッとSpring Initializerで作っていきます。
今回のテーマはKotlinなのでKotlin、SpringBoot3とJava17を選択。
他はささっとお好みで。
SpringWebとGraalVM Native Supportの追加を忘れずに。
![スクリーンショット 2022-12-22 8.40.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711175/fdba8ba9-51e7-10ec-167a-a7ba3b301f9a.png)

:::note info
Springboot3でNative Buildがサポートされるようになったためこちらを推奨しております。
業務ではSpringBoot2を使ってましたが、それはSpringboot3がまだ無かったことが理由です。
:::

プロジェクトを解凍して好きなIDEで開きましょう。
起動したあとGradleのビルドが走らせてちょっとコーヒーブレイク。

:::note warn
Native Buildのメリット/デメリットを以下にまとめててみました。
メリット
・プログラム起動時の速度が早い
・上記の関係でサーバレスアーキテクチャに適している
デメリット
・ビルド時間がすごく長い
・DynamicProxyやReflectionを用いる場合はきちんと設定しないといけない
・上記の関係で、ライブラリを追加すると痛い目に合うことがある。

とまぁ、メリットよりもデメリットの方が多いですが、Kotlinでサーバレス開発が可能というメリットだけでデメリットは相殺できる気がします。
:::

次にリクエストに対してHelloという文字列を返す定番のプログラムを作りましょう。

Entityから。
```kotlin:Hallo.kt
data class Hello(val text: String)
```

Controller
```kotlin:HalloController.kt
@RestController
class HalloController {
    private val template = "Hello, World!"

    @GetMapping("/hello")
    fun hello(): Hello? {
        return Hello(template)
    }
}
```

普通にビルドして動かして、http:localhost:8080/helloで動作確認しましょう。
Run
```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.0)

2022-12-22T10:25:21.203+09:00  INFO 53037 --- [           main] com.graaldemo.demo.DemoApplicationKt     : Starting DemoApplicationKt using Java 17.0.5 with PID 53037 (/projects/demo/build/classes/kotlin/main started by koyamakazuaki in /projects/demo)
2022-12-22T10:25:21.223+09:00  INFO 53037 --- [           main] com.graaldemo.demo.DemoApplicationKt     : No active profile set, falling back to 1 default profile: "default"
2022-12-22T10:25:22.378+09:00  INFO 53037 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-12-22T10:25:22.387+09:00  INFO 53037 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-12-22T10:25:22.387+09:00  INFO 53037 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.1]
2022-12-22T10:25:22.500+09:00  INFO 53037 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-12-22T10:25:22.513+09:00  INFO 53037 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1214 ms
2022-12-22T10:25:22.911+09:00  INFO 53037 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-12-22T10:25:22.919+09:00  INFO 53037 --- [           main] com.graaldemo.demo.DemoApplicationKt     : Started DemoApplicationKt in 2.089 seconds (process running for 8.574)

```

リクエスト
```
curl http://localhost:8080/hello

{"text":"Hello, World!"}%
```

ここまで行けたのなら今度はnativeCompileしてみましょう。
```
./gradlew nativeCompile
```
エラーなく進めば`/build/native/nativeCompile`に`demo`というバイナリファイルが生成されるはずです。

:::note warn
このnativeCompileでエラーが出る場合はPATHがきちんと通っていない可能性が高いです。
見直してみましょう。
なお、Docker上でビルドする場合はTips.に詰まりポイントを書いてみました。
:::

そうしたら後はバイナリファイルを起動するだけです！
```
./build/native/nativeCompile/demo
```

```
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.0)

2022-12-22T10:39:29.784+09:00  INFO 53353 --- [           main] com.graaldemo.demo.DemoApplicationKt     : Starting AOT-processed DemoApplicationKt using Java 17.0.5 with PID 53353 /projects/demo/build/native/nativeCompile/demo started by koyamakazuaki in /projects/demo)
2022-12-22T10:39:29.785+09:00  INFO 53353 --- [           main] com.graaldemo.demo.DemoApplicationKt     : No active profile set, falling back to 1 default profile: "default"
2022-12-22T10:39:29.851+09:00  INFO 53353 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-12-22T10:39:29.860+09:00  INFO 53353 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-12-22T10:39:29.860+09:00  INFO 53353 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.1]
2022-12-22T10:39:29.882+09:00  INFO 53353 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-12-22T10:39:29.882+09:00  INFO 53353 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 97 ms
2022-12-22T10:39:29.939+09:00  INFO 53353 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-12-22T10:39:29.939+09:00  INFO 53353 --- [           main] com.graaldemo.demo.DemoApplicationKt     : Started DemoApplicationKt in 0.187 seconds (process running for 0.213)
```

普通のビルドとNativeCompileによるビルドのSpringBootの速度に注目

普通のビルド
`Started DemoApplicationKt in 2.089 seconds (process running for 8.574)`

NativeCompile
`Started DemoApplicationKt in 0.187 seconds (process running for 0.213)`

2.089秒→0.187秒と早すぎますね！
通常のビルドだとむしろ起動だけで2.089秒もかかっていたらAPIのレスポンスなんて3秒余裕で超えちゃいますね。
普通のサーバであれば起動時間は無視できるものですが、サーバレスであれば初動の速度が何よりも大事ですので、NativeCompileぐらいの速度だと実用レベルですね。

とまぁ、ハンズオンはここまで。
実際にLambdaにあげて動作しようとするとなるとそれだけで１記事かけちゃうので今回はここでストップです。

## Tips.
- Lambdaでネイティブイメージを動かす場合はAmazonLinux2でビルドする必要があります。
- Dockerでビルドする場合はGradleのメモリとDockerのメモリを変える必要があります（どちらも8GBは欲しい）
- DockerFileに悩んだ場合はここが参考になるかと
https://github.com/marksailes/al2-graalvm


# まとめ
サーバレス開発がしたいだけならNodeJSが候補にあがりますが、それは中規模開発までだと筆者は考えております。
理由はいくつかありますが、Kotlin自体の（大きな規模での）開発のしやすさとライブラリの豊富さが一番かなと。
ただ、Graalコンパイルする際にDynamicProxyやReflectionを使っていると設定が必要だったり、使いたいライブラリがうまく動かなかったりと大変なのでそこはトレードオフかなとも思います。
それでも、Javaから続くライブラリーフレームワークを使用でき、比較的モダン且つNULL安全な言語としてKotlinを使えるのは開発者としてもメリットは多いのではないでしょうか。

今回はLinux上ではなくMac上でのビルドを想定していたため、ネイティブイメージもMacでしか動かないのですが、
もし次の記事を書くのであればLinux（Amazon Linux2）でビルドしてLambdaにあげて実際に動かせるところまでやれたらと思います！
短いですがこの記事はここまで！ありがとうございます！