Apache Spark用のスタンドアロンJDBCドライバ

SparkにJDBCでアクセスするにはThriftserverを入れたりHive Metastoreが必要だったりで色々面倒なのですが、単体で利用可能な方法はないのかなと思って探してみたところ、以下のものを見つけたので試してみました。

github.com

このJDBCドライバは以下のようなURLでJDBC経由でSparkSQLを使うことができます。

com.zensolution.jdbc.spark:/Users/foobar/temp/console?format=csv&csv.header=true&csv.delimiter=;

SQL内でアクセスされているテーブルはクエリの実行前にテンポラリビューとして自動的に登録されます。たとえば以下のようなSQLを実行したとします。

SELECT * FROM people

このJDBCドライバはまずクエリをパースし、このクエリの実行にpeopleテーブルが必要であることを認識します。そして/Users/foobar/temp/console/peopleディレクトリ配下に保存されているCSVファイルを参照するDataFrameを作成し、SparkSQLからアクセスできるようにテンポラリビューとして登録します。前述の通りこのプロセスはクエリの実行前に自動的に行われます。

このJDBCドライバには動作するのですが、2つの不満点がありました。1つはSparkのローカルモードでの実行しかサポートしていないこと、もう1つはJDBC URLで単一のデータソースしか指定できないことです。そこでこれらの不満点を改善すべく、フォークして変更を加えてみました。

github.com

まずはリポジトリをクローンしてJDBCドライバのjarファイルをビルドします。以下のコマンドでbuild/libs/spark-jdbc-all.jarが生成されるので、このファイルをクラスパスに追加します。

./gradlew clean shadowJar

次に以下のような設定ファイルを作成します。

{
  "tables": [
    {
      "name": "people",
      "path": "examples/src/main/resources/people.csv",
      "format": "csv",
      "options": {
        "header": "true",
        "inferSchema": "true",
        "delimiter": ";"
      }
    },
    {
      "name": "users",
      "path": "examples/src/main/resources/users.orc",
      "format": "orc"
    }
  ]
}

以下のようなURLで任意のスタンドアロンSparkクラスタ(もしくはローカルモード)へのJDBC接続が可能です。

実用的かどうかはさておき、一応動くものができたので個人的には満足ですw

Spark in Action, Second Edition: Covers Apache Spark 3 with Examples in Java, Python, and Scala

こちらもManningの元旦全品半額セールで購入して積んでおいたものです。

かなりページ数はあるものの、あまり効果的とは言えないような図が多用されていたり、丁寧というよりは冗長な記述が多かったりと、ページ数の割に肝心の内容は薄い印象です。コードの説明もまず実行結果を示してからコードの説明をするという流れで、自分の好みの問題もあるかもしれませんが読みにくいことこの上なかったです。もちろん英語だからということもあるとは思いますが、個人的には読み進めるのが苦痛な感じでした。

また、サブタイトルに「Examples in Java, Python, and Scala」とありますが、書籍内のサンプルはすべてJavaで書かれているのも意表を突かれる感じです。正直Spark3対応のSpark入門書であればオライリーのLearning Sparkの方がおすすめです。

takezoe.hatenablog.com

ただ、独自データソースの作り方などカスタマイズ部分にも触れられているのは本書のアドバンテージと言えるかもしれません。また、JavaからSparkを使いたいという場合も本書のサンプルコードが参考になるのではないかと思います。

全体的にはかなり初心者向けのSpark入門書で、内容の薄さやサンプルコードがJavaで書かれているという特殊性もあり、わざわざ英語でこの本を読まなくてもいいかなという感想です。改訂版も出ているということはFirst Editionもそれなりに売れたのではないかと思うのですが、Manningにしては若干期待外れの一冊でした。

presto-client-rubyのメンテナになりました

トレジャーデータでOSSとしてPrestoのRubyクライアントを公開しているのですが、最近諸事情によりこちらをいじる機会が出てきたのでメンテナにしてもらいました。

github.com

rubygems.org

これまでにHTTPのリダイレクト対応やgzip圧縮のサポートなどを追加したのですが、Trinoに対応させてtrino-client-rubyに転生させる作業なども進めていこうと思います。

なお、まだそんなに難しいことはしてないのもあるのですが、Rubyはまるでわからないのでこのライブラリを使っている社内のアプリケーションを含め、Rubyコードはほぼ勘とコピペで書いています。エコシステムもまだいまいちよくわかっていないのですが、とりあえずRubyGemsMavenと違って気軽にpublishできるのはいいですね…。

