【问题标题】:Ruby parallel csv importingRuby 并行 csv 导入
【发布时间】:2012-09-12 09:24:10
【问题描述】:

我正在导入巨大的 csv 文件,我想将其拆分,这样导入会更快(我没有直接导入到 db,我有一些计算)。 代码如下:

def import_shatem
    require 'csv'





    CSV.foreach("/#{Rails.public_path}/uploads/hshatem2.csv", {:encoding => 'ISO-8859-15:UTF-8', :col_sep => ';', :row_sep => :auto, :headers => :first_row}) do | row |

      @eur_cur = Currency.find_by_currency_name("EUR")
      abrakadabra = row[0].to_s()
      (ename,esupp) = abrakadabra.split(/_/)
      eprice = row[6].to_f / @eur_cur.currency_value
      eqnt = /(\d+)/.match(row[1])[0].to_f


        if ename.present? && ename.size>3
        search_condition = "*" + ename.upcase + "*"     

        if esupp.present?
          #supplier = @suppliers.find{|item| item['SUP_BRAND'] =~ Regexp.new(".*#{esupp}.*") }
          supplier = Supplier.where("SUP_BRAND like ?", "%#{esupp}%").first
          logger.warn("!!! *** supp !!!")

        end

        if supplier.present?

          @search = ArtLookup.find(:all, :conditions => ['MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE) and ARL_KIND = 1', search_condition.gsub(/[^0-9A-Za-z]/, '')])
          @articles = Article.find(:all, :conditions => { :ART_ID => @search.map(&:ARL_ART_ID)})
          #@art_concret = @articles.find_all{|item| item.ART_ARTICLE_NR.gsub(/[^0-9A-Za-z]/, '').include?(ename.gsub(/[^0-9A-Za-z]/, '')) }

          @aa = @articles.find{|item| item['ART_SUP_ID']==supplier.SUP_ID} #| @articles
          if @aa.present?
            @art = Article.find_by_ART_ID(@aa)
          end

          if @art.present?
            #require 'time_diff'
            #cur_time = Time.now.strftime('%Y-%m-%d %H:%M')
            #time_diff_components = Time.diff(@art.datetime_of_update, Time.parse(cur_time))
            limit_time = Time.now + 3.hours
            if  (@art.PRICEM.to_f >= eprice.to_f || @art.PRICEM.blank? ) #&& @art.datetime_of_update >= limit_time) 
              @art.PRICEM = eprice
              @art.QUANTITYM = eqnt
              @art.datetime_of_update = DateTime.now
              @art.save
            end
          end

        end     
      end
    end
  end

我怎么能平行呢?并获得更快的导入速度?

【问题讨论】:

  • 当我不得不处理类似的事情(数百万行)时,我只是将 CSV 拆分为几个文件(使用 Unix split 命令)并并行启动多个导入器......
  • 您的评论应该是对这个问题的回答。当我遇到同样的问题时,我做了完全相同的事情。
  • Speed up csv import 的可能重复项
  • 您在循环中分配了很多实例变量(@search,@art,...)。它们需要是实例变量吗?在尝试并行之前可以进行很多优化。
  • 请问您使用的是什么数据库?

标签: ruby-on-rails ruby fastercsv


【解决方案1】:

查看 Gem smarter_csv!它可以分块读取 CSV 文件,然后您可以创建 Sidekiqjobs 来处理这些块并将其插入到数据库中。

https://github.com/tilo/smarter_csv

【讨论】:

    【解决方案2】:

    查看代码,瓶颈将是数据库查询。并行运行它不会解决这个问题。相反,让我们看看我们是否可以提高效率。

    最大的问题可能是文章搜索。它在内存中进行多个查询和搜索。我们会讲到最后。


    Currency.find_by_currency_name 始终相同。从循环中提取 if。它不太可能成为瓶颈,但它会有所帮助。而且,假设currency_nameCurrency 的一列,我们可以通过获取单个值而不是使用pick 加载整个记录来节省一点时间。

      def currency_value
        @currency_value ||= Currency.where(currency_name: "EUR").pick(:currency_value)
      end
    

    同样,如果 CSV 包含许多重复值,Supplier.where 可以从缓存中受益。用Memoist缓存返回值。

      extend Memoist
    
      private def find_supplier_for_esupp(esupp)
        return if esupp.blank?
        Supplier.where("SUP_BRAND like ?", "%#{esupp}%").first
      end
      memoize :find_supplier_for_esupp
    

    %term% 不会使用普通的 B-Tree 索引,因此根据供应商表的大小,搜索可能会很慢。如果您使用的是 PostgreSQL,则可以使用 trigram index 加速此查询。

    add_index :suppliers, :SUP_BRAND, using: 'gin', opclass: :gin_trgm_ops
    

    最后,文章搜索可能是最大的瓶颈。它正在查询 ArtLookup,加载所有记录,将它们全部扔到一个列中。然后搜索Article,加载所有内存,过滤内存,最后一次搜索Article。

    假设在模型中正确设置了 Article 和 ArtLookup 之间的关系,则可以将其缩减为一个查询。

      art = Article
        .joins(:art_lookups)
        .merge(
          ArtLookup
            .where(ARL_KIND: 1)
            .where(
              'MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)',
              search_condition
            )
        )
        .where(
          ART_SUP_ID: supplier.SUP_ID
        )
        .first
    

    这应该会快得多。


    总之,还有其他一些改进,例如提前返回以避免所有嵌套的 if。

    require 'csv'
    
    class ShatemImporter
      extend Memoist
    
      # Cache the possibly expensive query to find suppliers.
      private def find_supplier_for_esupp(esupp)
        Supplier.where("SUP_BRAND like ?", "%#{esupp}%").first
      end
      memoize :find_supplier_for_esupp
    
      # Cache the currency value query outside the loop.
      private def currency_value
        @currency_value ||= Currency.find_by(currency_name: "EUR").currency_value
      end
    
      def import_shatem(csv_file)
        CSV.foreach(
          csv_file,
          {
            encoding: 'ISO-8859-15:UTF-8', :col_sep => ';', :row_sep => :auto, :headers => :first_row
          }
        ) do |row|
          (ename,esupp) = row[0].to_s().split(/_/)
          eprice = row[6].to_f / currency_value
          eqnt = row[1].match(/(\d+)/).first.to_f
    
          next if ename.blank? || ename.size < 4
          next if esupp.blank?
          
          supplier = find_supplier_for_esupp(esupp)      
          next if !supplier
    
          article = Article
            .joins(:art_lookups)
            .merge(
              ArtLookup
                .where(ARL_KIND: 1)
                .where(
                  'MATCH (ARL_SEARCH_NUMBER) AGAINST(? IN BOOLEAN MODE)',
                  "*#{ename.upcase}*"     
                )
            )
            .where(
              ART_SUP_ID: supplier.SUP_ID
            )
            .first
          next if !article
    
          if art.PRICEM.blank? || art.PRICEM.to_f >= eprice.to_f
            art.update!(
              PRICEM: eprice,
              QUANTITYM: eqnt,
              datetime_of_update: DateTime.now
            )
          end
        end
      end
    end
    

    这是用 Rails 6 编写的,您的代码看起来像 Rails 2,并且未经测试。但希望它能为您提供优化途径。

    【讨论】:

      猜你喜欢
      • 2012-08-23
      • 2017-01-19
      • 2013-05-06
      • 2017-07-21
      • 2012-02-14
      • 2016-03-24
      • 1970-01-01
      • 2012-06-13
      • 2021-11-17
      相关资源
      最近更新 更多