railsでコメント数をどう実装するかで悩んでいます。

https://gyazo.com/c7ac20038c53ad302091f718fd948d15

ニコ動だとこんな感じ。

https://gyazo.com/a124a4bbf487715768730ec4aea9d810

怖話でもこんな感じでコメント数/再生数などを保存しています。

一定以上の数になると素朴な処理では速度的に無理が来ます。

怖話での実装方法

怖話でのカウント数の実装の歴史。

  1. 何もせずDBに1レコードずつ保存時代。
  2. 一覧ページなどが重いのでrailsのcounter cahcheを使う。
  3. コメントや閲覧はpolymorphic関連なのでcounter cacheが対応してない。conditional_counter_cacheを使う。
  4. 削除に時間がかかり過ぎる。 ← イマココ

一覧の表示はconditional_counter_cacheで大丈夫なのですが、削除時の処理がかかりすぎてタイムアップする問題がでてきました。

削除時の問題

ここではわかりやすく閲覧数ではなくコメント数で説明します。

コメント数が1万件ある話を削除すると下記のような処理が走ります。

  1. storyを削除する。
  2. dependent: :destroyで依存するコメント1が自動的に削除される。
  3. コメントが削除されたのでstoryのcomments_countが-1でUPDATEされる。
  4. dependent: :destroyで依存するコメント2が自動的に削除される。
  5. コメントが削除されたのでstoryのcomments_countが-1でUPDATEされる。
  6. 以下1万回繰り返し

1件につき100msだとしても1000sかかるので無理がある。

これまでの考え

  • memcacheやredisを使うのは手間なので無理が来るまで避けたい。(開発環境の構築の手間が増える)
  • ランキングで集計するので時間の情報は欲しい。
  • 一番楽な方法で実装しよう。

今の考え

  • RDBは無理がある。memcacheやredis、他のストレージもやむなし。
  • rails的に一般的な実装はなんだろう?
  • ありがちな問題なので一般的な対処方法を構築したい。

皆さんこういうのどう実装されてますか?こんな風にやってるよという方がいらっしゃったら @komagata などにメッセージいただけるとありがたいです。 :bow:

更新:railsでコメント数の実装の悩み〜解決編〜 - komagataのブログ

ぼっち演算子を使える2.3系にアップデート。

$ CONFIGURE_OPTS="--with-openssl-dir=`brew --prefix openssl` --with-readline-dir=`brew --prefix readline`" rbenv install 2.3.1

railsのプロジェクトでbundleしたらeventmachineのインストールでコケた。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
/usr/local/var/rbenv/versions/2.3.1/bin/ruby -r ./siteconf20160518-21178-1i9l3os.rb extconf.rb
checking for rb_trap_immediate in ruby.h,rubysig.h... no
checking for rb_thread_blocking_region()... no
checking for ruby/thread.h... yes
checking for rb_thread_call_without_gvl() in ruby/thread.h... yes
checking for inotify_init() in sys/inotify.h... no
checking for __NR_inotify_init in sys/syscall.h... no
checking for writev() in sys/uio.h... yes
checking for rb_thread_fd_select()... yes
checking for rb_fdset_t in ruby/intern.h... yes
checking for rb_wait_for_single_fd()... yes
checking for rb_enable_interrupt()... no
checking for rb_time_new()... yes
checking for sys/event.h... yes
checking for sys/queue.h... yes
checking for clock_gettime()... no
checking for gethrtime()... no
creating Makefile

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/eventmachine-1.0.7/mkmf.log

current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
make "DESTDIR=" clean

current directory: /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7/ext
make "DESTDIR="
compiling binder.cpp
In file included from binder.cpp:20:
./project.h:116:10: fatal error: 'openssl/ssl.h' file not found
#include 
         ^
1 error generated.
make: *** [binder.o] Error 1

make failed, exit code 2

Gem files will remain installed in /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/eventmachine-1.0.7 for inspection.
Results logged to /usr/local/var/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/extensions/x86_64-darwin-15/2.3.0-static/eventmachine-1.0.7/gem_make.out

