Share
Embed
Road to Kotlin 〜10年続くPerl運用からの脱却〜
「後夜祭.kt 2024」の登壇資料です。
https://lycorptech-jp.connpass.com/event/322264/
LINE Digital Frontier - TECH
July 02, 2024
Transcript
24 slides
-
Road to Kotlin 〜10年間続くPerl運⽤からの脱却〜 2024.07.02 LM Server Dev1 / Hiroshi
Nagai © LINE Digital Frontier Corporation -
⽬次 01. はじめに 1.1 LINEマンガとは 1.2 ⾃⼰紹介 02. Kotlin移⾏の経緯 2.1
サーバーサイドのPerl運⽤の限界 2.2 Kotlin採⽤の理由 03. Kotlin移⾏⽅法 3.1 移⾏の注意点 3.2 リクエストからレスポンスまで 3.3 Kotlinの切り替えと切り戻し 3.4 Perl/Kotlinのレスポンス⽐較 3.5 AIの活⽤ 04. おわりに 3.1 まとめ -
LINEマンガについて • 2013年 サービス開始 • 「Google Play ベスト オブ 2023」において「ベ
ストアプリ 2023」と「エンターテイメント部 ⾨」⼤賞を受賞 • 国内マンガアプリ累計ダウンロード数で1位を記録 ※ (2013年4⽉〜2022年12⽉) iOS & Google Play合計 / 出典:data.ai(App Annieより名称変更 ) • 親会社のWEBTOON Entertainmentは⽶国NASD AQに2024年6⽉27⽇に上場 -
リクエストからレスポンスまで DB Gateway server Perl Server (Plack) Kotlin Server (JVM)
Redis Elasticsear ch etc… [1] Gateway serverからPerl / Kotlin serverに割り振る [1] [2] [3] [5] [4] [2] [3] [4] [5] [6] -
リクエストからレスポンスまで [1] Gateway serverからPerl / Kotlin serverに割り振る メリット • Gateway
serverの数だけ、⼀台ずつPerl/Kotli n serverに寄せられる • ⼀度設定すれば、ワンライナーでリリース/ ロールバックが可能 デメリット • どのアクセスがKotlinに⾏くかはランダム • ロジックの⼀部だけをKotlinに寄せることが難 しい # Gateway serverのnginx設定 if ( -f /home/www/html/P2J_API_FOO) { rewrite ^(/pl|)(/api/foo)$ /kt$2 last; } # Release時 for i in $(echo api-gateway{001..005}-server); ssh user@$i "t ouch /home/www/html/P2J_API_FOO"; done -
リクエストからレスポンスまで DB Gateway server Perl Server (Plack) Kotlin Server (JVM)
Redis Elasticsear ch [1] [2] [3] [4] [5] [6] [8] [7] [9] [10] etc… [2] Kotlin serverに100%振り、Kotlinサーバー内で制御 -
Kotlinの切り替えと切り戻し 1. いったん全てのリクエストを Kotlin コードで 受け付け 2. リアルタイム反映可能なパラメーターファイ ルに応じて分岐 (ソフトウェアエンジニアリングで制御)
※ パラメータファイル配信には、LINE の OSS である、CentralDogma を利⽤。 [2] Kotlin serverに100%振り、Kotlinサーバー内で制御 -
Kotlinの切り替えと切り戻し class FooService( private val centralDogma: CentralDogmaState<ApiConfig>, ) { fun
fooResponse(): FooResponse { val perlResponse = getPerlResponse() val kotlinResponse = getKotlinResponse() return FooResponse( componentA = getComponentA(kotlinResponse, perlResponse), // componentB, C, E... ) } private fun getComponentA( kotlinResponse: FooResponse, perlResponse: FooResponse, ): ComponentA { return if (fooSettings().useKotlinImplementation.componentA) { kotlinResponse.componentA } else { perlResponse.componentA } } } [2] Kotlin serverに100%振り、Kotlinサーバー内で制御 -
Kotlinの切り替えと切り戻し メリット • Kotlinコード内の処理で柔軟なリリースが可能 • Perl/Kotlinのレスポンスをコード内で処理するため、A PIで差分⽐較ができる デメリット • Perl/Kotlinのレスポンス⽐較するため、1リクエストで
2回データソースにアクセスする。リリース前の負荷確 認は必須 • 準備と実装に時間がかかる [2] Kotlin serverに100%振り、Kotlinコード内で制御 -
Perl/Kotlinのレスポンス⽐較 private fun compareWithPerlResponse( perlResponse: FooResponse, kotlinResponse: FooResponse, ): List<JsonNodeDiffInfo>
{ // perl serverからのレスポンス val perlResponseJsonNode = convertToJsonNode(perlResponse) // kotlin内の処理で⽣成されたレスポンス val responseJsonNode = convertToJsonNode(kotlinResponse) // 2つのJsonNodeを⽐較し、差分情報のリストを取得 return compareJsonNodes( perlResponseJsonNode, responseJsonNode, ).filter { diffInfo -> // 特定のキーを持つ差分のみを⽐較対象にする !skipComparableKeyPrefixes.any { prefix -> if (diffInfo.path.length > prefix.length) { diffInfo.path.startsWith("$prefix/") } else diffInfo.path == prefix } } } private fun convertToJsonNode(response: FooResponse): JsonNode { val responseJson = objectMapper.writeValueAsString(response) return objectMapper.readTree(responseJson) // return JsonNode } JsonNode classで再帰的にチェック。差分をログに出⼒ -
Perl/Kotlinのレスポンス⽐較 data class JsonNodeDiffInfo( val path: String, val reason: String,
) fun compareJsonNodes( node1: JsonNode, node2: JsonNode, path: String = "", ): List<JsonNodeDiffInfo> { // JsonNodeのタイプに基づいて⽐較を⾏う return when { node1.isObject -> compareObjectNodes(node1, node2, path) node1.isArray -> compareArrayNodes(node1, node2, path) node1 != node2 -> listOf(createDiffInfo(path, "$node1 != $node2")) else -> emptyList() } } private fun createDiffInfo(path: String, reason: String): JsonNodeDiffInfo { return JsonNodeDiffInfo( path = path, reason = reason, ) } private fun compareObjectNodes( node1: JsonNode, node2: JsonNode, path: String, ): List<JsonNodeDiffInfo> { return node1.fields().asSequence().toList().flatMap { (key, value) -> val newPath = "$path/$key" if (node2.at("/$key").isMissingNode) { // 2つ⽬のJsonNodeに同じフィールドが存在しない場合、差分情報を⽣成 listOf(createDiffInfo(newPath, "missing path")) } else { // 同じフィールドが存在する場合、そのフィールドの値(JsonNode)に対して再帰的に⽐較を⾏う compareJsonNodes(value, node2.get(key), newPath) } } } private fun compareArrayNodes( node1: JsonNode, node2: JsonNode, path: String, ): List<JsonNodeDiffInfo> { return node1.flatMapIndexed { index, value -> val newPath = "$path/$index" if (node2.at("/$index").isMissingNode) { // 2つ⽬のJsonNodeに同じインデックスの要素が存在しない場合、差分情報を⽣成 listOf(createDiffInfo(newPath, "missing path")) } else { // 同じインデックスの要素が存在する場合、その要素(JsonNode)に対して再帰的に⽐較を⾏う compareJsonNodes(value, node2.get(index), newPath) } } } -
AIの活⽤ # 発売⽇当⽇かどうかを返却 use Time::Piece; sub is_today { my $date_str
= shift; my $input_date = Time::Piece->strptime($date_str, '%Y-%m-%d %H:%M:%S'); my $today = localtime; return $input_date->ymd eq $today->ymd; } Perl -> Kotlinの書き換え作業はAIの得意分野