6

次のような比較的大規模な 4 層のリレーショナル データのセットアップがあります。

ClientApplication         has_many => ClientApplicationVersions
ClientApplicationVersions has_many => CloudLogs
CloudLogs                 has_many => Logs

client_applications: (潜在的に 1,000 のレコード)
   - ...
   - account_id
   - public_key
   -deleted_at

client_application_versions: (潜在的に数万のレコード)
   - ...
   - client_application_id
   - public_key
   -deleted_at

cloud_logs: (潜在的に 1,000,000 のレコード)
   - ...
   - client_application_version_id
   - public_key
   -deleted_at

logs: (潜在的に 1,000,000,000 のレコード)
   - ...
   - cloud_log_id
   - public_key
   - time_stamp
   -deleted_at


私はまだ開発中なので、構造とセットアップは確定していませんが、セットアップがうまくいくことを願っています。Rails 3.2.11 と InnoDB MySQL を使用します。データベースは現在、(最終的な db サイズと比較して) 小さいデータ セット ( logs500,000 行しかありません) でいっぱいです。

  1. ログの最初のページを取得します。タイムスタンプ順に並べ替え、制限はaccount_id, client_application.public_key, client_application_version.public_key(100 秒以上)
  2. ログの最初のページを取得します。タイムスタンプ順に並べ替え、制限付きaccount_id( client_application.public_key100 秒以上)
  3. ログの最初のページを取得し、タイムスタンプで並べ替え、制限account_id(100 秒以上)
  4. タイムスタンプ順に並べられたログの最初のページを取得します (~2 秒)

これらの呼び出しを行うために、レールスコープを使用しています。

  scope :account_id, proc {|account_id| joins(:client_application).where("client_applications.account_id = ?", account_id) }
  scope :client_application_key, proc {|client_application_key| joins(:client_application).where("client_applications.public_key = ?", client_application_key) }
  scope :client_application_version_key, proc {|client_application_version_key| joins(:client_application_version).where("client_application_versions.public_key = ?", client_application_version_key) }

  default_scope order('logs.timestamp DESC')

の各テーブルにインデックスがありますpublic_keylogsオプティマイザーが使用することを好むもの ( ) を含むテーブルにいくつかのインデックスがありますindex_logs_on_cloud_log_idが、クエリの実行にはまだ長い時間がかかります。


でメソッドを呼び出す方法は次のrails consoleとおりです。

Log.account_id(1).client_application_key('p0kZudG0').client_application_version_key('0HgoJRyE').page(1)

...ここにレールがそれを変換するものがあります:

SELECT `logs`.* FROM `logs` INNER JOIN `cloud_logs` ON `cloud_logs`.`id` = `logs`.`cloud_log_id` INNER JOIN `client_application_versions` ON `client_application_versions`.`id` = `cloud_logs`.`client_application_version_id` INNER JOIN `client_applications` ON `client_applications`.`id` = `client_application_versions`.`client_application_id` INNER JOIN `cloud_logs` `cloud_logs_logs_join` ON `cloud_logs_logs_join`.`id` = `logs`.`cloud_log_id` INNER JOIN `client_application_versions` `client_application_versions_logs` ON `client_application_versions_logs`.`id` = `cloud_logs_logs_join`.`client_application_version_id` WHERE (logs.deleted_at IS NULL) AND (client_applications.account_id = 1) AND (client_applications.public_key = 'p0kZudG0') AND (client_application_versions.public_key = '0HgoJRyE') ORDER BY logs.timestamp DESC LIMIT 100 OFFSET 0

... そして、これがそのクエリの EXPLAIN ステートメントです。

