読者です 読者をやめる 読者になる 読者になる

八発白中

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

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についてです。お楽しみに。

Day 19: clfreaks

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

今日はclfreaksについて話します。今回はライブラリではありません。

コミュニティの規模

Common Lispのコミュニティサイズは絶望するほど小さくはありませんが、決して大きくはありません。

以前、佐野さんがGitHub Awardsの世界ランクトップの総スター数を比較すればおおよそのコミュニティ規模がわかるのではないかと言っていました。スターする人の多くはそのコミュニティに属する人だと予想できるので、正確ではなくともあながち間違った指標でもないかもしれません。

この指標で行くと、Common Lispの一位は僕の3040です。

Rubyの一位はHomebrewの75968、二位がthoughtbotの59813です。Homebrewはツールなのでコミュニティ外からのスターも含まれるかもしれませんが、それでもCommon Lispの20倍〜25倍強のコミュニティサイズがあります。

最近人気のGoはDockerの59126を一位として、GoogleGolang、Hashicorp、CoreOSなど有名企業が並んでいます。CLの20倍ほどのコミュニティサイズです。

Haskellはどうでしょうか。一位はjgmの9599です。他より小さいのは確かですが、これでもCLの3倍以上のコミュニティサイズです。

同じくらいの規模だと、D言語とかですね。それより小さい言語となるとStandard MLPascalなどになります。

こう比較するとCommon Lispのコミュニティがどれほど小さいかが実感できます。

コミュニティを大きくしたい

コミュニティの規模が小さいとできることが限られてきます。代表的な問題としてはライブラリが足りないとか、あってもメンテナンスされてないとか、十分に枯れてないとか、ドキュメントがないとか、そういうことです。そういったコミュニティでは何か作ったとしても誰も見てくれなければ承認欲求も満たされません。すると作り手も増えません。ライブラリは依然足りないままという悪循環。

そういうわけで、コミュニティを大きくするという課題はCommon Lispにはずっとあると僕は思っています。

Shibuya.lisp

そういう視点から言えばShibuya.lispが2008年からずっと定期的に開催され続けているというのは奇跡的かもしれません。一度運営が入れ替わり、運営方針がガラッと変わりました。3ヶ月に1回のお祭りイベントから、毎月開催のMeetupイベントへ。参加者も順調に伸びていて、発表者も毎回3人ほどいます。当初の「発表者がいない。集めるコストが高い」という問題も解決できているし素晴らしいことです *1

会員制クラブ「小田急CL」

自分はコミュニティ運営のようなマメなことは向いておらず、Shibuya.lispの運営メンバーに名を連ねてはいるものの全く手伝えていませんでした。

その頃、隔週くらいの頻度で自宅にCommon Lisperの友人たちを集めてハッカソンをやるということを始めました。

みんな多かれ少なかれオープンソース開発者なのでGitHubでどういうプロダクトを作っているとかはざっくりと知ってはいるのですが、それでも一堂に集まることには意味がありました。問題意識が共有できるし、苦手分野についてより詳しい人に直接質問したりできます。お互いのプロダクトを使い合うような間柄なので、もしIssueを上げるか迷っているような問題を抱えていたら相談もできます。プロダクト制作者としてはユーザーから直接意見を聞ける場でもあります。

毎回参加する顔ぶれはだいたい同じでした。僕の知り合いくらいの狭い範囲で集まっており、これも一種のコミュニティと言えるのではないでしょうか。参加するにはメンバーからの紹介が必要なので会員制クラブみたいなイメージかもしれません。

このコミュニティの名前は当初「小田急CL(仮)」でした。そのとき僕の自宅が向ヶ丘遊園駅の近くにあり、メンバーの3人が小田急沿線に住んでいたのでこの名前が(一時的に)つきました。

clfreaks

小田急CL」に名前が決まったかと思われたある日、僕の気まぐれで名前が「clfreaks」になりました。会場(僕の自宅)が場所を移しても違和感のない名前をつける必要があること、それからださい、っていうのが主な理由でした。

