RubyKaigi 2019参加レポート〜sonots登壇セッション & エンジニア8名による厳選セッション

f:id:vasilyjp:20190426171300p:plain

こんにちは!
2019/4/18 - 20に福岡国際会議場で開催されたRubyKaigi 2019にZOZOテクノロジーズもRubyスポンサーとして協賛しました。
弊社からも8名のエンジニア(@takanamito, @rllllho, @katsuyan121, @TrsNium, @AmatsukiKu, @takeWakaMaru666, Takehiro Shiozaki ,@sh_ngsw)が参加し、SREスペシャリストである瀬尾(@sonots)が登壇しました!

今年のRubyKaigiは、60を超える講演があり、参加者も1000名を超える大規模なカンファレンスでした。 この記事では、多くの講演の中でも特に気になった講演を弊社から参加したメンバーがそれぞれ報告します。 またsonotsの登壇内容と、スポンサーとしての活動について報告します。

RubyKaigi 2019登壇

今年のRubyKaigiではZOZOテクノロジーズの瀬尾(sonots)がメルペイ社のhatappi氏と共同でRed Chainer and Cumo: Practical Deep Learning in Rubyというタイトルで登壇しました。

スライドはこちらです。

Rubyで深層学習を使えるようにするプロジェクトであるRed Chainerと、Rubyにおける数値演算ライブラリであるNumo/NArrayのGPU対応を行ったCumoライブラリの紹介および去年の発表からの進捗について解説しました。

hatappi氏は、Rubyにおける深層学習(DNN)ライブラリ周辺の現況の解説と、なぜRed Chainerを作り始めたのか動機について説明しました。現在のRed Chainerが抱えている課題として「速度」、「 他言語のDNNフレームワークとの連携」の2つをあげました。

速度の面はCumoが、連携の面はONNX(Open Neural Network Exchange Format)を仲介することによって解決できると説明しました。 hatappi氏は後者の解決を目的として新しくonnx-red-chainerというgemを作成したと紹介しました。 Preferred Networks, Inc.(PFN)が開発しているChainer(Python)で記述されたモデルをonnx-chainerでONNXモデルに変換し、そのONNXモデルからonnx-red-chainerでRed Chainer(Ruby)のモデルコードを自動生成できます。

sonotsは去年からの進捗として、CumoのRed Chainer組み込みと、CumoのConvolutional Neural Networks(CNN)対応について紹介しました。 Red Chainerによる正式なCumo対応によって、Numo(CPU)を使うのかCumo(GPU)を使うのか動的に選択できるAPIが増え、ユーザにとって格段に使いやすくなりました。 また、cuDNNライブラリを使うことによって、高速なCNNの学習に対応したと発表しました。昨今のDNN隆盛の走りとなったCNNの対応は「実用的な」DNNフレームワークとして必須の機能であり、そのCNNの1つであるResNet-18の学習が以前は23日かかっていたものが17時間と現実的な時間で終わるようになったと発表しました。「Ruby 3は3倍速になると言われていますが、Cumoを使うとRed Chainerの学習が32倍速になります」と成果を述べました。

最後にsonotsは自身が以前に一年半ほどPFNに出向して開発していたChainerXについて紹介しました。ChainerXによりPythonのオーバーヘッドが減り高速化し、新しいデバイスのサポートも容易になり、Python処理系のないIoTデバイスのような環境にもデプロイできるようになります。このChainerXへのRubyバインディング開発を行うのも1つのプロジェクトとして面白いのではないでしょうかと提案して締めくくりました。

RubyKaigi 2019講演紹介

Performance Optimization Techniques of MessagePack-Ruby

@katsuyan121 です。私からは@frsyuki氏のMessagePackのRuby実装に関するお話を紹介します。

MessagePackは、JSONと互換性のあるバイナリシリアライズフォーマットです。MessagePackは50を超える言語をサポートしており、多数のプロジェクトに使われています。 公式ページで対応言語の一覧やMessagePackを利用しているサービスを確認できます。 今回はそのうちのMessagePackのRuby実装についてのお話でした。