+----+-------------+----------------------------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------+---------+------------------------------------------------------------------------+------+----------------------------------------------+
| id | select_type | table                            | type   | possible_keys                                                                                                                                         | key                                               | key_len | ref                                                                    | rows | Extra                                        |
+----+-------------+----------------------------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------+---------+------------------------------------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | client_application_versions      | ref    | PRIMARY,index_client_application_versions_on_client_application_id,index_client_application_versions_on_public_key                                    | index_client_application_versions_on_public_key   | 768     | const                                                                  |    1 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | client_applications              | eq_ref | PRIMARY,index_client_applications_on_account_id,index_client_applications_on_public_key                                                               | PRIMARY                                           | 4       | cloudlog_production.client_application_versions.client_application_id  |    1 | Using where                                  |
|  1 | SIMPLE      | cloud_logs                       | ref    | PRIMARY,index_cloud_logs_on_client_application_version_id                                                                                             | index_cloud_logs_on_client_application_version_id | 5       | cloudlog_production.client_application_versions.id                     |  481 | Using where; Using index                     |
|  1 | SIMPLE      | cloud_logs_logs_join             | eq_ref | PRIMARY,index_cloud_logs_on_client_application_version_id                                                                                             | PRIMARY                                           | 4       | cloudlog_production.cloud_logs.id                                      |    1 |                                              |
|  1 | SIMPLE      | client_application_versions_logs | eq_ref | PRIMARY                                                                                                                                               | PRIMARY                                           | 4       | cloudlog_production.cloud_logs_logs_join.client_application_version_id |    1 | Using index                                  |
|  1 | SIMPLE      | logs                             | ref    | index_logs_on_cloud_log_id_and_deleted_at_and_timestamp,index_logs_on_cloud_log_id_and_deleted_at,index_logs_on_cloud_log_id,index_logs_on_deleted_at | index_logs_on_cloud_log_id                        | 5       | cloudlog_production.cloud_logs.id                                      |    4 | Using where                                  |
+----+-------------+----------------------------------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------------+---------+------------------------------------------------------------------------+------+----------------------------------------------+

この質問には 3 つの部分があります。

  1. これらのタイプの結合に依存する並べ替えクエリのパフォーマンスを向上させるために、追加のインデックスを使用して DB を最適化できますか?
  2. Railsコードを最適化して、このタイプのfind実行をより効率的にすることはできますか?
  3. 大規模なデータセットに対して、このスコープ検索に間違った方法でアプローチしているだけですか?




UPDATE 1/24/12
回答でGeoffとJ_MCCaffreyが示唆しているように、問題を特定するためにクエリを3つの異なるセクションに分割しました。さすがに最大のテーブルを扱う問題です。MYSQL オプティマイザは、異なるインデックスを使用してこれを異なる方法で処理しますが、遅延は持続します。このアプローチの EXPLAIN は次のとおりです。

ClientApplication.find_by_account_id_and_public_key(1, 'p0kZudG0').versions.select{|cav| cav.public_key = '0HgoJRyE'}.first.logs.page(2)
  ClientApplication Load (165.9ms)  SELECT `client_applications`.* FROM `client_applications` WHERE `client_applications`.`account_id` = 1 AND `client_applications`.`public_key` = 'p0kZudG0' AND (client_applications.deleted_at IS NULL) ORDER BY client_applications.id LIMIT 1
  ClientApplicationVersion Load (105.1ms)  SELECT `client_application_versions`.* FROM `client_application_versions` WHERE `client_application_versions`.`client_application_id` = 3 AND (client_application_versions.deleted_at IS NULL) ORDER BY client_application_versions.created_at DESC, client_application_versions.id DESC
  Log Load (57295.0ms)  SELECT `logs`.* FROM `logs` INNER JOIN `cloud_logs` ON `logs`.`cloud_log_id` = `cloud_logs`.`id` WHERE `cloud_logs`.`client_application_version_id` = 49 AND (logs.deleted_at IS NULL) AND (cloud_logs.deleted_at IS NULL) ORDER BY logs.timestamp DESC, cloud_logs.received_at DESC LIMIT 100 OFFSET 100
  EXPLAIN (214.5ms)  EXPLAIN SELECT `logs`.* FROM `logs` INNER JOIN `cloud_logs` ON `logs`.`cloud_log_id` = `cloud_logs`.`id` WHERE `cloud_logs`.`client_application_version_id` = 49 AND (logs.deleted_at IS NULL) AND (cloud_logs.deleted_at IS NULL) ORDER BY logs.timestamp DESC, cloud_logs.received_at DESC LIMIT 100 OFFSET 100
