今日は Apache Arrow 東京ミートアップ2018というイベントに登壇してきました。
おそらく今年最後の登壇になったかなと思います。
今日はRails Developers Meetup 2018 Day 4 Nouvelle Vagueがやっていて被ってなかったら行きたかったなぁ〜
さて今回は Ruby と Apache Arrow という内容で話してきました。
資料は↓こちらになります。
Ruby で Apache Arrow を使う時に「今こんな現状」で「今後こうなると嬉しいな」という話をしました。
このブログでは「今後こうなると嬉しいな」で紹介したMySQLの実行結果を Apahce Arrow で返すところを紹介していきたいと思います。
Apache Arrow に関しては須藤さんの資料が参考になるかと思います。
MySQLの実行結果をApache Arrowで返すとは??
現状 Ruby で Apache Arrow を扱う際には CSV であれば CSV Parser から高速で Arrow 形式に変換できるようになっています。
しかし MySQL に対象となるデータがある場合は一度なんらかのフォーマットでアウトプットすして変換したものを読み込む必要があります。例えばCSVとか
そのため MySQL から取り出したレコードをそのまま Arrow の形式に変換できると良さそうです。
どういうアプローチを検証してみたか
Rubyには mysql2 という libmysql を Ruby binding したgemがあります。
これは ActiveRecord で MySQL を選択した際にも使用されています。
これを Apache Arrow 形式で取り出せるようにすれば高速でデータ処理ができるはずです。
実装イメージとしては↓こんな感じです。
ポイントとしては mysql2 ではクエリを実行した後に MYSQL_RES
を保持しているMysql2::Result
が Ruby 側にかえってくるので、これを再度 C, C++ のレイヤーにもっていき MYSQL_RES
を取り出して Arrow 形式に変換しているところですね。
C, C++ のレイヤーで行うことで高速で処理できるというメリット以外にも C, C++ で行なっているので嬉しいのは Ruby だけではないところですね。
良い世界だ。
一番大変だったのは MYSQL_RES
からレコードを取り出してArrow形式に変換するところです。
なぜかというと C++ で書いたのですが、今まで書いたことなかったので、 Arrow の実装などを見たり結構時間がかかってしまいました。
このあたりはGitterで須藤さんやむらけんさんに色々アドバイスをいただきました。
ありがとうございました。
検証は↓こんな感じでやりたかったので ActiveRecord に Arrow 形式で取り出せるような口を用意してあげる必要があります。
# pluck User.limit(500000).pluck(:name).size # AR User.limit(500000).each_slice(3000){|r| p r.size } # Apache Arrow User.limit(500000).each_record_batch(size: 3000) { |r| puts r.n_rows }
今回は↓こんな感じで用意しました。。。。
もうちょっと良い感じにしたいけど良い案が浮かばなかった。
# Mysql2::Result を Arrow 形式に変換してくれる gem require 'mysql_arrow' module Mysql2AdapterExtend def exec_query_and_mysql2_result(sql, name="SQL") execute_and_free(sql, name) do |result| if result # 本来は result.to_a してrowをArrayでわたしているところを Mysql2::Result をわたす ActiveRecord::Result.new(result.fields, result) else ActiveRecord::Result.new([], []) end end end end ActiveRecord::ConnectionAdapters::Mysql2Adapter.send :include, Mysql2AdapterExtend module RelationExtend def each_record_batch(size: 1000) conn = klass.connection sql = conn.to_sql(arel) # rows は Mysql2::ResultなのでArrow形式で取り出せる each_record_batch が生えている rows = conn.exec_query_and_mysql2_result(sql).rows rows.each_record_batch(size: size) do |r| yield r end end end ActiveRecord::Relation.send :include, RelationExtend
検証した結果
# pluck User.limit(500000).pluck(:name).size # AR User.limit(500000).each_slice(3000){|r| p r.size } # Apache Arrow User.limit(500000).each_record_batch(size: 3000) { |r| puts r.n_rows }
ActiveRecord: 6.849s
pluck: 2.450s
Apache Arrow: 0.520s
こんな感じの結果になりました。
Apache Arrow 良いかんですね!!!
今後
検証してみて良さそうだったので継続して開発してリリースできる形にもっていきたいなと思っています。
直近は MYSQL_RES
を Arrow 形式へ変更する処理を C++ で作って arrow リポジトリに取り込めないかを提案していきたいと思います 💪
最後に
今回は発表側として参加したのですが、Ruby 以外での言語とApache Arrow や Arrow を使ったシステムの話など普段聞けないような話をたくさん聞けてさらに、コード懇親会でコードがかけて良い感じだった。
あとは今回の発表資料を作るなかではじめて Ruby の binding という世界に触れられて楽しかった。