ユーザーの1クリックで2000レコードINSERTするアクティビティ通知機能を作ったメモ
経緯
ブログサービス ( https://pressblog.me/ ) を運営していて、通知機能を開発することになりました。
通知機能の仕様
- 「いいね!」を押したらフォロワーに通知
- コメントしたらフォロワーに通知
- フォロワーは現在、最大で2000人程度
- すべてのログイン済みユーザーは、各々の通知ページからフォローしているユーザーのアクティビティ(いいね、コメント内容)を見ることができる
通知画面↓
環境
Rails 4.2
MySQL 5.6
大変だったところ
- フォロワー数に比例してINSERT文を増やす必要があった
例:2000フォロワーいるユーザーが、「いいね!」を押してコメントをしたら4000レコードINSERTする必要がある
(eachで回して4000回save走らせたら mac book air のローカル環境で4分くらいかかってつらい) - ログイン済みユーザーであれば手軽なアクティビティ(いいね、コメント)でガンガンINSERTが走ってしまう
解決方法
activerecord-importというgemを利用して、BULK INSERTをすることで解決しました
(アドバイスいただいた先輩に圧倒的感謝)
validate が走る仕組みもあるものの、速度を重視するためvalidateなしでBULK INSERTする仕組みで実装しました。
wikiにexampleが書かれていて分かりやすかったです。
Home · zdennis/activerecord-import Wiki · GitHub
validationの true,false, ○件ずつ実行もできる例もあります。
Examples · zdennis/activerecord-import Wiki · GitHub
mac book airでいろいろ立ち上げながら計測していたのでいろいろスペック不足ではありますが、 ↓のように改善されました。
each で 4000回 save | activerecord-import で BULK INSERT |
---|---|
4分程度 | 4秒 |
注意点
便利なgemではありますが、使い方を少し間違えると罠にハマるので以下注意です。
- 内部でSQL文を結合して発行しているため、一度に実行できるSQL文の最大サイズを超えないように気をつける
MySQL :: MySQL 5.6 リファレンスマニュアル :: B.5.2.10 パケットが大きすぎます
- Railsが提供している after_save, before_save, validate(オプションによる)が走らない
Railsで関連レコードの条件付き集計を行う
ブログサービスを作っているときに、1ユーザーあたりの投稿数(公開済み記事のみ)を取得したいというときに便利なgemがあったのでご紹介します!
環境
$ rails -v Rails 4.2.5
目的
Railsで関連レコードの条件付き集計をしたい。
DB
user と post が1対多の構成です。
(user:1 post:多)
準備
Rails標準の機能の counter_cache は単純な集計しかできないため counter_culture を使います。
Gemfileにcounter_cultureを追記してインストール
# Gemfile gem 'counter_culture'
bundle install
実装
is_draft が false かつ is_deleted が false のときにカウントするようにします。
counter_culture :user, column_name: proc { |model| (!model.is_draft && !model.is_deleted) ? 'posts_count' : nil }, column_names: { ["posts.is_draft= ? AND posts.is_deleted= ?", false, false] => 'posts_count', }
column_name
は databaseにCOMMITがあったとき、
column_names
は データ初期化するときに呼ばれます。
データ初期化
bin/rails c
にて下記コマンドを実行
Post.counter_culture_fix_counts
書いた人
Railsユーザーなのに、まだstrftime使っているの?と思っていたら Rails の DATE_FORMATS でハマった話
TL;DR
- strftime はクソ
- DATE_FORMATSは型ごとに設定する必要がある
- DATETIME型のデータは
Time::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")
- DATE型のデータは
Date::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")
前提
以下、今日(2016年10月23日)作成されたユーザーの作成時間(user.created_at、DATETIME型)を整形するという流れで進めます。
strftime を使う場合
日付のデータをフォーマットするときは、 strftime
関数を使います。
user.created_at.strftime("%Y年%m月%d日") # => 2016年10月23日
ただ strftime を使ってしまうと都度フォーマットを設定するため、日付フォーマットがバラけてしまいます。 (rails date format などで検索すると上位に表示される方法なので注意しましょう。)
そこで 以下の方法を使います。
time_formats を使う
まず config/initializers/time_formatsに フォーマットを指定します。
# config/initializers/time_formats.rb Time::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")
その後、
user.created_at.to_s(:ymd) # => 2016年10月23日
とするとフォーマットがinitializers/time_formats.rbに集約されていい感じになります。
罠
仮にユーザーの誕生日をフォーマットしたいとします。
誕生日は user.birthday(DATE型) に格納されています。
user.created_at のときに作ったフォーマットを使いまわして整形しようとすると・・・
user.birthday.to_s(:ymd) # => 2016-10-23
フォーマットされていない・・・?
解決方法
原因は、 initializer の設定ミスでした。
先程設定した Time::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")
のTime
はdatetime型のみ有効です。
そこで、DATE型も有効化されるように追記します。
# config/initializers/time_formats.rb Time::DATE_FORMATS[:ymd] = ("%Y年%m月%d日") Date::DATE_FORMATS[:ymd] = ("%Y年%m月%d日")
この状態で再度 user.birthday をフォーマットすると
user.birthday.to_s(:ymd) # => 2016年10月23日
無事できました。
所感
初見だとわかりにくいところだなと感じました。 30分くらいハマっていたのでメモしておきます。
WEBのことならなんでも聞いてください。↓