八発白中

はてなブログに引越しました。

Day 24: Mito

これは fukamachi products advent calendar 2016 の24日目の記事です。

今日はMitoについて話します。

Integralの失敗

11日目のIntegralの「抽象化の失敗」のところで取り上げましたが、Common LispのORMとして作られたIntegralはPostgreSQLのサポートに失敗し、サポートするには根幹部分から再設計が必要となりました。

その後dataflyを作ったりして、ORMよりももっと軽量なライブラリを好んで使っていました。けれどそれも、サムライトでIntegralを採用してから話が変わってきました。

いよいよ真面目にメンテナンスしないといけなさそうだけれども、Integralに手を入れ続けるのは限界がありそうです。そこで自信を持って使ってもらえるORMは必要だなと思って一から書き直したのが「Mito」でした。

Mito

Integralの経験を活かしてMitoでは慎重に抽象化を行いました。具体的にはdefclassCREATE TABLE文と紐付けるメタクラス層と、そのメタクラスをData Access Objectとして扱えるようにするメタクラス層で分離しました。

また、Integralではdefclass:typeRDBMSのカラム型に変換できる機能もあったのですが、Mitoではそこまでの高度な抽象化はしませんでした。そのため少し保守的な設計になっていますが、何よりちゃんと動くことが大事です。

最近は僕もいくつかのプロジェクトでMitoを使っており、マイグレーションも含めて問題なく動いています。

名前の由来

どうでもいいことなんですが、「なんでMitoって名前なんですか」とサムライトの社内ミーティングで訊かれました。

Mitoというのは京都市動物園にいるアジアゾウの「美都」からつけました。

これは猛暑真っ只中の写真で、飼育員さんに水浴びしてもらっているところです *1

f:id:nitro_idiot:20130817131454j:plain

飼育員さんがプールに入らせようと好物のりんごをプールに放ったのですが、プールが嫌いな美都は鼻を伸ばしてりんごだけ取っています。水こわいけどりんごは食べたい。

なぜアジアゾウの名前をつけたかというと。

昔、ElephantというCommon Lispのオブジェクトストアがありました。このAPIはよくできており、CLOSオブジェクトを永続化させるという高度な抽象化を行っていました。このElephantには問題も多かったのですが、目指した夢としての印象は僕にとっては強烈で、僕のORMにもその心意気は込めたいと思ったからです。あと愛着が湧くからです。美都かわいい。

まったく技術的な話ではありませんでした。

おわりに

MitoはGitHubで公開されており、現在Starは35ついています。

明日のアドベントカレンダーは最終日25日目のUtopianについてです。お楽しみに。

*1:写真は2013年当時のゾウ舎。今はゾウ舎が移築されて広くなっており、ゾウも4頭増えています。しばらく美都は奥のゾウ舎から出てこなくてまったく会えなかったのですが最近は見られるのでしょうか。

Day 23: Psychiq

これは fukamachi products advent calendar 2016 の23日目の記事です。

今日はPsychiqについて話します。

はてなでの経験

僕の経歴の中ではてなで働いていた時期はあまりOSS活動もせず、Common Lispを書いていませんでした。仕事がPerlということも理由の一つでしょう。

けれど直接的ではないにせよはてなでの経験は少なからず僕の活動には影響を与えています。Webアプリケーションのサーバーサイドからフロントエンドまで担当でき、どのようにWebアプリケーションを構成し運用するかを学ぶことができました。どこがボトルネックになりやすいのか、長期的に運用する上でどういう面が問題になるのかなどの生きた知見が得られました。

はてなではWebフレームワークから作ることもそう珍しいことではなく、その経験はCaveman2の開発にも活かせたり。

その過程でCommon Lispに足りないものもありました。それがメッセージキューでした。

メッセージキュー

メッセージキューはアプリケーションで実行に時間がかかりそうだが同期的にやらなくてもいいものを他のプロセスやスレッドに投げて非同期で実行できるミドルウェアです。

はてなではTheSchwartzを使っていました。サムネイル画像を作ったり動画の変換、通知やその他バッチ処理を走らせたりするのに使われていました。

最近だとAmazon SQSとか使ってるところが多いんでしょうか。