下記を参考にして設定したらいけました。

$ bundle config build.eventmachine --with-cppflags=-I$(brew --prefix openssl)/include
参照:Ruby2.3.0 igaiga diary(2015-12-25)

一日の中で頭を使ってるのが「ご飯何食べるか」な気がして決め打ちすることにしました。

本当はソイレントでいいんですが、なかなか手に入らないですからね。

飯確保・食事のフロー

2ヶ月ぐらい試行錯誤した結果、ニチレイフーズの冷凍弁当ウーディッシュまとめ買いコースが一番便利でした。

これをネットで注文してオフィスに送り、冷凍庫にストックしておきます。冷凍なので9ヶ月持ち、腐る心配はありません。

ウーディッシュまとめ買いコースの良いところは、14食いっぺんに買えるところと、コンパクトで冷凍庫にたくさん入るところです。(写真の手はおもちゃの手なので弁当が大きく見えています)

栄養的には自分で用意するのは不可能なほどバランスが取れていてヘルシーです。味もコンビニや外食で胃もたれするおっさんには優しく美味しいです。量的にはOLが食ってる小さい弁当ぐらいのイメージです。

普段良い物を食べてる人にはメリットが少ないかもしれませんが、味の濃い外食や自分で好きなモノばっかり買うために偏ったコンビニ飯ばかりで胃が持たれてばっかりだった僕にとっては体調は良くなるし、時間も節約できて嬉しい限りです。

ゆくゆくは弁当ストックが減ったら自動で注文するようにしたいです。

怖話でネストしたリソースのクラス名がぶつかってしまうので、どうつけるかで悩んでいます。

ぶつかるページ

  1. story 1個づつに紐づくcomments(怖い話1のコメント一覧)
  2. なんらかのstoryに紐づくcomments(怖い話のコメント一覧)

story(怖い話)以外にもcomic(ホラー漫画)とかurban_legend(都市伝説)とかあり、それぞれにもcommentがつく。

# config/routes.rb
Rails.application.routes.draw do
  namespace :stories do
    resources :comments, only: :index
  end

  resources :stories, only: :index do
    resources :comments, only: :index, controller: 'stories/comments'
  end
end
% rake routes        
        Prefix Verb URI Pattern                        Controller#Action
stories_comments GET  /stories/comments          stories/comments#index
 story_comments GET  /stories/:post_id/comments stories/comments#index
         posts GET  /stories                   stories#index

URLの意味的にどちらも上記のようにしたいが、commentsコントローラーが被ってしまう。

これを避けてnamespace無しのcommentsコントローラーを使うと今度はcomicのcommentなどとかぶってしまう。

それを避けるにはAllCommentsとかCommentsByStoryなどといったダサい名前しか思いつかない。まいったなあ。

主にアメトーーク!を録画するのに使っているnasneが容量一杯なので外付けHDDを買いました。

I-O DATA USB 3.0/2.0接続【家電対応】外付ハードディスク 4.0TB HDC-LA4.0

しかしnasne、FAT32しか対応してませんでした。メーカー独自拡張とか以外2TBまでしか対応してない。クソがぁ…。

4.0TBの方はPCで使うことにして2TBの別のを買いました。

WD HDD ポータブル ハードディスク 2TB USB3.0 TV録画対応 Elements Portable WDBU6Y0020BBK-JESN / 3年保証

こっちはいけました。ただ注意するのはFAT32でもGPTではなくMBRでフォーマットしないといけないこと。ちょっとハマっちゃいました。くそがぁ…。

最近やっちゃった買い物が多いのでスペックは注意して買って行きたい。

ということで話早えぇ@sotarokさんにApple Watch売ってもらいました。

新しいバンドも届きました。

本当はスキューバブルーのバンドを買ったんだけど間違えて42mmの方を買ってしまったので@machida さんにプレゼントということにして買い直しました…。やっちゃったな…。