最初にRubyオブジェクトとMessagePackとのシリアライズ・デシリアライズがどのような仕組みで動作しているかの解説がありました。 仕組みはすごく単純で、すごくわかりやすい図で解説されていたのでぜひ資料をご覧ください。

次にシリアライズ・デシリアライズを高速化する仕組みについて紹介していました。 1つ目にZero-copy writeについて紹介がありました。 長い文字列に関してはシリアライズ時に rb_str_dup を利用し copy-on-write することで高速化をしているそうです。 しかし、SHARABLE_SUBSTRING_P()true の時にしか使えないという問題についても言及していました。

2つ目にReserved memory poolについての紹介がありました。 MessagePackで使う領域を予め確保し、利用時は確保した領域を使うことでmemoryの割当時間を短縮しているとのことでした。

ベンチマークではJSONのシリアライズ・デシリアライズよりも圧倒的にMessagePackのほうが早いことを示していました。 また最適化がどのように効いているかもベンチマークにあらわれていて感動しました。

最後にRuby以外の言語でJavaとC#実装のMessagePackについての紹介がありました。それぞれの言語で行われている最適化手法が紹介されており、すごく興味深い内容でした。 Q&AではRubyコミッタとの白熱した議論がみられ大変深い話を聞くことができました。ぜひスライドだけではなくYouTubeの動画が公開されたらご覧になることをおすすめします。

Pattern matching - New feature in Ruby 2.7

@rllllho です。私からは@k_tsjさんによるRuby 2.7に実験的に導入されたパターンマッチングについて構文の説明と設計方針について紹介します。 スライドはこちらです。

Ruby 2.7ではパターンマッチングはcase/whenに複数の変数代入できるものという扱いで取り入れられています。
パターンマッチングを使用する際は case inを用います。

基本的なパターンマッチングの記法は下記の様になります。 elseがない場合は、NoMatchingPatternErrorがraiseされます。

case
in パターン
in パターン
else
  # どのパターンにも当てはまらない場合
end

Rubyで頻繁に使用するArrayとHashにもパターンマッチングが使用できます。 またパターンに変数を使用するとマッチした値を変数に代入して使用できます。

# Array
case [0, [1, 2, 3]]
in [a]
 # ここには当てはまらない
in [0, [a, 2, b]]
  p a #=> 1
  p b #=> 3
end

# Hash
case {a: 0, b: 1}
in {a: 0, x: 1}
  # ここには当てはまらない
in {a: 0, b: var}
  p var #=> 1
end

Hashのパターンマッチが使えると、パースしたJSONを簡単に扱える様になります。

# JSON
{
  "name": "Alice",
  "age": 30,
  "children": [
    {
       "name": "Bob",
       "age": 2
    }
  ]
}

# パターンマッチングを使う場合
case JSON.parse(json, symbolized_names: true)
in {name: "Alice", children: [{name: "Bob", age: age}]}
  p age #=> 2
end

# パターンマッチングを使わない場合
person = JSON.parse(json, symbolized_names: true)
if person[:name] == "Alice"
  children = person[:children]
  if children.length == 1 && children[0][:name] == "Bob"
    p children[0][:age] #=> 2
  end
end

現在もネストとしたHashを扱うことができるdigという記法がありますが、パターンマッチングを使うことでより柔軟にネストしたHashを扱えるようになりそうです。 APIレスポンスのJSONを扱う時などに活躍しそうです。

パターンマッチングを設計する上で、以下の2つを意識しているとのことです。

  • 互換性を保つ
  • Rubyらしくあること

互換性を保つために、新しい予約語は導入しないと決めていたそうです。

case文でパターンマッチングを導入する際に、whenの代わりに何を使うかについては下記の条件を満たす必要があります。

  • 語彙として自然であること
  • 文法としてなりたつ。式の先頭にくることができない

最初はこれらを満たす様な予約語は思いつかず記号を駆使してなんとかしようと考えられていたそうなのですが、ある日inを思いついたそうです。
inという語彙はすでにfor文で使われており、上記の条件を満たすということでcase inが採用されました。

