Databricks社がOSS化したSpark用ストレージレイヤ「Delta Lake」について

先日開催されたSpark + AI Summit 2019にあわせてDatabricks社からSpark用のストレージレイヤ「Delta Lake」のOSS化が発表されました。

databricks.com

GitHubリポジトリはこちら。

github.com

Delta LakeはSparkのライブラリとして実装されており、分散ストレージ上で以下のような機能を提供します。

実際に動かしてみる

Delta Lakeの動作にはSpark 2.4.2以降が必要です。ローカルファイルシステムでも動作するのでspark-shellで動きを確認してみました。

$ bin/spark-shell --packages io.delta:delta-core_2.12:0.1.0

まずはテーブルへの書き込みと読み込みをやってみます。

scala> val df1 = Seq((9, "Lacazette", "France"), (14, "Aubameyang", "Gabon"), (17, "Iwobi", "Nigeria"), (23, "Welbeck", "England")).toDF("number", "name", "country")
df: org.apache.spark.sql.DataFrame = [number: int, name: string ... 1 more field]

scala> df1.write.format("delta").save("/tmp/arsenal")

scala> val df2 = spark.read.format("delta").load("/tmp/arsenal")
df: org.apache.spark.sql.DataFrame = [number: int, name: string ... 1 more field]

scala> df2.show()
+------+----------+-------+
|number|      name|country|
+------+----------+-------+
|    14|Aubameyang|  Gabon|
|     9| Lacazette| France|
|    23|   Welbeck|England|
|    17|     Iwobi|Nigeria|
+------+----------+-------+

追記してみます。

scala> val df3 = Seq((10, "Ozil", "Germany"), (11, "Torreira", "Uruguay"), (15, "Maitland-Niles", "England"), (34, "Xhaka", "Swizerland")).toDF("number", "name", "country")
df3: org.apache.spark.sql.DataFrame = [number: int, name: string ... 1 more field]

scala> df3.write.format("delta").mode("append").save("/tmp/arsenal")

scala> val df4 = spark.read.format("delta").load("/tmp/arsenal")
df4: org.apache.spark.sql.DataFrame = [number: int, name: string ... 1 more field]

scala> df4.show()
+------+--------------+----------+
|number|          name|   country|
+------+--------------+----------+
|    15|Maitland-Niles|   England|
|    34|         Xhaka|Swizerland|
|    14|    Aubameyang|     Gabon|
|    11|      Torreira|   Uruguay|
|     9|     Lacazette|    France|
|    23|       Welbeck|   England|
|    17|         Iwobi|   Nigeria|
|    10|          Ozil|   Germany|
+------+--------------+----------+

タイムトラベルで最初のバージョンのテーブルを参照してみます。ここではversionAsOfというオプションでバージョンを指定していますが、timestampAsOfというオプションで日付を指定することもできるようです。

scala> val df5 = spark.read.format("delta").option("versionAsOf", 0).load("/tmp/arsenal")
df5: org.apache.spark.sql.DataFrame = [number: int, name: string ... 1 more field]

scala> df5.show()
+------+----------+-------+
|number|      name|country|
+------+----------+-------+
|    14|Aubameyang|  Gabon|
|     9| Lacazette| France|
|    23|   Welbeck|England|
|    17|     Iwobi|Nigeria|
+------+----------+-------+

スキーマバリデーションを試してみます。数値のフィールドに文字列を入れようとするとこんな感じでエラーになります。

scala> val df6 = Seq(("8", "Ramsey", "Wales")).toDF("number", "name", "country")
df6: org.apache.spark.sql.DataFrame = [number: string, name: string ... 1 more field]

scala> df6.write.format("delta").mode("append").save("/tmp/arsenal")
org.apache.spark.sql.AnalysisException: Failed to merge fields 'number' and 'number'. Failed to merge incompatible data types IntegerType and StringType;;
  at org.apache.spark.sql.delta.schema.SchemaUtils$.$anonfun$mergeSchemas$1(SchemaUtils.scala:526)
  ...

また、存在しないカラムを追加しようとすると以下のようなエラーになります。エラーメッセージにあるようにmergeSchemaオプションを付けるとスキーマを変更できます。

scala> val df6 = Seq((8, "Ramsey", "Wales", "Juventus")).toDF("number", "name", "country", "next_club")
df6: org.apache.spark.sql.DataFrame = [number: int, name: string ... 2 more fields]

scala> df6.write.format("delta").mode("append").save("/tmp/arsenal")
org.apache.spark.sql.AnalysisException: A schema mismatch detected when writing to the Delta table.
To enable schema migration, please set:
'.option("mergeSchema", "true")'.

Table schema:
root
-- number: integer (nullable = true)
-- name: string (nullable = true)
-- country: string (nullable = true)


