Slide 1

Slide 1 text

Road to Kotlin 〜10年間続くPerl運⽤からの脱却〜 2024.07.02 LM Server Dev1 / Hiroshi Nagai © LINE Digital Frontier Corporation

Slide 2

Slide 2 text

⽬次 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 まとめ

Slide 3

Slide 3 text

はじめに

Slide 4

Slide 4 text

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⽇に上場

Slide 5

Slide 5 text

⾃⼰紹介 名前 Hiroshi Nagai 所属 LINE Digital Frontier株式会社(2018年⼊社) LM Server Dev1

Slide 6

Slide 6 text

Kotlin移⾏の経緯

Slide 7

Slide 7 text

サーバーサイドのPerl運⽤の限界 • 当時、開発のスピード感を重視してPerlを採⽤したが・・・ • 運⽤上便利なツールの連携ができない(しづらい) • Perlの⽂法は⾃由度が⾼く、エンジニアの書き⽅に依存しやすいためコー ドの負債が蓄積 • Perlエンジニアの⼈⼝減少でエンジニアの採⽤が困難 LINEマンガの規模と成⻑速度の中で Perl運⽤を続けることは⾟い

Slide 8

Slide 8 text

Kotlin採⽤の理由 • Perlのデメリットの⼤半が解消される • バグの減少、可読性の向上、コード負債の解消、エンジニアの採⽤拡⼤ • もともとJavaに移⾏予定だったが、当時Kotlinが台頭し始め、社内で の開発実績も増えてきたため、開発途中で移⾏先の⾔語をシフト Perl移⾏開始から4年が経過 全リクエストの92.857%がKotlin移⾏完了

Slide 9

Slide 9 text

Kotlin移⾏⽅法

Slide 10

Slide 10 text

移⾏の注意点 1.サービスを⽌めない 2.移⾏前と移⾏後で同じレスポンスを返却する 3. APIの応答時間の維持、改善 4. 他の開発タスクに影響を及ぼさない

Slide 11

Slide 11 text

リクエストからレスポンスまで 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]

Slide 12

Slide 12 text

リクエストからレスポンスまで [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

Slide 13

Slide 13 text

リクエストからレスポンスまで 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サーバー内で制御

Slide 14

Slide 14 text

Kotlinの切り替えと切り戻し 1. いったん全てのリクエストを Kotlin コードで 受け付け 2. リアルタイム反映可能なパラメーターファイ ルに応じて分岐 (ソフトウェアエンジニアリングで制御) ※ パラメータファイル配信には、LINE の OSS である、CentralDogma を利⽤。 [2] Kotlin serverに100%振り、Kotlinサーバー内で制御

Slide 15

Slide 15 text

Kotlinの切り替えと切り戻し [2] Kotlin serverに100%振り、Kotlinサーバー内で制御

Slide 16

Slide 16 text

Kotlinの切り替えと切り戻し class FooService( private val centralDogma: CentralDogmaState, ) { 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サーバー内で制御

Slide 17

Slide 17 text

Kotlinの切り替えと切り戻し メリット • Kotlinコード内の処理で柔軟なリリースが可能 • Perl/Kotlinのレスポンスをコード内で処理するため、A PIで差分⽐較ができる デメリット • Perl/Kotlinのレスポンス⽐較するため、1リクエストで 2回データソースにアクセスする。リリース前の負荷確 認は必須 • 準備と実装に時間がかかる [2] Kotlin serverに100%振り、Kotlinコード内で制御

Slide 18

Slide 18 text

Perl/Kotlinのレスポンス⽐較 private fun compareWithPerlResponse( perlResponse: FooResponse, kotlinResponse: FooResponse, ): List { // 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で再帰的にチェック。差分をログに出⼒

Slide 19

Slide 19 text

Perl/Kotlinのレスポンス⽐較 data class JsonNodeDiffInfo( val path: String, val reason: String, ) fun compareJsonNodes( node1: JsonNode, node2: JsonNode, path: String = "", ): List { // 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 { 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 { 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) } } }

Slide 20

Slide 20 text

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の得意分野

Slide 21

Slide 21 text

AIの活⽤

Slide 22

Slide 22 text

おわりに

Slide 23

Slide 23 text

まとめ 1. コードの移⾏開発ではAIが⼤活躍 2. jackson.databind.JsonNode/ObjectMapper c lassを活⽤して移⾏前後のAPIの整合性を取る 3. ロジックが複雑なAPIはcentraldogma等を利⽤ した柔軟な移⾏フローを検討する

Slide 24

Slide 24 text

End Of doc.