8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Railsアンチパターン<スケーリング/デプロイ編>

Last updated at Posted at 2015-07-02

実際に運用していく段階を考える章。

ソリューション: なるべくスケーリング可能であるように布石を打っておく

  • シャーディング

データを水平に(つまり一つのテーブルを)複数のDBに分割するのがシャーディング。
アプリケーションのスケーリングはマシンを増やせばどうにかなることが多いけど、DBのスケーリングは初めからそれを意図した構築をしておかないと途中から移行するのはかなりしんどい。
RailsにはOctopusという、比較的シンプルにシャーディングを行うgemがあるので、それを利用すると良い。ただし、そもそもシャーディング自体がかなり難度の高い行為なので、あまり無理はしないようにしよう(参考スライド)

  • アセットファイルの扱い

ユーザーがコンテンツをuploadしたりするようなサービスの場合、クラウド上のスケーラブルなstorageを使うと後々楽という話。この本ではpaperclip×AWSS3の例が挙げられていた。この組み合わせは現代でも結構定石のようで、ググると解説エントリがちょこちょこ見つかる(paperclipのwikiにも事例としてある)。
自前鯖で持つのは色々こわいし、餅は餅屋ということで。

  • PaaSの話

herokuとかそのへん。便利ですよね。

結論: 最初からスケーリングの意識を持っておこう。ただし、シャーディングはこわいのでよく考えて。

アンチパターン:ひどいSQL

ソリューション インデックスを張る

以下、だいたいの目安。

  • primay key(デフォルトで貼ってある)
  • 外部キー(has_manyとかに頻繁に使用される)
  • ポリモーフィック関連(をまたぐクエリを発行するような際)
  • ANDでtypeとその他のキーを条件に指定するようなケースが存在するとき、複合インデックスを貼っておくべきだ、という話。
  • 複合インデックスを貼るときは、選択制の高いものから貼るということと、条件句のカラム指定の際に貼られているインデックスを意識することを忘れないように。
  • 一意性のvalidateをしたいとき。ユニークindexを利用する。
  • STI(シングルテーブル継承)時。typeカラム。
  • to_paramに渡されるもの(検索に利用される、という意味なので当然。(to_paramについて))
  • ほか、検索条件に指定される(whereメソッドに渡される)カラム

逆に貼るかどうかよく考えたほうがいいもの

  • 状態を表すStatusカラム、Booleanカラム、日付カラム
  • cardinalityを考えよう

indexにもデメリットはある(挿入、更新)。利用シーンを想定した上で適切に貼ることが重要。
ヒントとなるツールとしては、limerick_rake(古すぎるので今使うのは現実的ではない)、MYSQLのEXPLAIN、RailsfootnotesQueryReviewer(これもあまり古すぎて現実的ではない)などがあげられている。今だとar-proxyが使いやすいと思われる。
あと、new relic最強とも書いてある。お金があるならこれで。

結論: 適切にindexをはり、ボトルネッククエリを発見しよう

インデックスが実際に使われているかどうかはexplainで調べることができる。ActiveRecordは直接クエリいじりにくいんだけど、親切なgemがあるのでこれを使うと良いかも。explainの解釈について、詳しくは各DBのレファレンス(MySQL)を見れば良いのだが、簡単にいうと、subtype(どんな種類のクエリか),type(どのようにアクセスするか,()),extraを見れば良いようだ(解説)

ソリューション: 発行されるクエリについて自覚する

次の三つの違い。

.rb
@article.comments.count 
@article.comments.length 
@article.comments.size

答え:http://rubyist.g.hatena.ne.jp/rochefort/20090802/p1
のように、一見区別がつかないようなメソッドでも内部の挙動がかなり異なり、それがパフォーマンスのボトルネックにつながったりする。罠っぽい。
Rubyの配列いじるメソッドでごちゃごちゃやるようなことは、だいたいSQLでスッキリかけることが多い。select, where, joinsくらいとは仲良くなって、常識的なクエリを記述しよう。それでも生SQLを書くよりはだいぶとマシなはず。
比較例:
SQL

.sql
SELECT DISTINCT users.* FROM users INNER JOIN comments
ON comments.user_id = users.id INNER JOIN users users_comments
ON users_comments.id = comments.user_id INNER JOIN articles
ON articles.id = comments.article_id INNER JOIN comments comments_articles
ON comments_articles.article_id = articles.id WHERE (articles.id in (1) AND users.id != 1)

ActiveRecord

.rb
User.select("DISTINCT users.*").
  joins(:comments => [:user, {:article => :comments}]). 
  where(["articles.id in ? AND users.id != ?",
    self.article_ids, self.id])

結論: やっぱりSQLはわかろう

その他

  • ちゃんと正規化しよう
  • n+1問題はincludesで解決しよう。bulletによる検出が有名。
  • 非正規化(サマリカラム、キャッシュカラム)も考慮に入れよう
  • cronや、ジョブキューの利用も考えよう
    ドメインモデルの見直し
8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?