2022/09/03 このエントリーをはてなブックマークに追加 はてなブックマーク - Server-Side Kotlin meetup vol5.でLT発表してきました

Server-Side Kotlin meetup vol5.でLT発表してきました

カテゴリ: ,

こちらのイベントでLT発表させていただきました。
https://server-side-kotlin-meetup.connpass.com/event/252355/

改めて見ていてただいた方、運営の方ありがとうございました!
発表資料はこちらです。 

https://speakerdeck.com/yyyank/r2dbcdeapinogao-su-hua-wosiyoutositeyametahua

資料内の参照リンク集はこちら
https://gist.github.com/yyYank/cbcf8074b3b3bfa0fd68d9fc00cb6e73


一旦資料に目を通して欲しい前提はありつつ

当日時間の都合上、あまり丁寧に説明できなかった部分として、
テストでハマったところやMySQL の R2DBC ドライバが不安定であるというところついて補足説明をこのブログでさせていただきたいと思います。


大きく、以下2つの方針でテストをしていました。

  • 各エンドポイントに対しての@SpringBootTestでのエンドツーエンドなテスト
  • JDBCからR2DBCリプレース前後での企業データ全てのレスポンスが同一であることの検証とパフォーマンスの検証

当時、R2DBCやKotlin Coroutinesに対しての特徴的なユニットテストは書いていませんでした。
割とオーソドックスな@SpringBootTestでのユニットテストをやっていました。


とても簡略化して書くと、
TestContainerでこんな感じでDockerイメージをテスト実行時に立てていました。


import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.containers.MySQLContainer

class CustomMySqlContainer(
    private val user : String,
    private val pass: String,
    private val database : String,
    private val port : Int
) : MySQLContainer<Nothing>("mysql:x.x.x") {
    init {
        withConfigurationOverride("mysql-testconteiner-conf")
        withUsername(user)
        withPassword(pass)
        withDatabaseName(database)
        addFixedExposedPort(port, 3306)
    }
}

その前提でSpringBootTestでエンドポイントを叩いてレスポンスのアサートをしていました。


import com.ninja_squad.dbsetup_kotlin.dbSetup
import net.javacrumbs.jsonunit.JsonMatchers.jsonEquals
import org.junit.jupiter.api.*
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.TestConstructor
import org.springframework.test.web.servlet.*

@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
@SpringBootTest @AutoConfigureMockMvc @Testcontainers
internal class HogesTest(
    private val mockMvc: MockMvc
) {

    companion object {
        private const val user: String = "mysql"
        private const val password: String = "mysql"
        private const val databaseName: String = "mysql"
        private const val port: Int = 3306
        val dbContainer = CustomMySqlContainer(
            user, password, databaseName, port
        ).apply {
            start()
        }
    }

    @BeforeEach
    internal fun setUp() {
        dbSetup(to = dataSource) {
            val hoge = HogeFixture(hoge="fuga")
            insertHogeFixtures(
                listOf(hoge)
            )
        }.launch()
    }

    @Test
    internal fun getHoges() {
        mockMvc.get("/hoges") {
            headers {}
        }.andExpect {
            status { isOk() }
            jsonPath("$.hoges[*]") {
                value(
                    // language=JSON
                    jsonEquals<List<HogeJson>>(
                        """{ "expectedJsonValue": "isHere" }]"""
                    )
                )
            }
        }
    }
}


2つの問題がありました。


JUnitのテストがハングする


まず一点目
初めに使用していたのはJasync sqlのR2DBCのMySQLドライバ実装でした。
APIのエンドツーエンドなJUnitでテストをしていたところ、テストが不安定になりテストケースが実行しっぱなしで終了しなくなったという(SpringBootTest+ TestContainer + JUnit + R2DBCという組み合わせの)相性の問題があったため使用することがやめました。

上記の問題点あったためdev.mikuの方のR2DBCのMySQLドライバを使うことにしました。
切り替えたことでテストは安定するようになりました。
これに関してはR2DBCとの相性問題か、Kotlin Coroutinesとの相性問題な気がするのですが一旦ドライバを変えて回避した形になりました。


時々コネクションが死ぬ


と思っていたのも束の間、
実際に検証環境で百数万件ほどある全てのデータを網羅するようなテストを行っていたところ
本当に時々コネクションが死ぬという問題が発生しました。
百数万レベルのデータを網羅的にテストするのを10回行って1回発生するかしないかといったレベルです。つまり10 * 数百万のリクエストで起きるか起きないかというレベルで再現しない。コレが二つ目のハマりどころで使うのをやめる決断をした部分でした。
具体的には、Connection落ちてしまって例外が発生する事象だったので、ハングと言ってしまってたのは微妙な表現だったかもしれません。
それが、今後頻発する恐れがあるのかどうかというのも原因がわからないゆえ確信が持てず不安を持ったままのリリースは避けました。



2つの問題に対して、実装上特に間違った書き方をしているというのは思いつかなかったです。疑った点としては以下のようなものでした。


Kotlin Coroutinesの使い方が悪い?

であれば動作確認時にもっと高い確率でおかしな挙動になるはず。実装上も悪い使い方をしてる感じは見受けられず、至ってオーソドックスな感じでした。


ブロッキングしてSpring WebMvCを使っているのが悪い?


Spring WebFluxにしたほうが良いのか?しかし、フロントまでReactiveな要素を持っていきたいのではなく、バックエンドで非同期に動かして全ての非同期処理を集約したいだけなのでした(バックエンドにReactive Streamsを閉じておきたかった)。


それらしいissueはなかった?


Kotlin CoroutinesやR2DBCのissueは探し回ったのですが、これだ!というものが見つからずでした。
当時で言うと、このissueが一番近かったですが、Exceptioniの内容はこれとはちょっと違いました。(1年以上前だったので、どういうExceptionだったかは記憶が曖昧なのですが…)
https://github.com/mirromutth/r2dbc-mysql/issues/82#issuecomment-551401688




いただいた質問で答えきれなかったものへ回答しておきます。

Q. 今回断念した理由にリクエスト数(RPS)なども関係していたのでしょうか?また、どういうサービスなら導入良さそうなど所感伺いたいです!(どういう観点でも大丈夫です!)


A. 残念ながらパフォーマンス検証で終わったので、なんとも言えないところですが…!
例えば社内向けアプリケーションとかから小さくチャレンジしてみるのが良いかもしれません。
rpsでいうと、R2DBC自体は結構捌ける印象です。R2DBCのコネクションプール数を増やしたら検証環境のスペック的にDB自体が捌き切れなくなりDBの方にボトルネックが移った感じでした。


Q. MonoやFluxとCoroutineの繋ぎ込みの部分、デバッグが難しい、みたいな問題は発生しなかったですか?


A. 軽く発表内でも言ったつもりですが、ちょっと難しさはありました。
対処としてはデバッグがIntelliJ IDEAで出来るのと、実装上printデバッグみたいなのを仕込んでみたりなどはしてみました。ただ、個人的にはReactorを直に触るよりはラクだったかもしれません。

https://www.jetbrains.com/help/idea/debug-kotlin-coroutines.html



というところで具体的に説明したいなと思い補足を書きました。
でもうまく使えさえすれば良い感じというか
使ってて楽しかったのは間違い無いので、再チャレンジしたいなぁというところはあります。
yy_yankの次回作にご期待ください!(?)


0 件のコメント:

コメントを投稿

GA