Data schema:
root
-- number: integer (nullable = true)
-- name: string (nullable = true)
-- country: string (nullable = true)
-- next_club: string (nullable = true)


If Table ACLs are enabled, these options will be ignored. Please use the ALTER TABLE
command for changing the schema.
...

ファイルレイアウトや内部動作について

ファイルはテーブル毎に以下のようなレイアウトで書き込まれます。

f:id:takezoe:20190503131318p:plain

データはコミット毎に追加分がParquetで書き込まれ、_delta_logディレクトリに差分情報がJSON形式で保存されます。読み込む際は差分ログをリプレイして必要なParquetファイルを読み込みます。この仕組みによってACIDトランザクションJSONファイルの書き込みによってコミット)とタイムトラベル(JSONの差分ログをリプレイすることで任意の時点のデータを復元)が可能になっています。なお、トランザクションには楽観的排他制御が使用されており、別トランザクションで先に書き込みが行われた場合はトランザクションが失敗するようになっています。

このように差分を積み重ねていく方式のため、一度作成したParquetファイルを更新する必要がなく高速に書き込みを行うことができます。また、スキーマなどのメタデータJSONの差分ログ内に書き込まれるので、中央集約型のメタデータストアがボトルネックになることはないという設計のようです。

ただ、これだけだと履歴が増えるとParquetファイルも増えるので当然読み込みが遅くなります。そのため、_delta_logディレクトリ内にチェックポイント毎にスナップショットのParquetファイルが作成され、スナップショットが存在する場合は実際には直近のスナップショットに対してそれ以降の差分を適用するという動作になります。スナップショットはデフォルトでは10コミット毎に作成されますが、これはテーブルの性質によって調整する必要があると思います。

まとめ

Delta LakeはSparkに特化していることもあり、意外とコンパクトな実装になっています。スナップショットの作成などもSparkの機能が活用されており、実装としては思っていたよりもシンプルなものでした。今後レコードの更新・削除やデータのバリデーションなどもサポート予定のようです。

スナップショットをどんどん作っていくのでストレージの消費が大きくなりそうなのと、Spark(の特定バージョン)への依存性といったあたりが懸念材料でしょうか。ファイルレイアウトや仕組み自体はSparkに特化したものではないので他の分散コンピューティングエンジン向けのドライバを書くこともできそうです。

まだざっとコードを読みつつ手元で軽く動かしてみた程度なので見落としている部分もあるかもしれませんが、ひとまずこんなところです。ソースを読み込みつつもう少し研究してみたいと思います。

Scala用のLanguage Server「Metals」をAtomで使ってみる

MetalsはScalametaのサブプロジェクトとして開発されているScala用のLanguage Serverで、Visual Studio CodeAtomVimEmacsなどのテキストエディタScalaプログラミングを行う際にIDEのような機能を利用できるようにするものです。

scalameta.org

コード補完などの機能が実装されたMetals 0.5.0がリリースされたとのことなので早速試してみました。エディタとしては、フル機能は使用できないようですが普段使っているAtomを使ってみました。

セットアップは簡単で、Atomのパッケージマネージャで「ide-scala」パッケージをインストールするだけです(環境変数JAVA_HOMEが設定されている必要があります)。他にもいくつか必要なパッケージがありますが自動的にインストールされます。Visual Studio Codeでも同様に簡単にインストールできるようです。

パッケージの設定画面で使用するMetalsのバージョンを指定できます。

f:id:takezoe:20190414003701p:plain

パッケージをインストール後、build.sbtがあるディレクトリを開くとプロジェクトをインポートするかどうかを確認するダイアログが表示されます。Metalsは直接sbtを実行するのではなくBloopを使っているようです。Bloopについては以前ブログを書きました。

takezoe.hatenablog.com

インポートされない場合はコマンドパレットから「Metals: Import Build」というコマンドを実行すると強制的に再インポートを行うことができます。そこそこ時間がかかりますが、インポートが終わるとMetalsが提供する機能を利用できるようになります。

コンパイルエラーはエディタ上に表示されますし、CTRL + クリックでコードジャンプも可能です。

f:id:takezoe:20190414003719p:plain

ScaladocやJavadoc、メソッド定義などの情報をホバー表示することもできます。

f:id:takezoe:20190414003736p:plain

コード補完もちゃんと効きます。Visual Studio Codeだとパラメータヒントも表示されるようです。ただ、IntelliJのように同時にimport文を付け足してくれたりするような便利補完機能はないです。import文を再編成する機能もまだありません。

f:id:takezoe:20190414003809p:plain

なお、プロジェクトには.metals.bloopというディレクトリが作成されるので、.gitignoreに追加しておくとよいでしょう。