-WconfオプションでScalaコンパイラの警告を抑制する

Scala 2.13.2で-Wconfというオプションが追加されており、コンパイラが出力する警告を細かくカスタマイズすることができるようになっています。このオプションはScala 2.12系にもバックポートされており、Scala 2.12.13で利用可能です。

www.scala-lang.org

たとえばScalaコンパイラ-deprecationオプションを指定すると非推奨のAPIの使用に対して警告を出力しますが、build.sbtに以下のような設定を追加することで、自動生成されたコードに対しては警告を出力しないようにできます。

scalacOptions := Seq(
  "-deprecation",
  "-Wconf:cat=deprecation&src=src_managed/.*:s"
)

指定方法は-Wconf:<filters>:<action>,<filters>:<action>,...という感じで、フィルタ部分で対象の警告を指定、アクションでその警告をどう扱うかを指定します。

フィルタでは対象の警告を以下のような条件で指定可能です。条件は&区切りで複数指定可能です。また、deprecationの警告に関してはoriginsinceというオプションを使用してさらに細かく対象を絞り込むこともできます。

  • any すべての警告
  • cat=deprecation 警告のカテゴリを指定
  • msg=regex メッセージを正規表現で指定
  • site=my\.package\..* 対象クラスを正規表現で指定
  • src=src_managed/.* 対象ソースファイルを正規表現で指定

アクションではその警告をどう扱うかを指定します。warningとinfoはwarning-summary / ws もしくは info-summary / is と指定することでまとめて表示することもできます。

  • error / e エラーとして報告
  • warning / w 警告として報告(デフォルト)
  • info / i 警告としてはカウントせずに報告
  • silent / s 警告として報告しない

GitBucketで未使用のインポートに警告を出すようにしてみたところ、Twirlが自動的にインポートするクラスとSlickのモデルクラスで必要なインポートが警告になってしまうので以下のような感じの設定を追加してみました。

scalacOptions := Seq(
  "-Wunused:imports",
  "-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
)

また、-Wconfオプションだけでなく、コード中で @nowarn アノテーションを付与することで警告を抑制することもできるようです。

これまで自動生成コードなどで警告が出すぎてしまうのでコンパイラのlintオプションを有効にするのを躊躇っていた部分もあるのですが、このオプションで不要な警告を抑制できるのでコンパイラによるチェックを積極的に活用できるようになりそうです。

アーセン・ヴェンゲル自伝 赤と白、わが人生

以前読んだベンゲル監督の自伝の日本語版が出版されました。原著も読んでいたのですが、お布施と思ってこちらも購入してみました。

さすがに日本の出版社から出た書籍なので、ペーパーバッグクオリティだった原著と比べると装丁は非常に丁寧で紙質や印刷も段違いです。英語版の方が数は出ると思われるのですが、原著のペーパーバッグ版とさほど変わらない値段でこのクオリティの本が作れるのは一体どういうことなのだろう?と思ってしまいました。一度読んでいるので内容については特に言うことはないです。以下の原著のレビューを参考にしていただければと思います。

takezoe.hatenablog.com

ただ、残念なことに原著では巻末に結構なページ数を割いて掲載されていたスタッツが日本語版ではまるっと省略されています。日本語版ではきちんと印刷用にレイアウトされたスタッツが掲載されているのでは…と期待していただけに非常に残念です。

確かにこの部分を掲載するとページ数が結構増えてしまいそうですし、そもそも原著を読んでいなければこの部分が存在したことはわからないはずではあるのですが、ベンゲル監督の自伝自体ファンアイテムの一種と思われるので、こういった部分もファンにとっては大事な要素なのではないかと思うのですが、翻訳版としてこれはどうなのか…。

まあ、そんなわけで、本書は原著の完全な翻訳版というわけではなく省略されている部分があるのでお買い上げの際はご注意ください!ということを書きたかったのでした。

Classic Computer Science Problems in Java

正月にManningが全品半額セールをやっていたのでなんとなく購入しておいたものですが、読んでみたら思いの外面白かったです。

