2010-07-23 10 views
5

Como parte de la aplicación Rails, escribí un pequeño importador que toma datos de nuestro sistema LDAP y los almacena en una tabla de usuarios. Desafortunadamente, el código relacionado con LDAP filtra grandes cantidades de memoria mientras itera sobre nuestros usuarios de 32K, y no he podido encontrar la manera de solucionar el problema.Pérdida de memoria en el módulo de Ruby net/ldap

El problema parece estar relacionado con la biblioteca LDAP de alguna manera, ya que cuando elimino las llamadas a LDAP, el uso de memoria se estabiliza bien. Además, los objetos que están proliferando son Net :: BER :: BerIdentifiedString y Net :: BER :: BerIdentifiedArray, ambos parte de la biblioteca LDAP.

Cuando ejecuto la importación, el uso de la memoria finalmente alcanza un máximo de más de 1GB. Necesito encontrar alguna manera de corregir mi código si el problema está allí, o solucionar los problemas de memoria LDAP si es allí donde reside el problema. (O si hay una mejor biblioteca de LDAP para grandes importaciones de Rubí, estoy abierto a eso también.)

Aquí está la parte pertinente de nuestra mi código:

require 'net/ldap' 
require 'pp' 

class User < ActiveRecord::Base 
    validates_presence_of :name, :login, :email 

    # This method is resonsible for populating the User table with the 
    # login, name, and email of anybody who might be using the system. 
    def self.import_all 
    # initialization stuff. set bind_dn, bind_pass, ldap_host, base_dn and filter 

    ldap = Net::LDAP.new 
    ldap.host = ldap_host 
    ldap.auth bind_dn, bind_pass 
    ldap.bind 

    begin 
     # Build the list 
     records = records_updated = new_records = 0 
     ldap.search(:base => base_dn, :filter => filter) do |entry| 
     name = entry.givenName.to_s.strip + " " + entry.sn.to_s.strip 
     login = entry.name.to_s.strip 
     email = login + "@txstate.edu" 
     user = User.find_or_initialize_by_login :name => name, :login => login, :email => email 
     if user.name != name 
      user.name = name 
      user.save 
      logger.info("Updated: " + email) 
      records_updated = records_updated + 1 
     elsif user.new_record? 
      user.save 
      new_records = new_records + 1 
     else 
      # update timestamp so that we can delete old records later 
      user.touch 
     end 
     records = records + 1 
     end 

     # delete records that haven't been updated for 7 days 
     records_deleted = User.destroy_all(["updated_at < ?", Date.today - 7 ]).size 

     logger.info("LDAP Import Complete: " + Time.now.to_s) 
     logger.info("Total Records Processed: " + records.to_s) 
     logger.info("New Records: " + new_records.to_s) 
     logger.info("Updated Records: " + records_updated.to_s) 
     logger.info("Deleted Records: " + records_deleted.to_s) 

    end 

    end 
end 

Gracias de antemano por cualquier ayuda/punteros!

Por cierto, pregunté sobre esto también en el foro de soporte de net/ldap, pero no obtuve ningún puntero útil allí.

+0

¿Dónde está desvinculando la cadena de conexión? ldap.unbind? – Mike

+0

Hola Mike, Los documentos no incluyen un método de desvinculación, ni ninguno de los códigos de muestra, por lo que pensé que no era necesario. (http://net-ldap.rubyforge.org/) Además, uno no se desvincularía hasta después de iterar a través de los registros de todos modos, ¿verdad? La pérdida de memoria se produce durante la iteración. Aprecio la lluvia de ideas. –

+0

¿Qué tan grande es el conjunto de datos devuelto por esta búsqueda? Supongo que los datos pueden duplicarse una o dos veces. La versión de Ruby puede ser útil también. Además, ¿puedes compartir el 'filtro' que estás usando? Finalmente, no es el caso, pero he visto que las bibliotecas de ldap en otras plataformas hacen mucha iteración en grupos anidados. Solo me di cuenta al mirar un volcado de TCP de la comunicación ... – Brian

Respuesta

8

Una cosa muy importante a tener en cuenta es que nunca se usa el resultado de la llamada al método. Eso significa que usted debe pasar a :return_result => falseldap.search:

ldap.search(:base => base_dn, :filter => filter, :return_result => false) do |entry| 

A partir de los documentos: "Cuándo: return_result => false, #search volverá solamente un booleano para indicar si la operación tuvo éxito Esto puede mejorar el rendimiento con. conjuntos de resultados muy grandes, porque la biblioteca puede descartar cada entrada de la memoria después de que su bloque la procese ".

En otras palabras, si no usa esta bandera, todas las entradas se almacenarán en la memoria, ¡incluso si no las necesita fuera del bloque! Entonces, usa esta opción.

+0

El bloque devuelve un conjunto de enteros. Este es un buen puntero, pero dudo que sea el gran problema descrito. –

+0

Reformé la primera frase para "resultado de la llamada al método" en lugar de "resultado del bloque", ya que eso es lo importante. Pero creo sinceramente que esto dará como resultado una gran mejora. –

+0

Daniel, tienes razón. Acabo de probar eso con una consulta que arroja ~ 50000 resultados. Con el: return_result => false, el cliente permanece a unos 50MB de RAM durante el procesamiento del resultado donde sube a ~ 600MB sin este parámetro. –

Cuestiones relacionadas