まだ仕様は確定ではなく議論段階だそうですが、パターンマッチングが導入されることでさらにRubyを楽しく書くことができますね!
スライドの終盤には、今後のパターンマッチングの機能提案などもありパターンマッチングのこれからも楽しみです。とてもワクワクする発表でした!

Building Serverless Applications in Ruby with AWS Lambda

スライドはこちらです。

Takehiro Shiozaki です。 昨年のre:Inventで発表されたように、aws Lambdaの上でRubyが動くようになりました。awsのAlex WoodさんによるRuby on Lambdaでアプリケーションを作る時に使うと便利なツールやライブラリの紹介がありました。AlexさんはLambdaでRubyを動作させるためのランタイムの開発者であり、またaws-sdk gemのメンテナでもあります。

発表の前半ではサーバーレスとは何かという事に関する概要の説明がありました。 サーバーレスとは単にサーバー(EC2インスタンスなど)をなくすという意味ではないとのことです。 アプリケーションコードとテストコード以外の煩雑な部分をLambdaやその周辺ツールに任せることで、開発者が本質に集中できるような思想とのことです。 ユーザーの認証・スケーリング・OSのパッチなどの問題などが前述した煩雑な部分の一例です。

後半ではより具体的にどんなツールやgemを使うと便利なのかを紹介されていました。 まず紹介されていたツールはaws sam cliです。 これそのものはPythonで書かれたツールですが、各種言語でサーバレスアプリケーションを作る時の便利ツールがまとまっており、Rubyにも対応しています。ローカル環境でLambda関数のテストをしたり、Lambda関数のデプロイを行うための機能などがあります。次に紹介されていたgemはaws-sdk-rubyです。

github.com

awsでRubyを利用しているのならばほぼ間違いなく利用しているであろうgemです。 awsの各種サービスに対するアクセスをラップし、Rubyっぽいインタフェースを提供してくれます。 さらにaws-recordというgemも紹介されていました。 このgemはDynamoDBのO/Rマッパーで、ActiveRecordのようなAPIによってDynamoDBに対してアクセスを行うことが出来ます。 また、スキーママイグレーション機能も持ち合わせています。LambdaとRDBとの相性は悪いので、サーバーレスアプリケーションではDynamoDBを利用することが多いです。 そのため、このgemもLambda on Rubyのアプリケーションを作る時よく利用することになりそうです。

我々は今回のRubyKaigiのブース企画のためにRuby on Lambdaを利用したサーバーレスアプリケーションを作成しました。 その時にはこちらで紹介されていたgemのいくつかを利用しました。 アプリケーション作成にあたっていくつかのハマりポイントなどもありましたので、後日TECH BLOGにもまとめたいと思います。

A Type-level Ruby Interpreter for Testing and Understanding

@AmatsukiKuです。 MatzさんのKeynoteでもあったように、Ruby 3に向けて静的解析の取り組みが進んでおり、今回のRubyKaigi中にも関連する講演がいくつかありました。特に気になった@mametterさんによるType Profilerについての講演を中心に紹介したいと思います。

Type Profilerは、SteepSorbetといった技術と異なり、既存のRubyコードにSignatureなどの追加の記述を必要とせずに、静的解析を行える技術です。Type Profilerでは、これを実現するために、Rubyコードを型レベルで実行します。例えば、次のようなコードがあったとします。

def foo(n)
  n.to_s
end

foo(42)

通常のRubyの実行であれば、上記のコードの処理はfooの呼び出しは引数の 42 に対し返り値が ’42’ になります。それがType Profilerで実行した場合には、引数は 42 の代わりに Integer が渡され、返り値も String となります。このように、Type Profilerは型レベルで実行していくことにより型情報を集めて、型エラーを検知します。
しかし、この方式には、いくつか問題もあります。まず、呼び出しのためのテストが必要となります。型レベルで実行するという形で解析するため、必然的に呼ばれていないメソッドは解析されないことになるからです。
次に、型レベルでは評価できない条件文が存在するという問題があります。例えば、次のようなコードがあったとします。