必ずしもすべてのアプリケーションで必要なわけではないですが、必要になったときにCommon Lispになければ導入が困難になるかもしれません。

Lesque

そこで2014年の1月に作ったのが「Lesque」です。

これはRubyのResqueを元に作ったメッセージキューでした。作ったタイミングとしてははてなを退職する少し前くらいで、それから新しいWebサービスを作るのに必要だと見越しての開発でした。

blog.8arrow.org

Psychiq

さて、Lesqueを使ったはいいものの当初の予定とはかなり違って、使う機会のないまま日は経ちました。

そして2年後、とうとうLesqueを使えるかもな、というタイミングで見てみると、そのコードは古いですしCommon Lispの環境にもついていけておらず継続して開発するのもためらわれるものでした。この2年でRoswellがスタンダードになったにも関わらずRoswellスクリプトが提供されていなかったり、Travis CIでのテストが行われていなかったり。

メッセージキューの世界でも世代交代がありました。Sidekiqという新しいプレイヤーが現れました。Resqueのプリフォークスタイルではなく、スレッドを使うため高速に動作し、シンプルなアーキテクチャであるということが売りのようです。

そこで新たにSidekiqをCommon Lispに移植することにしました。それが「Psychiq」でした。

blog.8arrow.org

落語Bot

とはいえPsychiqもそれからしばらく使うことはなかったんですけど、先日とうとう使う機会がありました。

11月の末にLINE Botの「落語Bot」をリリースしました。これは落語の定席や落語家のスケジュール検索ができるLINE Botです。現在、その落語会情報のクローリング部分にPsychiqを使っています。

Psychiqを使ってみてJonathanのバグを見つけたりCL-DBIの問題を見つけたりとかありましたが今のところ順調に動いています。

おわりに

PsychiqはGitHubで公開されており、現在のStar数は16です。

明日のアドベントカレンダーは24日目のMitoについてです。お楽しみに。

Day 22: cl-coveralls

これは fukamachi products advent calendar 2016 の22日目の記事です。

今日はcl-coverallsについて話します。

CI

Common LispTravis CIのオフィシャルサポートはありませんが、cl-travisやRoswellを使って複数処理系をテストすることができます。

Common LispライブラリのCIは僕にとってはなかなかライフチェンジングで、今まで手元でSBCLでテストしてpushしていたのがClozure CLでは落ちるとかがすぐわかったり。CL-DBIのようにテスト用にRDBMSをたくさん用意しなければいけないようなものもPull Requestでテストできるのでパッチをもらっても安心してマージできます。

僕のライブラリはもちろんのこと、他の新しいライブラリでもTravis CIは当たり前に使われるようになりました。

しかし、それだけでは足りません。

テストがなくてもbuild passing

κeenさんのテンプレートエンジンのZenekindarl *1Travis CIでのテストが行われており、READMEに「build passing」という緑のバッジがありました。

しかし、じゃあ使えるのか、品質が高いのかというとそうではありません。当時はまだ機能を実装中でありテストもほとんどなかったのです。

このバッジだけでは品質の保証はできない。そこでテストがどれだけ書かれているかをカバレッジとして出すのはどうだろうと考えました。

他の言語ではテストカバレッジを表示するのにCoverallsというサイトを使っていました。Travisでテストを実行したときにカバレッジを計算し、JSONをCoverallsに投げることで表示されます。

Common LispはCoverallsではサポートしていないのでAPIを叩いてどうにか作りました。

それが「cl-coveralls」です。

カバレッジを表示することの意義

カバレッジは無意味だ、テストが十分かどうかはわからない、という意見もあるかと思います。もちろんそうです。けれど、無意味ではないと思います。テストが十分かはわからずとも、テストが不十分なときをいくらか拾えますから。

おわりに

cl-coverallsはGitHubで公開されており、Starは17ついています。

明日のアドベントカレンダーは23日目のPsychiqについてです。お楽しみに。

*1:当時はArrowsだったか

Day 21: Lack

これは fukamachi products advent calendar 2016 の21日目の記事です。

今日はLackについて話します。Rackではないです。

Clackの高速化

