代码之家  ›  专栏  ›  技术社区  ›  Mark Swardstrom

在Rails应用程序中记录搜索结果

  •  4
  • Mark Swardstrom  · 技术社区  · 16 年前

    我们有兴趣记录和计算一个项目在搜索或列表页面上出现的次数。每天有5万名独特的访客,我们希望每天能产生300-400万个“印象”,这不是一个非常高的数量,但我们想很好地设计一个。

    我们不需要实时读取这些数据,但希望能够生成每日总计和分析趋势等,类似于业务分析工具。

    我们计划在页面呈现后使用Ajax发布来实现这一点——这将允许我们计算结果,即使这些结果是缓存的。我们可以在每页的一篇文章中这样做,以发送一个以逗号分隔的ID列表及其在页面上的位置。

    我希望有一些关于这个的设计模式/gem/blog帖子可以帮助我避免可能出现的常见的第一时间错误。我也没有太多的经验记录或阅读日志。

    我目前的策略是:制作一些东西,将事件写入日志文件,并创建一个后台作业,在一天结束时对结果进行汇总,然后将结果放回MySQL。

    3 回复  |  直到 16 年前
        1
  •  1
  •   EmFi    16 年前

    根据不需要列出项目所需的操作,您可能可以在控制器中执行该操作并为自己节省一次往返行程。你可以用一个后滤器来做,以使添加不受干扰。

    仅当列出要记录的项目的所有操作都需要参数时,此操作才有效。这是因为页面缓存忽略带有参数的GET请求。

    假设您只想在搜索操作中记录搜索数据。

    class ItemsController < ApplicationController
      after_filter :log_searches, :only => :search
    
      def log_searches
        @items.each do |item|
          # write to log here
        end
      end
    
      ...
      # rest of controller remains unchanged
      ...
    end
    

    否则,您就可以使用Ajax和一个onload远程函数。

    至于处理,您可以使用cron作业运行的rake任务来收集统计信息,并可能更新项目以获得流行度评级。

    无论哪种方式,您都希望阅读 Ruby Logging class .学习cron作业和rake任务也不会有什么伤害。

        2
  •  3
  •   cwninja    16 年前

    好的,我有三种方法:

    1)队列

    在Ajax处理程序中,编写最简单的方法(使用机架中间件或Rails金属)将查询参数推送到队列中。然后,轮询队列并收集消息。

    来自机架中间件的队列推送速度惊人。我们在一个流量非常大的站点上使用它来记录类似的数据。

    下面是一个机架中间件示例(从我们的应用程序中提取,可以在大约2毫秒内处理请求:

    class TrackingMiddleware
      CACHE_BUSTER = {"Cache-Control" => "no-cache, no-store, max-age=0, must-revalidate", "Pragma" => "no-cache", "Expires" => "Fri, 29 Aug 1997 02:14:00 EST"}
    
      IMAGE_RESPONSE_HEADERS = CACHE_BUSTER.merge("Content-Type" => "image/gif").freeze
      IMAGE_RESPONSE_BODY = [File.open(Rails.root + "public/images/tracker.gif").read].freeze
    
      def initialize(app)
        @app = app
      end
    
      def call(env)
        if env["PATH_INFO"] =~ %r{^/track.gif}
          request = Rack::Request.new(env)
          YOUR_QUEUE.push([Time.now, request.GET.symbolize_keys])
          [200, IMAGE_RESPONSE_BODY, IMAGE_RESPONSE_HEADERS]
        else
          @app.call(env)
        end
      end
    end
    

    我建议排队 starling 我只享受过美好的时光。

    在解析结束时,我将使用 super-poller toolkit 但是我会说,我写的。

    2)日志

    将所有参数作为查询参数传递给静态文件(/1x1.gif?foo=1&bar=2&baz=3)。 这不会撞到轨道堆栈,而且速度会非常快。

    当您需要数据时,只需解析日志文件!

    这是最好的家庭自制方法。

    3)谷歌分析

    当谷歌为你做这件事的时候,为什么还要处理这件事呢?你会惊讶于谷歌分析有多好,在你自制任何东西之前,看看它吧!

    这将无限扩大,因为谷歌购买服务器的速度比你快。


    我可以大喊大叫很久,但我现在必须走了。希望这有帮助!

        3
  •  1
  •   Mark Swardstrom    16 年前

    这就是我最终所做的——这已经足够我们现在使用了,并且通过一些简单的基准测试,我对它感觉良好。在我们向客户展示结果之前,我们将观察它在生产中的表现。

    组成部分:

    class EventsController < ApplicationController
      def create
        logger = Logger.new("#{RAILS_ROOT}/log/impressions/#{Date.today}.log")
        logger.info "#{DateTime.now.strftime} #{params[:ids]}" unless params[:ids].blank?
        render :nothing => true
      end
    end
    

    这是从站点布局中的Ajax调用调用调用的…

    <% javascript_tag do %>
      var list = '';
      $$('div.item').each(function(item) { list += item.id + ','; });
      <%= remote_function(:url => { :controller => :events, :action => :create}, :with => "'ids=' + list" ) %>
    <% end %>
    

    然后我做了一个rake任务,将这些以逗号分隔的ID行导入到数据库中。这将在第二天运行:

    desc "Calculate impressions"
    task :count_impressions => :environment do
      date = ENV['DATE'] || (Date.today - 1).to_s # defaults to yesterday (yyyy-mm-dd)
      file = File.new("log/impressions/#{date}.log", "r")
      item_impressions = {}
      while (line = file.gets)
        ids_string = line.split(' ')[1]
        next unless ids_string
        ids = ids_string.split(',')
        ids.each {|i| item_impressions[i] ||= 0; item_impressions[i] += 1 }
      end
      item_impressions.keys.each do |id|
        ActiveRecord::Base.connection.execute "insert into item_stats(item_id, impression_count, collected_on) values('#{id}',#{item_impressions[id]},'#{date}')", 'Insert Item Stats'
      end
    
      file.close
    end
    

    需要注意的一点是,logger变量是在controller操作中声明的,而不是在environment.rb中声明的,正如您通常对logger所做的那样。我把它作为基准——10000次写大约需要20秒。平均每次写入约2毫秒。使用envirnment.rb中的文件名,大约需要14秒。我们做了这个权衡,这样我们就可以动态地确定文件名——一种在午夜切换文件的简单方法。

    我们现在主要关心的是——我们不知道每天要计算多少不同的项目——也就是说,我们不知道尾巴有多长。这将决定每天向数据库添加多少行。我们预计,我们需要限制每日报告的保存时间,并在这一点上进一步提高结果的作用。

    推荐文章