SlickでStackOverflowErrorが発生する場合の対処法

GitBucketに以前からGitBucketのバッシュレポートでStackOverflowErrorが発生するというバグレポートが上がっていたのですが、最近直接の知り合いからも言われたので重い腰を上げて修正にトライしてみることにしました。

github.com

原因は以下のようなコードで、検索条件をORで繋げている部分が、数が増えるとスタックを食いつぶしてしまうというものでした。

val seq = Range(1, 500)
Accounts
  .filter { t => seq.map { x => t.userId === x.bind }
  .reduce(_ || _)}.list

実は当時Slickにもイシューを上げてありました。

github.com

ただ、このイシューも放置状態ですし、Slickの構造上修正するのも難しそうだったので(ちなみにSlickはクエリビルダで組み立てたASTをSQLコンパイルして実行するという構造になっているのですが、StackOverflowErrorはASTを組み立てる際ではなく、SQLへのコンパイル時の処理で発生していました)、GitBucket側で以下のようにINを使ったクエリに書き換えることにしました。

Accounts.filter { t => t.userId inSerBind(seq) }.list

これで今までStackOverflowErrorになっていた件数を与えてもとりあえずエラーは起きなくなりました。ただ、条件によっては単純にINに書き換えるとインデックスが効かなくなってしまったり、そもそもINへの書き換えができないケースもあるでしょうし、データベースによってはINで指定可能なパラメータ数に上限もあるので、だいぶやっつけ感のあるワークアラウンドです。

Slickではこういうものは素直にネイティブSQLで記述するべきなのかもしれませんが、せっかく強力なクエリビルダがあるのに肝心な複雑な条件指定が必要な箇所で使えないというのも勿体無い気がするので何か良い解決策があるといいのですが…。

トレジャーデータ株式会社に入社しました

トレジャーデータに入社したと思ったらいつのまにかArmに入社していました。

何を言っているのかわからないと思いますが、私もよくわかりませんでした。

株式会社ビズリーチを退職しました

本日、2018年7月31日をもって4年と4ヶ月勤務した株式会社ビズリーチを退職しました。いわゆる退職エントリというもので、誰得な気もしますが自分の振り返りという意味で書いてみたいと思います。

入社しばらくしてから3年ほどはScalaで作っている新規サービスのバックエンドの開発・運用をやっていました。社の誇るスーパーエンジニアや優秀な若者たちと共に新規サービスの立ち上げというエキサイティングな仕事ができたのは大きな刺激になりました。大人数でのScala開発は自分も初めてだったので、もうちょっとこうしておけばよかったなと感じる部分もあった一方で、新規サービスの開発ということで技術以外の部分でいろいろと難しさを感じる部分も多かったです。

その後、サービスの開発からは離れ、ここ1年ほどはApache PredictionIOを始めとしたオープンソース活動にも時間を割くことができました。PredictionIOは業務との絡みもあって関わり始めたものですが、コミッタとしての活動は退職後も続けていきたいと思っています。最近はMLOpsというキーワードも注目を集めていますのでもうちょっと盛り上げていけるといいですね。

takezoe.hatenablog.com

開発以外では社内勉強会や渋谷javaの運営、JJUG CCCやScalaMatsuriなど技術系イベントのスポンサー業、登壇や書籍・雑誌記事の執筆なども行なっていました。特に社内勉強会では様々な外部講師の方々に講演をしていただきました。以下のエントリは2015年の分だけですが、この他にも多くの方にお話しいただきました。ご協力いただいた皆さま、本当にありがとうございました。

takezoe.hatenablog.com

執筆活動では、以前から一度技術書の翻訳をやってみたいと思っていたのですが、在籍中にScala Puzzlersの日本語版を出すことができ、夢が一つ叶いました。社内の若者たちと本を書いたりできたのもよかったです。

takezoe.hatenablog.com

takezoe.hatenablog.com

また、Scala Daysなど海外のScalaカンファレンスにも参加することができました。特に2016年のScala Days NewYorkではProgramming in Scala 3rd Editionの原著に小田好先生のサインをいただいたり、昨年はフランスで開催されたScalaIOとイギリスで開催されたScala Exchangeにスピーカーとして登壇させていただいたのはとても良い経験になりました。海外カンファレンスへの参加を通じて英語力の必要性も強く感じました。

takezoe.hatenablog.com

振り返ってみると、急成長する未上場企業の様子をCTOでもある上司の傍らから見ることができたのは非常に良い社会勉強になりました。正直いろいろと厳しい部分が多かったですが、これはこれでここでなければできない経験だったと思いますし、今後のキャリアを考える上でも得るものの多い4年間でした。振り返ると個人的に反省すべきと感じる点も多々ありますが、そこも含めて今後に活かしていければと思います。

そんなわけで気づけば私ももうアラフォーだったりするわけですが、まだしばらくはプログラマーとしてやっていきたい気持ちでいますので引き続きよろしくお願いいたします。

f:id:takezoe:20180731201144j:plain

GitBucket 4.27.0をリリースしました

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

https://github.com/takezoe/gitbucket/releases/tag/4.27.0

ブラウザ上でタグを作成可能に

リポジトリビューア上でタグを作成できるようになりました。

f:id:takezoe:20180728173917p:plain

EditorConfigのサポート

様々なテキストエディタIDEでコーディングスタイルを統一するためのEditorConfigの設定の一部をGitBucketのコードビューアとエディタでサポートしました。リポジトリのルートディレクトリに.editorconfigがあると、以下の設定が反映されます。

  • indent_style
  • indent_size (tab_width)
  • end_of_line