Bloopを使っているのでsbtプラグインに依存するプロジェクトだと問題があったり、機能面でもIntelliJの代わりに使うにはまだまだ厳しい感じがありますが、Ensimeの開発が止まってしまったのでテキストエディタを使ってScalaを書きたいという方には期待のプロジェクトでしょう。個人的にはEmacsでも試してみたいところです。

Java逆引きレシピ 第2版が発売されます

5年ほど前に翔泳社さんで共著で執筆させていただいた逆引きJavaレシピの改訂版が発売になります。

Java逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)

Java逆引きレシピ 第2版 (PROGRAMMER’S RECiPE)

第一版の内容をベースにしつつ、大きなトピックとしてはJava 11、JUnit 5に対応しています。Java 11については執筆期間などの問題や、本格的な採用はこれからということを考えると情報がまとまっていた方がキャッチアップにも役立つのではということで、最後の章にまとめる形にしました。

今回の執筆にあたりJava 9以降の変更を改めて振り返ってみて、ここ数年でJavaも大きく変化しているということを実感しました。これまで言語として安定しているという点は良くも悪くもJavaの大きな特徴の1つでもあったわけですが、今後は開発言語としてJavaを採用する場合でもきちんと新しいバージョンをキャッチアップしていくことができるのか?ということを考えないといけないのかもしれません。

作業自体は昨年の夏頃から始めていたのですが、Java 11のリリースを待つ必要があり、だいぶ時間がかかってしまいました。また、改訂作業なので楽かと思いきや、第一版の原稿に校正作業で入れた変更を反映したり、正誤表の内容を反映したりといった事前作業だけで数ヶ月かかってしまったり、個人的にも転職を挟む形になってしまうなど、しんどい部分も多かったですがなんとか予定通り出すことができてようやく一区切りという感じです。

GitBucket 4.31.0、4.31.1をリリースしました

Scalaで実装されたオープンソースのGitサーバ、GitBucket 4.31.0および4.31.1をリリースしました。

4.31.0では多くの新機能が利用可能ですが、すでにバグ修正版である4.31.1がリリースされていますので、こちらを利用することをお勧めします。

CIプラグインでDockerをサポート

gitbucket-ci-plugindockerdocker-composeがサポートされました。

f:id:takezoe:20190317114732p:plain

f:id:takezoe:20190317114753p:plain

管理者が有効にすることで各リポジトリのビルド設定でビルドに使用するDockerfileもしくがdocker-compose.ymlを選択できるようになります。なお、この機能を利用するにはGitBucketが稼働しているマシン上にdockerがインストールされている必要があります。

GPG署名済みコミットの検証

GPG署名済みコミットの検証結果をコミット一覧画面に表示するようになりました。

f:id:takezoe:20190317114809p:plain

Web APIの強化

OAuth2 Token (sent as a parameter)認証がサポートされました。また、以下のAPIが新たに追加されました。

OGPのサポート

Open Graph protocolで定義されたmetaタグ(og:title, og:imageなど)を出力するようになりました。ブログやソーシャルメディア、チャットなどでGitBucketのURLを共有する際に便利です。

ユーザ名補完にアバターも表示

ユーザ名の補完時にユーザ名だけでなくアバターを表示するようになりました。

f:id:takezoe:20190317114845p:plain

今回のバージョンではこの他にもMySQL8のサポートなどの改善や、様々なバグフィックスを行っています。詳細については4.31.0でクローズされたIssueの一覧および4.31.1でクローズされたIssueの一覧をご覧ください。

Cherry MX ロープロファイル赤軸キーボード「Majestouch Stingray」を買ってみた

以前購入した青軸ロープロファイルスイッチのキーボードが思いの外具合が良かったので、これの赤軸版があればなぁ…と思っていたところFILCOからCherry MXのロープロファイルスイッチを使ったキーボードが発売されたとのことで思わず購入してしまいました。

実際に試してみたところ、もっとスコスコした感じを想像していたのですが、思っていたより重いキータッチだなという印象です。キーキャップはマットな感じで筐体もしっかりしているので高級感はあります。自分が購入したのはテンキーレスモデルですが、それでも重量がかなりあるので持ち運びには向いていません。また、キートップを外すための工具や交換用のキートップPS2用のアダプタ(!)が付属しています。

f:id:takezoe:20190307011313j:plain

自宅ではデスクが狭いこともありHHKBやThinkPadキーボードのようなコンパクトキーボードをずっと使っていたのですが、このくらいの配列の方がFnキーとのコンビネーションを使う必要もありませんし、実はThinkPadキーボードのトラックポイントが意外と肩凝りの原因になっているのではという疑惑もあるのでしばらくこのキーボードを使ってみようと思います。

キーボードを買ってはトラックポイントの便利さに抗えずThinkPadキーボードに戻るという行為を繰り返しているのですが、果たして今回は使い続けられるでしょうか…。

Treasure Data hosted Tokyo Scala Developers Meetup!