EXPLAIN for: SELECT  `logs`.* FROM `logs` INNER JOIN `cloud_logs` ON `logs`.`cloud_log_id` = `cloud_logs`.`id` WHERE `cloud_logs`.`client_application_version_id` = 49 AND (logs.deleted_at IS NULL) AND (cloud_logs.deleted_at IS NULL) ORDER BY logs.timestamp DESC, cloud_logs.received_at DESC LIMIT 100 OFFSET 100
+----+-------------+------------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------+---------+-----------------------------------+------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| id | select_type | table      | type        | possible_keys                                                                                                                                         | key                                                                              | key_len | ref                               | rows | Extra                                                                                                                                           |
+----+-------------+------------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------+---------+-----------------------------------+------+-------------------------------------------------------------------------------------------------------------------------------------------------+
|  1 | SIMPLE      | cloud_logs | index_merge | PRIMARY,index_cloud_logs_on_client_application_version_id,index_cloud_logs_on_deleted_at                                                              | index_cloud_logs_on_client_application_version_id,index_cloud_logs_on_deleted_at | 5,9     | NULL                              | 1874 | Using intersect(index_cloud_logs_on_client_application_version_id,index_cloud_logs_on_deleted_at); Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | logs       | ref         | index_logs_on_cloud_log_id_and_deleted_at_and_timestamp,index_logs_on_cloud_log_id_and_deleted_at,index_logs_on_cloud_log_id,index_logs_on_deleted_at | index_logs_on_cloud_log_id                                                       | 5       | cloudlog_production.cloud_logs.id |    4 | Using where                                                                                                                                     |
+----+-------------+------------+-------------+-------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------+---------+-----------------------------------+------+-------------------------------------------------------------------------------------------------------------------------------------------------+




UPDATE 1/25/12
すべての関連テーブルのインデックスは次のとおりです。

CLIENT_APPLICATIONS:
  PRIMARY KEY  (`id`),
  UNIQUE KEY `index_client_applications_on_key` (`key`),
  KEY `index_client_applications_on_account_id` (`account_id`),
  KEY `index_client_applications_on_deleted_at` (`deleted_at`),
  KEY `index_client_applications_on_public_key` (`public_key`)

CLIENT_APPLICATION_VERSIONS:
  PRIMARY KEY  (`id`),
  KEY `index_client_application_versions_on_client_application_id` (`client_application_id`),
  KEY `index_client_application_versions_on_deleted_at` (`deleted_at`),
  KEY `index_client_application_versions_on_public_key` (`public_key`)

CLOUD_LOGS:
  PRIMARY KEY  (`id`),
  KEY `index_cloud_logs_on_api_client_version_id` (`api_client_version_id`),
  KEY `index_cloud_logs_on_client_application_version_id` (`client_application_version_id`),
  KEY `index_cloud_logs_on_deleted_at` (`deleted_at`),
  KEY `index_cloud_logs_on_device_id` (`device_id`),
  KEY `index_cloud_logs_on_public_key` (`public_key`),
  KEY `index_cloud_logs_on_received_at` (`received_at`)

LOGS:
  PRIMARY KEY  (`id`),
  KEY `index_logs_on_class_name` (`class_name`),
  KEY `index_logs_on_cloud_log_id_and_deleted_at_and_timestamp` (`cloud_log_id`,`deleted_at`,`timestamp`),
  KEY `index_logs_on_cloud_log_id_and_deleted_at` (`cloud_log_id`,`deleted_at`),
  KEY `index_logs_on_cloud_log_id` (`cloud_log_id`),
  KEY `index_logs_on_deleted_at` (`deleted_at`),
  KEY `index_logs_on_file_name` (`file_name`),
  KEY `index_logs_on_method_name` (`method_name`),
  KEY `index_logs_on_public_key` (`public_key`),
  KEY `index_logs_on_timestamp` USING BTREE (`timestamp`)