イシュー/プルリクエスト検索の改善

これまではイシュー/プルリクエストの検索結果は同一ページに表示されており、イシューとプルリクエストの区別がつかなかったのですが、今回のバージョンからは別々のページに表示されるようになりました。また、検索結果にクローズされているかどうかも表示されるようになりました。

f:id:takezoe:20180728173931p:plain

今回のバージョンではこの他にも様々な改善やバグフィックスを行っています。特にプラグインのインストールやプルリクエストのコメント周りについては多くの改善を含んでいます。詳細についてはIssueの一覧をご覧ください。

Scalaをはじめよう! ─マルチパラダイム言語への招待─

技術書典で販売されていた同人誌がインプレスR&Dさんから電子書籍&オンデマンドで発売されたものとのことで、Scala初心者向けの入門用書籍にどうだろうと思い、Kindle版を購入して読んでみました。

内容的にはScalaの基本的な部分がわかりやすく解説されています。IDEではなくsbtだけで進めていくのも環境構築でつまづく可能性が減るので入門書としてはよい判断なのではないかと思います。とはいえ、本当のプログラミング初心者ではなく、他の言語で一定のプログラミング経験のある方を対象読者として書かれているようです。説明がやや簡素すぎるのではと感じたり、理系の論文ぽい言い回しが気になる部分もありますが、想定されている対象読者層であれば問題ないのかもしれません。

内容については前から読み進めていくことでクラス、トレイト、オブジェクト、ケースクラス、for式、パターンマッチ、コレクション、implicitなどScalaでコードを書く際に押さえておくべきトピックが順次登場してきますし(個人的にはこの流れならFutureはなくても良かったかもという気もしますが)、モナドのような最初から理解していなくても問題ないトピックについてはコラムの形で解説されているなど、Scalaのエッセンスを切り捨てることなく、かつできるだけわかりやすく説明しようという工夫がなされています。

もちろん実際に業務でScalaを使うにはこの他にも様々な知識が必要になりますが、他の言語でプログラミング経験のある方が初めてScalaに触る際の手引きとしてはなかなか良い書籍なのではないかと思いました。

Akka StreamsのElasticsearchコネクタでupdateやdeleteもできるようにした

AlpakkaにはElasticsearch用のコネクタも含まれていて、これは元々自分がコントリビュートしたものなのですが、これまではFlow/Sinkによるバルク書き込みはindexupsertにしか対応していませんでした。また、1つのストリームでindexupsertを混ぜることもできなかったのですが、最近わけあってupdatedeleteも含め1つのストリームで処理したい事情があったのでできるようにしてみました。

github.com

このプルリクはすでにマージされているのでAlpakkaの次のリリース(Alpakka 0.21)から利用可能になると思います。

使い方は以下のような感じで、indexupdateupsertdeleteそれぞれの操作に応じたメッセージを生成するだけです。すべてのメッセージをIncomingIndexMessageもしくはIncomingUpsertMessageにすればこれまでと同じ動作になります。

val requests = List[IncomingMessage[Book, NotUsed]](
  IncomingIndexMessage(id = "00001", source = Book("Book 1")),
  IncomingUpsertMessage(id = "00002", source = Book("Book 2")),
  IncomingUpsertMessage(id = "00003", source = Book("Book 3")),
  IncomingUpdateMessage(id = "00004", source = Book("Book 4")),
  IncomingDeleteMessage(id = "00002")
)

val f = Source(requests)
  .via(ElasticsearchFlow.create[Book]("books", "_doc"))
  .runWith(Sink.seq)

これでただ単に流れてきたデータを突っ込むだけでなく、更新や削除もできるようになるので、Akka Streams + Elasticsearchの用途がもう少し広がるのではないかと思います。

Akka Streamsで巨大なXMLをストリーム処理する

Akka Streams用のコネクタを提供するAlpakkaにはXMLサポートも含まれており、XMLの読み込み・書き込みを行うためのFlowやSinkを利用することができます。

しかし読み込みはXMLのパースイベントをストリームするだけなので、実際には下流の処理で状態を管理したり、イベントをスタックするなどしてXMLの構造を判別する必要があります。複雑な構造のXMLの場合、これはかなりしんどいことになります。というわけでパスを指定するとその要素をorg.w3c.dom.Elementとして扱うことができるFlowを作ってみました。

github.com

このプルリクエストはすでにマージされていますので、Alpakkaの次のリリースから利用できるようになるかと思います。

使い方は以下のような感じでXmlParsing.parserと組み合わせて使います。ここではAkka Streams標準のFileIOを使ってファイルから読み込んでいますが、ByteStringを出力するコネクタであればなんでも使うことができます。

val graph = FileIO.fromPath(Paths.get("dataset.xml"))
  .via(XmlParsing.parser)
  .via(XmlParsing.subtree("doc" :: "elem" :: "item" :: Nil))
  .map { element =>
    ...
  }
  .to(Sink.ignore)

パースイベントのストリームと比べるとメモリ消費量は大きくなると思いますが、まあどっちみちXMLから情報を抽出するのであれば同じような処理を自分でやらないといけないと思いますし、DOM Elementなら他のXMLライブラリとも組み合わせて使いやすいかなと。

最近は巨大なデータを扱う場合はXMLではなく、より効率的なフォーマットを使うことが多いと思いますが、諸事情によりデータソースがXMLというケースもままあるのではないでしょうか。Akka StreamsとAlpakkaを使えば繰り返し構造を持つXMLを別のデータ形式に変換したり、データベースに格納するみたいな処理をシュッと書くことができるんじゃないかなと思います。