アトラシエの開発ブログ

株式会社アトラシエのブログです

railsの画像最適化でpietを使う

前回の記事でレスポンシブの画像は多少妥協しても大きめのものを使うしかまともな解決方法がないという話をしました。

blog.attracie.com

ところで、弊社の運営しているミライエはリフォームの施工を解説しているのですが、写真が多いものだと20~30枚くらい1記事にあります。 これのそれぞれが一眼レフで撮った場合は元のサイズで5MBとかあるのですが、横幅が700pxになるように圧縮するとだいたい150KBくらいになります。

ただ、150KBも20枚あると3MBで、1ページに3MBもあると通信環境が悪いと画像の読み込みが遅くなります。

どうしたものかなと考えていたのですが、画像の最適化でよく使うこちらのサイトでは品質をあまり劣化させずにサイズが60%くらい削れるので、やはり画像の圧縮・最適化をちゃんとやろうと考えました。

optimizilla.com

画像のデータ形式ごとに圧縮は異なる

単純に言えばイラストやアイコンはpngのほうがよく、写真はjpgのほうがデータ形式として良いです。

で、これらは見た目には同じでもデータ形式が異なるので圧縮ロジックも違います。jpgの圧縮ユーティリティがpngで使えるわけではありません。

また、仮にコマンドラインで圧縮ユーティリティがあっても普段使っているcarrierwaveから楽に呼び出せないとコードの保守性が悪くなるので、そのあたりにいいgemがないか探しました。

piet

github.com

Pietはoptipng, jpegoptimという比較的有名な画像圧縮ユーティリティのrubyインターフェースです。またcarrierwave用のモジュールも提供しているので、carrierwaveからjpegoptimを呼び出したいときは使ったほうがいいでしょう。メンテナがちゃんとついてること、ソースが非常にシンプルなのでいざとなったとき自分で保守できることも魅力です。

piet-binary

この手の話でよくあるのがバイナリ依存のためデプロイで本番サーバに入って手動でコマンドラインをインストールのが面倒という話です。このあたりの解決策がcapistranoだったりdockerだと思いますが、pietは非常に嬉しい事に依存バイナリをgemとして提供してくれています。それがpiet-binaryです。

railsでPDFを生成するときにwkhtmltopdfというライブラリがありますが、それも同様にwkhtmltopdf-binaryという依存バイナリをgemで提供してくれています。

gemで提供されていることでデプロイ設定をいじったり、本番サーバに入って行くこともないので保守性も良くなります。

実際に使ってみた

実際にpietを使って画像を圧縮してみました。

100% (167KB)

50% (76KB)

30% (58KB)

10% (35KB)

700pxいっぱいで表示すると30%あたりもかなりキツイですが、実際は300,400px程度の幅で100%表示されていることが多いので30%か50%で十分でした。

ベンチャーにとってのLTV(顧客生涯価値)

顧客生涯価値とは?

最近顧客生涯価値(Life Time Value = LTV)について考えることがあったので、その内容をまとめてみます。

LTVとは顧客一人を獲得したときにどれだけ企業にとって価値があるかを、長期に渡って考える一つの考え方です。

例えば新聞を購読する例を考えます。1ヶ月3000円の新聞に契約すると、その顧客を一人獲得するのにどれだけコストをかけてよいでしょうか。 単純に最低1ヶ月読んでくれる契約であればどんなに短くても3000円はもらえるので、3000円以内で獲得すれば良さそうです。ただ、実際には何ヶ月かは最低でも読んでくれるし、長期的に契約してもらえれば20年,30年と顧客になってくれます。それならもう少しコストをかけて新規顧客を獲得しても良さそうです。

簡単なLTVの計算方法

最もシンプルに考えれば、平均して何ヶ月購読してくれるか、1ヶ月いくらかで考えてみましょう。 平均が24ヶ月なら1ヶ月3000円で3000*24=72,000円です。このうち粗利は50%だとして36,000円になりました。

このとき、何が変動費で何が固定費かに注意します。例えば新聞を売るのには営業所があったり、印刷会社と契約したりする必要があるでしょうが、そういった管理費や研究開発費などをコストに含めずに計算します。ここでいうコストとは製造コストのことだと思ってください。

新聞のように毎月支払うビジネスモデルの場合は月ごとの継続率を使って計算すべきです。顧客の全体平均で24ヶ月というよりは、例えば1~24ヶ月の間の月ごとの解約率は3%(実際97%の24乗が50%くらいですね)、それ以降の解約率は1%とか、そういう計算方法です。