f:id:takezoe:20190303134341j:plain

2月28日(木)に大手町のGlobal Business Hub Tokyoで開催されたTokyo Scala Developersコミュニティのミートアップをトレジャーデータでホストさせていただきました。

www.meetup.com

Tokyo Scala Developersは主に東京在住の外国人Scalaプログラマのコミュニティで、主催者のGabrielさんがTwitterで開催場所を探しているのを見かけてトレジャーデータでの会場提供を提案させていただきました。

当日はあいにくの雨天でしたが20名以上の方に参加いただきました。トークの前に弊チームの@taroleoさんからトレジャーデータや、トレジャーデータのメンバーが開発しているScalaOSSの紹介をさせていただきました。

f:id:takezoe:20190303132429j:plain

発表者はお二方ともPaidyさんの方とのこと。まずはValentinさんによる「Literally type magic」というトーク

f:id:takezoe:20190303132457j:plain

歓談タイムを挟んでGabrielさんによる「Haskell for Scala developers」。ScalaHaskellで同じことをするコードを比較されていました。

f:id:takezoe:20190303132528j:plain

運営がややバタついてしまった部分もあり、私自身はあまりトークを聞いている余裕がなかったのですが、参加者の皆さんにお楽しみいただけたのであれば幸いです。また、個人的に久しぶりにお会いできた方もおり、近況を伺うことができてよかったです。

今回はトレジャーデータとして会場提供させていただきましたが、東京で参加できるScalaの英語コミュニティということで、次回以降も都合がつけば是非参加したいと思います。(発表者も求むとのことです!)

トレジャーデータのマウンテンビューオフィスで勤務してみた

f:id:takezoe:20190302011613j:plain

トレジャーデータはマウンテンビューに本社があり、東京とマウンテンビューのオフィス以外にも世界中に分散したチームを持っています。

私の所属するチームはTech Leadの@taroleoさんがマウンテンビューオフィスで勤務しているということもあり、オンボーディングの一環として私も2週間マウンテンビューのオフィスで勤務させてもらうことになりました(通常は入社3ヶ月以内に本社に行くケースが多いのですが、昨年は中々都合がつかず、入社から約半年経ってからの渡米となりました)。

到着直後はジェットラグが酷く、2日ほどはまるで使い物にならない感じでしたが、3日目くらいからは夜もちゃんと眠れるようになり、まともに仕事ができる状態になってきました。1週間の滞在だと落ち着いて仕事をするには短すぎるので、2週間はちょうどいい期間なのではと思います。オフィスから車で10分ほどのアパートメントに滞在し、毎日Uber通勤だったので通勤によるストレスがほぼゼロだったのは有難かったです。

こちらのオフィスは東京のオフィスと比べるとべらぼうに広く、空間的にだいぶ余裕がありました。デスクはスタンディングにもできる電動昇降式のものが導入されており、座って作業する場合でも高さを微調整できてなかなか良いものでした。

ランチにはEAT Clubというデリバリーサービス(毎日自分の食べたいメニューをWebサイトで選んでオーダーしておくとオフィスに届けてくれる)が導入されている他、ドリンクやアメリカンなスナックなどが無料で提供されており、とりあえず食べるものに困ることはありませんでした。一方で宿泊していたアパートメントの周囲には大きなスーパーマーケットなどがなく、買い物には結構困りました。夕食はオフィスからカップ麺を持ち帰って食べたりもしていましたw

f:id:takezoe:20190302011721j:plain

さて、肝心の仕事に関してですが、オンサイトで@taroleoさんと作業することができ、これまでずっと悩んでいたことが一発で解決したり、私の滞在とタイミングをあわせて渡米してきてくれたチームメイトも交えて3人でハッカソン的に開発をしたりと、同じロケーションで作業するメリットを大いに実感しました。

リモートワークや分散チームには採用対象の拡大、サポートや運用のタイムゾーンを分散できるなどのメリットもあるのですが、やはりオーバーヘッドも大きいと感じます。私の場合は英語力的な問題でリモートでのコミュニケーションに難しさを感じることも多いため、なおさらオンサイトで作業する機会の重要性を感じました。

また、マウンテンビュー滞在中に偶然にも前々職の同僚と2人も会うことができたのは嬉しい驚きでした。お二方とも数年ぶりの再会がまさかシリコンバレーとは予想だにしていなかったので世の中本当に狭いものだなぁと思いました。昔話をしつつ、現在や未来の話もしつつ、楽しいひと時を過ごさせていただきました。

これまでの海外滞在はカンファレンスへの参加など長くても一週間弱だったので、出発前は「二週間は長いなぁ」と思っていたのですが、終わってみればあっという間、短期間ではありましたが実りの多いマウンテンビュー勤務の二週間でした。東京に戻って心機一転頑張っていきたいと思います。