Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Kotlin Tech Talk 〜Kotlin開発で経験した失敗事例や課題解決の知見共有会〜
https://mercari.connpass.com/event/206751/
で発表した資料です。

主にKotlinのコルーチンを並行タスク処理で使う時のサンプルです。

8a43d544df4886ea5ef714e78f3420a7?s=128

Hideyuki Takeuchi

April 05, 2021
Tweet

Transcript

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

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

  3. イエソド社 提供サービス SaaS 統制 プラットフォーム 「 YESOD 」 ⼈・組織・情報と SaaS

    をシームレスに 管理するためのSaaS
  4. None
  5. None
  6. None
  7. None
  8. 本題

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

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

  11. None
  12. SaaS 連携サーバの課題 SaaS 連携サーバからのHTTP レスポンスが、 2 秒〜20 分 SaaS 連携サーバは各SaaS

    からアカウントの状態を取り込む SaaS 連携サーバは無限に⽔平スケールする想定(GKE) もちろんDDoS っぽくならないようにスロットリングはする
  13. そこから派⽣する課題 「全てのお客さんが使っている、全てのSaaS のアカウント」 100 企業 × 100 SaaS = 10,000

    リクエスト 1,000 企業 × 100 SaaS = 100,000 リクエスト Kotlin/Java でHTTP のブロッキングAPI を利⽤して作ると、 スレッド数の限界を超える
  14. スレッドを利⽤したダメな例 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 時間。
  15. 並⾏タスク実⾏における スレッド & コルーチンおさらい

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

    メモリ空間: 共有 タスク切り替え: OS レイヤー コルーチン ノンプリエンプティブマルチタスク メモリ空間: 共有 タスク切り替え: アプリレイヤー
  17. 同期・⾮同期プログラミング観点から スレッド ブロッキングモデル タスク実⾏中に、待ちが発⽣した際に、他のタスクにスレッドを 明け渡せない コルーチン ノンブロッキングモデル タスク実⾏中に、待ちが発⽣した際に、スレッドを明け渡せる 変な待ち⽅をすると他の全てのタスクが⽌まる

  18. スレッド vs コルーチン スレッド コルーチン タスク辺りのメモリ使⽤量 多くなる 少ない コンテキストスイッチのオーバーヘッド ある

    少ない プログラムのしやすさ 簡単 難しい (?) プログラムのしやすさは諸説ある コルーチンは、プログラマが他のタスクに、 いつCPU を明け渡すか制御しなければならない。
  19. スレッド と コルーチン の関係性 スレッド と コルーチン は排他ではない。 複数スレッドの上で複数コルーチンを動かすことができる Node.js

    1 スレッド x 複数コルーチン (Promise) Kotlin 複数スレッド x 複数コルーチン (suspending function)
  20. Coroutines | Kotlin https://kotlinlang.org/docs/coroutines-overview.html コルーチンをいい感じに実装できるAPI 群

  21. 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 タスク全部終わる
  22. 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 つタスクが流れるだけ
  23. Coroutines を使っているのに、なにが違うか? delay(10000L) suspending function → ノンブロッキング 他のコルーチンが動ける Thread.sleep(10000L) 普通のfunction

    → ブロッキング 他のコルーチンが動けない
  24. Ktor HTTP Client https://ktor.io/docs/client.html JetBrains 謹製のフレームワーク「Ktor 」の HTTP Client API

    群 Kotlin coroutines を利⽤した、awesome な HTTP Client ( 意訳) もちろんすべてノンブロッキングAPI
  25. Ktor HTTP Client で HTTP リクエストを投げる fun main() { val

    client = HttpClient() val pool = Executors.newFixedThreadPool(4) runBlocking(pool.asCoroutineDispatcher()) { repeat(10000) { async { val response = client.get<String>("http://localhost:4000/healthcheck") println("DONE [${Thread.currentThread().name}]: $response") } } } pool.shutdown() } → HTTP サーバからレスポンスが待たされたとしても、 他のリクエストが並⾏で実⾏される
  26. 並⾏ / 並列処理プログラミング TIPS ローカルでの開発時には、 すべてのスレッドプールの数を1 にする DB のコネクションプールの数も1 にする

  27. Ktor TIPS デフォルトのHTTP 待ち受けスレッドプールの数が Runtime.getRuntime().availableProcessors() (CPU 論理コア数) に 設定される。 クラスタのノードがしょぼいインスタンスだとこれが1

    になって、 本番環境だとなんか動かないと焦る時がある ktor { deployment { connectionGroupSize = < 適切な値> workerGroupSize = < 適切な値> callGroupSize = < 適切な値> } }
  28. まとめ 並⾏・並列処理と コルーチンはおもしろいよ 並⾏と並列の⾔葉遣い間違ってたら すみません 右の画像は、15 年前の本だけどガチ でお勧めな本

  29. Kotlin Ktor Exposed TypeScript Vue.js Vuex Kubenetes (GKE) Istio Grafana

    Prometheus Argo CD