サムライトに入社してWooによるサーバーの高速化を主な課題と取り組んでいましたが、アプリケーション全体で見ればその基盤部分のClackの高速化も重要でした。Wooは十分に高速だけど、Clackに載ることで大きくパフォーマンスが下がるなら残念なことです。

まあ正直、当時でも十分な速度は出ていたんですけどね。ずっと高速化ばっかりやってたので頭がもう高速化脳になっていて、何でも高速でなければ気がすまなくなっていたんでしょう。

疎結合性を確保しつつなのでWooのようにinline化や型宣言などの高速化のテクニックは使えませんが、それでもClackに速度面で改善の余地はありました。

ClackではComponentもMiddlewareも全部CLOSクラスになっており、Clack.Builderでビルドしたとしても実行時にメソッドディスパッチが挟みます。これをフラットなクロージャーにするだけで少しは高速化できるのではないか、と考えました。

実はこれには違う目論見もありました。

Clackを使うメリットは疎結合性でした。けれどClackのコアは開発の過程でだんだん大きくなり、依存ライブラリもまた多くなりました。単にclackupをしたいだけで、セッションを使わないアプリケーションだとしてもClack.Middleware.Sessionで使われているIroncladまでロードされてしまいます。

こういった目的意識を持って、高速に、もっと疎結合に、そういう意図を持って作り始めたのが「Lack」です。

LackはClackから分離したもの

LackはClackからアプリケーション構築フレームワーク部分を抜き出したライブラリです。ミドルウェアやLack.Builderなどがここに入ります。

Clackにはハンドラーが残り、Webサーバーの抽象化ライブラリの役割だけを与えました。

Common Lisperの多くはWeb開発に疎く、最近のWebアプリケーション構築にキャッチアップできているわけではありません。そういう中でClackは説明が難しかったですし、LackとClackの2つの目的別のライブラリに分離したほうがわかりやすいかなという意図もありました。

Lackの疎結合

Lackが提供する疎結合性は完全なものです。Middlewareはもはや単なるクロージャーを返すクロージャーであり、Clack.Middlewareを継承する必要がありません。これはLackのミドルウェアがLackにすら依存しなくてもよくなったという意味でもあります。

意味わかりますか。LackのミドルウェアがLackに依存する必要がないのです。単に呼び出すとappをラップしたapp (関数) を返すだけなんです。そのルールに従っていればLack.Builderに渡すことができます。

Lackというのはアプリケーションを構成するルールのようなものです。つまりは仕組みは単なる関数群ですし、自前でLackのような新たな疎結合フレームワークを構築することもできます。

ちなみにClackについても依存の形式は同じです。たとえば、WooがClackに依存していないというと驚かれることがあります。ningleやCavemanもClackに依存していません。ライブラリごとに必要な部分だけをロードできることでロード時間が短くなりますし、柔軟に部品を入れ替えてアプリケーションを構築できるようになります。

速度

リリース当時に取ったベンチマークによると、結果的にLackはClackの1.1倍程度の高速化に成功しました。

Hunchentootより1.25倍の高速化とも書いてあります。勘がいい人は不思議に思うかもしれません。ClackでもLackでも裏側はHunchentootなはずなのに、なぜ素のHunchentootより速いのか。

理由はHunchentootのセッションミドルウェアです。セッション処理が遅すぎるためHunchentootはClackと比較しても遅くなっています。

おわりに

LackはGitHubで公開されており、Starは30ついています。

明日のアドベントカレンダーは22日目のcl-coverallsについてです。お楽しみに。

Day 20: Dexador

これは fukamachi products advent calendar 2016 の20日目の記事です。

今日はDexadorについて話します。

Drakma

Common Lispには古くからDrakmaというHTTPクライアントがあります。Quickdocs によると74のライブラリで利用されており広く使われているようです。

Edi Weitzと言えばCommon Lispをやっている人のほとんどは知っているでしょう。cl-ppcreやHunchentootのようなテクニカルであり高速であり品質の高いライブラリを多く作っています。

そしてDrakmaもEdi Weitzのプロダクトです。

じゃあDrakmaも彼のプロダクトならば信頼できるか、と言うと、実は期待するほどは品質が高くありません。Drakmaを使ってハマったという人も少なくないのではないでしょうか。

