2012-02-08 17 views
5

Ho un file xml da 1,6 gb e quando lo analizzo con Sax Machine non sembra che stia trasmettendo o mangiando il file in blocchi - piuttosto sembra per caricare l'intero file in memoria (o forse c'è una perdita di memoria da qualche parte?) perché il mio processo di rubino sale di 2.5 grammi di ram. Non so dove smette di crescere perché ho esaurito la memoria.L'analisi di file di grandi dimensioni con SaxMachine sembra caricare l'intero file in memoria

Su un file più piccolo (50 MB) sembra che venga caricato l'intero file. Il mio compito esegue iterazioni sui record nel file xml e salva ogni record in un database. Ci vogliono circa 30 secondi di "idling" e poi all'improvviso le query del database iniziano ad essere eseguite.

Pensavo che SAX avrebbe dovuto consentire di lavorare con file di grandi dimensioni come questo senza caricare tutto in memoria.

C'è qualcosa che sto trascurando?

Molte grazie

aggiornamento per aggiungere codice di esempio

class FeedImporter 

    class FeedListing 
    include ::SAXMachine 

    element :id 
    element :title 
    element :description 
    element :url 

    def to_hash 
     {}.tap do |hash| 
     self.class.column_names.each do |key| 
      hash[key] = send(key) 
     end 
     end 
    end 
    end 

    class Feed 
    include ::SAXMachine 
    elements :listing, :as => :listings, :class => FeedListing 
    end 

    def perform 
    open('~/feeds/large_feed.xml') do |file| 

     # I think that SAXMachine is trying to load All of the listing elements into this one ruby object. 
     puts 'Parsing' 
     feed = Feed.parse(file) 

     # We are now iterating over each of the listing elements, but they have been "parsed" from the feed already. 
     puts 'Importing' 
     feed.listings.each do |listing| 
     Listing.import(listing.to_hash) 
     end 

    end 
    end 

end 

Come potete vedere, non mi interessa circa l'elemento <listings> nel feed. Voglio solo gli attributi di ogni elemento <listing>.

L'output è simile al seguente:

Parsing 
... wait forever 
Importing (actually, I don't ever see this on the big file (1.6gb) because too much memory is used :(
+0

Semplice risposta alla tua domanda: sì, c'è qualcosa che si affaccia su. Sfortunatamente non ci hai detto di cosa si tratta. Nessuno può trovare perdite di memoria nel codice che non possono vedere. –

+0

@ MichaelKay Ho aggiunto un esempio. Grazie – jakeonrails

risposta

2

I biforcuta sax-macchina in modo che utilizza la memoria costante: https://github.com/gregwebs/sax-machine

Buone notizie: c'è un nuovo manutentore che sta progettando sulla fusione miei cambiamenti. Io e il nuovo manutentore stiamo usando la mia forcella senza problemi da un anno.

+0

Questo ramo sembra non sincronizzato con il repository canonico e non è stato toccato in due anni. Stava anche generando errori sul cedimento da una fibra di radice ... –

+0

Anche io ottengo l'errore "(FiberError) non può produrre da fibra radice", sembra che questo ramo sia stato abbandonato. – doomspork

0

Hai ragione, SAXMachine legge l'intero documento con entusiasmo. Date un'occhiata alle sue fonti di gestori: https://github.com/pauldix/sax-machine/blob/master/lib/sax-machine/sax_handler.rb

Per risolvere il vostro problema, vorrei usare direttamente http://nokogiri.rubyforge.org/nokogiri/Nokogiri/XML/SAX/Parser.html e implementare personalmente il gestore.

+0

grazie per aver confermato il mio sospetto. È una macchina da vergognoso sax che non fa una valutazione pigra o fornisce un vero meccanismo di callback - sarebbe magnifico. – jakeonrails

3

Ecco un lettore che produrrà XML di ogni elenco ad un blocco, in modo da poter elaborare ogni elenco senza caricare l'intero documento in memoria

reader = Nokogiri::XML::Reader(file) 
while reader.read 
    if reader.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT and reader.name == 'listing' 
    listing = FeedListing.parse(reader.outer_xml) 
    Listing.import(listing.to_hash) 
    end 
end 

Se gli elementi messa in vendita potrebbe essere annidato, e di voler analizzare la annunci più esterni come singoli documenti, si potrebbe fare questo:

require 'rubygems' 
require 'nokogiri' 


# Monkey-patch Nokogiri to make this easier 
class Nokogiri::XML::Reader 
    def element? 
    node_type == TYPE_ELEMENT 
    end 

    def end_element? 
    node_type == TYPE_END_ELEMENT 
    end 

    def opens?(name) 
    element? && self.name == name 
    end 

    def closes?(name) 
    (end_element? && self.name == name) || 
     (self_closing? && opens?(name)) 
    end 

    def skip_until_close 
    raise "node must be TYPE_ELEMENT" unless element? 
    name_to_close = self.name 

    if self_closing? 
     # DONE! 
    else 
     level = 1 
     while read 
     level += 1 if opens?(name_to_close) 
     level -= 1 if closes?(name_to_close) 

     return if level == 0 
     end 
    end 
    end 

    def each_outer_xml(name, &block) 
    while read 
     if opens?(name) 
     yield(outer_xml) 
     skip_until_close 
     end 
    end 
    end 

end 

volta che lo avete scimmia-patch, è facile trattare con ogni messa in vendita singolarmente:

open('~/feeds/large_feed.xml') do |file| 
    reader = Nokogiri::XML::Reader(file) 
    reader.each_outer_xml('listing') do |outer_xml| 

    listing = FeedListing.parse(outer_xml) 
    Listing.import(listing.to_hash) 

    end 
end 
+0

Fantastico, funziona benissimo. Sembra anche piuttosto veloce, dato che il mio db sul mio computer locale diventa il collo di bottiglia per l'importazione. Grazie, John! – jakeonrails

+0

Sono stato in grado di analizzare il mio grande documento XML utilizzando questo approccio insieme alla gemma della macchina sax canonica. Grazie! –

3

Purtroppo ci sono ora threedifferentrepos per sax-machine. E peggio, la versione di gemspec non è stata urtata.

Nonostante il commento su Greg Weber's blog, non penso che questo codice sia stato integrato nei fork di pauldix o ezkl.Per utilizzare la versione pigro, a base di fibre del codice, penso che è necessario fare riferimento specifico gregweb's versione nella Gemfile come questo:

gem 'sax-machine', :git => 'https://github.com/gregwebs/sax-machine' 
+0

Sembra che tu abbia ragione. Il grafico della rete Github (https://github.com/gregwebs/sax-machine/network) mostra che le modifiche di Greg non sono state unite nel repository canonico SAXMachine (gestito da pauldix) – Ivar

Problemi correlati