4

7 に答える 7

1

より良い構造で表示すると、クエリは次のようになります(すでに再配置されています)

SELECT
  `logs`.*
FROM
  `logs` as l
  INNER JOIN `cloud_logs` as cl1
    ON
      cl1.id = l.cloud_log_id
  INNER JOIN `cloud_logs` as cl2
    ON
      cl2.id = l.cloud_log_id
  INNER JOIN `client_application_versions` as cav1
    ON
      cav1.id = cl1.client_application_version_id
  INNER JOIN `client_application_versions` as cav2
    ON
      cav2.id = cl2.client_application_version_id
  INNER JOIN `client_applications` as ca
    ON
      ca.id = cav1.client_application_id
WHERE
  (l.deleted_at IS NULL)
    AND
  (ca.account_id = 1)
    AND
  (ca.public_key = 'p0kZudG0')
    AND
  (cav.public_key = '0HgoJRyE')
ORDER BY
  logs.timestamp DESC
LIMIT
  0, 100

cav1/cl1とcav2/cl2を見ると、cav2とcl2が使用されていないことがわかります。ONステートメントを除いて、それらに適用されるフィルターはありません。

したがって、cav1は正しいアカウントにリンクされており、cav2はどのアカウントにもリンクされておらず、一致するすべてのアカウントが含まれています。これは、クエリの結果には問題ありませんが、結合バッファのサイズには問題があります。

結合(およびそれらのON部分)を削除すると、次のようになります。

SELECT
  `logs`.*
FROM
  `logs` as l
  INNER JOIN `cloud_logs` as cl1
    ON
      cl1.id = l.cloud_log_id
--  INNER JOIN `cloud_logs` as cl2
--    ON
--      cl2.id = l.cloud_log_id
  INNER JOIN `client_application_versions` as cav1 use index for join (`index_cavs_on_client_application_id_and_public_key`)
    ON
      cav1.id = cl1.client_application_version_id
        AND
      cav1.public_key = '0HgoJRyE'

--  INNER JOIN `client_application_versions` as cav2
--    ON
--      cav2.id = cl2.client_application_version_id
  INNER JOIN `client_applications` as ca
    ON
      ca.id = cav1.client_application_id
WHERE
  (l.deleted_at IS NULL)
    AND
  (ca.account_id = 1)
    AND
  (ca.public_key = 'p0kZudG0')
ORDER BY
  logs.timestamp DESC
LIMIT
  0, 100

これはもっと速いはずです。

これをコンソールで使用可能なものにパックします(正しいテーブルリレーションとmeta_whereを想定):

Log.where(:deleted_at.ne => nil).order("logs.timestamp desc").joins(:cloud_logs) & \
CloudLog.joins(:client_application_versions) & \
ClientApplicationVersion.where(:public_key => '0HgoJRyE').joins(:client_applications) & \
ClientApplication.where(:public_key => 'p0kZudG0', :account_id => 1)

ここではこれを再現できないため、自分で試してみる(または最後にto_sqlを追加する)必要があるかもしれません。また、上記の短縮されたクエリに説明を追加する必要があります。

結果は興味深いかもしれません。

更新:結果と定義(以下のコメント)を見た後:

キーを追加してみてください:

alter table client_application_versions add key (`client_application_id`, `public_key`);

これにより、ファイルソートが防止され、処理速度が向上します。

編集:キーについてmysqlにヒントを与えるようにクエリを更新しました。

于 2013-01-25T02:27:36.340 に答える
0

