【问题标题】:Updating massive number of records -- performance optimization更新海量记录——性能优化
【发布时间】:2011-12-30 01:05:00
【问题描述】:

我有一个棒球工具,可以让用户分析球员的历史击球数据。例如,在过去 7 天的夜间条件下,A-Rod 有多少次点击?我想扩大时间范围,以便用户可以分析球员的击球统计数据,最早可以追溯到 365 天。但是,这样做需要进行一些严格的性能优化。这是我目前的模型集:

class AtBat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :pitcher
  belongs_to :weather_condition

  ### DATA MODEL ###
  # id
  # batter_id
  # pitcher_id
  # weather_condition_id
  # hit (boolean)
  ##################
end

class BattingStat < ActiveRecord::Base
  belongs_to :batter
  belongs_to :recordable, :polymorphic => true # e.g., Batter, Pitcher, WeatherCondition

  ### DATA MODEL ###
  # id
  # batter_id
  # recordable_id
  # recordable_type
  # hits7
  # outs7
  # at_bats7
  # batting_avg7
  # ...
  # hits365
  # outs365
  # at_bats365
  # batting_avg365
  ##################
end

class Batter < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class Pitcher < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

class WeatherCondition < ActiveRecord::Base
  has_many :batting_stats, :as => :recordable, :dependent => :destroy
  has_many :at_bats, :dependent => :destroy
end

为了使我的问题保持合理的长度,让我叙述一下我正在做什么来更新 batting_stats 表,而不是复制一堆代码。让我们从 7 天开始。

  1. 检索过去 7 天的所有 at_bat 记录。
  2. 遍历每个 at_bat 记录...
  3. 给定一个at_bat记录,获取关联的batter和关联weather_condition,找到正确的batting_stat记录(BattingStat.find_or_create_by_batter_and_recordable(batter, weather_condition),然后更新batting_stat记录。
  4. 对击球手和投手重复第 3 步(可记录)。

其他时间段也会重复步骤 1-4 - 15 天、30 天等。

现在我可以想象,如果我要将时间段从可控制的 7/15/30 扩展到 7/15/30/45/60/90/,那么每天运行一个脚本来进行这些更新将是多么费力。 180/365。

所以我的问题是,您将如何让它以最高性能运行?

【问题讨论】:

  • 我为高尔夫应用构建了一个类似的系统。我愿意分享,但这需要相当广泛的解释。您是否愿意改变您的架构,或者您只是在寻找一种方法来优化您现有的架构?
  • 很高兴听到您是如何做到的。愿意更新拱门,但可能会在路上。
  • 您要处理多少条记录?肯定不会有那么多棒球数据点(数十万?)。你不能把这块地块保存在内存中,如果需要的话,可能会被玩家分割成一张地图,然后即时计算?
  • 我不确定您是否可以将其移植到 MySQL,但对于 PostgreSQL,您会执行类似tech.jonathangardner.net/wiki/PostgreSQL/Materialized_Views 的操作(您绝对应该尝试直接使用数据库,因此请考虑在 @ 上提问987654322@)

标签: mysql ruby-on-rails ruby performance optimization


【解决方案1】:

当我以前不得不做这种工作时,我打破了我的 SQL 引用并重新思考如何进行复杂的更新。通常,您可以通过良好的查询在短时间内进行大量更新。此外,您应该能够找到有关查询的直接帮助(如果它们真的很大,请在 gist 中发布您的架构和开始查询)

我最近不得不播种一个 counter_cache 值,在将它作为一堆 ruby​​ 代码加载父母并计算他们的孩子之前,我试了一下这个查询:

UPDATE rates r SET children_count = child_counts.my_count from (SELECT parent_id, count(*) as my_count FROM rates GROUP BY parent_id having parent_id is not null) as child_counts where child_counts.parent_id = r.id;

在几秒钟内更新了 20 万行

如果您不能在一个查询中完成,并且如果它是一次性操作,您可以将您的流程分为 2 个步骤。首先进行繁重的工作并将结果存储在新表中,然后从该表中读取并进行最终更新。我最近不得不做一些海量的数据聚合,所有繁重的工作都花了 2 天的时间处理和计算。结果被放入一个带有相关行 ID 和最终总数的新表中。在生产中,我只有一个从该新表中读取并更新相关行的快速脚本。这也让我可以停止并从我停止的地方重新开始,并在产品更新之前预先检查结果。此外,它使产品更新非常快。

在执行此操作的同时,我还了解到,如果可以的话,分批完成您的工作并尽可能频繁/安全地提交事务非常重要,这样您就不会长时间持有大型事务。

【讨论】:

    【解决方案2】:

    每周批量加载 600,000 条美国租赁数据记录时,我们也遇到了类似的问题。连续处理每条记录需要 24 小时以上。但瓶颈不一定是数据库——尽管每次插入都花费了固定的时间,但数据库并没有被活动最大化/固定/扁平化。

    我知道将文件拆分为单独的字符串记录既简单又快速。在我们的例子中,输入文件是 XML 的形式,我使用了一个简单的 Java StringTokenizer 在 ... 标签上分割文件。

    这很快给了我大量的 XML sn-ps 数组,其中包含我需要解析和导入的出租物业信息。

    然后,我使用 Java ThreadPoolExecutor/FutureTask/Callable 约定创建了一个包含 20 个线程的池,这些线程将每个 XML sn-p 作为输入,提取相关数据并执行数据库插入。我不知道你的架构相当于什么,但我猜有类似的东西。

    最后,我能够通过监控不同测试条件下的数据库服务器负载来调整线程池的大小以最大化记录吞吐量。我们确定线程池大小为 25。

    【讨论】:

      【解决方案3】:

      AR 并不是真的要像这样进行批量处理。您最好通过适当地插入 SQL 并执行 INSERT FROM SELECT 来进行批量更新(或者可能使用为您执行此操作的 gem。)

      【讨论】:

        【解决方案4】:

        您基本上需要以这样一种方式存储数据,即您可以删除最后一天并用新的第一天替换它,这样您就不必重新计算总数。

        这样做的一种方法是存储先前的加法值并从中减去最后一天的值,然后添加新的日期值,然后除以 15/30/90/365 等等。

        这将 366 操作变为 3。现在从数据库中读取比 363 操作慢吗?

        这也为您节省了迭代次数,因此您只需每天检查哪些天气状况需要更新。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-24
          • 2011-09-20
          • 2011-08-03
          • 2018-11-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多