Rails中的报告生成设计模式?

Lan*_*ard 11 ruby-on-rails

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

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

  1. 报告应该能够处理实时(使用node.js或ajax轮询)
  2. 报告应以优化的方式更新
    • 如果报告是关于页面浏览的,并且您每秒获得数千个,那么每个页面视图更新报告可能不是最好,但可能每10或100个更新一次.
    • 但它应该仍然接近实时(所以每天/每小时的cron不是一个可接受的选择).
  3. 报告不应重新计算已计算的内容.
    • 如果它有计数,它会增加一个计数器.
    • 如果它有平均值,也许它可以某种方式更新平均值而不抓取所有记录它每秒平均并重新计算(不知道如何做到这一点).
    • 如果它具有日期范围(今天,last_week,last_month等)的计数/平均值,并且它是实时的,那么它不应该每秒/请求重新计算那些平均值,不知何故只做最小的操作.
  4. 如果报告是关于记录并且记录的"生命周期"已经完成(比如a 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,
    }
  },
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的事情:

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
Run Code Online (Sandbox Code Playgroud)

这两个记录都是MongoMapper::Document对象

Map-reduce在现场计算真的很慢,我还没有花时间弄清楚如何使这项工作实时化(检查出蜂鸟).基本上我做同样的事情:将记录分块,将结果添加到哈希,就是这样.

3.每次计算都是自己的SQL/NoSQL查询

这是Rails 统计宝石所采用的方法.我唯一不喜欢的是这可能会产生的查询量(没有基准测试是否每个请求每个报告执行30个查询比将所有对象分块到内存中并且直接在红宝石中排序更好)

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

除了传统的优化(更好的日期实现,sql/nosql最佳实践),我可以在哪里找到一些关于构建报告的实用且经过验证的文章?我可以建立报告没问题,问题是,你如何快速,实时,优化和正确?真的没找到任何东西.

mpo*_*lov 1

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

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

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
Run Code Online (Sandbox Code Playgroud)

在客户端,您只需使用 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);
Run Code Online (Sandbox Code Playgroud)

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