そうして始まったclfreaksのハッカソンですが、僕自身にはハッカソン以外にもやりたいことがありました。

clfreaksというのはShibuya.lispと違っていわゆるクローズドなコミュニティです。つまりは、そこで得られた知見などはメンバーしか知ることができないということです。

知識がインターネットで共有されることで価値を生むということを意義としていたWebプログラマの僕としてはクローズドなコミュニティはいただけません。情報をクローズドに保つのは健全ではないでしょう。

そういうわけで解決策としてハッカソンと並行して始めたのがPodcastでした。

Podcast

まあ聴く人なんてほとんどいないと思うけど、clfreaksでPodcastやってみようぜ、ってノリで始めたPodcastだったので終始雑な感じでした。

ただ、Podcastを始めてみて発見もありました。今までGitHubTwitterやブログなどでは伝えられなかったことが伝えられるようになっているという感覚です。

たとえば普段からCommon Lispを書いているメンバーが今何を課題と思っているのか、ってどれだけの人に伝わっているでしょうか。個々の完成物をGitHubや紹介ブログで見ることはできます。けれど、そのプロダクトがどういう背景で作られ、どういうストーリーを持って実アプリケーションに適応しうるのか、どういう優位性を持つのかなどはほとんど伝わっていないように感じます *2

clfreaksのPodcastはそのずばり解決になっていると思っています。音声データなので聴く人は限られるかもしれませんが、まあ全くないところからは一歩進歩です。

その後Quicklisp作者のZachに拾われたりしてそれなりに反響もありました。

twitter.com

この一年はほとんどお休みでしたが、また一段落したら集まってclfreaksを開催する予定です。

おわりに

clfreaksはTumblrで運用されており、Podcastで聞いたりWebページでMP3を直接聞けたりします。詳しくは以下のURLをご覧ください。

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

*1:先月、運営に主に携わってくださっていた神田(potix2)さんやκeenさんから終了のお知らせがありました。それを残念がる声もありつつ、次期の運営として手を挙げてくださっている方も複数人いらっしゃるので、Shibuya.lispというのはまだ継続する可能性が高そうです。

*2:ちなみにこのアドベントカレンダーは、個々のプロダクトの背景を語ることで全体としての僕の課題意識とストーリーを知れるのではないかという試みから続いています。なので、それぞれのプロダクトの紹介というよりも作られたバックグラウンドストーリーなどが中心の内容になっています。

Day 18: trivial-signal

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

今日はtrivial-signalについて話します。土日に細かいライブラリ紹介が並んでいるのはただの手抜きです。

Common LIspスクリプト

継続的なCommon Lispの課題として、スクリプトを書きづらいというものがありました*1

開発時はREPLなので問題にならないのですが、アプリケーションの起動などを考えればコマンドラインで起動できなければいけません。他のUNIXコマンドとの連携を考えてもCLIから離れられるわけではありません。

これは8日目に紹介したShellyで解決しようとした課題でもありますが、それでもまだその他の軽量プログラム言語ほどの手軽さはありませんでした。

その理由の一つがシグナルハンドリングでした。

POSIX シグナルハンドリング

シグナルハンドリングはUNIXでのプロセス間通信の方法の一つです。プロセスが特定のシグナルを受け取ったときにどうするかを指定しておけば他プロセスから制御することができます。

たとえば、スクリプトをターミナルで起動して、C-cを押すとINTが送られます。コマンドラインだとkill -INT <pid>のように送ることができます。

Common LispではPOSIX APIが仕様に含まれておらず *2、当然シグナルハンドリングもできません。C-cをするとSBCLではSB-SYS:INTERACTIVE-INTERRUPTというコンディションが発生できSIGINTのハンドリング自体はできますが、これでは2つ問題があります。

  1. 処理系依存である
  2. 他のシグナルはハンドリングできない