def foo(n)
  if n < 10
    n
  else
    ‘error’
  end
end

foo(42)

この場合、Type Profilerは nInteger という情報しか持っていないため、 n < 10 を評価できません。そのため、Type Profilerでは、このような条件文に対しては条件に関係なく分岐全てを実行するというアプローチを取っています。 しかし、今度は、次の2つの問題が起こります。

  • 条件が増えるほど状態の組み合わせが指数関数的に増えていくという状態爆発が発生する
  • 本来、存在し得ない状態の組み合わせが発生する

まず、前者については、同じ状態をマージするというアプローチである程度緩和できます。一方、後者については、そういった状態が発生するコードを書かない以外に現状の解決方法はないそうです。

Type Profilerは解析によって型エラーを出力するだけでなく、Type Signatureを出力することも予定しています。これは、SteepやSorbetといった別の静的解析ツールに使うもののプロトタイプという位置付けであり、実際に使う場合には手直ししたものを利用する想定のようです。

Type Profilerは既存のRubyコードのみから解析できるので、他の静的解析の技術に比べ、テストを必要とする点を考慮しても既存のプロダクトへ導入しやすいと感じました。
Type ProfilerがSignatureを生成し、それを元にしたSignatureで他の型検査器が検査を行うといったエコシステムの動向は今後も気になるところです。Signatureのフォーマットや共有方法については、松本宗太郎さんの講演でも紹介していたので、合わせてチェックすることをお勧めします。

Ruby for NLP

