2010-02-17 15 views
5

Estoy intentando crear una utilidad de rake que actualizará mi base de datos de vez en cuando.base de datos eficiente de actualización de raíles

Este es el código que tengo hasta ahora:

namespace :utils do 

    # utils:update_ip 
    # Downloads the file frim <url> to the temp folder then unzips it in <file_path> 
    # Then updates the database. 

    desc "Update ip-to-country database" 
    task :update_ip => :environment do 

    require 'open-uri' 
    require 'zip/zipfilesystem' 
    require 'csv' 

    file_name = "ip-to-country.csv" 
    file_path = "#{RAILS_ROOT}/db/" + file_name 
    url = 'http://ip-to-country.webhosting.info/downloads/ip-to-country.csv.zip' 


    #check last time we updated the database. 
    mod_time = '' 
    mod_time = File.new(file_path).mtime.httpdate if File.exists? file_path 

    begin 
     puts 'Downloading update...' 
     #send conditional GET to server 
     zipped_file = open(url, {'If-Modified-Since' => mod_time}) 
    rescue OpenURI::HTTPError => the_error 
     if the_error.io.status[0] == '304' 
     puts 'Nothing to update.' 
     else 
     puts 'HTTPError: ' + the_error.message 
     end 
    else # file was downloaded without error. 

     Rails.logger.info 'ip-to-coutry: Remote database was last updated: ' + zipped_file.meta['last-modified'] 
     delay = Time.now - zipped_file.last_modified 
     Rails.logger.info "ip-to-country: Database was outdated for: #{delay} seconds (#{delay/60/60/24 } days)" 

     puts 'Unzipping...' 
     File.delete(file_path) if File.exists? file_path 
     Zip::ZipFile.open(zipped_file.path) do |zipfile| 
     zipfile.extract(file_name, file_path) 
     end 

     Iptocs.delete_all 

     puts "Importing new database..." 


     # TODO: way, way too heavy find a better solution. 


     CSV.open(file_path, 'r') do |row| 
     ip = Iptocs.new( :ip_from  => row.shift, 
         :ip_to   => row.shift, 
         :country_code2 => row.shift, 
         :country_code3 => row.shift, 
         :country_name => row.shift) 
     ip.save 
     end #CSV 
     puts "Complete." 

    end #begin-resuce 
    end #task 
end #namespace 

El problema que estoy teniendo es que esta toma unos minutos para entrar en las 100 mil entradas más. Me gustaría encontrar una manera más eficiente de actualizar mi base de datos. Idealmente, esto seguirá siendo independiente del tipo de base de datos, pero si no, mi servidor de producción se ejecutará en MySQL.

Gracias por cualquier idea.

Respuesta

9

¿Ha intentado utilizar AR Extensions para la importación masiva? Obtiene impresionantes mejoras de rendimiento cuando inserta miles de filas en DB. Visite su website para más detalles.

Consulte estos ejemplos para más información

Usage Example 1

Usage Example 2

Usage Example 3

+0

Esto es exactamente lo que estaba buscando, gracias. – codr

+0

La gema admite importación desde CSV. Esto elimina los costos de instanciación y validación de 'ActiveRecord'. Consulte este artículo para más detalles. http://www.rubyinside.com/advent2006/17-extendingar.html –

+0

Me ayudó a mí también, ¡gracias! – ambertch

1

Se puede generar un archivo de texto con todas las inserciones que necesita y luego ejecutar:

mysql -u user -p db_name < mytextfile.txt 

No estoy seguro si esto va a ser más rápido, pero vale la pena intentarlo ...

+0

Rails usa la instrucción de inserción de SQL. - Ver el registro de sus rieles. Entonces este método no sería una mejora de velocidad. –

+2

Por supuesto, Rails INSERTA, ¿de qué otra forma podría agregar registros a la base de datos? Pero en su autor original del post está usando el método "guardar" que tiene más sobrecarga que solo un simple inserto. Estoy seguro de que implica hacer commit en cada inserción, hacer validaciones del modelo, etc. – Zepplock

0

Como dice Larry, utilizar sus utilidades de importación específicos de DB si el archivo viene en el formato que desee. Sin embargo, si necesita manipular los datos antes de insertarlos, puede generar una única consulta INSERT con datos para muchas filas, que es más rápido que usar una consulta separada para cada fila (como lo hará ActiveRecord). Por ejemplo:

INSERT INTO iptocs (ip_from, ip_to, country_code) VALUES 
    ('xxx', 'xxx', 'xxx'), 
    ('yyy', 'yyy', 'yyy'), 
    ...; 
Cuestiones relacionadas