今回は、文学フリマ版の電書サーバーの補足説明を行った後に、文学フリマにおける電書販売のフィードバックを受けて、電書フリマではどのように電書サーバーを改善したのかを解説します。
文学フリマ版電書サーバーの補足
URLハンドラ
iOSには「ダウンロードしたデータを指定したアプリで起動する」という機能がなかった(バージョン3.2から追加)ため、URLハンドラがよく使われます。例えば、あるURLのPDFをGoodReader(iPad/iPhone用のPDFリーダー)で読み込むためには、リンクを以下のようにします。
httpではなくghttpというプロトコルを使うことで、ダウンロードしたデータがGoodReaderに引き渡されます。EPUBリーダーのStanzaならstanza://です。文学フリマ版電書サーバーが購入者に送信するメールには、通常のhttpの他、ePubならstanza、pdfならghttpのリンクを載せています。
運営コスト
コストをざっと計算すると、フリマの参加費が4,500円。herokuの使用料が約9ドル、s3の使用料は5セントでした。ちなみにherokuで課金されたのは、フリマ当日にダイノ(同時に起動するWebのインスタンス数)を1→2と増やしたのと、メール送信のプラグインSendgridを当日だけproバージョンにした部分のみです。電書部で作成した「電子書籍の出てくる本のレビュー集」である「未来のテキスト」は約160冊(単価100円)販売できたため、サーバー経費は十分にまかなえた計算になります。
電書フリマ構想
この成功を受けて構想されたのが、電書のみを販売する「電書フリマ」です。電書フリマは、ブース一つだった文学フリマとは異なり、渋谷のコラボカフェを借りて電書部が主催するイベントです。実際には、2010年7月17日に開催されました。
フィードバックとして、技術班には以下の要望が寄せられました。
- タイムアウトの解消/PDF分割の解消
- 複数の場所でフリマを開催して、売上計算は個別にしたい
- iPhone対応
- タグのサポート
電書の数も大幅に増えることになったため、PDF担当はtk_zombieに加えていなちょう(inachou)、EPUB担当は小嶋に加えて早坂(tamisuke)と2名の応援を仰ぐことになりました。
タイムアウトの解消/PDF分割の解消
第3回で解説したとおり、文学フリマ版の電書サーバーは以下のような処理の流れでした。
- 購入ボタンを押す
- 電書マスターをAmazon S3(Amazonのストレージサービス)からダウンロード
- 電書にメアドを埋め込む
- 再びS3にアップロード
- 購入完了メール(ダウンロードURL付き)を送信
1~5の処理がherokuの制限である30秒を超えるとタイムアウトエラーになります。特に電書マスターのPDFが大きすぎると処理が間に合わないため、サイズの大きなPDFは分割して対応していました。
電書フリマ版の電書サーバーでは、この問題を解決するために処理フローを以下のように変更しました。
- 購入ボタンを押す
- 電書生成処理をバックグラウンドにセット
- ありがとうメールを送信(ここで販売は完了)
- バックグラウンドで電書生成(マスターDL、メアド埋め込み、S3アップロード)
- 完了メール(ダウンロードURL付き)を送信
Delayed_job
電書生成をバックグラウンドで行われることで、販売処理は即座に終了するためタイムアウトはなくなり、かつ電書マスターPDFを分割する必要もなくなります。
rubyにはいくつかバックグラウンド処理用のライブラリがあり、言語自身にもスレッド機能がありますが、herokuではDelayed_job(DJ)を使います。DJで使えるバックグラウンドプロセスの数は、ワーカ(worker)で指定します。デフォルトでは0で、一つ増やす毎に0.05ドル/時間(36ドル/月)課金されます。
DJの特徴は、特定のメソッドだけを非同期動作にしたり、あるクラス全体を非同期動作にしたり、柔軟な設定ができる点です。以下は実際に使っているコードの一部です。handle_asynchronously宣言したメソッドを、通常のrubyのメソッドのように呼び出せば非同期に動作します。
非同期処理をスタート/ストップさせるにはrakeを使います。herokuコマンドを使って、サーバ側のrakeを起動します。
複数の場所で開催
電書フリマは、メイン開催は渋谷のコラボカフェ、そのほかにも「僕たちだけがおもしろい」公開収録現場、電書フリマ@京都で開催することになりました。また、渋谷でも周辺のカフェで販売する企画も出ました。そのため、だれが、どの電書を、何冊販売したの集計をとる必要があります。文学フリマ版で認証を導入していたので、これを拡張することにしました。
文学フリマ版で電書を販売すると、送信先のメールアドレスを持つCustomerモデルのインスタンスをつくります。これは複数のBookモデル(名前がよくないのですが、購入した電書の情報)のインスタンスを持っています。電書フリマ版は、Bookモデルにsold_byという属性を追加して、ログインしたユーザー名を販売時に保存するようにしました。ユーザー名はcurrent_userメソッドで取得できます。
売上は、sold_byカラムでグルーピングして計算しています。
電書フリマはすべてのアカウントで同じ電書を販売するので、アカウント毎のカスタマイズは不要です。そのため販売する人のアカウントはモデルにしませんでした。
iPhone対応
電書フリマ版サーバーは、販売画面をiPhoneに対応しました。メイン会場周辺のカフェや(実際にはやりませんでしたが)道端でもゲリラ的に販売できるようになります。電書フリマのテーマのひとつである同時多発開催のための機能です。
そのために販売画面の構成を見直し、sinatraのviewの一部だった販売画面を切り離して、htmlとしてどこにでも置けるようにしました。コントローラ側にapiを用意して、htmlはJavaScriptでコントローラ側と通信します。プロトコルはJSONPです。PCとiPhoneは、htmlを2つ用意してエージェントを見て切り替えています。
利用したライブラリはrack-jsonpです。rackミドルウェアなので上位のライブラリに関係なく利用できます。オブジェクトにto_jsonメソッドが追加され、レスポンスで返すだけなのでとても簡単。呼び出す側にcallbackパラメータがついていれば、自動的にJSONからJSONPに切り替わります。以下のコードではコンテントタイプを指定していますが、JSONPのときにはコンテントタイプも切り替わります。
タグ
文学フリマで15冊だった電書は、電書フリマでは大幅に増えることが予想されました。そこで電書をタグで管理すること考えました。販売画面でもタグを表示して、電書を一括して選択できる仕組みです。電書のマスターであるBookMasterモデル(テーブル)とTagモデル(テーブル)を、多対多対応させています。DataMapperでは、以下のようになります。
sinatra_more
第3回で述べたように、電書サーバーはWebフレームワークとしてsinatraを使っています。小規模アプリを作るにはrailsより向いていますが、画面数が多くなってくるとソースのあちこちにロジックが散らばって管理できなくなってきます。電書フリマ版でもだいぶ機能が増えたので、sinatara_moreを使うことにしました。sinatara_moreは、sinatraのシンプルさはそのままに、rails風の機能を追加する拡張ライブラリです。sinatara_moreは5つのプラグインから構成されていて、不要な機能はオフにしておくことができます。
MarkupPluginはlink_toなどのrails風のタグヘルパーを多数追加します。DataMapperのインスタンスと連動するformのヘルパーもあります。
RenderPluginはHTMLテンプレートから別のテンプレートを読み込むpartial機能がメインです。
WardenPluginはUserモデルにもとづく認証機能を提供します(sinatra-authorizationを使ったので、これは使っていません)。
MailerPluginはメール送信機能です。
RoutingPluginはルート機能を大幅に拡張します。
ただしsinatra_moreは開発はすでに終了しているため、バグの対応などは期待できません(開発者はPadrinoに移行したようです)。実際にディレクトリの中にあるテンプレートをレンダリングするとfromヘルパーがうまく展開されないというバグがあります。
電書フリマの結果
事前に簡単な予約を募って、来場の時間帯があまり重ならないようしたのですが、電書フリマは想像を遥かにこえる来場者数になりました。会場のコラボカフェは大混雑で、2台あるパソコンはフル稼動、ときどきiPhoneも使われていたようです。コラボカフェは地下にあるのですが、地上につながる階段まで人があふれ、店の前には入れない電書部員やお客さんが群れをなす(幸い歩道が広かったので、あまり混乱はありませんでしたが)状態でした。
その結果、総計でかなりの数を販売することができ、「電子書籍を対面で売る」のは、十分に成立することが分かりました。
電書サーバーのコストは、文書フリマより増えました。バックグラウンド処理のためにワーカを増やしたコストが効いています。ワーカはフリマ当日だけではなく、テストのため開発中も増やしていました。一つのワーカを一ヶ月使うと36ドルになります。これが主なコスト原因です。ワーカはプログラム中から動的に制御できるため、最適化の余地があるかもしれません。
次回最終回は現在開発中の電書サーバーの最新型「電書サーバー秋」と、今後の展開について解説します。