Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slower while generating the XML from the bunch of model object

class GenericFormatter < Formatter
 attr_accessor :tag_name,:objects

 def generate_xml
   builder = Nokogiri::XML::Builder.new do |xml|
   xml.send(tag_name.pluralize) {
   objects.each do |obj|
        xml.send(tag_name.singularize){

            self.generate_obj_row obj,xml
        }                
    end
    }
   end
   builder.to_xml
 end


def initialize tag_name,objects
  self.tag_name = tag_name
  self.objects = objects
end


def generate_obj_row obj,xml
   obj.attributes.except("updated_at").map do |key,value|
     xml.send(key, value)
   end
   xml.updated_at obj.updated_at.try(:strftime,"%m/%d/%Y %H:%M:%S") if obj.attributes.key?('updated_at')
end
 end 

In the above code, I have implemented the formatter where I have used the nokogiri XML Builder to generate the XML by manipulating the objects passing out inside the code.It's generated the faster XML when the data is not too large if data is larger like more than 10,000 records then It's slow down the XML to generate and takes at least 50-60 seconds.

Problem: Is there any way to generate the XML faster, I have tried XML Builders on view as well but did n't work.How can I generate the XML Faster? Should the solution be an application on rails 3 and suggestions to optimized above code?

like image 973
Tushar Pal Avatar asked Jun 29 '17 07:06

Tushar Pal


2 Answers

Your main problem is processing everything in one go instead of splitting your data into batches. It all requires a lot of memory, first to build all those ActiveRecord models and then to build memory representation of the whole xml document. Meta-programming is also quite expensive (I mean those send methods).

Take a look at this code:

class XmlGenerator
  attr_accessor :tag_name, :ar_relation

  def initialize(tag_name, ar_relation)
    @ar_relation = ar_relation
    @tag_name = tag_name
  end

  def generate_xml
    singular_tag_name = tag_name.singularize
    plural_tag_name = tag_name.pluralize

    xml = ""
    xml << "<#{plural_tag_name}>"

    ar_relation.find_in_batches(batch_size: 1000) do |batch|
      batch.each do |obj|
        xml << "<#{singular_tag_name}>"

        obj.attributes.except("updated_at").each do |key, value|
          xml << "<#{key}>#{value}</#{key}>"
        end

        if obj.attributes.key?("updated_at")
          xml << "<updated_at>#{obj.updated_at.strftime('%m/%d/%Y %H:%M:%S')}</updated_at>"
        end

        xml << "</#{singular_tag_name}>"
      end
    end

    xml << "</#{tag_name.pluralize}>"
    xml
  end
end

# example usage
XmlGenerator.new("user", User.where("age < 21")).generate_xml

Major improvements are:

  • fetching data from database in batches, you need to pass ActiveRecord collection instead of array of ActiveRecord models
  • generating xml by constructing strings, this has a risk of producing invalid xml, but it is much faster than using builder

I tested it on over 60k records. It took around 40 seconds to generate such xml document.

There is much more that can be done to improve this even further, but it all depends on your application.

Here are some ideas:

  • do not use ActiveRecord to fetch data, instead use lighter library or plain database driver
  • fetch only data that you need
  • tweak batch size
  • write generated xml directly to a file (if that is your use case) to save memory
like image 76
Michał Młoźniak Avatar answered Nov 16 '22 04:11

Michał Młoźniak


The Nokogiri gem has a nice interface for creating XML from scratch, Nokogiri is a wrapper around libxml2.

Gemfile gem 'nokogiri' To generate xml simple use the Nokogiri XML Builder like this

xml = Nokogiri::XML::Builder.new { |xml| 
    xml.body do
        xml.test1 "some string"
        xml.test2 890
        xml.test3 do
            xml.test3_1 "some string"
        end
        xml.test4 "with attributes", :attribute => "some attribute"
        xml.closing
    end
}.to_xml

output

<?xml version="1.0"?>
<body>
  <test1>some string</test1>
  <test2>890</test2>
  <test3>
    <test3_1>some string</test3_1>
  </test3>
  <test4 attribute="some attribute">with attributes</test4>
  <closing/>
</body>

Demo: http://www.jakobbeyer.de/xml-with-nokogiri

like image 1
Mayur Shah Avatar answered Nov 16 '22 02:11

Mayur Shah