Slide 1

Slide 1 text

Kotlin Coroutines と Ktor HTTP Client で作る スケールするタスク実⾏

Slide 2

Slide 2 text

⾃⼰紹介 たけうち ひでゆき @chimerast 株式会社イエソド エンジニア

Slide 3

Slide 3 text

イエソド社 提供サービス SaaS 統制 プラットフォーム 「 YESOD 」 ⼈・組織・情報と SaaS をシームレスに 管理するためのSaaS

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

本題

Slide 9

Slide 9 text

本⽇扱うコルーチンの対象範囲 ⼤量のタスクの並⾏実⾏

Slide 10

Slide 10 text

サービスの要件の⼀つ 毎朝午前 4 時に、顧客が利⽤している、 全ての SaaS のアカウント状態を取り込みたい

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

SaaS 連携サーバの課題 SaaS 連携サーバからのHTTP レスポンスが、 2 秒〜20 分 SaaS 連携サーバは各SaaS からアカウントの状態を取り込む SaaS 連携サーバは無限に⽔平スケールする想定(GKE) もちろんDDoS っぽくならないようにスロットリングはする

Slide 13

Slide 13 text

そこから派⽣する課題 「全てのお客さんが使っている、全てのSaaS のアカウント」 100 企業 × 100 SaaS = 10,000 リクエスト 1,000 企業 × 100 SaaS = 100,000 リクエスト Kotlin/Java でHTTP のブロッキングAPI を利⽤して作ると、 スレッド数の限界を超える

Slide 14

Slide 14 text

スレッドを利⽤したダメな例 fun main() { val pool = Executors.newFixedThreadPool(4) repeat(10000) { // 10,000 タスク pool.submit { // 10 秒レスポンスを待つだけのタスク Thread.sleep(10000L) println("DONE: " + Thread.currentThread().name) } } pool.shutdown() } → 実質4 並列でしか動かず、10 秒に4 つタスクが終わる。 全て終わるまで約7 時間。

Slide 15

Slide 15 text

並⾏タスク実⾏における スレッド & コルーチンおさらい

Slide 16

Slide 16 text

並⾏プログラミング観点から プロセス プリエンプティブマルチタスク メモリ空間: 独⽴ タスク切り替え: OS レイヤー スレッド プリエンプティブマルチタスク メモリ空間: 共有 タスク切り替え: OS レイヤー コルーチン ノンプリエンプティブマルチタスク メモリ空間: 共有 タスク切り替え: アプリレイヤー

Slide 17

Slide 17 text

同期・⾮同期プログラミング観点から スレッド ブロッキングモデル タスク実⾏中に、待ちが発⽣した際に、他のタスクにスレッドを 明け渡せない コルーチン ノンブロッキングモデル タスク実⾏中に、待ちが発⽣した際に、スレッドを明け渡せる 変な待ち⽅をすると他の全てのタスクが⽌まる

Slide 18

Slide 18 text

スレッド vs コルーチン スレッド コルーチン タスク辺りのメモリ使⽤量 多くなる 少ない コンテキストスイッチのオーバーヘッド ある 少ない プログラムのしやすさ 簡単 難しい (?) プログラムのしやすさは諸説ある コルーチンは、プログラマが他のタスクに、 いつCPU を明け渡すか制御しなければならない。

Slide 19

Slide 19 text

スレッド と コルーチン の関係性 スレッド と コルーチン は排他ではない。 複数スレッドの上で複数コルーチンを動かすことができる Node.js 1 スレッド x 複数コルーチン (Promise) Kotlin 複数スレッド x 複数コルーチン (suspending function)

Slide 20

Slide 20 text

Coroutines | Kotlin https://kotlinlang.org/docs/coroutines-overview.html コルーチンをいい感じに実装できるAPI 群

Slide 21

Slide 21 text

Coroutines を複数スレッドで⾛らせる正しい例 fun main() { val pool = Executors.newFixedThreadPool(4) runBlocking(pool.asCoroutineDispatcher()) { repeat(10000) { // 10,000 タスク async { // 10 秒レスポンスを待つだけのタスク delay(10000L) println("DONE: " + Thread.currentThread().name) } } } pool.shutdown() } → 10 秒(10000L) で10000 タスク全部終わる

Slide 22

Slide 22 text

Coroutines を使ってるけどダメな例 fun main() { val pool = Executors.newFixedThreadPool(4) runBlocking(pool.asCoroutineDispatcher()) { repeat(10000) { // 10,000 タスク async { // 10 秒レスポンスを待つだけのタスク Thread.sleep(10000L) // delay(10000L) println("DONE: " + Thread.currentThread().name) } } } pool.shutdown() } → 10 秒(10000L) に⼀回4 つタスクが流れるだけ

Slide 23

Slide 23 text

Coroutines を使っているのに、なにが違うか? delay(10000L) suspending function → ノンブロッキング 他のコルーチンが動ける Thread.sleep(10000L) 普通のfunction → ブロッキング 他のコルーチンが動けない

Slide 24

Slide 24 text

Ktor HTTP Client https://ktor.io/docs/client.html JetBrains 謹製のフレームワーク「Ktor 」の HTTP Client API 群 Kotlin coroutines を利⽤した、awesome な HTTP Client ( 意訳) もちろんすべてノンブロッキングAPI

Slide 25

Slide 25 text

Ktor HTTP Client で HTTP リクエストを投げる fun main() { val client = HttpClient() val pool = Executors.newFixedThreadPool(4) runBlocking(pool.asCoroutineDispatcher()) { repeat(10000) { async { val response = client.get("http://localhost:4000/healthcheck") println("DONE [${Thread.currentThread().name}]: $response") } } } pool.shutdown() } → HTTP サーバからレスポンスが待たされたとしても、 他のリクエストが並⾏で実⾏される

Slide 26

Slide 26 text

並⾏ / 並列処理プログラミング TIPS ローカルでの開発時には、 すべてのスレッドプールの数を1 にする DB のコネクションプールの数も1 にする

Slide 27

Slide 27 text

Ktor TIPS デフォルトのHTTP 待ち受けスレッドプールの数が Runtime.getRuntime().availableProcessors() (CPU 論理コア数) に 設定される。 クラスタのノードがしょぼいインスタンスだとこれが1 になって、 本番環境だとなんか動かないと焦る時がある ktor { deployment { connectionGroupSize = < 適切な値> workerGroupSize = < 適切な値> callGroupSize = < 適切な値> } }

Slide 28

Slide 28 text

まとめ 並⾏・並列処理と コルーチンはおもしろいよ 並⾏と並列の⾔葉遣い間違ってたら すみません 右の画像は、15 年前の本だけどガチ でお勧めな本

Slide 29

Slide 29 text

Kotlin Ktor Exposed TypeScript Vue.js Vuex Kubenetes (GKE) Istio Grafana Prometheus Argo CD