Preparing Apache Kafka® for Scala 3の翻訳です。
2021年12月7日
Scala 3向けApache Kafka®の準備
AivenのOSPOはApache KafkaのScala 3への移行に取り組んでいます。彼らがどのように管理したかをご覧ください!
Apache Kafkaは分散イベントストリーミングプラットフォームで、オープンソースです。今日の投稿は、Kafkaを使ってできるクールなことについてではありません。今回はKafkaの内部についてです。具体的には、Aivenのオープンソースプログラムオフィスで行われている最新のタスクの1つ、Apache KafkaのScala 3への移行についてです。
Scalaとは?
Scalaは、オブジェクト指向と関数型プログラミングのパラダイムを組み合わせた強力な静的型付けプログラミング言語です。
Scalaのコードは、JVM、JavaScriptエンジン、あるいはLLVMコンパイラ上で実行できるようにコンパイルすることができます。
Scalaの特徴
Scalaはメジャーバージョン間(例えば2.12
から2.13
へ)の移行が面倒なことで知られている。つまり、Scalaプロジェクトを新しいメジャーバージョンに移行するには、まずすべての依存関係を移行する必要があり、大規模なプロジェクトでは大幅な遅延が発生する。しかしScala 3では、コンパイラがScala 2.13
用にコンパイルされた依存関係ファイルを読めるようになったため、この状況は大きく変わった1。
Apache Kafka と Scala の関係は?
Apache Kafkaの内部コードに詳しくない方は、ScalaとKafkaにどんな関係があるのかと思うかもしれません。答えはいたってシンプルだ:KafkaはJavaとScalaで書かれています。しかし、Kafkaのコードベースに占めるScalaコードの割合はバージョンごとに減少しており、Apache Kafka 0.7では約50%だったのが、現在は23%[2]になっている(#脚注-2)。Apache 3.1.0の時点で、Scalaで書かれた最大かつ最も重要なモジュールはcore
で、その名の通りKafkaの心臓部だ。Scalaで書かれた他のモジュールは、Kafka Streams用のScala APIモジュールである。
しかしKafkaは、Scalaの事実上標準的なツール(ビルド・ツール、テスト・ライブラリ...)のほとんどを使っていない。また、Kafkaで書かれたScalaのコードは、広く受け入れられている慣用的な方法で書かれていないという意見もあるかもしれない。
なぜScala 3にアップデートするのか?
多くのライブラリや他の多くの言語と同様に、バージョンが古すぎるとアップデートが受けられなくなります。これは、新たに発見された脆弱性が修正されなかったり、そのバージョンに新たな改良が加えられなかったりすることを意味します。Scalaも例外ではない。そのため、Scala 3に移行することで、セキュリティ・アップデートや今後の機能、パフォーマンスの改善に対応することができるのです。
さらに、Scala 3に移行することで、ScalaでコンパイルされたKafkaのアーティファクトに依存している人たちの作業が容易になります。
Scala 3への移行
Scala 3への移行の実現可能性を確認するための概念実証中に、私たちはいくつかの問題に遭遇しました。ビルド・ツール、バイトコード生成、そしてJavaの相互運用性です。それぞれの点について、さらに詳しく説明します。
Gradleの強化
Scala 3への移行というタスクに直面したとき、私たちが最初に直面した問題は、Kafkaで使用されているビルドツールであるGradleがまだScala 3をサポートしていないということでした。Gradleのチケッティング・システムにはすでに機能拡張のリクエストがあったのですが、コミュニティから誰かが参加してくれるのを待つばかりで、作業は進められていませんでした。
私たちはScalaセンター3に、Gradleのサポートに貢献できる帯域幅があるかどうかを尋ねました。そのメンバーの一人である Virtus Lab の Tomasz Godzik 氏は、様々なビルドツールや IDE サポートの仕事で Scala コミュニティでは非常に有名であり、このタスクに興味を示し、Gradle に Scala 3 サポートを提供してくれました。この機能を提供してくれた Tomasz に本当に感謝しています!
マイグレーションそのもの(構文)
Scala3のサポートがGradleで(ナイトリービルドとして)利用可能になると、移行を本格的に開始することができました。発見された問題の大部分は、KafkaのScalaコードの書き方に関するもので、現在では推奨されていない機能や機能を使用していました。
括弧が多すぎる
Scalaでは、パラメータを取らないメソッドを括弧なしメソッドとして定義できる。Scala 2 では、このようなメソッドは括弧の有無にかかわらず呼び出すことができました。しかし Scala 3 では、いくつかの特殊な状況下で、コンパイラーは括弧が括弧なしメソッドに属すると正しく仮定することができず、結果に括弧を適用しようとします。これは、括弧のないメソッドが括弧付きで呼び出されるすべての例に適用されるわけではなく、特定の例にのみ適用されます。
ここでは、関連する部分のみを表示した例を見ることができます:
与えられた
クラス KafkaConfig private(doLog:Boolean, val props: java.util.Map[˶_,˶_], dynamicConfigOverride:オプション[DynamicBrokerConfig])
extends AbstractConfig(KafkaConfig.configDef, props, doLog) with Logging { ...
...
オーバーライド def originals: util.Map[String, AnyRef] =
if (this eq currentConfig) super.originals else currentConfig.originals
...
}` クリップボードにコピー
ここで、AbstractConfig
は以下のJavaクラスである:
publicクラス AbstractConfig { ...
...
public Map<String, Object> originals() { ...
Map<String, Object> copy = new RecordingMap<>();
copy.putAll(originals);
copy.putAll(originals); return copy;
}
...
クリップボードにコピーする
以下のコードはScala 2ではコンパイルされるが、Scala 3ではコンパイルされない。
クラス ReplicaManager(val config:KafkaConfig、
...
){
...
protected def createReplicaSelector():Option[ReplicaSelector] = { ...
config.replicaSelectorClassName.mapの{ className =>のようにします。
val tmpReplicaSelector:ReplicaSelector = CoreUtils.createObject[ReplicaSelector](className)
tmpReplicaSelector.configure(config.originals())
tmpReplicaSelector
}
}
...
クリップボードにコピー
このエラーは、Scala 3 のコンパイラが originals
呼び出しの余分な括弧が、たまたま Map
であった戻り値の型を参照していると判断したことに起因する。Scala でオブジェクトに対して ()
を呼び出すと、apply()
に変換されます。このメソッドは存在しないので、コンパイラー・エラーになる。
Scala 2 は賢いのか寛容なのか、余分な括弧が originals
メソッドを参照していると推測した。
シャドーイング
シャドーイングとは、あるスコープに以前存在した名前を宣言すると、以前の名前が見えなくなる状況を指します。
Scala 2ではスコープ内で以前に定義された名前をシャドウイングすることができたが、Scala 3ではその領域が厳しくなり、二重定義とみなされるようになった。一般的な例をここで見ることができる:
クラス Shadow (shadowedName: String) { シャドウ (shadowedName: String)
def shadowedName():文字列 = shadowedName
クリップボードにコピー
自動変換が多すぎる
以下のコードはScala 2では動作するが、Scala 3ではコンパイルに失敗する。
クラス TooManyImplicitsAtOnce () {
val shortNumber1:short = 1
val shortNumber2:Short = 4
val range = shortNumber1 to shortNumber2
クリップボードにコピーする
その理由は、Scala 3はScala 2に比べて、暗黙的な変換や拡張メソッドをいくつも連続して適用することが少ないように思われるからだ。Scala 3では暗黙変換の仕組みが少し見直された[^4]。Scala 2では、to
メソッドが存在しないため、提供されたShort
は自動的にInt
に拡張され、コンパイラはコンパイルを満たすためにスコープ内で可能な暗黙の変換を探していた。Scala 2 は Int
を RichInt
にラップする変換を見つけた。
バイトコード生成の問題
Scalaの歴史的な弱点の一つは、JavaからScalaのコードを利用することだった。この分野ではここ数年の間に多くの改善が行われ、Scala 2.13
ではJavaコードの中からScalaにアクセスできるようになりました。しかし、Scala 3が開発される間に(Scala 2と並行して開発されたことを覚えておいてください)、これらの改善の一部は失われてしまったようです。
大半のケースでは、回避策が可能であり、コードベースの移行を成功させるためにはJavaコードに最小限の変更を加えるだけでよかった。これらの問題の本質は技術的に深く、おそらく別のブログ記事を書く価値がある。
これらのバグがScala 3の開発中ではなく今になって発見された主な理由のひとつは、Scalaの新バージョンはすべて、ほぼ完全にScalaで書かれたプロジェクトのコーパスに対してテストされるからです。つまり、新しいScalaリリースの品質保証テストでは、ScalaとJavaの相互運用性のようないくつかの側面が見落とされる可能性があるということです。Scalaのオープン・ソース・プロジェクトのほとんどは、完全にScalaで書かれているか、Javaクライアントの使い勝手を良くするために薄いAPIレイヤーだけがJavaで書かれている。
Apache Kafkaは、コードのほとんどがJavaで書かれ、Scala 3への移行を試みた最大のプロジェクトであることは間違いない。つまり、KafkaにはScalaのコードを使ったJavaのコードが多くあり、これはScalaのバイトコードがどのように生成されるかという深い内部構造に依存している。
このScala 3への移行を評価する作業のおかげで、Scala 3の将来のバージョンで修正される(または修正される予定の)ユーザビリティの問題をいくつか発見することができました。
もっと見る
もし、この移行の詳細を知りたいのであれば、PR-11350 を見てください。また、この移行について議論しているメーリングリストの スレッド も読んでみてください。
また、AivenブログにもApache Kafka関連のコンテンツがたくさんあります。
*脚注
[1]:Scala 2.13.4
でコンパイルされたアーティファクトは、Scala 3コンパイラで消費できる。
[2]:Apache Kafka 3.1.0
の時点で。
[3]:Scala Centerは、Scalaのツールや教育コンテンツに取り組む非営利団体である。
まとめ
Aivenのサービスをまだご利用でない方はhttps://console.aiven.io/signupから無料トライアルにお申し込みください!
また、changelogとblogのRSSフィード、またはLinkedInとTwitterのアカウントをフォローし、製品や機能関連の最新情報をご確認ください。