たとえばこんな感じ。

よしdrakma:http-requestにURLを渡せばいいのかな。簡単だわ。あれ、日本語クエリ含む場合にちゃんとGETしてくれない。なんか自動でASCIIでデコード・エンコードされるなあ。PURIの問題? ふむふむ。事前にUTF8 でエンコードした上で:preserve-uri tを渡せばいいらしい。よしできた。…あれ、エンコーディングエラーが出てる。Shift_JISのページは対応してないのかー。じゃあ:force-binary tを渡して自分でBabelでデコードするしかないなあ。Content-Typeヘッダを雑にパースして…。よしできた。…あれ、意図しないHTMLが返ってきてる。うーん、手元ではうまくいくんだけどなあ。ん? たまに503が返ってきてるみたいだ。じゃあステータスコードを見て4xxと5xxのときはエラーにしなきゃなあ。できれば何度かリトライもしたい。

とかやってるとコードがとても複雑になってきます。我々はただHTTPリクエスト投げたいだけなのに。

この辺りは後のLisp Meetupでの発表資料に詳しくまとめています。

曖昧にDrakmaで使いづらいと思っている内容を明確にリストアップしています。

twitter.com

こういった状況を改善すべく、よりよいAPIを提供することでDrakmaを置き換えられないかなと考えました。そこで作ったのが「Dexador」です。

良いAPIでは不十分

新しくHTTPクライアントを作ることは可能でしょうが、使ってもらわなければ意味がありません。その観点から言うと「Drakmaより良いAPIを提供します」というだけでは不十分だとは感じていました。「良い、なんてものは主観的だ」と言われればそれまでです。細かい改善も伝わりづらく、なかなか移行するほどアピールできるとは思えません。

そこで思い至ったのが高速化です。

Wooで作ったfast-httpやQURIを使うことでDrakmaよりも圧倒的に高速になれば優位性が数値として証明できます。

これは良い戦略かと思われたのですが、fast-httpとQURIでは1.2倍程度の速度しか出ませんでした。そこで最大のボトルネックであるコネクション確立部分をスキップすることでさらなる高速化を行いました。

マクロレスエラーハンドリング

Dexadorの売りの一つは4xxや5xx系のステータスコードが返ってきたときはエラーを発生するということです。もしそのエラーハンドリングをしたければhandler-casehandler-bindを使います。

(handler-case (dex:get "http://lisp.org")
  (dex:http-request-bad-request ()
    ;; 400が返ったときの処理
    )
  (dex:http-request-failed (e)
    ;; それ以外のエラーの処理
    (format *error-output* "The server returned ~D" (dex:response-status e))))

エラーの無視や自動リトライなどもhandler-bindでできます。

;; 404は無視する
(handler-bind ((dex:http-request-not-found #'dex:ignore-and-continue))
  (dex:get "http://lisp.org"))

;; 失敗したら自動リトライ
(handler-bind ((dex:http-request-failed #'dex:retry-request))
  (dex:get "http://lisp.org"))

;; 失敗したら5回まで自動リトライ (間は3秒間あける)
(handler-bind ((dex:http-request-failed (dex:retry-request 5 :interval 3)))
  (dex:get "http://lisp.org"))

こういった構文は安易な発想をすればマクロを使うシーンです。しかし、独自のマクロは使い方を覚えてもらう必要があり学習コストがかかります。一方でhandler-bindhandler-caseは言語仕様の機能なので誰でも使えます。

ここにも11日目でも取り上げたマクロレス思想の一端が見られます。

Drakmaの微妙な不具合

Dexadorを作っている過程でいくつか不具合も出ました。新しいプロダクトだから仕方ありませんね。直しましょう。ところでDrakmaはどうやっているのだろう。試してみると同じ不具合があった、というようなことも多くありました。結果的にDrakmaのあまり知られていない微妙な不具合をいくつか知っています。

twitter.com

twitter.com

「Drakmaでもコネクション再利用できるよ」なんて言ってくる人なんて今までいませんでしたけど、いたとしたらドキュメントを読んだだけで使ったことがない人なのでしょうね。

おわりに

明日のアドベントカレンダーは21日目、たぶんLackについてです。お楽しみに。