資本コスト

上の計算方法には重要な概念が抜けています。それは「24ヶ月後の3000円と今月の3000円は価値が違う」という点です。24ヶ月後の3000円は支払い時期が遅い分、今月手に入る3000円と比べて割り引く必要があります。

そこでいくら割り引くべきかという計算に資本コストという概念が用いられます。 現在x円を得るかわりに、年利rで2年貸しつけて、その支払を2年後に3000円として受け取った。と考えてみましょう。

方程式は x * (1 + r)^2 = 3000

となりますね。

従って x = 3000 / (1 + r)^2

となります。 もう少し一般化すればt年後に得られる利益pの現在価値vは、資本コストをrとしたとき

v = p / (1 + r)^t

です。

ということはtまたはrが大きくなればなるほど、現在価値は小さくなります。 t(受け取る年数)は直観的にわかりますが、rとはなんでしょうか。国にとってのrは国債利回りでしょう。 ではベンチャーにとってのrはなんでしょうか。 一つ言えるのはベンチャーにとっては1年目の1000万円は5年後ぐらいには10億円にしてほしいくらいの気持ちで運用されているという点です。なのでベンチャーが調達するお金はある意味で非常に高い利息を求められています。

rとは実はこの利息と似たようなものです。一般にベンチャーでLTVを計算するときはr=0.5くらいの非常に高い資本コストで計算します。 仮にr=0.5で計算するならば3年後に得られる10000円は、現在では2962円しか価値がありません。 これに先ほどの継続率も掛けあわせましょう。年0.8で継続してくれるのなら、この2962円を得られるのは実際には0.8*0.8*0.8=50%なので、たったの1500円です。

スタートアップにとってのLTV

以上のことを見れば、長期に渡って安定的にお金を生み出してくれる顧客も、資本コストが高い企業と低い企業で価値が異なることがわかります。スタートアップは資本コストが高いので、短期でお金を生み出しながら、ベンチャーが成熟したタイミングで長期的な支払い顧客として転換してくれるととてもうれしいわけです。

資本コストと継続率を深く考えれば考える程、スタートアップにとっては案外ニコニコ動画やクックパッドのプレミアム会員のようなモデルが意外と旨味がないという考え方ができます。

COCA (Cost Of Customer Acquisition) 顧客獲得コスト

新聞購読者のLTVが3万円だったとして、この顧客を獲得するのに3万円使っていいでしょうか。 よくありません。実際は粗利計算のコストに生産工程の管理コストなどは含まれていないからです。 だから余裕を持ってCOCAを割り出すなら、LTVの1/3が目安とされています。

すなわちこの場合は1万円までならコストをかけてよいということです。 なぜ1/3か。各業界によって異なりますが、現実的に1/2くらいに納めたいし、LTV計算は概してどこか楽観的になりがちなので、1/3という厳しい基準を引いておけば多少誤差があっても1/2くらいになる、ということらしいです。

参考

ビジネス・クリエーション! ---アイデアや技術から新しい製品・サービスを創る24ステップ

ビジネス・クリエーション! ---アイデアや技術から新しい製品・サービスを創る24ステップ

responsiveなサイトの画像サイズ問題は無理やり頑張るより妥協したほうが良さそう

ミライエは工数の問題やSEOの観点からレスポンシブレイアウトを採用しています。

miraie.me

レスポンシブレイアウトがSEO上良いという点はこちらを御覧ください。

www.suzukikenichi.com

ところがこの記事で言及されている通り、基本的にPCと同じhtmlやassetsがサーバから送られるので、スマホ用に最適化したものよりも転送量が多くなってしまいます。 実際のところシビアなのは画像で、その他はある程度工夫しようと思えば乗り切れます。

極端な話PC/スマホだけで使用するcssやjsは別にしてminifyした1枚を追加してしまえばいいので出し分けが難しくありません。(ユーザエージェントとviewportは同一ではないという点はありますが。まぁviewportを見て内部的なフラグを持つことも可能です。)

問題は画像で、例えばPCの幅1000pxの画面で横いっぱいに表示する画像が1000x500だったとして、仮にPCのretinaディスプレイをケアすると2000x1000の解像度が必要になります。ところがスマホだとたかだか320x160のretinaで640x320なので、20001000/(640320)=9.7倍もの無駄な転送量がかかってしまいます。

wifiだと問題ないですが、LTEだとけっこう気になってしまいますし、レスポンシブの場合は<img>タグにwidthやheightを書けないので、画像サイズはWebサーバから配信された時点でようやくブラウザ側から判断可能になります。従って画像が読み込まれるたびに表示されているコンテンツが画像高さ分ズレる、というようなダサい挙動が発生します。

