【问题标题】:Report Generation Design Patterns in Rails?Rails 中的报告生成设计模式?
【发布时间】:2018-05-03 22:13:24
【问题描述】:

我正在一个应用程序中构建多个报告,并且遇到了几种构建报告的方法,并希望让您了解构建可扩展且尽可能实时的报告的最佳/常用方法。

首先,一些条件/限制/目标:

  1. 报告应该能够实时处理(使用 node.js 或 ajax 轮询)
  2. 报告应该以优化的方式更新
    • 如果报告是关于页面浏览量的,而您每秒获得数千次,则最好不要在每次页面浏览时更新报告,而是每 10 或 100 次更新报告。
    • 但它仍应接近实时(因此每日/每小时 cron 不是可接受的替代方案)。
  3. 报告不应该重新计算它已经计算过的东西。
    • 如果有计数,它会增加一个计数器。
    • 如果它有平均值,也许它可以以某种方式更新平均值,而无需获取它每秒平均的所有记录并重新计算(尚不确定如何执行此操作)。
    • 如果它具有某个日期范围(今天last_weeklast_month等)的计数/平均值,并且它是实时的,它不应该每秒/请求重新计算这些平均值,以某种方式只执行最少的操作。
  4. 如果报告是关于记录并且记录的“生命周期”是完整的(比如Project,并且该项目持续了 6 个月,有很多活动,但现在结束了),则应该永久保存报告因此后续检索只需提取一个预先计算的文档。

报告不需要是可搜索的,因此一旦数据在文档中,我们只是显示文档。客户端基本上得到一个表示所有统计数据、图表等的 JSON 树,因此它可以在 Javascript 中呈现。

之所以提出我的问题,是因为我正试图找出一种方法来对大型数据集进行实时报告

假设我正在报告网站上的整体用户注册和活动。该网站有 100 万用户,平均每秒有 1000 次页面浏览量。假设有一个User 模型和一个PageView 模型,其中User has_many :page_views。假设我有这些统计数据:

report = {
  :users => {
    :counts => {
      :all        => user_count,
      :active     => active_user_count,
      :inactive   => inactive_user_count
    },
    :averages => {
      :daily      => average_user_registrations_per_day,
      :weekly     => average_user_registrations_per_week,
      :monthly    => average_user_registrations_per_month,
    }
  },
  :page_views => {
    :counts => {
      :all        => user_page_view_count,
      :active     => active_user_page_view_count,
      :inactive   => inactive_user_page_view_count
    },
    :averages => {
      :daily      => average_user_page_view_registrations_per_day,
      :weekly     => average_user_page_view_registrations_per_week,
      :monthly    => average_user_page_view_registrations_per_month,
    }
  },
}

我尝试过的事情:

1。其中UserPageView 都是ActiveRecord 对象,所以一切都通过SQL。

我将所有用户分块抓取,如下所示:

class User < ActiveRecord::Base
  class << self
    def report
      result = {}
      User.find_in_batches(:include => :page_views) do |users|
        # some calculations
        # result[:users]...
        users.each do |user|
          # result[:users][:counts][:active]...
          # some more calculations
        end
      end
      result
    end
  end
end

2。两条记录都是MongoMapper::Document 对象

当场计算 Map-reduce 真的很慢,而且我还没有花时间弄清楚如何使这项工作成为实时式(查看hummingbird)。基本上我做同样的事情:对记录进行分块,将结果添加到哈希中,就是这样。

3。每个计算都是它自己的 SQL/NoSQL 查询

这是 Rails statistics gem 采用的一种方法。我唯一不喜欢的是这可能产生的查询量(没有基准测试每个请求每个报告进行 30 个查询是否比将所有对象分块到内存中并在直接 ruby​​ 中排序更好)

问题

我想问题是,根据您的经验,对大型数据集进行实时报告的最佳方式是什么?通过对每个请求的内存中的记录进行分块/排序(我现在正在做的事情,我可以使用每小时 cron 进行一些优化,但它不是实时的),生成报告大约需要一秒钟(复杂的日期公式和这样),有时更长。

除了传统的优化(更好的日期实现、sql/nosql 最佳实践)之外,我在哪里可以找到一些关于构建报告的实用且经过验证的文章?我可以毫无问题地生成报告,问题是,您如何使其快速、实时、优化且正确?真的什么都没找到。

【问题讨论】:

  • 你能分享你的方法吗?我想实现同样的目标。你能分享你的面向可扩展性的面向对象设计吗?
  • @Lance Pollard 嗨,您对上述问题有什么解决方案吗?
  • @krunalshah 你找到解决方案了吗。请与我分享我也需要同样的

标签: ruby-on-rails


【解决方案1】:

为您的用例构建近乎实时的报告的最简单方法是使用缓存。

所以在report方法中,需要使用rails cache

class User < ActiveRecord::Base
  class << self
    def report
      Rails.cache.fetch('users_report', expires_in: 10.seconds) do
        result = {}
        User.find_in_batches(:include => :page_views) do |users|
          # some calculations
          # result[:users]...
          users.each do |user|
            # result[:users][:counts][:active]...
            # some more calculations
          end
        end
        result
      end
    end
  end
end

在客户端,您只需使用 ajax 池请求此报告。这样生成这些报告不会成为瓶颈,因为生成它们需要大约 1 秒,而且许多客户可以轻松获得最新结果。

为了更好的用户体验,您可以在两个报告之间存储增量,并使用此增量预测在客户端增加您的报表,如下所示:

let nextPredictedReport = null;
let currentReport = null;

const startDrawingPredicted = () => {
  const step = 500;
  const timePassed = 0;
  setInterval(() => {
    timePassed += step;
    const predictedReport = calcDeletaReport(currentReport, nextPredictedReport, timePassed);
    drawReport(predictedReport);
  }, step);
};

setInterval(() => {
  doReportAjaxRequest().then((response) => {
    drawReport(response.report);
    currentReport = response.report;
    nextPredictedReport = response.next_report;
    startDrawingPredicted();
  });
}, 10000);

这只是该方法的一个示例,calcDeletaReportdrawReport 应该由您自己实现 + 此解决方案可能存在问题,因为这只是一个想法 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多