まあ、それは少し難しいです。なぜなら、パフォーマンスを得るには、読みやすさを犠牲にする必要があるからです。だから、あなたの質問に答える:

  1. インデックスはアイデアですが、成功するかどうかは、テーブルサイズ、クエリを実行する頻度とキーの組み合わせによって異なる場合があります。しかし、私にはあなたが同じことをクエリしているように見えます、あなたはそれを異なって注文するだけです。では...DBビューを使用してみませんか?Railsでの実装は最悪ですが、使用可能です:https ://github.com/eladmeidar/PlainViews
  2. (私の見解の提案で行くかどうかを忘れて)はい、できます。スコープは使用しないでください。ARelはそれらを事実上時代遅れにしました、そしてあなたの例では、あなたがARelだけに頼るのと同じくらい記述的であることができるので、あなたの例では最も確実です。そしてもう1つ、ARelを使用してSQLを記述しないでください。結合などに適応するためにテーブルの名前を変更するなど、インデックスを混乱させる可能性のある多くの利点を見逃すことになります。このようなもの:

    YourObject.joins(:client_application).
               where(ClientApplication.arel_table[:public_key].eq(client_application_key))
    
  3. どこで使用するかによります。クエリがアプリケーション機能の中核である場合は、パフォーマンスを向上させる別の方法でさらに調査する必要があります。たとえば、DBは、前述のビューやストアドプロシージャなど、Webフレームワーク(特にRails)が推奨していない多くの機能を提供します。それをどのように活用し、コードの可読性を維持できるかは、私たちの仲間の開発者の日々の課題です。

しかし、まだ質問があります。なぜMongoDBを使用しなかったのですか?http://nosql.mypopescu.com/post/1016320617/mongodb-is-web-scale

于 2013-01-28T15:47:41.300 に答える
0

インデックスが正しく定義されていませんindex_logs_on_cloud_log_id_and_deleted_at_and_timestamp

クエリで時間がかかりすぎる部分はorder by句であり、順序付けを行っていますが、timestampこれがtimestampインデックスの最後のキーです。MySQLでは、前のキーが句order by内の定数でない限り、インデックス内の後のキーは最適化に使用されません。http://dev.mysql.com/doc/refman/5.0/en/order-by-optimization.htmlwhereを参照してください

手始めに、インデックスを作成してtimestamp、クエリが高速化されるかどうかを確認します。

于 2013-01-25T05:17:19.030 に答える
0

残念ながら、Rails の最適化に関する私の経験はすべて PostgreSQL であったため、そのほとんどは当てはまらないでしょう。ただし、おそらく適用できるいくつかの提案があります。

スコープ内のjoins代わりに使用してみてください-熱心な読み込みをトリガーするために使用されます - あなたが見ているスローダウンの一部は、不要なモデルが読み込まれている可能性があります. そうでない場合でも、代わりに使用すると、より読みやすいクエリが生成されるはずです。つまり、すべての列を「t2_r8」などにエイリアスしています。includesincludesjoinsincludes