解決策1. viewportを見てjsで切り分ける

http://blog.cntlog.net/?p=691

こちらの記事ではdevice-pixel-ratioを見て画像ファイル名に@2xをつけるとか、そういう解決策が紹介されています。

例えば

<img data-small="images/hoge_small.png" data-medium="images/hoge_medium.png" data-large="images/hoge_large.png">とかにして

viewport, device-pixel-ratio等から最適なサイズを出し分けるというのはアリな気もしますが、全体的にこれをやろうと思うと手間もかかりますし、ファイル名にルールを持たせるにしても@2xをつければ2倍になるというようなルールをクライアント、サーバ両側に強いるのは非現実的です。

この手のものはアイキャッチ画像や、ページ中で非常に重要な画像に限り有効だと思います。

解決策2. HTML5のsrcset等を利用する

私も知らなかったのですがsrcset, sizesという属性がHTML5で追加されているそうです。

http://kia-king.com/blog/tutorial/responsive-images-with-srcset/

対応ブラウザが少ないのはpolyfillがあるので大丈夫、ということですが、どうなんでしょうか・・・。 まず対応ブラウザが少ないものはスタンダードとなっていないということで、何か問題があるのでは? と疑ってしまうのとか、いかに技術的に便利でも標準の流れにないものを採用するにはけっこうコストがかかるという経験則、ついでにいえばpolyfill的なものは一見便利で2年後ぐらいにメンテされなくなったりするので、けっこう地雷だと思っているので、まぁまだ手が出せないなという印象でした。

解決策3. 妥協する

話はそもそもPCのretinaに画像を対応させようというところから来ているので、

  1. PCのretina対応を基本的にはしない
  2. スマホのretina対応もやる画像とやらない画像(1x採用)で分ける
  3. レスポンシブのブレークポイント(col-xs-xxみたいなやつ)で画像の表示が大きく変わるときに画像サイズが表示のサイズより小さくて荒くなったりしても気にしない

ということにしてみました。

で、そうすると480x360くらいで呼び出していた画像が1ページに20枚くらいあったんですが、これを160x120で呼び出し、さらにimagemagickで画像クオリティを70%に落としました。

これでファイルサイズ的には一気に1.5MBほど減らすことができました。

Google PageInsights

デバッグにはこれが便利です。他のサイトを見ても完全な最適化をせずにここで「最適化すれば700KB~900KB削れる」と指摘されるくらいまでが許容できる気がします。

たいていのケースで画像サイズによって転送量が増え、ユーザビリティが下がってしまうのはモバイルの場合なので、あとはモバイル単体で大きめの画像を使い過ぎないデザイン、および強調してモバイルに無理強いをしないPCデザインをいい按配で考える、というのが妥当かなと思います。

起業する場所の選び方

弊社は2014年4月設立ですが、2014年2月に私が港区赤坂に引っ越し、2ヶ月後に自宅兼事務所という形で自宅住所を使って会社登記しました。

こういうように、起業する場合でも自宅を事務所にしたり、バーチャルオフィスを使うことで、いきなり事務所を借りたりせずに済みます。また、案外事務所を借りて会社登記も同時に行うのは大変です。というのは、会社の運営実績がないのでどういった会社なのかも分からず、支払い能力があるか不明なので好立地の物件ほど新設法人に貸す必要がないからです。

自宅を事務所として登記/使用すべきか

まず、登記と使用は分けて考えます。 自宅を登記して、オフィスの実態はレンタルオフィス(バーチャルオフィス)というケースはあります。 普通の自宅では人を呼んで仕事をするにも居住地なので外部の人を呼びづらかったりします。積極的に外部の人を呼ぶ事業の場合には自宅を事務所として使用すべきではありません。

ただし登記は別で、レンタルオフィスを登記に使うと履歴事項全部証明書、いわゆる登記簿にレンタルオフィスの住所が載ります。レンタルオフィスの住所が「東京都千代田区永田町2-11-1 山王パークタワー 3F」とかだと一般的に社会的信用は高まるようで、そうとも限りません。銀行やカード会社はレンタルオフィス登記の企業をあまり受け付けません。そうすると取引銀行もネットバンクのような口座に限定されるので、やはり社会的信用には限度があります。

行政区の選択

