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サービスを作るのに必要だと見越しての開発でした。
Psychiq
さて、Lesqueを使ったはいいものの当初の予定とはかなり違って、使う機会のないまま日は経ちました。
そして2年後、とうとうLesqueを使えるかもな、というタイミングで見てみると、そのコードは古いですしCommon Lispの環境にもついていけておらず継続して開発するのもためらわれるものでした。この2年でRoswellがスタンダードになったにも関わらずRoswellスクリプトが提供されていなかったり、Travis CIでのテストが行われていなかったり。
メッセージキューの世界でも世代交代がありました。Sidekiqという新しいプレイヤーが現れました。Resqueのプリフォークスタイルではなく、スレッドを使うため高速に動作し、シンプルなアーキテクチャであるということが売りのようです。
そこで新たにSidekiqをCommon Lispに移植することにしました。それが「Psychiq」でした。
落語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 LispはTravis CIのオフィシャルサポートはありませんが、cl-travisやRoswellを使って複数処理系をテストすることができます。
Common LispライブラリのCIは僕にとってはなかなかライフチェンジングで、今まで手元でSBCLでテストしてpushしていたのがClozure CLでは落ちるとかがすぐわかったり。CL-DBIのようにテスト用にRDBMSをたくさん用意しなければいけないようなものもPull Requestでテストできるのでパッチをもらっても安心してマージできます。
僕のライブラリはもちろんのこと、他の新しいライブラリでもTravis CIは当たり前に使われるようになりました。
しかし、それだけでは足りません。
テストがなくてもbuild passing
κeenさんのテンプレートエンジンのZenekindarl *1 もTravis 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.comClear explanations of Drakma's Pitfalls https://t.co/CoXxojYH6V
— PuercoPop (@PuercoPop) 2015年8月26日
こういった状況を改善すべく、よりよいAPIを提供することでDrakmaを置き換えられないかなと考えました。そこで作ったのが「Dexador」です。
良いAPIでは不十分
新しくHTTPクライアントを作ることは可能でしょうが、使ってもらわなければ意味がありません。その観点から言うと「Drakmaより良いAPIを提供します」というだけでは不十分だとは感じていました。「良い、なんてものは主観的だ」と言われればそれまでです。細かい改善も伝わりづらく、なかなか移行するほどアピールできるとは思えません。
そこで思い至ったのが高速化です。
Wooで作ったfast-httpやQURIを使うことでDrakmaよりも圧倒的に高速になれば優位性が数値として証明できます。
これは良い戦略かと思われたのですが、fast-httpとQURIでは1.2倍程度の速度しか出ませんでした。そこで最大のボトルネックであるコネクション確立部分をスキップすることでさらなる高速化を行いました。
マクロレスエラーハンドリング
Dexadorの売りの一つは4xxや5xx系のステータスコードが返ってきたときはエラーを発生するということです。もしそのエラーハンドリングをしたければhandler-case
やhandler-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-bind
やhandler-case
は言語仕様の機能なので誰でも使えます。
ここにも11日目でも取り上げたマクロレス思想の一端が見られます。
Drakmaの微妙な不具合
Dexadorを作っている過程でいくつか不具合も出ました。新しいプロダクトだから仕方ありませんね。直しましょう。ところでDrakmaはどうやっているのだろう。試してみると同じ不具合があった、というようなことも多くありました。結果的にDrakmaのあまり知られていない微妙な不具合をいくつか知っています。
twitter.comDrakmaはgzipで返ってきたレスポンスをwant-stream tでストリームで受け取るとかなり面倒なことになるな。
— fukamachi (@nitro_idiot) 2015年7月28日
twitter.comDexadorでkeep-aliveのコネクションをストリームで受け取るとブロッキングする問題が見つかって、これCHUNGAがおかしそうだけどDrakmaはどうやってんだ、って思って試したらDrakmaも同じ挙動だった。Drakmaはデフォルトでcloseだから気づきづらいだけ
— fukamachi (@nitro_idiot) 2015年8月27日
「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を一位として、Google、Golang、Hashicorp、CoreOSなど有名企業が並んでいます。CLの20倍ほどのコミュニティサイズです。
Haskellはどうでしょうか。一位はjgmの9599です。他より小さいのは確かですが、これでもCLの3倍以上のコミュニティサイズです。
同じくらいの規模だと、D言語とかですね。それより小さい言語となるとStandard MLやPascalなどになります。
こう比較すると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を始めてみて発見もありました。今までGitHubやTwitterやブログなどでは伝えられなかったことが伝えられるようになっているという感覚です。
たとえば普段からCommon Lispを書いているメンバーが今何を課題と思っているのか、ってどれだけの人に伝わっているでしょうか。個々の完成物をGitHubや紹介ブログで見ることはできます。けれど、そのプロダクトがどういう背景で作られ、どういうストーリーを持って実アプリケーションに適応しうるのか、どういう優位性を持つのかなどはほとんど伝わっていないように感じます *2。
clfreaksのPodcastはそのずばり解決になっていると思っています。音声データなので聴く人は限られるかもしれませんが、まあ全くないところからは一歩進歩です。
その後Quicklisp作者のZachに拾われたりしてそれなりに反響もありました。
twitter.comI wish I understood Japanese because http://t.co/GNPb5EnHZe looks enjoyable.
— Zach Beane (@xach) 2015年2月27日
この一年はほとんどお休みでしたが、また一段落したら集まってclfreaksを開催する予定です。
おわりに
clfreaksはTumblrで運用されており、Podcastで聞いたりWebページでMP3を直接聞けたりします。詳しくは以下のURLをご覧ください。
明日のアドベントカレンダーは20日目のDexadorについてです。お楽しみに。
*1:先月、運営に主に携わってくださっていた神田(potix2)さんやκeenさんから終了のお知らせがありました。それを残念がる声もありつつ、次期の運営として手を挙げてくださっている方も複数人いらっしゃるので、Shibuya.lispというのはまだ継続する可能性が高そうです。
*2:ちなみにこのアドベントカレンダーは、個々のプロダクトの背景を語ることで全体としての僕の課題意識とストーリーを知れるのではないかという試みから続いています。なので、それぞれのプロダクトの紹介というよりも作られたバックグラウンドストーリーなどが中心の内容になっています。