また、フィルタリングされる可能性のあるすべての列がインデックス化されていることを確認する必要があります。一般的に、で終わる列_idはこの方法で参照される可能性があり、おそらくインデックス化する必要があります。スコープ内(のようにclient_application_version_key

于 2013-01-19T22:33:23.977 に答える
0

このようなクエリのパフォーマンスで問題が発生した場合、Rails が何を行っているかを調べて、必要なものを取得するためのより良い方法がないかどうかを判断します。ほとんどの場合、Rails クエリは問題ありませんが、必要なものをより高速でクリーンな方法で取得できることに気付くことがあります。

2 つのクエリで必要なものを取得できる可能性がありますが、まず結合を分割し、結合から取得したデータを入力した場合にクエリがどのように実行されるかを確認します。

制限とオフセットなしで結果をテストしましたか? 並べ替えと制限の部分を除外して、パフォーマンスを観察したいと思います。私は以前に制限とオフセットに関する大きな問題を見てきました.現在あなたが行っているように一時テーブルとファイルソートを使用する代わりに、mysqlがメモリ内のソートを処理する方法を調整する方法があります.

編集

最初に ID を照会してから、ID に基づいてすべての列を照会できます。

INNER JOIN ONlogsから .idを選択します。= . どこで。= 49 AND (logs.deleted_at IS NULL) AND (cloud_logs.deleted_at IS NULL) ORDER BY logs.timestamp DESC limit 100logscloud_logslogscloud_log_idcloud_logsidcloud_logsclient_application_version_id

そのクエリは高速ですか?(テーブルをスキャンせずにインデックスからIDを取得できるはずです)より侵襲的な変更は、DBレベルでデータを分割することかもしれませんが、それを提案するには時期尚早だと思います.

于 2013-01-23T22:48:15.053 に答える
0

あなたの質問のそれぞれに答えようとしています:

  1. そうです!あなたが検索しているものは、おそらく索引付けされているはずです。インデックスが作成されていない場合は、完全なテーブル スキャンを実行する必要があります。referencesの機能を使用した場合、最初の移行を行ったときに作成された関連付け ID にcreate_table加えて、少なくとも次のものを検索します。

    • ログ.タイムスタンプ
    • client_application_versions.public_key
    • client_applications.public_key
    • logs.deleted_at

    これらはおそらくすべて索引付けされているはずです。referencesもちろん、関連付けの外部キーを定義するときに使用しなかった場合は、それらも追加します。もちろん、インデックスとのトレードオフがあります。それらは読み取りの魔法のようなものですが、書き込みが大幅に遅くなる可能性があります。それらがあなたを遅くしたり速くしたりする程度は、おそらくデータベースにも大きく依存しています.

  2. 私はそうは思わない。あなたのレールコードは私にはほぼ正しいように見えます。私が持っている唯一のコメントは、scope実際には関数を定義する簡単な方法にすぎないということです。関数を直接定義した方が読みやすいと思います:

    self.account_id(account_id)
      joins(:client_application).where("client_applications.account_id = ?", account_id)
    end
    
  3. 多分!残念ながら、これはデータがどのように見えるかに大きく依存するため、簡単に答えられる質問ではありません。同じデータベース テーブルに何十億ものログが本当に必要ですか? おそらく同じスキーマを持つ異なるテーブルにデータを自然に分割する方法はありますか? データベースのシャーディングについても調査することをお勧めします。

それが役立つことを願っています。

編集:

なぜあなたは最も苦痛な方向に質問をしているのですか? あなたはそれを好転させようとしましたか:

Log.account_id(1).client_application_key('p0kZudG0').client_application_version_key('0HgoJRyE').page(1)

このようなものに:

ClientApplication.find_all_by_account_id(1).where(public_key: 'p0kZudG0').joins(:client_application_version).where("client_application_versions.public_key=?",'0HgoJRyE').logs.page(1)

読みやすくするためにいくつかのスコープを定義する必要がありますが、うまくいけばアイデアが得られます。

于 2013-01-21T22:33:44.350 に答える
0

より良い答えが得られることを期待して、これを私自身の質問に対する可能な解決策として書いています。現在、DB は完全にセットアップされており、リレーショナルです。

ClientApplication         has_many => ClientApplicationVersions
ClientApplicationVersions has_many => CloudLogs
CloudLogs                 has_many => Logs

これは、クライアント アプリケーションに属するログを見つける必要がある場合、それを取得するためだけに 3 つの追加の結合を行う必要があることを意味します。Logs テーブルに非正規化を導入するforeign_keyことで、すべての結合をスキップできます。

ClientApplication         has_many => ClientApplicationVersions
ClientApplication         has_many => Logs
ClientApplicationVersions has_many => CloudLogs
ClientApplicationVersions has_many => Logs
CloudLogs                 has_many => Logs

client_application_key最終的に、Logs テーブルに 、client_application_version_key、およびの列がいくつか追加されますcloud_log_key

データに一貫性がないリスクがありますが、クエリのパフォーマンスを低下させる 3 つの結合を回避できます。誰かが私にこれを話してください。

于 2013-01-20T16:13:13.117 に答える