シンプルな検索アルゴリズムから後半はクラスタリングニューラルネットワークなどまで、シンプルなJavaプログラムで解説されています。まずはジェネリックフレームワークを作成し、そのフレームワークを使用して具体的な問題を解く、という流れになっており、サンプルコードのシンプルさや、各章の最後にあるエクササイズのほどよい難易度(難しすぎない)もあって、自分で実際に手を動かしてみようという気にさせてくれます。ノリ的には「作って学ぶアルゴリズム」とでもいうような感じです。また、Javaのデータ構造や普段あまり使わない数学関係のライブラリについて復習することができたのも予想外のメリットでした。

一方で、扱っているのはプリミティブなデータ構造やアルゴリズムではなく比較的抽象度の高いもので「Classic Computer Science Problems in Java」というタイトルから想像される内容とは若干異なるイメージを持たれる方もいらっしゃるかもしれません。数学的な知識が必要な箇所もあるのですが、この書籍の範囲外ということで説明は必要最小限に留められています。アルゴリズムの理解そのものについては支障はないとは思うのですが、そのような部分を求めてこの本を読むと肩透かしを食らったような感じになってしまうかもしれません。

なお、このClassic Computer Science ProblemsシリーズはJava版以外にもPython版となぜかSwift版がすでに出ているようです。目次を見る限り扱っているアルゴリズムは同じようなので、これらの言語をお使いの方はそちらを読まれるのが良いかもしれません。

とにかく完成度が高く、読んでいて楽しいエンターテイメント性のある一冊でした。普段ビジネスアプリケーションの開発に従事しているとこのようなアルゴリズムに触れる機会はなかなかないと思うのですが、そういった方にこそおすすめしたい一冊です。

Apache SparkをLivy経由でZeppelinから使ってみる

SparkにREST API経由でアクセスできるようにするApache Livyは以前から知っていたのですが、アドホックなジョブを投げるにはやっぱり画面がないと辛いなぁと思っていたところ、ZeppelinにもLivy用のインタプリタがあるっぽいのでこれを使えばZeppelinのノートブックからLivy経由で簡単にSparkにジョブを投げつけることができるのではと思い試してみました。

github.com

github.com

Livy側の設定

LivyはデフォルトではSparkをローカルモードで実行します。Sparkクラスタに接続するには conf/livy.conf にSparkのmasterのアドレスを設定しておきます。Livyによってセッションが動的に生成されるためYARNモードでの実行が推奨されていますが、スタンドアロンクラスタでも動作はします。

# What spark master Livy sessions should use.
livy.spark.master = spark://spark-master:7077

SPARK_HOMEHADOOP_CONF_DIRを指定して起動。

$ export SPARK_HOME=/usr/lib/spark
$ export HADOOP_CONF_DIR=/etc/hadoop/conf
$ ./bin/livy-server start

LivyのURLはデフォルトでは http://localhost:8998 になります。ブラウザでアクセスするとWeb UIが表示されます。

Zeppelin側の設定

特に何も考えずに起動すればOKです。ZeppelinはSparkも内蔵しているので別途インストールする必要がないのは楽です。

$ ./bin/zeppelin-daemon.sh start

ブラウザで http://localhost:8080 を開き、Livyインタプリタの設定でLivyサーバのアドレスを指定しておきます。Livyをローカルで立ち上げている場合はデフォルトの設定のままで大丈夫なはずです。リソースのアロケーションなど必要な設定も行っておきます。

f:id:takezoe:20210222124622p:plain

使ってみる

Zeppelin上でlivyインタプリタを指定してノートブックを作成します。

f:id:takezoe:20210222125102p:plain

適当なScalaコードを実行。

f:id:takezoe:20210222125118p:plain

ZeppelinからアクセスするとLivy側ではこんな感じでセッションが自動的に作られます。同一のZeppelin上で複数のノートブックを作成した場合、セッションは共有されるようでした。

f:id:takezoe:20210222125132p:plain

Sparkクラスタに直接アクセスできない環境でも、ありものを組み合わせるだけでUIからHTTPプロキシ経由でジョブを実行できるようにできるのは便利なのですが、ZeppelinはせっかくScalaで書いていても入力補完が微妙だったりするのが残念な感じなので、IntelliJから直接Livyに投げつけられたりするプラグインがあったりすると便利かもしれないと思いました。ただ、Livyは最近あまり開発が活発ではない感じなのが若干気になるところです。Spark 3対応もmasterブランチにマージはされているもののまだリリースされていないという状態のようです。