次に登記する行政区も重要です。なぜなら会社の手続きは行政区によってどこで手続きするかが決まり、それ以外の場所ではできないからです。さらにこの行政区は変更するのにコストがかかります。具体的には、定款に東京都港区を本店所在地とすると記載してある場合、東京都渋谷区にオフィスを移転させると定款を変更する必要があります。

また、 税務署 , 年金事務所 , 労働基準監督署 , 法務局 , メガバンク支店 なども所在地によって決定していきます。これらは基本的には所在地の近くの支店/営業所が選ばれるのですが、案外時間距離的に遠かったりします。

例えば赤坂の場合、税務署は西麻布寄りの麻布、年金事務所は浜松町、労基署は三田、法務局は十番寄りの麻布にあり、それぞれが赤坂から地味にアクセスが悪いです。

最近知ったのですが渋谷は税務署、法務局、労基署、年金事務所が全て渋谷駅から北へ徒歩10分程度歩いたNHKの近くに集中しているので便利だなと思いました。

また、なんだかんだ会社の住所は一定の信用度につながります。弊社は「港区赤坂2-12-xx-307」ですが、赤坂はマンションの一室をオフィスにしている会社が多く、芸能事務所や税理士事務所など、割と大手のところでもこういう住所なので、それなりの信用度はあります。(が、やはり港区赤坂2-12-xx xxビル2Fとかのほうが良いのでしょうが)

一方、私が前住んでいたような「北区東田端1-7-xx-105」とかになると「これは個人のマンションかアパートだな」という感じがすごく出てしまい、対外的に住所を示す際に見栄えが悪いです。(見栄えの問題というか、初めて会うような人に対して警戒心を与えてしまうというようなデメリットがあります)

なので、可能であれば渋谷区、港区、千代田区といったオフィス街のある程度名前の知れた土地をオフィスにすることはそれなりにメリットがあります。特にxx号室のオフィスの場合は有効です。

結論

もし今から登記したいという方が、すでに主たる活動場所(大学や貸し会議室など)を持っていてそれに不満でない場合、また、自宅と作業場を分けて考えたい場合は自宅を登記住所にして、活動場所を維持すべきだと思います。

一方でそれで困るのは対外的に住所を示して営業を行ったり、なるべくホワイトボードやデスクから特殊な設備までを自社内で持って完結させたいようなケースです。 この場合は自宅をオフィスっぽいところに引っ越して、最初だけそこを活動拠点にして、あとで自宅とオフィスを分離するというパターンになります。

これで、個人で事務所を契約することができる(新設法人の手続きがめんどくさくない), 自宅とオフィスを兼用すれば光熱費や通信費をある程度抑えられる、最悪事業がスケールしなくても自宅として住めるなどのメリットが有ります。

私はそういう観点で赤坂にオフィスを構えましたが、渋谷の桜が丘町のマンションなどで探してみるとよいのではないでしょうか。

Railsであまり重要でないcolumnをスキーマレスに設定したい

Railsではserializeをうまく使えばスキーマレスにシンプルなデータを定義することができます。 これをもう少し通常のカラムライクに使用したかったので、gemを作成しました。

github.com

使い方ですが、まずはRailsのモデルを作成します。このとき適当でいいのでTEXT型のカラムを一つ用意します。とりあえずvirtualというカラムにします。

次にモデルの定義を以下のようにします。

class Blog < ActiveRecord::Base
  include Virtualize

  set_virtual_attributes :memo, on: :virtual
end

Virtualizeを読み込み、カラムレスに追加したいattributesを上のように宣言します。

あとは

@blog.memo = 'hoge'
@blog.memo #=> 'hoge'
@blog.save
@blog.memo => 'hoge

ということが実現できます。

やっていることはserializeにセッターとゲッターをラップしてつけてあげているだけですが、それなりに便利です。 こういうことは、redis-objectを使えばできるのですが、redisをいれる必要があり、とにかくリリースして検証するフェーズではMySQLだけにしたいので簡易的に作りました。

なお、当然のことながらこれはserializeされている=MySQL側からは見えないので、この値を使って絞込などはできません。使いどきに気をつけましょう。

bootstrapでresponsiveのgrid systemだけ使いたい

bootstrapでは.containerでセンタリングし.rowでマイナスマージンをセット, .col-sm-3.col-xs-6等でデバイス幅に応じたレスポンシブレイアウトを可能にする グリッドシステム を提供しています。

この機能は非常に便利です。他にグリッドシステムを提供しているcss frameworkはないことにはないですが、bootstrapほどメジャーなものは存在しません。 しかし、bootstrapでp,ul,li等にデフォルトで設定されるcss styleは細かいレイアウトを自分で調整したい場合には不都合なことが多いです。