@TrsNiumです。 @youchanさんのRuby for NLPについて紹介します。 (スライド: http://youchan.org/RubyKaigi2019/)

今、ディープラーニングは画像処理や自然言語処理の分野などで注目されています。 しかし、多くの機械学習や深層学習ライブラリはPythonで実装・利用されており、Rubyでの実装・利用実績は少ないです。 このセッションでは機械学習タスクの1つである自然言語処理をRubyを用い紹介しています。要点を纏めつつ、実際にツールを使ってみた感想を述べたいと思います。 セッションは2つのパートに分かれており前半は基本的な自然言語処理の方法についての解説で、後半はRubyを使った自然言語処理の処理方法についての解説でした。

前半部では自然言語処理に使用する言語モデルには大きく2つの系統があり、確率的アプローチとニューラルネットワークがあるということが紹介されました。 日本語の言語処理で多く利用されている形態素解析ライブラリMeCabは確率的なアプローチを元にできています。一方で、ニューラルネットワークを用いたものは、機械翻訳やチャットボットなどで利用されています。

後半部のRubyでの自然言語処理部分では言語処理ライブラリの紹介やディープラーニングを用いいた実際のデモが紹介されていました。 言語処理ライブラリでは、StanfordNLPやRuby製のtreatやjuman_knpが紹介されてました。またディープラーニングフレームワークでは「Red-Chainer」の紹介がされ、デモでも使用されていました。デモでは語句を与えると、次の語句を予測するようなCLIツールを作成するようなものでした。

実際にRed-Chainerを用いWord2Vector(CBoW)の実装を行ってみました。 Word2Vectorでは単語をn次元のベクトルで表現するため、単語通しの演算ができます。

github.com

Red-Chainerを実際に使ってみましたがRNNやCNNコンポーネントなどが足りず、まだまだコントリビュートできる場面があると思いました。Rubyを用いた機械学習や深層学習はまだ発展途上だと思うので、一緒に盛り上げていきたいです。

Crystalball: predicting test failures

スライドはこちらです。

@takanamitoです。@p0deje氏のCrystalball: predicting test failuresについて紹介します。

長くなりがちなテストの実行時間に対して変更したコードから関連するテストコードを絞り込んで実行するというアプローチのgem crystalballを紹介されていました。

セッションの中心はcrystalballの仕組みについてで、まず最初にcrystalballを構成する3つのフローについて説明がありました。
MapGeneratorPredictorRunnerという大きく3つのフローを経て実現されており、実際に仕組みを聞いてみるとRubyならではのアプローチで実装されていることがわかりました。

まず MapGeneratorはspecのコードとアプリケーションが実装されているファイルとの関連を示す tmp/crystalball_data.ymlを生成する役割を持っているとのことでした。
CRYSTALBALL=true bundle exec rspec .を叩き、1度全てのspecを実行することでこのファイルが生成されるようです。
Crystalball Manualに丁寧なドキュメントが用意されていてすごい。

この関連を取得するロジックの実装がかなり愚直で TracePoint, parser, モンキーパッチでひたすらspecに関連するクラスや、FactoryBotの定義などのsupport系ファイルまで関連を見に行く仕組みになっているとのことでした。

また Predictorではgitから差分のあるファイルを読み取り MapGeneratorで作ったテストとの関連データを使って実行するspecを絞り込み
Runnerで実際にspecを実行するようです。

実際に手元のRailsアプリケーションで使ってみたところ、修正したモデルのspecに限定して実行されている様子が出力されていました。

$ bundle exec crystalball
I, [2019-04-25T16:42:28.037405 #84894]  INFO -- : Crystalball starts to glow...
I, [2019-04-25T16:42:28.117924 #84894]  INFO -- : Starting RSpec.
Run options: include {:ids=>{"./spec/models/hogehoge_spec.rb"=>["1:4:1", "1:5:1", "1:1:3:1", "1:1:4:1", "1:1:5:1", "1:1:6:1", "1:1:7:1", "1:1:1:1", "1:2:2:1", "1:2:3:1", "1:3:1:1", "1:3:2:1", "1:3:3:1", "1:2:1:1", "1:1:2:1"]}}
.......FFF.....

いま私が関わっているプロダクトではまだspecの実行時間は課題になっていませんが、今後課題に上がってきたときは有力な選択肢になりそうです。
またgitのpre-commit hookなどを利用してcommitするタイミングで関連するテストだけを実行する様な使い方もできそうなので作業効率UPのために導入も検討しています。

How to use OpenAPI3 for API developer

スライドはこちらです。

@sh_ngswです。Ota(@ota42y)さんのセッションについてご紹介します。 このセッションは、特にMicroserviceやBackends for frontendsの採用を検討(あるいはすでに採用)されている方にオススメです。 主にOpenAPI 3.0committeeというgemについての発表でしたが、 これからのAPI開発の手法として大変参考になりました。

初めにOpenAPIの定義・使い方・アーキテクチャについて説明があり、続いてcommitteeによるvalidationの実装方法とOpenAPI 2.0から3.0への移行についての発表がありました。

OpenAPIはRESTful APIをYAML/JSONファイルで記述するフォーマットであり、OpenAPI 3.0はその最新のメジャーバージョンです。 OpenAPIは周辺ツールとの組み合わせによって様々な機能を利用できます。

committeeはその中でもAPIのrequest/responseのvalidationが実装できるgemです。例えば、URLのパス、requestの必須parameter、parameterのtypeなどが定義できます。これによって、APIの実装ミスや実装と定義の乖離をチェックできます。

昨今のサービス開発ではPaaS・SaaS連携やマルチデバイス対応を求められる機会が多々あります。そうした情勢ではOpenAPIによるAPI定義やAPIのvalidationの実装はますます増えていきそうです。

一方でOpenAPIの周辺ツールではまだ3.0に対応していない場合があります。それゆえにツール次第では安易に3.0へ移行できない可能性があるため要注意です(むしろPRチャンスでもあります!)。 たとえば、テストコードからAPI定義を生成できるrswagがそうです。

ちなみに弊社のエンジニアもopenapi2rubyというOpenAPI用のgemを開発しています。このgemでは、OpenAPIのYAMLファイルからActiveModel::Serializerを継承するクラスを生成できます。 このserializerの自動生成によって、定義と実際のresponseの乖離の問題を解決できます。こちらはすでにOpenAPI 3.0に対応済みです! 今後もOpenAPIを中心としたAPI開発の事例が増えていきそうなので、動向を追っていきたいですね。

なお、過去のRubyKaigiでも同じAPI開発というテーマで、 大仲さん(@onk)『API Development in 2017』というタイトルで発表されていました。 こちらの発表ではRESTful APIとRailsでのAPI開発の歴史がまとめられています。 合わせて読むとOpenAPIやcommitteeの立ち位置が俯瞰的に理解できるのでオススメです。

参考リンク: 『RubyKaigi 2017 でどんな発表をしたか』 『スキーマファースト開発のススメ

GraphQL Migration: A Proper Use Case for Metaprogramming?

スライドはこちらです。

@takeWakaMaru666 です。shawneegaoさんのGraphQLに関するセッションをご紹介します。 APIサーバーのGraphQL層をメタプログラミングを使って実装することで、ボイラープレートを排除しDX(Developer Experience)を向上させるというものでした。

shawneegaoさんはコントローラーの数が200を超えるRailsで書かれた大きなRestful APIをGraphQLでリプレイスする際にメタプログラミングを使ったそうです。 コントローラーを1つずつGraphQLのフィールドとして再実装する作業は非常に長い時間を要するものであり、また大量のボイラープレートがDXを損なうと判断したためメタプログラミングを使って実装することを考えたそうです。

具体的にはRailsのモデル層のクラスからGraphQLのタイプクラスを生成する部分と、生成したそれらのタイプをルートタイプ内でフィールドとして定義する部分をメタプログラミングでカバーしていました。 またメタプログラミングで実装しやすくするためにモデル層のクラスのインタフェースを統一していました。

このメタプログラミングを使った方法のメリットはボイラープレートが減ることだと思います。 デメリットとしては、Railsのモデル層にGraphQL依存のコードが書かれてしまうこと、可読性が失われること、デバッグが難しくなることなどが考えられます。

ボイラープレートの排除は魅力的ですが、可読性が失われたりと付随するデメリットがあまり無視できないので私が同じ立場であればやらない選択をすると思います。 ただしGraphQLのタイプを定義する作業の単調な部分を自動化するという部分には大賛成で、スクリプトを書いて自動生成できるところは自動生成するというのが落とし所な気がしました。

スポンサー

今年は、Rubyスポンサーとして、スポンサーブースの出展とスポンサートークの登壇をさせていただきました。

スポンサーブース

f:id:vasilyjp:20190425181556j:plain f:id:vasilyjp:20190425181559j:plain 昨年に引き続き、今回もスポンサーブースを出展しました。
今回のブースでは、ZOZOSUITの展示に加え、以前本ブログでも紹介したファッションチェックアプリの展示を行いました。
また、今回の展示に際して、ファッションチェックアプリにRuby on AWS Lambdaを用いて実装したランキングの機能を加えました。
Ruby on AWS Lambdaは昨年11月から提供されたばかりということもあり、ブースを訪れた方々にも興味を持っていただけたようでした。
今回追加した機能の詳しい実装についてはまた別の記事で紹介するので、公開をお待ちください。

スポンサートーク

スポンサートークでは、弊社の企業・事業紹介や、弊社がなぜRubyKaigiにスポンサーとして参加しているかについて話させていただきました。

f:id:vasilyjp:20190425181623j:plain

最後に

カンファレンス参加に関わる渡航費・宿泊費などは全て会社に負担してもらいました。自分から希望すれば参加する機会をいただけて本当に感謝です。ZOZOテクノロジーズでは、Rubyエンジニアを大募集中です! ご興味のある方は、以下のリンクからぜひご応募ください!

tech.zozo.com

おまけ

RubyKaigiを楽しんでいる社員の様子です。 5Fにブースを出していました! f:id:vasilyjp:20190425181744j:plain

ファッションチェックアプリのデバッグ中 f:id:vasilyjp:20190425181552j:plain

Matzさんと写真をとらせてもらいました!

f:id:vasilyjp:20190425181609j:plain

初日のOfficial Partyがまさかの商店街貸切! f:id:vasilyjp:20190425181603j:plain

楽しかったです!来年の松本も楽しみです!

カテゴリー