処理系可搬で、SIGINT以外のシグナルもハンドリングしたい。

それを解決するために生まれたのが「trivial-signal」です。

誰か作ってくれ

trivial-signalに関しては、他に誰か得意な人がやってくれたほうがいい、と当初から考えていました。あまり自分自身が使う機会も多くありません。とはいえ誰かが都合よく書いてくれるわけでもないので今必要な自分がとりあえず書く、けれどいつか誰かに引き継げるなら引き継ぎたいと思っていました。

名前に"trivial"と含まれるライブラリはAPIが自明の小さなライブラリがほとんどで、言ってしまえば標準ライブラリのような立ち位置の名前です。"Woo"や"Envy"のようなユニークな名前でなく、公共性の高い名前を敢えてつけたのは、やっぱりあまり思い入れがなくメンテナンスも頑張るつもりがなかったからでしょうかね。

ライセンスも珍しく"public domain"にしており、誰でも自由に使ってもらうようにしました。

浅井さん登場

そしてGitHubに置いてQuicklispに申請してほんの2ヶ月ほど。浅井さん(@guicho271828)からPull Requestを2つもらいました。

浅井さんと言えばCommon Lispやclfreaks界隈をウォッチしている人はご存知かもしれません。eazy-processなどGitHubでいくつかCommon Lispライブラリを公開しています。

Pull Requestをもらったとき、もはや早くもtrivial-signalの存在さえ忘れていました。この機会だから浅井さんにメンテナお願いしよーっとと思って声をかけます *3

深町: 実はこのライブラリあんまり使わないし、質を高く保てる自信ないんだよね。よかったらメンテしてくれない?
浅井: わかった

というような二つ返事であっさりメンテナを引き受けてくれました。ありがたし。

そういった経緯で、trivial-signalはこのアドベントカレンダーで紹介するライブラリで唯一僕がメンテナではありません。作者にあまり愛されずにドナドナされた不遇なライブラリだと思うとかわいそうなのでここで紹介して罪滅ぼしとします。

おわりに

trivial-signalはGitHubで公開されています。

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

*1:今ではRoswellスクリプトにより随分と快適になっている

*2:SBCLなど一部の処理系では処理系依存のパッケージとして提供している

*3:https://github.com/guicho271828/trivial-signal/pull/3

Day 17: smart-buffer

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

今日はsmart-bufferについて話します。裏側で使っているライブラリなのでたぶん知らない人がほとんどかと思うので紹介です。

Wooの成功

15日目に話したWooは想像以上に受け入れられ、すぐに実際に使ってみようという人が多く見られました。そしていくつか質問も飛んできます。

よくあった質問がこれでした。「ファイルアップロードしたときにファイルが全部メモリに載ってるよね?大きいファイルアップロードしたらやばくない?」

twitter.com

大きなリクエストの保持方法

もちろん、全部メモリに載せてたら問題です。Wooではこの問題に対してスマートな方法で対処しています。

リクエスト本文を読み込んでいき、サイズが小さいうちはインメモリバッファにロードします。もしサイズがしきい値を超えたらバッファの内容をファイルに書き込み、ファイルバッファに切り替えます。

つまり、小さいリクエストのときはインメモリで高速に処理し、大きいリクエストはファイルに書き出してメモリ使用を抑えます。

twitter.com

元々fast-httpのmultipartパーサーの機能だったのですが、これは汎用的に使えるな、と思ってのちに別ライブラリにしました。それが「smart-buffer」です。

smart-bufferのアイデア

このアイデアは僕のオリジナルではありません。

Pythonmultipartというmultipart/form-dataのパーサーライブラリがあります。これが全く同じ仕組みで動作し、小さいファイルアップロードにはメモリで、大きいファイルアップロードにはディスクで対応しています。

おわりに

smart-bufferはGitHubで公開されており、Starは7です。使っているライブラリはfast-httpとWooだけですね。

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