今のところやはり時計が一番のキラーアプリなのでAndroid Wareと変わりませんな。

RailsGuidesにちゃんと書いてありますが、ちょっとハマりました。

こういうのはダメ。

Rails.application.routes.draw do
  constraints subdomain: :api do
    scope module: :api do
      namespace :v1, format: :json do
        resources :posts
      end
    end
  end
end

ちゃんと文字列にする。

Rails.application.routes.draw do
  constraints subdomain: 'api' do
    scope module: 'api' do
      namespace 'v1', format: 'json' do
        resources :posts
      end
    end
  end
end

3.9 リクエスト内容に応じて制限を加える

リクエストベースの制限は、Requestオブジェクトに対してあるメソッドを呼び出すことで実行されます。メソッド呼び出し時にハッシュキーと同じ名前をメソッドに渡し、返された値をハッシュ値と比較します。従って、制限された値は、対応するRequestオブジェクトメソッドが返す型と一致する必要があります。たとえば、constraints: { subdomain: 'api' }という制限はapiサブドメインに期待どおりマッチしますが、constraints: { subdomain: :api }のようにシンボルを使用した場合はapiサブドメインに一致しません。request.subdomainが返す'api'は文字列型であるためです。

ドメインやPATHなど、文字列っぽいものは文字列で指定すべきと考えておけば良さそうです。

Nexus PlayerからApple TVに乗り換えました。

理由は最近iPhoneに乗り換えてすこぶる快適なので合わせたかったのと、Nexus PlayerのGoogle MusicアプリはGoogle Musicの音楽聴き放題に入っていてもその機能が使えないのに対して、Apple TVならばApple Musicの聴き放題をそのまま使えるからです。

結果、対して変わらないっすね。主な用途がHulu、Netflix、Youtubeなのでどっちでも変わらんです。どちらもアプリ少ないし。(現時点ではAndroid TVの方が多い)

唯一気になったのは、僕は時々Google Play Storeで映画を借りていたんですが、Google Play Storeの映画のプレビューってのは本当のプレビューで、最初の数分をそのまま見れる形になっています。

これ、海外ドラマじゃなく映画なので最初5分なんてスゲー退屈なんですよね。インターステラーをみたいと思ったんですが、最初の5分見せられてもさっぱりでした。

それに対してiTune Storeはプレビューがそれ用の予告編ムービーになっています。予告編ムービーは「おもしろいぞー」って感じでこれでもかと煽ってくるので非常にみたくなります。インターステラーはなかったけど、ゼロ・グラビティの予告編みたら、これ見てぇ!ってなりました。

最初の5分プレビューは無いほうがいいですね。

Nexus Playerのゲームパッドは誰も使ってない可能性が巨レ存・・・? - komagataのブログ

何の話かというと、

  • Aの学習にはBの学習が必須
  • Bの学習はAの経験がないとまったくピンとこない

という問題があって困るってことです。

まあ学校の数学の授業みたいにとにかくBは黙って覚えろ話はそれからだスタイルで行けばいいんですけど、僕自身がそういう学習辛かったなーと思うので何とかしたい。

具体的に弊社のインターンシップのカリキュラムで言うと、

  • railsアプリ作成の学習(has_many :throughtを使うところ)にはRDB設計の学習(正規化や多対多の関連)が必須。
  • RDB設計の学習アプリ作成の経験ないとまったくピンとこない

という問題が起きております。

まずrailsで簡単なhas_many :throughtを使わないアプリ作成を経験し、その後RDB設計の学習をして、戻ってきてhas_many :throught有りのアプリ作成をするというのがいいのかなぁ。

カリキュラムの順番的に行ったり来たりするのは嫌だなぁ。

Kindle Voyage買った。

結果、Paperwhiteの新しいやつでよかったなと思います…。

初代のPaperwhite持ってますが、確かにDPIと速度上がってるんだけど思ったほどではなかった。

ただ、Kindle自体は相変わらず良い。