そこで弊社では管理画面のようなデザインで妥協できる画面はbootstrapを全て採用し、ユーザの目に触れる部分ではbootstrapのgrid systemだけを使うようにしました。

bootstrapのインストール

弊社ではrails-assetsを使っているので、これを用いてインストールします。 (※ 2016/12追記、rails-assetsは今では廃れているのでnpmを使いましょう)

Gemfile

source 'https://rails-assets.org'
gem 'rails-assets-bootstrap-sass-official'

必要なモジュールをimportする

application.css.scss

@import 'bootstrap-sass-official/bootstrap-sprockets';
@import "bootstrap-sass-official/bootstrap/variables";
@import "bootstrap-sass-official/bootstrap/mixins";
@import 'bootstrap-sass-official/bootstrap/grid';
@import 'bootstrap-sass-official/bootstrap/scaffolding';
@import 'bootstrap-sass-official/bootstrap/responsive-utilities';

パッケージの中身を見たところ、grid systemを提供しているのはbootstrap/gridというモジュールのようです。そこでこれをimportしますが、どうやらbootstrapのパッケージ構成は

bootstrap/mixins/** alertやnavなど各種のいわゆるbootstrapっぽいスタイルやcssをmixinで提供する bootstrap/_alerts.scss等 実際にalertやnavのスタイルを使いたいときはこっちをimportする必要がある 例えば.alertのようなクラスをつけるとbootstrapっぽくなるという機能は_alerts.scssで提供されていますが、実際にはそのscssの中では

.alert-success {
  @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text);
}

のようにmixinの読み込みが行われています。

何が言いたいかというと、

@import "bootstrap-sass-official/bootstrap/mixins";
@import "bootstrap-sass-official/bootstrap/variables"

の部分ではmixinと変数をすべて読み込むのですが、 これらをincludeしない限り(あるいはincludeしたscssをimportしないかぎり)余計なcssが吐き出されることはありません。

そのほかですが、scaffoldingではなんとなくresetっぽい機能があったりimg-responsiveが入っているので使用しました。responsive-utilitiesはvisible-xsのような機能ですね。これはほしいので入れました。

おまけ1

デバイスサイズはxsのときだけに有効にしたいスタイルのためのmixinです。xs環境(幅768px以下)だけに有効化したいcssが多かったので作りました。

responsive_utility.sass

=only-xs
  @media only screen and (max-width: $screen-sm)
    @content

使うときは

.hoge
  +only-xs
    padding: 0

おまけ2

bootstrapで読み込んでいるscaffoldingの中でスマートフォンでリンクをタップした時の色を透明(無効化)しているのですが、これはスマホでのインタラクティブさが減少してよくなかったので上書きしました。

my/_scaffolding
html
  -webkit-tap-highlight-color: rgba(170, 170, 170, 0.3)

Bootstrapとのつきあいかた

弊社のプロジェクトにかぎらずbootstrapを使用したプロジェクトに何度も取り組んだことがありますが、bootstrapは強力な機能なので毒にも薬にもなります。毒というのは、bootstrap依存になってしまったデザインをbootstrapから離れたデザインにしようとすると途端にスパゲティcssになってしまう。一方薬とは、なんといっても開発効率の高さです。

そこでbootstrapを使用する場合は慎重に必要な機能を見極めて、不要なものを排除するようにしてはいかがでしょうか。

ERROR Rack::Multipart::MultipartPartLimitError: Too many open files - Maximum file multiparts in content reached

Rails4.2がリリースされましたね。

目玉機能はActiveJobということですが、これは実態はSidekiqやresqueのアダプタということで、本質的には新機能という感じではないですね。

AdequateRecordはすごそうですね。要するに内部でもろもろをキャッシュしてうまい具合にクエリを減らしてくれるという機能だそうです。

Railsではオブジェクトとして違うものはクエリを再発行してしまう(そしてSQL側のキャッシュを読み出す)ので、それが発行されないで済むという認識でいいんでしょうか・・・?

ちなみに弊社で運用中のサービスでは表題のエラーが発生しました。単純な画像アップロード機能で発生して、最初はファイルディスクリプタの問題かと思ったんですが大してプロセスもファイルも多くない状況で発生したのでバージョンを戻してみたところエラーにならない。

stackoverflow.com

このへんが関係しているようです。単純なソリューションとしてはイニシャライザで Rack::Utils.multipart_part_limit = 0 で黙らせてしまえばいいですね。