1

ログインしたユーザーに月ごとの統計の概要を提供するアプリケーションを開発しました。

これは私の現在のアプローチです:

Statistics.html.haml:

#(@parsed months is an array of monthnames.)

- @parsed_months.each do |month|
  = render :partial => "statistic", :locals => {:month => month}

_statistic.html.haml:

%tr{:class => cycle("odd", "even")}
  %td= l(month, :format => "%B").capitalize
  %td= current_user.total_views_count(month)
  %td= current_user.total_leads_count(month)
  %td= current_user.total_clicks_count(month)

合計ビューを返すメソッド (User.rb 内):

def total_views_count(month = nil)
  if month == nil
    v = 0
    self.companies.each {|c| v += c.counts.size}
    return v
  else
    v = 0
    self.companies.each {|c| v += c.counts.where(:created_at => Date.today.beginning_of_year..Date.today.end_of_year).where(:created_at => month.beginning_of_month..month.end_of_month).size}
    return v
  end
end

会社.rb:

belongs_to :user
has_many :counts, :as => :countable, :dependent => :destroy

カウント.rb:

belongs_to :countable, :polymorphic => true

ユーザー.rb:

has_many :companies

これはうまく機能していましたが、数か月後に Count モデルが 100 万件以上のレコードに成長し、heroku でリクエストのタイムアウトが発生しました。

このクエリを最適化するにはどうすればよいですか、またはこれを行うためのより良い方法はありますか?

前もって感謝します!

4

2 に答える 2

1

クエリを最適化するには、次の点に注意する必要があります。

  1. 1 回のリクエストでクエリを減らすようにしてください
  2. インデックスを最適化する
  3. ロールアップ テーブルを作成する

ポイント 2 と 3 は、@opensourcechris が言及したものと同じです。

私はかなり長い間アクティブレコードを扱っていないので、クエリのアレル構文を提供することはできませんが、主な問題は、大量のデータがあり、単一のリクエストで多くの重いクエリを実行しているためです。結合を使用してクエリを減らし、インデックスを慎重に使用して結合とクエリを最適化する必要があります。結合を含むクエリは次のようになります。

SELECT count(c.id) FROM users u
  JOIN companies comp ON comp.user_id = u.id
  JOIN counts c ON c.company_id = comp.id
                   AND c.countable_type = 'Company'
                   AND c.created_at BETWEEN date_range
  WHERE u.id = currrent_user_id

ここを使用して、1 つのクエリですべての月のデータを取得することもできGROUP BYます。ただし、月単位でカウントを保持します。

結合が効果的に機能するには、 にインデックスを設定companies.user_idし、 に複合インデックスを設定する必要がありますcounts.countable_id, counts.countable_type, counts.created_at

今のところはこれで解決するはずですが、わずか数か月でカウントが数百万を超える数に増えたため、長期的には問題を解決することはできません. カウント テーブルが大きくなると、このクエリでも速度が低下し始めます。リレーショナル データベースでは、クエリ時間は行数の増加にほぼ比例して増加しますが、特定のしきい値を超えると、より急速に増加し始めます。したがって、頻繁に作業する必要があるテーブルのサイズを含めることは常に賢明です。それがローリングテーブルの出番です。

この量のデータが挿入されると、速度も懸念されます。そのため、おそらくインデックスのないテーブルを作成し、そのテーブルのすべてのカウント データをログに記録する必要があります。データは、定期的に他のテーブルにロールアップできます。ロールアップ テーブルは、レポート作成に必要な粒度で作成できます。一般的なオプションは、時間別、日別、週別、月別、および年別のロールアップ テーブルです。

データをアーカイブ テーブルにダンプして履歴レコードを保持することもできるため、異なる粒度またはその他の要件でロールアップ テーブルをいつでも再作成できます。データがアーカイブ テーブルにダンプされた後、メイン テーブルからパージできるため、時間の経過とともに挿入速度が低下することはありません。また、ローリングの前にデータをクレンジングできるため、10 分ルールなどの制約を気にすることなく、あらゆるビューをログに記録できます。

PS: ビューを正しくカウントするには、session_id(uuid) と IP アドレスを使用する必要があると思います。一般に、パブリック IP アドレスは多くのインターネット ユーザー間で共有されます。

于 2012-06-25T22:48:11.263 に答える
0

クエリを最適化するには、まず各テーブルのインデックスを監査する必要があります。あなたの WHERE は日付フィールド上にあるので、これを使用してインデックスを表示すると、インデックスがうまく機能すると思います。

USE *database*;
SHOW INDEX FROM *tablename*;

次に、 where 列にインデックスを付けていることを確認してください。

もう 1 つのオプションは、過去の月の合計を計算するのを忘れて、月末に合計を計算し、新しい集計テーブルに保存することです。したがって、その場で計算するのは当月のデータだけです。前の月は新しい集計テーブルから返すことができます。

于 2012-06-25T18:35:14.897 に答える